Commit Graph

14820 Commits

Author SHA1 Message Date
Ben Taylor 8a8e52d9e9 fix(runtime): non-optional listener.channels and honest lifecycle docs (OSS-646) (#6207)
Closes OSS-646. Split out of OSS-641 as the unambiguous half. This PR
does **not** change when activation happens — whether the long-running
wrappers should auto-connect stays open on OSS-641.

## Why

`createCopilotRuntimeHandler` builds the `ChannelManager` but opens no
connection; activation is lazy, triggered by the first
`channels.ready()`. That is deliberate (`fbf35ac59`, OSS-473) —
Cloudflare/Next isolates freeze and recycle per request, so cold starts
would mint conflicting listeners. Two things were left inconsistent with
it:

1. `endpoints/node.ts` still documented the pre-`fbf35ac59` world — "the
same `ChannelsControl` surface the underlying fetch handler **activates
at creation time**" — and labelled the one required call as `//
Optional:`. That's the TSDoc developers and coding agents see in-editor,
and it contradicted every channel-package README. Same failure class as
OSS-634.
2. `68349bc1f` gave the fetch handler a branded overload so
`handler.channels.ready()` type-checks without `?.`, but the node
wrapper never got it — so every call site, including our own example and
all nine showcase docs pages, was written defensively.

### A live consequence, found en route

`examples/slack/app/managed.ts` never called `ready()`. It built the
runtime, mounted the listener, logged `[channel] started managed Channel
"…"`, and only ever called `stop()` — so since activation went lazy it
has connected nothing while reporting success. It was written against
exactly the creation-time model the TSDoc described. Fixed here, with a
regression assertion.

## What changed

- **Types** — `createCopilotNodeListener` gets the branded overload pair
mirroring `createCopilotRuntimeHandler`: a runtime with at least one
declared Channel yields non-optional `.channels`; `activateChannels:
false` and channel-less runtimes keep the optional shape. Adds
`NodeCopilotListenerWithChannels`; both listener types are now exported
from `@copilotkit/runtime/v2/node`.
- **Docs** — node/express/hono TSDoc corrected: creation opens no
connection, `ready()` is what activates, and it is required on a
long-running host. Same stale claim fixed in the three example comments
and `examples/slack/README.md` that repeated it.
- **Call sites** — `?.` dropped from `examples/slack`, `examples/teams`,
both READMEs, and the nine `showcase/shell-docs` channel pages.

## Deliberate scope choices, called out

- **Express/Hono keep an optional `.channels`.** Only their TSDoc is
corrected here. Their own type docs name Node as the lifecycle-owning
surface and attach `.channels` best-effort, so the branded overload is
Node-only for now; `endpoints-channels.test.ts` still uses `!` for those
two. Say the word if the overload should extend to them.
- **The non-optional shape requires a literal `channels` tuple**
(`readonly [Channel, ...Channel[]]`). A runtime built from a
dynamically-assembled `Channel[]` is unbranded and still needs `?.`. Now
stated in the node TSDoc.
- **`examples/slack/app/managed.ts` now exits nonzero if activation
fails**, where before it stayed up serving HTTP with nothing connected.
Intentional — fail loud, and it matches `index.ts`. Note that `ready()`
resolves for `setup_required`, so a declared-but-unprovisioned channel
still logs as started.
- **Signal handlers are registered before awaiting activation** in
`managed.ts`, so a Ctrl-C inside the 30s activation window still tears
the Channel down instead of hitting Node's default handler.

## Verification

- **Type contract, red → green:** the new `KeyIsRequired<typeof
listener, "channels">` assertion in `handler-channels-types.test.ts`
failed to compile before the overload (`error TS2344: Type 'false' does
not satisfy the constraint 'true'`) and passes after.
- **Example bug, red → green:** stashing only `managed.ts` fails the new
guard with `expected "vi.fn()" to be called once, but got 0 times`.
- **Strict-null proof:** `slack-example` and `teams-example` both `tsc
--noEmit` clean under `strict: true` with the `?.` removed. This matters
because the runtime package compiles with `strict: false`, so its own
type test can only probe the optionality modifier structurally.
- Runtime channel suites 54/54; slack example 63/63.
- **Coverage limit:** the `managed.ts` guard is mocked — it proves the
example *calls* `ready()` with a bound, not that a Channel connects.
Nothing in CI exercises a real gateway connect for these examples.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-28 14:11:53 -05:00
Maxim 0850c5e522 Merge remote-tracking branch 'origin/main' into blitz/glass-inspector/integration
Realigns the inspector/memory work onto the banking demo as it shipped in
#6136 (ChatGPT-style shell, gen-UI beats, durable-memory self-learning) and
#6202 (README refresh).

All six conflicts were the same collision: this branch removes the bespoke
Glass Engine inspector, while #6136 kept and rebuilt around it.

- run-handler.ts: kept both sides (our CopilotKitCoreCatalogComponent and
  main's MAX_FOLLOW_UP_DEPTH landed at the same spot).
- wrapper.tsx / layout.tsx: took main's rewritten provider tree and
  right-hand icon rail, minus the Glass Engine providers, pane, and
  telescope toggle. Also dropped main's `padClass` (it reserved space for
  the Glass pane and referenced a now-removed `glassActive`) and
  `<ProactiveNotice />` (main removed it; the import is already gone).
- memory-tab.tsx, lib/intelligence/memory.ts: confirmed the deletions.
  Their only remaining importers were the bespoke inspector and the
  banking-local /api/memories routes, all removed here. seed-memories.ts
  is unaffected: it POSTs to INTELLIGENCE_API_URL, not the local route.
- README.md: kept our product-inspector section over main's Glass Engine
  availability/activation prose, and documented the Capabilities tab.

Drive-by fixes to comment rot the migration created: user-id.ts and the
copilotkit route doc comments referenced the deleted Memory-panel proxies,
and the README pointed the presenter-reset control at the removed
telescope toggle.

Also replaces a literal NUL byte in capabilityKey() with a unicode escape.
The raw control character made tsc/grep/diff treat run-handler.ts as a
binary file, which hid this very merge's conflict markers from grep.
Behavior is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 21:10:21 +02:00
Tyler Slaton 1d483e53bf fix(docs): centralize frontend selection decisions 2026-07-28 14:59:52 -04:00
Tyler Slaton df97ad4c8a fix(docs): preserve active frontend selection 2026-07-28 14:49:23 -04:00
Tyler Slaton 0c96f54a43 fix(docs): show neutral Channels overview selector 2026-07-28 14:38:08 -04:00
Benjamin Taylor b2e825050f fix(examples): match teams teardown semantics to slack (guarded handler, nonzero on failure) (OSS-646) 2026-07-28 13:34:50 -05:00
Benjamin Taylor bcf7c58fe5 fix(examples): make channel teardown failure-safe and register signals before activation (OSS-646) 2026-07-28 13:29:41 -05:00
Tyler Slaton cb85329f3c fix(docs): match Channels overview path exactly 2026-07-28 14:26:44 -04:00
Benjamin Taylor a26990971e docs(runtime): note that the non-optional channels shape needs a literal channels tuple (OSS-646) 2026-07-28 13:26:05 -05:00
Maxim a1c9b31478 docs(banking): refresh the demo README to match what shipped in #6136 (#6202)
Follow-up to #6136, which merged while this was being written. That PR
changed the demo substantially and the README still described an older
build.

## Corrected

| Was | Now |
| --- | --- |
| `CopilotPopup` | Left-docked `CopilotSidebar`, two columns (thread
rail + conversation), app nav rail on the right |
| learned memory is `procedural` | `operational` — what the code
actually writes |
| Memory tab is "top-k recall, not a full enumeration" | "Stored
memories (N)" — a complete enumeration aggregated across identities;
semantic search is a separate box |
| per-run reset is "a separate follow-up" | Ships behind
`PRESENTER_RESET_ENABLED`; also re-seeds memory and returns to the bare
root |
| `showTransactions` is a `useFrontendTool` | `useComponent`, so it
persists in the transcript |
| "change my Visa PIN from the team page" via navigation | PIN is a
component in the chat; the agent never receives the digits |

## Added

- The **eight-pill demo script** as a table, with which beats need
Intelligence mode
- The `/charges` page and the **PDF / Q2 multimodal** beat
- **Seeded memory** — and why the over-limit procedure is deliberately
*not* seeded (seeding it would remove the teach beat entirely)
- The **components-not-markdown-tables** rule, including why `showTable`
renders nothing below two rows
- Per-team chart colour via `teamColor()`
- The two **incompatible helper families** in `actions.ts` —
destructuring `ok` off the wrong one reports a success as a failure,
which bit twice during #6136

## Screenshots

All three replaced. They showed the old right-docked chat layout, so
they contradicted the first paragraph of the README. New captures are
from the shipped build (filed report dashboard, chat with components +
tool-activity line, recording vignette).

Docs only — no source changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-28 14:25:15 -04:00
Ben Taylor 661ac447be feat(runtime): default the Intelligence platform URLs to the managed service (#6205)
Makes `new CopilotKitIntelligence({ apiKey })` the whole managed-service
setup. `apiUrl` and `wsUrl` were required on every construction, so the
two correct hosts had to be found and copied by hand — which is how an
agent came to invent them. Linear:
[OSS-638](https://linear.app/copilotkit/issue/OSS-638/add-default-rest-and-ws-api-urls-to-intelligence-sdk).

## The change

Both fields are now optional and default to CopilotKit's managed
platform (`https://api.intelligence.copilotkit.ai` /
`wss://realtime.intelligence.copilotkit.ai`). Overrides are unchanged
for self-hosted and non-production deployments, with two guards that the
previous required-field signature made unnecessary:

