The v1 error toast imported BasicMarkdownRenderer one folder too short,
so react-core failed to build and every downstream CI job went red.
Also: aria-pressed on the react-router markdown mode buttons, README
entry points, and docs snippets that CodeRabbit flagged as copy-paste
errors.
Keep the pluggable markdown renderer (drop bundled streamdown/katex).
Take main's inspector context, threads-drawer rename, and showcase moves.
--no-verify: this worktree has no node_modules, so lefthook cannot run.
## Summary
- normalize `examples/teams/appPackage/color.png` and `outline.png` into
Git LFS pointers
- preserve the original PNG contents and dimensions
- leave the `examples/integrations/a2a-a2ui/agent/images/` symlinks
untouched
## Root cause
The two PNGs match the repository's Git LFS attributes but were
committed as raw Git blobs. Git therefore continually cleans the working
files into LFS pointers and reports the worktree as dirty, which can
also block rebases.
## Validation
- verified both committed blobs are valid Git LFS pointers
- verified both LFS objects can be fetched from a fresh checkout of the
fork
- verified SHA-256 hashes match the original PNG contents:
- `color.png`:
`ee46987787ab5dfff4792e2df112a8d5422046296ddb1cb83fa1a85cf546d19f`
- `outline.png`:
`e653c3c3e4a700a8a46ee349463bba2169c368be803b5f48c2ae5c7ac4a7e452`
- verified image dimensions remain 192×192 and 32×32
- `MICROSOFT_APP_ID=00000000-0000-0000-0000-000000000000 NX_DAEMON=false
pnpm nx run teams-example:package --skip-nx-cache`
- independently reran previously flaky Nx test targets successfully:
- `@copilotkit/web-inspector:test` (373 tests)
- `@copilotkit/channels-slack:test` (389 tests)
- `@copilotkit/vue:test` (1074 tests)
- checked against the latest upstream `main`; the affected paths are
unchanged and merge cleanly
Fixes#6420
## What
The MCP Apps example widgets report their intrinsic size with
`ui/notifications/size-change`, but both the host and the ext-apps spec
use
`ui/notifications/size-changed`:
- Host: `MCPAppsActivityRenderer` only handles `case
"ui/notifications/size-changed"` and
reads `{ width, height }` from it to size the iframe.
- Spec: `@modelcontextprotocol/ext-apps` defines
`McpUiSizeChangedNotification` with
`method: "ui/notifications/size-changed"` (`App.sendSizeChanged`).
Because the names differ by one letter, the host never receives the size
and the widget
iframe stays at its initial height instead of growing to fit its
content.
## Fix
Rename the notification to the spec name in the affected widgets. The
payload is unchanged
(`{ width, height }`), which is exactly what the host reads, so this is
a one-line change per
widget on the sender side only.
No host change: in JSON-RPC a notification (no `id`) must not be
answered, so the host
silently ignoring the old name is correct behavior; the bug is purely
that the widgets sent
the wrong method name.
## Affected widgets
- `examples/showcases/mcp-apps/mcp-server/apps`: flights, hotels,
kanban, trading
- `examples/showcases/generative-ui-playground/mcp-server/apps`:
calculator, flights, hotels,
kanban, todo, trading
## Testing
Reproduced against a local run of `examples/showcases/mcp-apps` (Next
frontend + MCP server):
before, the widget iframe rendered at its initial height; after the
rename the host receives
`size-changed` and the iframe resizes to the widget content.
## A note from the contributors
From the team at MCP Apps Builders - part of our ongoing series to round
out MCP Apps host
support in CopilotKit. Opening as a draft for review.
The `examples/integrations/agentcore` README references
`docs/LOCAL_DEVELOPMENT.md` and `docs/LOCAL_DOCKER_TESTING.md`, but the
example has no `docs/` directory and neither file exists anywhere in the
example. The local-development workflow is already fully documented
inline in the "Local Development" section, so these references are dead
links.
Verified: `docs/` is absent from `examples/integrations/agentcore/`, and
a repo-wide search finds no `LOCAL_DEVELOPMENT.md` /
`LOCAL_DOCKER_TESTING.md`.
The Angular demo and Storybook use Angular 22.1, while @copilotkit/angular is developed against Angular 22.0. Both consumers map @copilotkit/angular directly to its source files.
That causes TypeScript to load Angular types from two dependency contexts. Angular signal types contain unique-symbol brands, so signals originating from Angular 22.0 are incompatible with otherwise equivalent signals from Angular 22.1.
Pin the demo and Storybook to Angular 22.0 so the source-linked library and its consumers share the same Angular type identity. Update pnpm-lock.yaml to keep frozen installs reproducible.
The existing unit workflow intentionally selects only packages/**, while the packed-package test does not exercise these source-linked monorepo consumers.
The lib/hooks/README.md in the travel example links to
../../agent/travel/search.py, but the file is at
xamples/v1/travel/agent/src/search.py. Fix the relative path to
../../agent/src/search.py.
useAgentContext stringifies any non-string value before it leaves the
browser, and the AG-UI protocol types Context.value as a string on both
ends. An agent therefore always reads a JSON string, never the object or
array that was registered. None of the four reference pages said so; they
stopped at "serialized automatically", which reads as "the framework
handles it".
An author who believes that writes an agent that reads the object. When
the resulting shape check fails, the agent cannot distinguish "context
arrived JSON-encoded" from "no context was sent" -- the two are
identical -- so it refuses every request while the browser is registering
context correctly. That is what happened on the both-oss
langgraph-python conversion journey, where the agent's
isinstance(value, list) guard could never pass and the journey was dead
on arrival.
Each page now carries a "What the agent receives" section: the wire shape
as literal JSON, json.loads and JSON.parse examples, and a callout naming
the shape check as the trap. The value parameter description and the
Serialization behavior bullet now name the consequence for the agent
author instead of stopping at the browser half.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## What
Converts `examples/integrations/agentcore` from unlocked
`requirements.txt` files to uv projects (`pyproject.toml` + `uv.lock`),
matching every other Python integration example in this repo.
## Why
The example already used uv as an *installer* — the agent images are
built from `ghcr.io/astral-sh/uv` and ran `uv pip install -r
requirements.txt` — but nothing was locked, so each image build
re-resolved transitive dependencies against whatever PyPI had that day.
That had already drifted into a hard break. `langgraph==1.0.10rc1`
resolved alongside a `langgraph-prebuilt` that reads `ExecutionInfo` off
`langgraph.runtime`, which 1.0.x does not export, so the LangGraph agent
raised `ImportError` at container start.
## The migration
- Both agents get a `pyproject.toml` + `uv.lock`; the Dockerfiles
install with `uv sync --locked` and run out of `/app/.venv`.
- LangGraph agent moves to `langgraph==1.1.6` / `langchain==1.2.15`, the
pair used by `examples/integrations/langgraph-python`, which resolves
the import failure. `langchain` was previously `>=0.3.0` while the code
uses the 1.x `create_agent` API.
- Four packages that shipped code imports directly were declared nowhere
and survived only as transitives: `boto3` (both agents, via
`agents/utils/ssm.py`), `PyJWT` (langgraph, via `agents/utils/auth.py`),
`langchain-core` (langgraph, via `tools/todos.py`), `botocore` (root,
via `scripts/utils.py`). All now declared; no resolved version changed.
- `aws-opentelemetry-distro` moves from a loose second `uv pip install`
into the locked set.
- The example root gets a project for the `scripts/` helpers. Their
`requirements.txt` listed the dependencies but nothing installed it, so
`uv run scripts/test-agent.py` — the command that script's own docstring
gives — failed on a missing `requests`.
- Deploy and local-dev scripts call `uv run` instead of bare `python3`;
preflight checks for `uv` rather than `python3`.
## Terraform
The docker-mode image hash read `patterns/<pattern>/requirements.txt`,
`patterns/utils`, a root-level `gateway/` and `tools/`, and a root
`pyproject.toml` — none of which exist here. `filesha256` on a missing
file is a plan-time error. Repointed at `agents/<pattern>` and
`agents/utils`, now hashing `pyproject.toml` and `uv.lock`.
The hash then had to exclude the virtualenv the migration creates:
`fileset(pattern_dir, "**/*.py")` saw 3114 files instead of 5, and the
computed hash differed depending on whether a developer had ever run the
agent locally — feeding `replace_triggered_by` and forcing a runtime
replacement. Measured: `5f9a98ef…` with a venv present vs `bacab1e1…`
without, on identical committed sources. The fix filters `.venv/` and
`venv/` and produces the clean-checkout digest in both cases.
## Review
Five review rounds plus a promotion audit, and nine defects this
migration introduced were found and fixed before merge. Each fix was
verified by running the thing, not by reading it:
- The image hash sweeping the local virtualenv (above).
- The undeclared direct dependencies (above).
- `scripts/test-agent.py` piped the child's output and never drained it,
deadlocking the agent, while the startup-timeout branch blocked forever
on a read — the only channel carrying uv's lockfile-drift error.
- `--local` treated any listener on port 8080 as the agent, so a foreign
process produced "Agent started successfully" while the real child died.
Adopting a running process is now explicit opt-in.
- The documented invocation contract had drifted across the two READMEs
and both scripts' usage text; a single reconciliation pass now owns all
of them, and every documented command was executed to confirm it works.
- The Terraform README named the CloudFormation deploy script, which
cannot read a Terraform deployment.
- Two `.env.example` entries parsed as their own trailing comment text
under Docker Compose.
- The build context shipped 898 MB of local-only artifacts (measured);
now 11 kB.
- Assorted comment and message inaccuracies, including a container env
block whose comment attributed a runtime-critical variable to uv.
## Repository hygiene
Two fixes the pre-merge gates surfaced rather than the review rounds:
- `oxfmt` on the example README and `tofu fmt` on the Terraform locals
file. The README break was introduced by this branch; the Terraform one
predates it but sits in a file this branch edits.
- The Terraform ignore rules were anchored to the top level, so the
provider cache `terraform init` writes beside every *nested* module was
fully stageable — a measured 834 MB one `git add -A` from being
committed. Switched to unanchored patterns, matching what this branch
already did for the virtualenv layout. Verified with `git check-ignore`
that the nested cache and lock file are now covered, tfstate and tfvars
still are, and no tracked file is caught by the wider patterns.
## Not addressed
Roughly 90 further findings are real but pre-existing and belong to four
follow-up subjects, listed in full in the review ledger:
- **Terraform module repair** — `terraform validate` fails on five
undeclared resources, so this module cannot plan at all. Zip mode
references an entry point and a packager directory that do not exist.
- **test-agent AG-UI correctness** — the request body fails
`RunAgentInput.model_validate` on both agents, and the response decoder
parses a pre-AG-UI format, so failures print nothing and exit 0.
- **README accuracy** — a `docs/` directory that does not exist, a wrong
Node floor, a teardown block whose second command never runs.
- **Deploy-script hardening** —
`infra-terraform/scripts/deploy-frontend.py` requires a Terraform output
nothing declares, so it exits 1 every run; the README now says so rather
than presenting it as a working path.
## Verification
Both images built for `linux/arm64` and the agent module imported inside
each, after every fix cycle. `uv lock --check` clean on all three
projects. Every documented command executed from its documented
directory. The virtualenv hash filter, the build-context reduction, the
pipe deadlock, the port-adoption fix and the env-file parsing were each
verified by measurement with before/after output. No repo CI job builds
or lints this example, so these local runs are the only coverage that
exists.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
**Merge order: #6711 → #6718 → this PR.**
#6718 rewrites `identifyUser` in the same 22 blocks and is a sibling of
this branch, not stacked on it, so the two overlap on the same lines.
Landing the gate last means it ratchets on the finished shape and avoids
a conflict. If this PR goes first instead, #6718 goes red until it moves
all 22 sites together — which is the gate working, but noisier.
Stacked on #6711 — merge that first. This branch descends from it, so
the diff below carries its commit too; GitHub drops those once #6711
lands.
Basing this PR on `main` rather than on #6711's branch is deliberate: 17
of 35 workflows filter `pull_request: branches: [main]`, including the
one this PR extends, so a PR based on the 981 branch would not run the
check it adds.
## Problem
The marked block that wires managed Intelligence is the region a hosted
reader copies verbatim, and nothing checked it. Both gaps are
deliberate, not accidental:
- `examples/integrations/_parity/manifest.json` lists
`src/app/api/copilotkit/**` under `allowedDivergence` for every instance
it tracks. What parity does hold byte-identical is the demo frontend: 54
verbatim paths of example canvas, todo columns and charts.
- No `examples/integrations/*/docker-compose.test.yml` sets
`COPILOTKIT_LICENSE_TOKEN`. The wiring is a ternary on that variable, so
all 13 smoke-tested starters take the else arm. The `intelligence:` arm
has never executed in CI, in any starter.
The cost was already visible. The block's code was byte-identical in 21
of 22 starters, but its warning comment had drifted into five variants
and the two `ms-agent-framework-*` starters shipped the `demo-user` stub
with no warning at all. Comment drift is harmless by itself; it is the
tracer showing nothing held the region still, and it is how the
localhost default of #6711 survived in all 22 copies at once.
## Change
`scripts/validate-intelligence-wiring-block.ts` greps the opening
marker, compares every site against the north-star starter, and fails on
the first line that differs. Two normalisations keep it usable:
- The block is dedented, because `agentcore` nests it two levels deeper
— its runtime is a Lambda handler, not a Next.js route.
- The else arm's runner name is masked, because `agentcore` runs
`AgentCoreRunner` in front of a Bedrock AgentCore session where an
in-process runner has nothing to run. `EXPECTED_RUNNER` holds that one
exception, so a runner swapped in by accident still fails.
Everything else, comment text included, must match to the byte. Then the
warning is unified at all 22 sites on the fullest existing wording,
which also says the id must exist in Intelligence or thread operations
can fail.
It compares against the north star rather than a literal kept in the
script, so improving the block means editing `langgraph-python` and
running the other 21 to match.
## What it does and does not guarantee
It is a shape gate, not a content gate: 22 identically wrong copies
still pass. What it guarantees is that a fix reaches all of them or
none. The check passes on day one — 21 of 22 already matched on code —
so it is a ratchet, not a migration.
## Verification
Mutating a real starter three ways, each caught:
| Mutation | Reported as |
| --- | --- |
| Dropped one comment line | `line 15 differs from the north star`, exit
1 |
| \`InMemoryAgentRunner\` → \`SomeOtherRunner\` | `else arm uses
SomeOtherRunner; expected InMemoryAgentRunner` |
| Reintroduced \`?? \"http://localhost:4201\"\` | `line 6 differs`, both
sides shown |
The third matters: the #6711 regression is now caught at a second site,
independent of the env-name validator.
Commands run, all exit 0:
- `pnpm exec vitest run
scripts/__tests__/validate-intelligence-wiring-block.test.ts
scripts/__tests__/validate-intelligence-env-names.test.ts` — 30 passed
- `pnpm check:intelligence-wiring-block` — `All 22 Intelligence wiring
sites match langgraph-python.`
- `pnpm check:intelligence-env-names` — unaffected, still canonical
- `pnpm parity:verify`
- `oxfmt --check`, `oxlint`, and `tsc --noEmit --strict` on the new pair
Two tests guard the gate against going vacuous: one asserts at least 22
marker files are discovered, so an empty violation list cannot pass on
an empty file list.
Not run locally: the lefthook pre-commit suite, which fails
environmentally in a worktree without per-package installs (`sh: vite:
command not found`). This diff touches no package source.
## Not covered
Enrolling the `intelligence:` arm in the smoke path. It needs a license
token in CI secrets and an endpoint reachable from the compose network —
a different size of job, tracked separately.
## What does this PR do?
`CopilotKitIntelligence` is built to be correct when the caller says
nothing: omitting `apiUrl`/`wsUrl` resolves to
`https://api.intelligence.copilotkit.ai` and
`wss://realtime.intelligence.copilotkit.ai`, and its docstring says so
outright — *"leaving both unset is always correct against it."*
Every starter's runtime route defeated that default:
```ts
apiUrl: process.env.INTELLIGENCE_API_URL ?? "http://localhost:4201",
wsUrl:
process.env.INTELLIGENCE_GATEWAY_WS_URL ?? "ws://localhost:4401",
```
With the variables unset — the correct configuration for a managed user
— the `??` supplies localhost and the runtime aims at a local stack that
is not running. This is the artifact `copilotkit init` clones, so it is
the first thing a new managed user runs.
The starter's own `.env.example` already warns about exactly this
failure, two files away:
> `INTELLIGENCE_API_URL` and `INTELLIGENCE_GATEWAY_WS_URL` point at a
self-hosted or local Intelligence deployment only — leave them unset
when using managed Intelligence, or the channel host and runtime will
try to reach a local stack that usually is not running.
So the documentation was right and the code contradicted it.
`channel-host.mts`, in the same directories, already had the correct
shape.
### The fix
**22 runtime wiring sites** (20 route handlers, `adk-angular/server.ts`,
and the AgentCore Lambda) now use the conditional spread these starters
already use in `channel-host.mts`, so a self-hosted override still works
and the managed default applies when absent:
```ts
...(process.env.INTELLIGENCE_API_URL
? { apiUrl: process.env.INTELLIGENCE_API_URL }
: {}),
```
No hosted URL is written into the examples — the library already owns
them, so this is a deletion.
**3 `.env.example` files** (`agent-spec`, `llamaindex`, `mcp-apps`) set
the same values *uncommented*. Two do it directly beneath a comment
telling the reader to leave them unset, and an `.env.example` is copied
to `.env`, so these were the remaining route to a localhost value once
the code default was gone. Commented out to match the other nineteen
starters; `agent-spec` had no explanation at all and gets the standard
one.
**A guard**, added to the existing
`scripts/validate-intelligence-env-names.ts` rather than a new script —
it already polices the canonical Intelligence key name and the two dead
hosts, and its workflow is deliberately unfiltered so it sees every
README, example and skill. Two rules: `managedUrlFallback` (a `??`/`||`
default on either variable) and `managedUrlEnvFileAssignment` (an
uncommented env-example assignment). The rule is the *pattern*, not the
literal, so a staging host substituted for localhost fails the same way.
Five files legitimately want a local target and are allowlisted with
their reasons: the `playwright.config.ts` and `.env.example` of the
banking and reskinnable-demo showcases (own vendored compose ports
7050/7053 and 7250/7253, own seeded org keys) and
`agentcore/docker/.env.example` (the documented local development
stack). Resolving those to the managed hosts would aim an offline test
suite at production.
`scripts/__tests__` has no general runner, so the workflow runs this
test file explicitly, following the `plugin-skills-check.yml` precedent
— otherwise a rule that silently stopped matching would leave the check
passing on an empty result.
## Related PRs and Issues
- Closes OSS-981.
- Supersedes the canceled ENT-922, whose blocker ("do not invent hosted
URLs; rewrite once the managed env contract is final") no longer
applies: the contract shipped as `MANAGED_INTELLIGENCE_API_URL` /
`MANAGED_INTELLIGENCE_WS_URL`, and the fix removes a fallback rather
than adding a URL.
- ENT-949 shipped a warning for this class of mistake, but
`warnOnPartialHostOverride` only fires on a *partial* override — both
values defaulting to localhost together is not partial, so nothing
warned.
## Verification
- `pnpm exec vitest run
scripts/__tests__/validate-intelligence-env-names.test.ts` — 13 passed.
Written first: the rules were red before they existed, then reported
**48 violations across 24 files** for the code rule and **10 across 5**
for the env-file rule; the fixes took both to green.
- `pnpm check:intelligence-env-names` — exit 0.
- `oxfmt --check` and `oxlint` over all 25 touched files — clean.
- The spread typechecks under `strict` + `exactOptionalPropertyTypes`,
the setting that would reject `apiUrl: string | undefined`.
- The marked wiring block stays byte-identical across 21 of 22 starters
(`agentcore` differs only in its runner), and no `localhost` remains
inside any marked block.
- Not run locally: the 13 starter Next builds. `test_smoke-starter.yml`
typechecks the route handlers in CI on this PR.
### Out of scope
`agentcore/docker/docker-compose.yml` keeps its
`${INTELLIGENCE_API_URL:-http://localhost:4201}`: it is compose
substitution in the documented local-dev stack, not shipped runtime
code. Separately that default cannot work anyway — inside the bridge
container `localhost` is the container's own loopback — but that is a
different bug.
## Checklist
- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] "Allow edits by maintainers" is checked
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## What
Sunsets `examples/showcases/banking`. It is superseded by
`examples/showcases/reskinnable-demo`, which ships the same banking
experience as one of its runtime-swappable skins (alongside airline) on
top of a shared shell. Keeping both means maintaining two copies of the
same demo.
160 files deleted, plus the five places that pointed at the app:
| File | Change |
| --- | --- |
| `pnpm-workspace.yaml` | Drops the workspace entry. Also fixes the
adjacent NOTE, which attributed the canary AG-UI pin to "banking's
agent" when it is reskinnable-demo's own Python deep agent that needs
it. |
| `pnpm-lock.yaml` | Regenerated (−919/+12). The 12 additions are
peer-suffix re-keying caused by removing the importer — banking pinned a
different `eslint`/`vitest` peer combination. No dependency version
changes. |
| `examples/README.md` | The banking row becomes a reskinnable-demo row,
so the successor is listed and the showcase count is unchanged. |
| `showcase/shell-docs/src/content/docs/faq.mdx` | The "Banking
Assistant" link retargets to reskinnable-demo instead of 404ing. |
| `.github/config-allowlist.txt` | Drops the deleted `next.config.mjs`.
|
Note that banking was a real pnpm workspace member using `workspace:*`
deps, unlike reskinnable-demo, which sits deliberately outside the
workspace with its own lockfile. That is why the root lockfile has to be
regenerated here.
## Deliberately not changed
- `scripts/migrate-demos.sh` and `scripts/archive-demo-repos.sh` still
name `examples/showcases/banking`. Those are the already-executed
one-shot manifests for the repo consolidation; the path is a historical
record in them, not a live reference.
- `reskinnable-demo`'s `.env.example` and `docker-compose.yml` still
explain their +200 port offset in terms of banking's stack. The offset
stays real, and "was cloned from banking" stays true.
- The `banking` mentions in `test_reskinnable-demo.yml` refer to
reskinnable-demo's **banking skin**, not this app.
## Verification
- `.github/scripts/check-config-allowlist.sh` passes.
- A full `pnpm install` agrees with the regenerated lockfile (no further
diff).
- The lockfile-only regen and the full install produce identical output.
The `test-and-check-packages` pre-commit hook fires on any
`pnpm-lock.yaml` change, so it ran `test,publint,attw` across all 25
packages. Four suites failed locally — `sqlite-runner`, `web-inspector`,
`vue`, `react-core` — in a worktree installed with `--ignore-scripts`,
which skips `better-sqlite3`'s native build. This change touches no
package source, so CI is the gate on those; please confirm they are
green here before merging.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
CopilotKitIntelligence resolves apiUrl/wsUrl to the managed hosts when they are
omitted, and its own docstring says leaving both unset is always correct against
the managed service. Every starter's runtime route supplied
`?? "http://localhost:4201"` instead, so a managed reader who copied the block
got a runtime aimed at a local stack that is not running -- the failure the
starter's own .env.example warns about two files away.
Replace the fallbacks with the conditional spread these same starters already use
in channel-host.mts, so a self-hosted override still works and the managed
default applies when it is absent. Three .env.example files also set the values
uncommented, two of them directly under a comment telling the reader to leave
them unset; comment those out to match the other nineteen starters.
Guard both shapes in validate-intelligence-env-names.ts, which already polices
the canonical Intelligence key name and hosts and runs unfiltered on every PR.
The rule is the pattern rather than the literal, so a staging host substituted
for localhost fails the same way. Local e2e harnesses and demo stacks that
genuinely target a local deployment are allowlisted with their reasons.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The marked block that wires managed Intelligence is the region a hosted reader
copies verbatim, and nothing checked it. Both gaps were deliberate: the parity
manifest lists `src/app/api/copilotkit/**` under `allowedDivergence` for every
instance it tracks, and no `docker-compose.test.yml` sets
`COPILOTKIT_LICENSE_TOKEN`, so every smoke-tested starter takes the else arm and
the `intelligence:` arm has never run in CI.
The cost was already visible. The block's code was byte-identical in 21 of 22
starters, but its warning comment had drifted into five variants and the two
`ms-agent-framework-*` starters shipped the `demo-user` stub with no warning at
all. That drift is how the localhost default of OSS-981 survived in all 22
copies at once.
Add `scripts/validate-intelligence-wiring-block.ts`, which greps the opening
marker, compares every site against the north-star starter, and fails on the
first line that differs. Two normalisations keep it usable: the block is
dedented, because `agentcore` nests it deeper, and the else arm's runner name is
masked, because `agentcore` runs `AgentCoreRunner` in front of a Bedrock session
where an in-process runner has nothing to run. Everything else, comment text
included, must match to the byte.
Then unify the warning at all 22 sites on the fullest wording, which also says
the id must exist in Intelligence or thread operations can fail.
The check passes on day one, so it is a ratchet rather than a migration. It is a
shape gate, not a content gate: 22 identically wrong copies still pass. What it
guarantees is that a fix reaches all of them or none.
Not covered: enrolling the `intelligence:` arm in the smoke path. That needs a
license token in CI and a reachable endpoint from the compose network, and is
tracked separately.
CopilotKitIntelligence resolves apiUrl/wsUrl to the managed hosts when they are
omitted, and its own docstring says leaving both unset is always correct against
the managed service. Every starter's runtime route supplied
`?? "http://localhost:4201"` instead, so a managed reader who copied the block
got a runtime aimed at a local stack that is not running -- the failure the
starter's own .env.example warns about two files away.
Replace the fallbacks with the conditional spread these same starters already use
in channel-host.mts, so a self-hosted override still works and the managed
default applies when it is absent. Three .env.example files also set the values
uncommented, two of them directly under a comment telling the reader to leave
them unset; comment those out to match the other nineteen starters.
Guard both shapes in validate-intelligence-env-names.ts, which already polices
the canonical Intelligence key name and hosts and runs unfiltered on every PR.
The rule is the pattern rather than the literal, so a staging host substituted
for localhost fails the same way. Local e2e harnesses and demo stacks that
genuinely target a local deployment are allowlisted with their reasons.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The demo mounts the runtime as its own Next route handler, so app and runtime
share one process. Restarting the runtime restarts the dev server and reloads
the page, which re-runs the startup handshake and hides any mid-session
connection behaviour under observation.
Read the runtime URL from NEXT_PUBLIC_COPILOTKIT_RUNTIME_URL when it is set, so
the demo can be pointed at a runtime running as a separate process (e.g.
examples/v2/runtime/express). Unset, behaviour is unchanged.
The MCP Apps example widgets reported their size with
`ui/notifications/size-change`, but the host (MCPAppsActivityRenderer) and the
ext-apps spec both use `ui/notifications/size-changed` (McpUiSizeChangedNotification,
App.sendSizeChanged). The host therefore never received the size and the widget
iframe stayed at its initial height instead of matching the content.
Rename the notification to the spec name in the affected widgets (payload
`{ width, height }` already matches what the host reads). No host change: a
notification cannot be answered in JSON-RPC, so ignoring the old name is correct;
this fixes the sender side.
Affected: showcases/mcp-apps (flights, hotels, kanban, trading) and
showcases/generative-ui-playground (calculator, flights, hotels, kanban, todo, trading).
## What does this PR do?
Design review on the launcher and its notification dot asked for three
things: a milder face than solid black, fewer borders and background
layers, and a subtle shadow in place of the dot's heavy border. This is
all three, plus the removal of six utilities that never had any effect.
Everything here was compared side by side at production size, on a light
*and* a dark host page, before it was chosen. Two of my own first
proposals were dropped after measuring them, both described below.
**Two concerns, three commits.** `4fa38d91f` and `cd711b369` are the
launcher itself — the package change, 2 files. `295f75495` gives the
react-router lab a dark mode, because a dark host page is what this
change has to be judged against and the lab could not produce one. If
you would rather review those separately, say so and I will split them.
## The face
`#181C1F` at 95%, which review asked for.
Worth recording so it does not come up again: the near-black the review
saw was `#010507`, 20.5:1 against white. What shipped yesterday was
already `#1C1F24` at 16.5:1, so this value is a hair *darker* than the
one it replaces (17.2:1) and the difference between them is a ΔE of 2.3,
at the floor of what an eye can separate. It settles the question rather
than changing the look.
## Fewer layers
Six Tailwind utilities on the launcher set properties the unlayered
`css` block sets again — `bg-slate-950/95`, `border-white/20`, `ring-1`,
`ring-white/10` and the two hover variants. Unlayered declarations beat
layered ones regardless of specificity or source order, so none of them
has ever had any effect. Each was the package's only use, so the
checked-in stylesheet drops 980 bytes.
Of the *visible* layers, two went:
**The outer hairline.** The launcher carried two concentric lilac rings:
the border, and a second one 1px outside it as a box-shadow. The outer
one also hardcoded the lilac rather than reading `--cpk-launcher-edge`,
so it silently could not follow the token. It is replaced by a one-pixel
light edge along the top, which is what keeps the face from reading flat
without drawing a frame.
**`backdrop-blur-md`.** It sat behind a 95%-opaque fill and bought close
to nothing, while mounting a permanent blur compositing layer over a
customer's page.
**The border stays, and this is the finding that changed my mind.** I
first proposed removing it too. Against a dark host page the face
measures 1.10:1 (GitHub dark), 1.04:1 (Tailwind slate-900) and 1.22:1
(black) — indistinguishable from the page. The border is the only thing
that gives the launcher an outline there. It is not decoration.
## The dot
The collar was `1.5px`, opaque, zero blur, and 21% of the dot's
footprint. Because the dot's centre sits *on* the rim, its outer half
painted a hard dark crescent onto the **host page** rather than onto the
launcher — which is what read as "heavy". A hairline plus a soft drop
separates it just as well.
The fill becomes a lens lit from the upper left. Both stops are derived
from `--cpk-launcher-signal`, so a new tone needs no new values;
verified for the rose error tone and the violet announcement tone.
**Dropped after looking at it:** a coloured glow around the dot. It was
the obvious reading of "more premium", but the launcher already pulses
in that same colour when a failure is new, and a permanent glow competes
with the thing that is supposed to draw the eye.
**Also dropped:** tinting the border in the signal colour, which was
suggested in review. On a dark page the border is the entire silhouette,
so tinting it recolours the whole launcher for a state that can persist
for hours.
## One non-obvious consequence
Removing the blur removed a side effect nobody had written down:
`backdrop-filter` promotes the element to its own compositing layer.
Without a layer, the hover `scale(1.05)` re-rasterises the mark every
frame and it visibly jitters — geometrically nothing moves, the mark's
centre holds to three decimals, but the vector is re-rendered at
fractional offsets. `will-change: transform` asks for the layer directly
and the jitter is gone. Confirmed by eye on the running demo before this
was chosen.
## Tests
`packages/web-inspector` stays at **28 files / 611 tests**, all passing.
No new tests. The colour tests here are deliberately token-shaped rather
than value-shaped — they assert the custom property and the *sharing* of
one face and one edge between the launcher and its pill, never a hex —
so face and edge values are free to move and this change is exactly the
kind they were written to allow. The one test that constrains it, `"the
pill and the launcher share one surface and one edge"`, still passes.
What is genuinely unguarded, and was before this PR too: the dot's
collar width, the double hairline, and the Tailwind class list.
Asserting rendered geometry would need a browser test runner, which this
package does not have — jsdom computes no layout.
## How to see it
`pnpm --filter react-router-example dev`, then `http://localhost:5173`.
The launcher is top right; `Break runtime` arms the error tone and
`Break run` the announcement one. Hover it to check the mark no longer
jitters.
One thing worth knowing while reviewing: the launcher anchors top-right
and is `position: fixed` on an element mounted directly under `<body>`,
so on this page it sits over the lab's toolbar. Drag it to the lower
right and it is out of the way.
## The lab's dark mode
A dark host page is where the launcher's border earns its place, and the
lab had no way to produce one, so reviewing this change was not possible
without it.
It follows `examples/v2/react/demo` rather than inventing anything: the
host owns a `theme` state, and `CopilotChat` gets `className="dark"` —
which is what makes the package swap its own variable set. The colours
are the demo's by another route; it writes the oklch literals
CopilotKit's variables use, and those are Tailwind's neutral steps
(`neutral-950` is `oklch(0.145 0 0)`, `neutral-50` is `oklch(0.985 0
0)`, `neutral-800` is `oklch(0.269 0 0)`). Measured identical on the
running lab.
`@custom-variant dark (&:is(.dark *))` is needed in the lab's stylesheet
because Tailwind v4 points `dark:` at `prefers-color-scheme` by default,
so the toggle would have lost to the OS. Same declaration the package
uses for its own sheet.
Two details that are decisions rather than oversights. The **error
banner keeps a rose tint** in dark mode instead of going neutral,
because an error banner that looks like every other surface is not an
error banner. And the **toolbar buttons keep a visible on/off contrast**
— active inverts to a light face, inactive sits on `neutral-800` —
because the lab's whole purpose is knowing which failure is currently
armed.
My first attempt stripped every background instead of theming, and that
is worth recording because it looked plausible: the chat bubble, the
send button, the button states and the banner all collapsed into one
flat grey. The chat paints its own surfaces and has to be told what
theme it is in, not undressed.
## A separate bug found on the way
`CopilotKitProvider` documents `inspectorDefaultAnchor` — *"Default
anchor corner for the inspector button and window"* — and it has no
effect. `defaultAnchor` is typed on the React wrapper and forwarded to
the element, but the string `defaultAnchor` does not occur anywhere in
`packages/web-inspector`, so it lands as `defaultanchor="[object
Object]"` and is ignored. The corner stays hardcoded `{ horizontal:
"right", vertical: "top" }` in two places.
Not fixed here, to keep this PR to one concern. It is worth fixing: any
host with a top navigation bar hits exactly this, finds exactly that
prop, and it does nothing.
## Related PRs and Issues
- Follows #6646
Removed redundant text and improved clarity in the README. Removed stale
and internal references
## What does this PR do?
Fixed the Readme references for the open mcp app example, it was
referencing stale docs folder which does not exist.
## Related PRs and Issues
- (Direct link to related PR or issue, if relevant)
## Checklist
- [ *] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [ ] If the PR changes or adds functionality, I have updated the
relevant documentation
# fix(showcases): make MCP Apps deployable
## Summary
- keep the approved `@copilotkit/*` `1.68.1` upgrade and frontend
Railway health check
- import the endpoint stack and built-in agent from
`@copilotkit/runtime/v2`, matching the current
`examples/integrations/mcp-apps` runtime pattern
- align MCP Apps on one AG-UI `0.0.58` client/core/encoder/proto graph
with `@ag-ui/mcp-apps-middleware@^0.0.3`
- replace the stale standalone pnpm lock with the npm lock consumed by
Docker, and make Docker install it deterministically with `npm ci
--legacy-peer-deps`
- remove only unused frontend direct dependencies;
`@copilotkit/react-core`, `@copilotkit/runtime`, and
`@copilotkit/shared` remain direct `1.68.1` dependencies, while the
separately packaged MCP server retains its own `zod@^4.3.5`
## Root cause and RED evidence
- Baseline commit: `0daa38f9a7e50522e246a273802ef3ffde92556b` on current
`origin/main` `c2abbea9cf`.
- `./node_modules/.bin/tsc -p examples/showcases/mcp-apps/tsconfig.json
--noEmit --pretty false` reproduced three compatibility failures:
`TS2305` for `createCopilotEndpoint`, `TS2305` for
`InMemoryAgentRunner`, and `TS2345` because `MCPAppsMiddleware` and the
runtime resolved incompatible `@ag-ui/client` types.
- Before production edits, `npm test` ran the new compatibility contract
and failed `3/3` assertions for the legacy runtime entrypoint, stale
AG-UI versions, and missing npm lockfile.
- Before changing the Docker install command, the deployment contract
failed `1/4` because the Dockerfile still used `npm install
--legacy-peer-deps`.
- The first Docker build sent `889.11 MB` because no `.dockerignore`
excluded host `node_modules` and `.next`; the final context is `5.64
kB`.
## GREEN and verification evidence
- PASS — scoped formatter: repo-pinned `oxfmt --check` exited `0` for
the changed source, contract, and manifest inputs.
- PASS — scoped lint: repo-pinned `oxlint` reported `0 warnings` and `0
errors` for the changed route and compatibility test.
- PASS — explicit typecheck: standalone `./node_modules/.bin/tsc
--noEmit --pretty false` exited `0`.
- PASS — focused tests: `npm test` passed `5/5` contracts covering the
v2 runtime entrypoint, AG-UI graph, npm lock truth, deterministic Docker
install, and Docker context exclusions.
- PASS — production build: `npm run build` compiled, typechecked,
generated all four static pages, and emitted the
`/api/copilotkit/[[...slug]]` dynamic route.
- PASS — npm lock/install truth: `npm ci --legacy-peer-deps
--ignore-scripts` installed `1,234` packages from `package-lock.json`;
`npm ls @ag-ui/client @ag-ui/core @ag-ui/encoder @ag-ui/proto --all`
showed every instance deduped/overridden to `0.0.58`.
- PASS — no-cache container gate: `docker build --no-cache -f Dockerfile
-t codex-mcp-apps-pr .` completed `npm ci`, the in-image Next production
build, image export, and unpack on `node:20-slim` with exit `0`.
- PASS — scope/secret/type hygiene: the final PR changes only
`examples/showcases/mcp-apps/**`; high-confidence secret patterns, type
suppressions, swallowed catches, and floating calls were absent from the
staged diff.
- PASS — branch commit: `db929211253c5f6e4a9567833bdb0d4b6e7c7d8c`
(`fix(showcases): align MCP Apps runtime graph`), following the approved
deployability commit without modifying its Railway file.
## Non-blocking warnings
- npm reports 10 transitive audit findings (5 low, 1 moderate, 4 high);
this compatibility change does not force unrelated breaking upgrades.
- The Node 20 container install emits `EBADENGINE` warnings for
transitive `@azure/*`, `@typespec/ts-http-runtime`, and `openai@7.5.0`
packages that declare Node 22, but the clean Node 20 install and
production build both complete successfully. The existing `node:20-slim`
base is intentionally unchanged because the gate did not reproduce a
failure.
- Local Next builds inside the monorepo warn about the root pnpm lock
plus the standalone npm lock; the standalone build still completes
successfully and Docker uses only the showcase-local npm lock.
# fix(world): preserve LangGraph assistant config
## Summary
- restore the World demo's browser-supplied OpenAI key under
`assistantConfig`
- preserve the behavior of the authoritative legacy World commit
- add a dependency-free migration contract test
## Verification
- rebased cleanly onto current `main` at
`c2abbea9cf6a48c22b1dcd19e9dc469d5cfd458f`
- verified the authoritative legacy source still uses `assistantConfig`
at `markmdev/copilotkit-world@7a27a37b628b9e339d1284ec42382008e67884f5`
- verified `@ag-ui/langgraph@0.0.7` declares, stores, and merges
`assistantConfig`
- regression check against current `main` failed for the expected
missing-`assistantConfig` reason; the branch's dependency-free Node test
passed (`1/1`)
- `oxfmt --write` plus `oxfmt --check` passed on both changed files
- Nx reported no affected lint targets; direct `oxlint` completed with
`0` errors and one pre-existing `NextRequest` type-import warning on the
unchanged import line
- root dependency bootstrap passed with the repository-pinned pnpm
`10.33.4` and `--frozen-lockfile --ignore-scripts`
- `nx run @copilotkit/runtime-client-gql:build` and its 13 dependency
builds passed; this does not make that package resolvable from the
independently managed nested ChatKit Studio workspace
- exact-base and branch World typechecks used the same pnpm `9.15.0`
provisioning: base reports seven diagnostics, branch reports six, and
the branch-only diagnostic count is zero
- the removed base diagnostic is the changed route's invalid `config`
property; all six remaining diagnostics are identical current-`main`
errors in unchanged `page.tsx`, `useCountryData.ts`, and
`countryData.ts`
- both production builds compiled the optimized application source; base
then failed on the invalid `config`, while the branch advanced past the
changed route and stopped on the pre-existing undeclared
`@copilotkit/runtime-client-gql` import
- Python agent compilation passed
- worktree is clean; commit `a25941f9017fcd2a0a8e7660fe768c69cfaa28b6`
changes only the World route and its migration contract test
## Existing baseline debt
The branch introduces no type or production-build regression and removes
the route error it targets. Six unrelated current-`main` type
diagnostics remain:
```text
src/app/page.tsx(14,42): error TS2307: Cannot find module '@copilotkit/runtime-client-gql'
src/hooks/useCountryData.ts(2,50): error TS2307: Cannot find module 'geojson'
src/hooks/useCountryData.ts(4,51): error TS2307: Cannot find module 'topojson-specification'
src/hooks/useCountryData.ts(32,27): error TS2345: Argument of type 'unknown' is not assignable to parameter of type 'string'.
src/hooks/useCountryData.ts(43,12): error TS7006: Parameter 'country' implicitly has an 'any' type.
src/utils/countryData.ts(1,40): error TS2307: Cannot find module 'geojson'
```
The nested ChatKit Studio frozen install also reproduces the separate
current-`main` Playground importer drift covered by D006. D007 no longer
changes `package.json`, so it does not duplicate that lockfile fix.
Legacy source: `markmdev/copilotkit-world` at
`7a27a37b628b9e339d1284ec42382008e67884f5`.
# fix(showcases): sync chatkit studio lockfile
## Summary
- remove stale ESLint importer entries from the shared ChatKit Studio
lockfile
- restore frozen-lockfile validation for Playground, Studio, and World
without reserializing the lockfile
- keep the change limited to the shared dependency contract required by
the Playground deployment
## Verification
- Formatter: N/A for the changed YAML lockfile. The repository's oxfmt
0.36.0 does not accept YAML targets (`Expected at least one target
file`); its full-repository check listed only 28 pre-existing files
outside this branch's one-file diff.
- YAML parsing and structural validation passed: lockfile version 9.0,
four exact manifest importers, 1,431 packages, and 1,431 snapshots.
- The shared four-project workspace completed `pnpm install
--frozen-lockfile --ignore-scripts` with repository-pinned pnpm 9.15.0;
the resolution step was skipped because the lockfile is current.
- Playground and Studio passed explicit `tsc --noEmit`; their Next.js
production builds also passed. Both Python agent modules compiled
successfully, and the workspace defines no JavaScript test suite.
- Lint is N/A for this lockfile-only diff: Playground and Studio's
existing `next lint` scripts prompt to create an ESLint configuration,
while World defines no lint script.
- World's existing source/dependency type errors reproduce in both `tsc
--noEmit` and `next build`; they are unrelated to the removed
ESLint-only importer metadata. World otherwise compiled before its
existing type-validation failure.
- Diff, scope, secret, and worktree hygiene passed: the commit changes
only `examples/showcases/chatkit-studio/pnpm-lock.yaml` with 24
deletions and no additions.
The launcher floats over a customer's page, and its border only earns its
place against a dark one -- so reviewing this branch needs a dark host page,
and the lab had no way to produce one.
Copied from `examples/v2/react/demo` rather than invented: the host owns a
theme state, and `CopilotChat` gets `className="dark"`, which is what makes
the package swap its own variable set. My first attempt stripped every
background instead, which is why the chat bubble, the send button, the
toolbar's on/off states and the error banner all vanished into one flat
grey -- the chat paints its own surfaces and has to be told, not undressed.
The colours are the demo's, by another route: it writes the oklch literals
that CopilotKit's variables use, and those are Tailwind's neutral steps --
`neutral-950` is `oklch(0.145 0 0)`, `neutral-50` is `oklch(0.985 0 0)`,
`neutral-800` is `oklch(0.269 0 0)`. Measured identical on the running lab.
`@custom-variant dark (&:is(.dark *))` is needed because Tailwind v4 points
`dark:` at `prefers-color-scheme` by default, so the toggle would have been
ignored in favour of the OS. Same declaration the package uses for its own
sheet.
The toggle sits top left, where the react demo puts it. Top right is where
the launcher floats.
The banking showcase is superseded by `examples/showcases/reskinnable-demo`,
which ships the same banking experience as one of its runtime-swappable skins
(alongside airline) on top of a shared shell. Keeping both means maintaining
two copies of the same demo, so banking is sunset here.
Removes the app and the things that referenced it:
- `pnpm-workspace.yaml` — drops the workspace entry. Also fixes the adjacent
NOTE, which attributed the canary AG-UI pin to "banking's agent" when it is
reskinnable-demo's own Python deep agent that needs it.
- `pnpm-lock.yaml` — regenerated. Only the removed importer and the peer-suffix
re-keying it caused; no dependency version changes.
- `examples/README.md` — the banking row becomes a reskinnable-demo row, so the
successor is listed and the showcase count is unchanged.
- `showcase/shell-docs/.../faq.mdx` — the Banking Assistant link retargets to
reskinnable-demo instead of 404ing.
- `.github/config-allowlist.txt` — drops the deleted `next.config.mjs`.
Not changed: `scripts/migrate-demos.sh` and `scripts/archive-demo-repos.sh`
still name `examples/showcases/banking`. Those are the already-executed one-shot
manifests for the repo consolidation; the path is a historical record there, not
a live reference. reskinnable-demo's `.env.example` and `docker-compose.yml`
likewise still explain their +200 port offset in terms of banking's stack — the
offset stays real, and "was cloned from banking" stays true.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>