**A blank value counts as unset.** These URLs are almost always wired
from env vars, and a declared-but-empty variable
(`COPILOTKIT_INTELLIGENCE_URL=`, common in generated `.env` files and
container configs) arrives as `""`. Left as-is it slips past a `??`
default and produces host-relative requests — the failing test showed a
request to `/api/threads?...` with no host at all.

**Setting only one of the pair warns.** The API and realtime planes are
separate hosts, so a lone override silently pairs (say) a self-hosted
API with the managed gateway. That failure surfaces as a 30s hang rather
than an error, because the socket layer treats an unreachable host as a
retryable reconnect — so it is worth a log line at construction.

## Docs

Swept the copy-paste surfaces to the short form so nobody is handed URLs
to get wrong: channels quickstart/index/mcp + the 5 platform pages, the
7 channels package READMEs, the runtime and react-core skill references
(package and root copies), and the slack/teams examples with their
READMEs and `.env.example`s. The "separate hosts, override both or
neither" lesson now lives in the quickstart's override note and the SDK
JSDoc instead of in every snippet.

Two things beyond the mechanical sweep:

- `dev-docs/architecture/setup-intelligence.md` documented `apiUrl:
"https://your-intelligence-host/api"` and `wsUrl:
"wss://your-intelligence-host/socket"` — the exact double-`/api` and
fake-`/socket` suffixes the runtime skill reference warns against. Fixed
while here.
- The `CopilotKitIntelligence` class JSDoc sat above
`ThreadDeletedPayload`, so it never appeared on hover of the class.
Reattached — that is the discoverability half of the original complaint.

The integration examples (langgraph, mastra, adk, …) are deliberately
untouched: they pass `process.env.INTELLIGENCE_API_URL ??
"http://localhost:4201"` to target a local stack, where the managed
defaults would be wrong.

## Verification

Tests written first and watched fail — 5 on undefined URLs, then the
blank-URL case on the host-relative request.

- `client.test.ts` 54/54; full `runtime/src/v2` suite 1128/1128
- slack example 63/63, teams example 2/2, channels-intelligence 195/195
- `tsc --noEmit` clean on `packages/runtime`; both edited examples
typecheck clean against the new signature (verified by resolving
`@copilotkit/runtime` at the worktree source, since the symlinked
`node_modules` otherwise reports stale required-field types)
- oxlint 0 warnings / 0 errors on touched files, oxfmt clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-28 13:23:02 -05:00
Benjamin Taylor 318651480f fix(examples): register signal handlers before awaiting channel activation (OSS-646) 2026-07-28 13:22:26 -05:00
Tyler Slaton 80683c6836 fix(docs): route Channels overview picker exits 2026-07-28 14:22:25 -04:00
Benjamin Taylor 660c686fed chore: drop changeset (repo does not use changesets) (OSS-646) 2026-07-28 13:20:58 -05:00
Ben Taylor 73eaecdbe4 fix(channels): surface run_error on HTTP-fallback path and isolate ack failures (closes OSS-491) (#6034)
## What

Two SDK-side delivery / terminal-signal hardening fixes in the managed
channels
adapter (`packages/channels-intelligence/src/intelligence-adapter.ts`),
from the
pre-merge adversarial CR of #5972. Closes OSS-491.

## Scope correction (verified end-to-end during review)

The ticket originally framed Finding 1 as a live "self-hosted /
no-Outbox"
silent-failure. That premise is **wrong**, and I traced the full render
path to
confirm it:

- Every real deployment wires a render sink: the realtime gateway
launcher passes
one, and the config-free `intelligenceAdapter()` auto-wires
`HttpRenderEventSink`
  in `start()`. On that path a `run_error` is POSTed to app-api's
`/api/channels/deliveries/:id/render-events/accept` route, durably
persisted,
projected into canonical history as a `RUN_ERROR` terminal, **and** the
Connector
  Outbox posts a user-visible `⚠️ Agent error: …` to Slack.
- The Connector Outbox is the **same app-api binary** and runs in
**self-hosted / composite** deployments too (gated only by the channels
feature
flag, not deployment mode). Self-hosted handles `run_error` identically
— there
  is no server-side gap.
- The ad-hoc fallback sink in `renderSinkFor` — the code Finding 1
patches — is
reached **only** when a consumer injects both `source` and `egress` but
no
  `renderSink` (in-memory tests, and hypothetical BYO custom-transport
  integrations). It is not on any default or self-hosted path.

So Finding 1 is **defensive hardening of a fallback plus a lifecycle
fix**, not a
HIGH-severity production silent-failure (severity retriaged to Low on
the ticket).
Finding 2 is on the shared `dispatch()` path and does affect real
deployments.

## Finding 1 — `run_error` on the ad-hoc HTTP-fallback sink

`renderSinkFor`'s ad-hoc plain-text sink (used only when no `renderSink`
is wired)
had no `run_error` branch, so on that path a `run_error` was dropped,
the run
resolved, and `dispatch()` acked a text-less turn as success — silent,
nothing
logged. Now that sink logs the raw error via `config.log`, flushes any
buffered
partial text, and posts a generic user-visible error. Posted + logged +
**acked, not nacked**: a `run_error` is frequently a deterministic agent
failure
and the Channel path reprocesses redeliveries (`skipIngressDedup`), so
nacking
would livelock. A genuine egress failure of the error post itself still
nacks
(`emit` throws → `drain()` rejects).

Also — and this part **does** apply to the realtime path —
`onRunErrorEvent` now
calls `ensureRunStarted()`, so a `run_error` that precedes any text/tool
event
emits a preceding `run_started` (parity with the text/tool handlers; a
well-formed
run lifecycle on both paths). This is the one intentional realtime-path
change: a
text-less error now yields `[run_started, run_error, finalize]` instead
of
`[run_error, finalize]`. `run_error` **rendering** is unchanged — still
a frame to
the sink, never converted to an egress error post.

## Finding 2 — a failed `ack()` no longer nacks a completed turn

`ack()` sat inside the same `try` as `dispatchTo`, so an `ack()` network
failure
fell into the catch and nacked an already-processed turn with a
misleading reason;
with `skipIngressDedup`, the whole turn reran (duplicate agent run —
re-calls the
model, re-runs tools — even though egress op-id dedupe catches duplicate
posts).
Ack is now a distinct phase: a failed ack is logged, never nacked. This
kills the
**spurious** rerun — notably the common "ack landed server-side but the
response
was lost" case. A genuinely-failed ack still redelivers via lease expiry
(inherent
to at-least-once, unavoidable); the turn already posted its reply, so
only
delivery bookkeeping is delayed.

## Tests

TDD; the behavior tests were written failing first.

- Fallback text-less `run_error` ⇒ emits a user-visible error post
**and** logs it.
- `run_error` before any text/tool event ⇒ `run_started` precedes it.
- Realtime path ⇒ `run_error` still forwarded to the render sink,
**not** converted
  to an egress post (regression guard).
- Failed `ack()` on a successful turn ⇒ not nacked, no rerun, ack
failure logged.
- Failed error-post emit on the fallback path ⇒ `drain()` rejects
(nacks/retries).
- Buffered partial text flushed before the error post (order preserved).

## Verification

- `nx run @copilotkit/channels-intelligence:check-types` ✅
- `nx run @copilotkit/channels-intelligence:test` ✅ (all green)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-28 13:16:44 -05:00
Ben Taylor 01550e6358 fix(channels-intelligence): classify channel_not_declared as setup-required (#6198)
## What does this PR do?

A runtime that declares a managed channel the Intelligence project does
not have
crashed startup: `ready()` rejected and the channel settled to `error`.

```
realtime gateway session join failed: channel_declaration_unavailable (channel states: runtime_not_declared)
status: { overall: "error", channels: { "managed-teams-probe": "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 anyone reached the happy path.

### Cause

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.

The fix is split by which side owns the mistake. app-api now sends
`channel_not_declared` for that case (Intelligence#615). This PR teaches
the
classifier the new token.

### Change

- `channel_not_declared` added to `SETUP_REQUIRED_CHANNEL_STATES`, so
the channel
  settles to `setup_required` and `ready()` resolves.
- `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`.
- Retired 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 a
heartbeat.
  Replaced the TODO with what the answer turned out to be.
- Documented that `SETUP_REQUIRED_CHANNEL_STATES` now draws on two
Intelligence
  vocabularies: most members are read-model `CHANNEL_STATE_KINDS`, while
`channel_not_declared` is declaration-only and has no read-model
counterpart.

An SDK without this change treats the unknown token as a hard error,
which is the
pre-fix behavior — so old clients are no worse off, they just keep the
bug.

## Related PRs and Issues

- Server side: CopilotKit/Intelligence#615
- OSS-622

## Checklist

- [x] I have read the Contribution Guide
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] "Allow edits by maintainers" is checked

## Tests

Added `classifies channel_not_declared as SETUP_REQUIRED (OSS-622)`,
plus an
`error-channel-not-declared` join mode. RED before the fix: the join
produced
`RealtimeGatewayChannelStateError` instead of
`RealtimeGatewaySetupRequiredError`.

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

33 failures elsewhere in this package reproduce on `origin/main` with
these
changes stashed (a stale `@copilotkit/channels` build missing the
`ɵruntime`
symbol) and are unrelated.
2026-07-28 13:14:12 -05:00
Benjamin Taylor 3f0bbe4a7b feat(runtime): default the Intelligence platform URLs to the managed service
`CopilotKitIntelligence` required `apiUrl` and `wsUrl` on every construction,
so the two correct hosts had to be found and copied by hand — which is how an
agent came to invent them. Both now default to CopilotKit's managed platform,
making `new CopilotKitIntelligence({ apiKey })` the whole managed-service setup.

Overrides are unchanged for self-hosted and non-production deployments, with two
guards that the previous required-field signature made unnecessary:

- A blank value counts as unset. These URLs are usually wired from env vars, and
  a declared-but-empty variable arrives as `""`, which would otherwise produce
  host-relative requests instead of falling back to the managed platform.
- Setting only one of the pair warns. The API and realtime planes are separate
  hosts, so a lone override silently splits the client across two deployments —
  and that failure surfaces as a hang, not an error.

Sweeps the doc, skill, README, and example surfaces to the short form so the
copy-paste path no longer hands anyone URLs to get wrong, and reattaches the
`CopilotKitIntelligence` class JSDoc, which was orphaned above an interface and
so never appeared on hover.

Linear: OSS-638
2026-07-28 13:12:15 -05:00
Benjamin Taylor 956c6448a7 fix(runtime): non-optional listener.channels and honest lifecycle docs (OSS-646)
`createCopilotNodeListener` now mirrors `createCopilotRuntimeHandler`'s branded
overload pair, so a runtime with at least one declared Channel yields a listener
whose `.channels` is non-optional and the documented `listener.channels.ready()`
call type-checks with no `!` and no `?.`. `activateChannels: false` and
channel-less runtimes keep the optional shape. Both listener types are exported
from `@copilotkit/runtime/v2/node`.

Corrects TSDoc on the node, express, and hono wrappers that still claimed
activation happens "at creation time" and labelled `ready()` as optional — stale
since activation was deferred to make the Fetch handler serverless-safe. On a
long-running host that call is required, not optional.

Fixes a live consequence of that stale model: `examples/slack/app/managed.ts`
never called `ready()`, so it mounted a listener, logged "started managed
Channel", and connected nothing. Covered by a regression assertion.

Drops the now-unnecessary `?.` from the examples, READMEs, and channel docs, and
adds compile-time contracts for the listener shape alongside the existing
handler ones. The examples compile with `strict: true`, so they prove the
`?.`-free call under strict null checks, which the runtime package (strict:
false) cannot.
2026-07-28 13:11:55 -05:00
Benjamin Taylor a9a073e145 test(channels): cover fallback run_error egress-failure and partial-text ordering (OSS-491)
Close two assert-without-a-test gaps found in review of the OSS-491 fix:
a failed error-post emit on the fallback run_error path propagates (drain
rejects → the delivery nacks/retries rather than acking a dropped error),
and buffered partial text is flushed before the error post (order preserved,
no lost or duplicated output).
2026-07-28 13:04:25 -05:00
Benjamin Taylor fc0bb4f498 fix(channels): surface run_error on HTTP-fallback path and isolate ack failures (closes OSS-491)
Two pre-existing SDK delivery / terminal-signal defects in the managed
channels adapter, surfaced by the pre-merge adversarial CR of #5972 and
deliberately left out of that PR's scope. Both bite the HTTP-fallback /
self-hosted path (no Connector Outbox); the realtime + Outbox path is
unchanged.

Finding 1 (HIGH) — run_error swallowed on the HTTP-fallback render path,
delivery acked as SUCCESS. `renderSinkFor`'s ad-hoc plain-text sink had no
`run_error` branch, so an agent-side failure with no accompanying text (model
error, tool crash, upstream 5xx) resolved the run, drained, and acked the
delivery as success — no user-visible message and nothing logged ("bot goes
silent on every error"). Now the fallback sink logs the raw error via
`config.log`, flushes any buffered partial text, and posts a generic
user-visible error. Posted + logged + acked (not nacked) on purpose: a
run_error is frequently a deterministic agent failure and the Channel path
reprocesses redeliveries (skipIngressDedup), so nacking would livelock. A
genuine egress failure of the error post itself still nacks (emit throws →
drain rejects). Also: `onRunErrorEvent` now calls `ensureRunStarted()` so a
run_error that precedes any text/tool event still emits a preceding
run_started.

Finding 2 (LOW) — a failed ack() nacked an already-processed turn. `ack()`
sat inside the same try as `dispatchTo`, so an ack network failure fell into
the catch and nacked a completed turn with a misleading reason; because the
Channel path reprocesses redeliveries, the whole turn reran (duplicate agent
run / posts). Ack is now a distinct phase: a failed ack is logged, never
conflated with a turn failure and never nacked.
2026-07-28 13:04:25 -05:00
Tyler Slaton 2eba274a99 fix(channels-core): default runAgent prompt from inbound message 2026-07-28 13:58:11 -04:00
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
Benjamin Taylor 5c2faac569 test(runtime,shared): make telemetry tests control their own env
The OSS-565 workflow env vars leaked into the unit job and turned 17 tests
red across @copilotkit/shared and @copilotkit/runtime. Every one of them
assumed telemetry was enabled because the ambient environment happened to
have no opt-out set — they were asserting on the environment, not the code.
A developer with DO_NOT_TRACK exported would have seen the same failures.

- shared telemetry-client + runtime get-runtime-info: clear the opt-out vars
  per test (and restore), so each case sets the state it asserts on.
- The three license→sink integration suites need `vi.hoisted`, not a hook:
  the telemetry singleton latches `telemetryDisabled` at module-import time,
  which happens before any beforeEach runs.

Verified both with the CI env set and with it unset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:46 -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
Benjamin Taylor b2c3c49201 chore: drop the changeset
Releases go through the release workflow scopes, not per-PR changesets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:09:49 -05:00
Benjamin Taylor 749a8fb2b3 refactor(web-inspector): move the telemetry egress guard under src
Importing the factory from vitest.setup.ts tripped TS6059 (file outside
rootDir) in the package's typecheck. The helper now lives in
src/lib/testing/ — test-only, never referenced by the bundle entry, and
confirmed absent from dist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:54:56 -05:00
Benjamin Taylor 20369b990c fix(web-inspector): bound the pre-handshake telemetry queues
An open/close loop against a runtime that never connects would otherwise
queue indefinitely and burst on reconnect. Caps both pending queues at 20
events — this is a signal, not a ledger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:53:27 -05:00
Benjamin Taylor 9bf283969c ci(telemetry): disable CopilotKit telemetry in automated jobs
OSS-565: no CI workflow disabled telemetry, so every job that runs or boots
real CopilotKit code emitted `oss.runtime.*` / `oss.inspector.*` events that
are indistinguishable from user activity in PostHog. Because the browser-side
events come from a real (headless) Chromium, the `$useragent != node`
heuristic can't filter them out after the fact — which is what made the
`banner_viewed` reach number untrustworthy.

Sets COPILOTKIT_TELEMETRY_DISABLED / DO_NOT_TRACK at the workflow level for
the jobs that execute app or runtime code (unit, integration, e2e-dojo,
e2e-legacy-v1, smoke-starter, showcase eval / frontend-matrix / on-demand).
The runtime reads them at startup and reports `telemetryDisabled` on /info,
which is also how the Inspector in a CI-booted app learns to stay quiet. The
frozen integration container gets them via explicit `--env` because
workflow-level env does not cross the container boundary, and the legacy-v1
Playwright webServer defaults them so local e2e runs are covered too.

The inspector's own unit tests can't be reached by an env var at all — its
telemetry keys off the /info handshake, not process.env — so they were
POSTing real events to the live sink on every run. A setup-file guard now
swallows requests to the sink and passes everything else through.

Out of scope, deliberately: showcase capture-previews drives Playwright
against DEPLOYED Railway apps, so no CI-side env can reach them; silencing
those would mean disabling telemetry on the deployed services, which would
also drop real visitors. Keep-alive needs nothing — /api/health is a static
route with no runtime and no browser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:51:46 -05:00
Benjamin Taylor 75882820c6 feat(web-inspector): add inspector-opened event and banner surface telemetry
OSS-566: there was no event recording that the Inspector panel was opened.
Opens could only be inferred from in-panel activity (a floor) or from
`banner_clicked` cta=body, which misses the floating-button path entirely.
Adds `oss.inspector.opened` with an `open_source` property, queued behind
the runtime handshake so an open is never reported for a runtime that has
telemetry disabled. Restoring a persisted-open panel deliberately does not
count — otherwise every reload / dev-server hot reload would be an "open".

OSS-568: `banner_viewed` fired once at fetch time and could not say which
of the two announcement surfaces the user actually saw — the bubble on the
collapsed widget or the card inside the opened panel. The event now carries
a `surface` stamped at fire time and dedups per (banner, surface), so
opening the panel records the card impression as its own signal. Dismissal
is also promoted to a first-class `oss.inspector.banner_dismissed` event,
emitted alongside the existing `banner_clicked { cta: "dismiss" }` so
dashboards reading the `cta` value keep working.

Both events pass the sink's `oss.inspector.` prefix gate, so no
telemetry-sink deploy is required.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:46:21 -05:00
Lukas Moschitz c70eff232c fix(showcase): fix langgraph-typescript a2ui-recovery (dom-missing / flaky heal)
Two langgraph-typescript-scope causes:
1) Fixture prompt drift: the a2ui-recovery.json userMessage keys + suggestions.ts
   pills still carried langgraph-python's copied prompt while the shared probe
   sends a langgraph-typescript-UNIQUE prompt -> aimock 404 -> heal surface never
   mounts. Retarget the 4 keys + 2 pills to the probe's unique prompt (kept
   distinct: a2ui-recovery fixtures have no x-aimock-context, so per-slug-unique
   prompts are load-bearing to avoid cross-framework collisions).
2) Flaky heal (green-then-red): TS @ag-ui/langgraph getA2UITools invokes its inner
   render_a2ui sub-agent via a config-less model.stream(), so config-based header
   forwarding never reaches it; the inner aimock call carries no x-test-id, its
   sequenceIndex falls into the never-reset DEFAULT_TEST_ID bucket, and the
   seq0->seq1 heal staging only works on the first run. Add wrapModelCall/
   wrapToolCall middleware + AsyncLocalStorage + a custom OpenAI fetch that
   forwards inbound x-* headers onto every outbound call (outer emit AND inner
   render) — mirroring the mechanism the green TS sibling mastra uses.

Not the shared Python recovery-loop defect (mastra, also TS getA2UITools, is
green — the TS path is fixable). D6 a2ui-recovery now green + STABLE (6 real ~7.7s
runs); aimock fixtures test 844 passed. Divergence documented in PARITY_NOTES.md.
2026-07-28 18:29:43 +02: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
Lukas Moschitz 0476fa6f08 fix(showcase): stop langgraph-typescript frontend-tools-async D6 loop (stale turnIndex fixture)
frontend-tools-async was flaky-red (done-signal-missing / text-unstable): the
fixture carried a legacy duplicate last fixture (userMessage 'project planning',
turnIndex:0, content + a query_notes tool call with NO id) that is absent from
the canonical D5 source. On iteration 2 (last msg = the query_notes tool result),
aimock's selectByTurnIndex prefers the turnIndex-bearing bare fixture (0<=1) over
the toolCallId-keyed follow-up, re-emitting query_notes with a fresh id whose
result never matches -> infinite re-emit loop -> turn never completes.

Remove the stale bare 'project planning' turnIndex:0 fixture so the toolCallId
follow-up is the sole iteration-2 candidate and the turn ends cleanly.

D6 frontend-tools-async now green and STABLE (4/4 consecutive real runs); aimock
fixtures test 844 passed. (Same stale-fixture defect exists on the langgraph-python
north star — flagged for a separate fleet-wide follow-up.)
2026-07-28 17:44:33 +02: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
Lukas Moschitz 557f6f7318 fix(showcase): fix langgraph-typescript tool-rendering (get_weather card missing)
tool-rendering red (expected weather-card, 0 elements): the 'weather in Tokyo'
fixture returns assistant content + a get_weather tool call together; langchain-js
streamed-chunk reassembly left the tool call only under
additional_kwargs.tool_calls (surfacing intermediate chunks as invalid_tool_calls)
and produced a bare 'generic' message, so top-level tool_calls stayed empty.
shouldContinue then routed to __end__, the tool never ran, no TOOL_CALL_* events,
no weather card. LGP's Python create_agent parses the identical fixture correctly.

Add normalizeAssistantMessage() in tool-rendering.ts (called in chatNode): when
the response isn't a well-formed AIMessage with populated top-level tool_calls,
reconstruct a clean AIMessage, promoting additional_kwargs.tool_calls into parsed
tool_calls (no-op passthrough when already correct). Restores TS<->Python parity.

D6 tool-rendering now green (two real ~5s runs); fixture + frontend untouched
(byte-identical to LGP). Only the backend agent changed.
2026-07-28 16:50:35 +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
Lukas Moschitz 629a70d918 fix(showcase): fix langgraph-typescript reasoning-display (text-unstable)
reasoning-display red with text-unstable: @langchain/openai@1.4.4's streaming
Responses converter pushes the reasoning-summary delta and the answer
output_text delta to the same content-block index (both 0), so the streaming
reducer collapses them into one reasoning block that swallows the answer.
@ag-ui/langgraph then routes the whole turn to REASONING_MESSAGE_* events and no
assistant TEXT_MESSAGE renders — the probe never settles.

Set disableStreaming:true in reasoning-agent.ts to force the non-streaming
Responses converter (processes final output items, yields separate reasoning +
text blocks), so reasoning and answer both render, matching langgraph-python's
content. Document the one behavioral divergence in PARITY_NOTES.md: the summary
no longer token-streams (arrives at once) until the upstream langchain-openai
index-collision bug is fixed.

D6 reasoning-display now green (real runs, reasoning-custom + reasoning-default);
fixture untouched (LGP-identical shape). Only the backend agent changed.
2026-07-28 16:30:49 +02:00
Lukas Moschitz 9c9cba1dbe fix(showcase): stop langgraph-typescript open-gen-ui D6 loop (turnIndex shadowing)
gen-ui-open and gen-ui-open-advanced looped (done-signal-missing): the 7 leg-1
tool-call fixtures for the open pills carried turnIndex:0, which aimock treats
as turnIndex-relaxed (default AIMOCK_STRICT_TURN_INDEX off) and keeps as a
candidate on the follow-up turn, out-selecting the toolCallId terminal so the
done signal never fires.

Change those 7 leg-1 fixtures from turnIndex:0 -> hasToolResult:false (the
request-shape predicate that excludes them once the tool result is present),
mirroring langgraph-python's canonical gen-ui-open.json shape. The 4 single-turn
narration fixtures keep turnIndex:0.

D6 gen-ui-open + gen-ui-open-advanced now green (two real ~11s/~13s runs each);
no aimock no-match/relaxed.
2026-07-28 16:17:32 +02:00
Alem Tuzlak 7e3d06f0d0 Merge branch 'main' into ci/showcase-latest-tag-race 2026-07-28 16:15:00 +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
Lukas Moschitz f6c768344c fix(showcase): register gen_ui_tool_based in langgraph-typescript server.mjs
The langgraph-typescript agent runs a custom server.mjs whose hardcoded
graphSpec — NOT langgraph.json — is the runtime graph registry. gen_ui_tool_based
was missing from it, so the langgraph server returned 404 for that graph and the
Tool-Based Generative UI cell fell through. Add gen_ui_tool_based and drop the
three redundant graph ids (reasoning-default-render, tool-rendering-{default,
custom}-catchall) that route.ts now consolidates onto shared graphs, keeping
server.mjs in sync with langgraph.json.
2026-07-28 14:38:13 +02:00
github-actions[bot] cc06f4430d style: auto-fix formatting 2026-07-28 12:19:24 +00:00
Lukas Moschitz 267f98ddda fix(showcase): align langgraph-typescript to langgraph-python north star
Bring the langgraph-typescript Showcase integration to parity with the
reference langgraph-python: byte-identical demo frontends, the same demo set
and manifest overview, framework-native TypeScript backend wiring, and
canonical mirrored aimock D6 fixtures. Scope is limited to
integrations/langgraph-typescript and its aimock/d6 fixtures.

Frontend: realign ~25 drifted demo files to the north star; add missing
src/components/ui primitives, READMEs, declarative-gen-ui/sales-context, and
the threadid-frontend-tool-roundtrip page; remove LGT-only drift; replace the
hand-rolled landing page with the manifest-driven one. Only the sanctioned
"LangGraph (TypeScript)" identity strings differ.

Manifest: rebuild to match langgraph-python's feature order, demo order,
names, tags, descriptions, and routes; drop two bogus NSF entries that were
never real features (reasoning-default-render, agentic-chat-reasoning); restore
shared-state-streaming and tool-rendering-reasoning-chain as supported cells.
not_supported_features now matches the north star (gen-ui-interrupt /
interrupt-headless — a shared upstream react-core resume-path limitation).

Backend: add the dedicated gen-ui-tool-based data-viz graph; consolidate the
tool-rendering variants and reasoning cells onto their shared graphs (matching
langgraph-python) and drop redundant duplicate graph ids; register
threadid-frontend-tool-roundtrip; move the MCP Apps runtime to the
[[...slug]] catch-all route.

Fixtures: re-mirror all 43 aimock/d6 fixtures from the canonical langgraph-python
set with match.context re-keyed, replacing previously drifted per-integration
fixtures.
2026-07-28 14:17:11 +02: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