Two small follow-ups to #5534 (which made `examples/slack` build from
workspace source) needed to make the Railway deploy actually succeed.
## 1. Pin Node 22 (`.nvmrc`) — the deploy blocker
#5534 merged **without** the Node pin, so Railway still builds on **Node
18**. Now that the build compiles the `@copilotkit/*` libs from source
(tsdown/rolldown), it needs **Node ≥20.12** (`node:util`'s `styleText`)
and fails on 18:
```
SyntaxError: The requested module 'node:util' does not provide an export named 'styleText'
Node.js v18.20.8
```
The builder defaults to 18 from root `engines.node: ">=18"`. A root
**`.nvmrc` = 22** pins the build/dev Node (read by the Railway builder
and nvm/fnm). CI is unaffected (no workflow reads `.nvmrc`), and the
published `engines` runtime contract is left as-is — a build-environment
pin, not a runtime change.
> If your builder ignores `.nvmrc`, set `NIXPACKS_NODE_VERSION=22` on
the service as a fallback.
## 2. Move the `@ai-sdk/mcp` pin to root `pnpm.overrides`
The example pinned `@ai-sdk/mcp` to `1.0.21` (protocolVersion incompat —
`88a2d82`) via its **own** `pnpm.overrides`. That only worked installed
in isolation; as a workspace member pnpm ignores package-level
overrides, so the pin was silently dropped (and pnpm warns).
`packages/runtime` allows `^1.0.21`, so a future lockfile regen could
drift to a newer, incompatible 1.x. Moved it to the **root**
`pnpm.overrides` (runtime is the only consumer) and removed the dead one
from the example.
> Pre-commit `test-and-check-packages` bypassed: the lockfile change
makes nx mark everything affected, and the only failure is the
pre-existing, environmental `@copilotkit/sqlite-runner:test` (native
sqlite on Node 24).
Re-lands the Node pin that was lost when #5534 merged without it. The
Railway slack-example build now compiles the @copilotkit/* libs from source
(tsdown/rolldown), which needs Node >=20.12 (`node:util`'s `styleText`), but
the builder defaults to Node 18 from the root `engines.node: ">=18"` —
failing with "does not provide an export named 'styleText'". Pin the
build/dev Node to 22 (read by the Railway builder and nvm/fnm). CI is
unaffected (no workflow reads .nvmrc) and the published `engines` runtime
contract is left as-is.
The example pinned @ai-sdk/mcp to 1.0.21 (protocolVersion incompat, see
88a2d82) via its own pnpm.overrides. That only took effect when the example
was installed in isolation; as a workspace member pnpm ignores package-level
overrides, so the pin was silently dropped — packages/runtime's `^1.0.21`
could drift to a newer, incompatible 1.x on the next lockfile regen.
Move the override to the root package.json's pnpm.overrides (runtime is the
only consumer, so this enforces exactly 1.0.21 with no wider impact) and
remove the now-dead override from the example (also silences the pnpm warning
that surfaced once the example became a workspace member).
## Problem
`examples/slack` is deployed on Railway and declared its sibling
packages as **npm version ranges** (`@copilotkit/bot: ~0.0.2`, …).
Because the service resolves those from the registry, every PR that
changes the packages required a brittle dance:
1. Merge → Railway redeploys → installs the **old** published packages →
broken/stale.
2. Manually release the npm packages.
3. Bump the example's versions → that change finally triggers a redeploy
that pulls the new release.
A chicken-and-egg with a broken-deploy window in between.
## Fix
Consume the siblings via the **`workspace:*`** protocol (the example is
`private`, so this never affects publishing) so the example always
builds from **in-repo source**, and add a graph-aware `build` script
that compiles the workspace libs it imports (and their deps) via Nx.
- `examples/slack/package.json`: `@copilotkit/bot`, `-slack`,
`-discord`, `-ui`, `runtime` → `workspace:*`; new `build` script (`nx
run-many -t build -p @copilotkit/bot-slack @copilotkit/bot-discord
@copilotkit/runtime`).
- `pnpm-lock.yaml`: regenerated (drops the registry trees those deps
pulled).
- README: documents the Railway settings and the copy-out caveat.
As a side benefit, this also removes the local "rebuild → re-sync
injected copy" friction — `workspace:*` symlinks the packages, so a
rebuilt `dist` is seen live.
## Required Railway config (apply in the dashboard)
The service must build **as a workspace member from the repo root** —
`packages/**` lives above `examples/slack`, so a service rooted at
`/examples/slack` cannot watch or build it.
| Setting | Value |
|---|---|
| **Root Directory** | `/` (repo root) — **change from
`/examples/slack`** |
| **Build Command** | `pnpm install && pnpm --filter slack-example
build` |
| **Start Command** | `pnpm --filter slack-example start` (bot) / `pnpm
--filter slack-example run runtime` (runtime) |
| **Watch Paths** | `packages/**`, `examples/slack/**`,
`pnpm-lock.yaml`, `package.json` |
## Result
- A `packages/**` change redeploys the example with the new code
**immediately** — no release required.
- `npm publish` becomes an **independent, manual** step. No more
publish-then-bump dance.
## Verification
- `pnpm --filter slack-example build` ✓ (builds 3 libs + 5 deps via Nx)
- `pnpm --filter slack-example check-types` ✓
- `pnpm --filter slack-example test` ✓ (38 tests)
> Pre-commit `test-and-check-packages` was bypassed for the commit: the
lockfile change makes nx mark everything affected, and the only failure
is a pre-existing, environmental `@copilotkit/sqlite-runner:test` break
(native sqlite on Node 24) unrelated to this change.
`examples/slack/pnpm-lock.yaml` only existed for the old isolated deploy
(root dir `/examples/slack`, `pnpm install --ignore-workspace
--frozen-lockfile` resolving the @copilotkit/* deps from npm). Now that the
example is a workspace member built from source (`workspace:*`, root-dir
`/`), pnpm uses the single root `pnpm-lock.yaml`; the per-example lock is
never consulted and was left stale — it still pins the published `~0.0.2`
versions, which contradicts the `workspace:*` package.json and would break
any `--frozen-lockfile` install.
The example declared its sibling @copilotkit/* packages as npm version
ranges, so the Railway service (which builds examples/slack in isolation)
resolved them from the registry — forcing a "publish first, then bump the
example" dance on every PR, with a broken deploy window in between.
Switch those deps to the workspace:* protocol (the example is private, so
it never affects publishing) so the example always builds from in-repo
source, and add a graph-aware `build` script that compiles the workspace
libs it imports (and their deps) via Nx. README documents the Railway
settings (root dir / build / start / watch paths) and the copy-out caveat.
Result: a packages/** change redeploys the example with the new code
immediately, and npm publishing becomes an independent manual step.
## What
Regenerates `examples/slack/pnpm-lock.yaml` so it matches the example's
`package.json`.
## Why
The `@copilotkit/bot-discord` dependency was added to
`examples/slack/package.json` (in #5524 / the "run Slack and Discord
from one bot app" change) but the standalone lockfile was never
regenerated, leaving it out of sync. A `--frozen-lockfile` install in
`examples/slack` would fail.
## How
Ran `pnpm@10.33.4 install --lockfile-only --ignore-workspace` inside
`examples/slack` (matching the repo's pinned pnpm and the example's
standalone lockfile setup). The diff is purely additive — it adds
`@copilotkit/bot-discord` and its `discord.js` subtree; the
`@ai-sdk/mcp` override and all existing entries are preserved. Verified
with a `--frozen-lockfile` check.
## Summary
Adds **`@copilotkit/bot-discord`** — a Discord `PlatformAdapter` for
`@copilotkit/bot`, built on **discord.js v14**, mirroring the existing
`@copilotkit/bot-slack`. It lets users build Discord bots on the shared
`@copilotkit/bot` + `@copilotkit/bot-ui` primitives, running on agents
via the AG-UI protocol.
Ships three things: the **package**, a runnable **example**, and
**docs**.
## Package (`packages/bot-discord`)
- **Gateway ingress** via discord.js — intents `Guilds`,
`GuildMessages`, `MessageContent` (privileged), `DirectMessages`,
`GuildMembers` (privileged). Both privileged intents must be enabled in
the Discord Developer Portal; `GuildMembers` powers user lookup.
- **Components V2 egress** — IR → `ContainerBuilder` with
`MessageFlags.IsComponentsV2`; full
`Message`/`Header`/`Section`/`Markdown`/`Fields`/`Context`/`Actions`/`Button`/`Select`/`Image`/`Divider`/`Table`
mapping, budget-clamped via `DISCORD_LIMITS`.
- **Streaming replies** — 1100 ms edit throttle; rollover at a 1900-char
soft limit / 2000 hard limit.
- **Ack-first interactions** (3000 ms deadline) — slash commands ack
with an ephemeral reply; buttons/selects ack via `deferUpdate`.
- **Slash-command registration** — per-guild (instant) when `guildId` is
set, else global (propagates within ~1h).
- **HITL**, **typing + reactions**, **built-in tools**
(`lookup_discord_user`) + **context**, in-memory conversation store.
- Capabilities: `{ supportsModals: false, supportsTyping: true,
supportsReactions: true, supportsStreaming: true, maxBlocksPerMessage:
40 }`.
## Example (`examples/discord`)
Runnable Discord bot — `BuiltInAgent` + Linear/Notion MCP (model
`openai/gpt-5.5`), app tools/components/commands/context/HITL, plus a
**`DISCORD_E2E`-gated manual e2e harness**.
```
DISCORD_E2E=1 pnpm --filter discord-example exec tsx e2e/run.ts
```
## Docs
- `showcase/shell-docs` reference under `reference/bot/discord/` —
`index`, `defaultDiscordTools`, `defaultDiscordContext`,
`renderComponents`, `DISCORD_LIMITS`; bot reference index + nav.
- Package `README.md` + `ARCHITECTURE.md`.
## Review summary
Built and reviewed via a multi-module adversarial CR process:
- **Module 1 (package):** 4 CR rounds, ~22 bugs fixed, **156 tests**.
- **Module 2 (example):** 1 round, ~10 fixes (stale Slack-port residue +
missing error guards), **38 tests**.
- **Module 3 (docs):** 1 round — fixed a blocker (doc invented three
non-existent adapter options) + majors (missing `GuildMembers`, wrong
streaming throttle/rollover numbers).
- **Module 4 (cross-cutting, 7 lenses + promotion audit):** caught &
fixed a **blocker — slash-command interactions were never
acknowledged**, so every slash command showed the user Discord's "The
application did not respond" error. Now acked within the 3 s window.
Also fixed: frozen `_thinking…_` placeholder on mid-stream throw, an
unhandled-rejection window in the chunked stream, an accidental NUL byte
that made a test file binary, and `ARCHITECTURE.md` intent-list
accuracy.
## Deferred follow-ups (not blocking)
- **True modals / multi-step forms** — `supportsModals: false` in v1.
- **Inbound-attachment auto-wiring** — `buildFileContentParts` is
exported + tested but not yet wired into the listener, so a user
attaching a file gets nothing delivered to the agent. Two reviewers
rated this blocker-severity; the promotion audit confirmed there's no
false promise (README claims no upload support) and nothing depends on
it. **Strong fast-follow candidate.**
- **`@copilotkit/bot-slack` port** — bot-discord fixed a latent
`flushNow` ordering bug and a select `custom_id` JSON encode/decode
asymmetry that still exist in bot-slack.
- **Latent (not reachable by shipped example):** select-value JSON
round-trip type coercion; `>5` action-row silent drop (no overflow
marker).
- **Adapter-glue test coverage** —
`post`/`update`/`delete`/`lookupUser`/`getMessages`/`postFile`/ack-ordering/`interruptEventNames`/`custom_id`
round-trip are covered indirectly; direct tests would harden them.
- **Minor:** `getMessages` placeholder filtering (shared with
bot-slack); `buildFileContentParts` config-key/default divergence vs
bot-slack; type-cleanliness (`ChannelLike` `as never` bridge,
barrel-surface trim).
- **e2e harness** is a `DISCORD_E2E`-gated **manual** tool — its
synthetic interaction lacks an Ed25519 signature, so it works only
against a test shim, not real Discord.
## Caveat (pre-existing, unrelated to this PR)
`@copilotkit/core` (tsc error) and `@copilotkit/runtime` (check-types
JS-heap OOM) are already broken on `main`. They are not touched by this
branch; typecheck/build were scoped to `@copilotkit/bot-discord` +
`discord-example`.
## Test plan
- [x] `nx run @copilotkit/bot-discord:test` — 156 passing
- [x] `nx run discord-example:test` — 38 passing
- [x] `tsc --noEmit` (both `tsconfig.json` + `tsconfig.check.json`)
clean
- [x] `nx run @copilotkit/bot-discord:build` succeeds
- [x] oxfmt `--check` clean, oxlint 0 errors, `pnpm-lock.yaml` in sync
- [ ] Manual smoke against a real Discord app (mention, slash command,
button, streaming reply)
## Summary
Closes the production 503 gap on aimock-staging for the **"Show me my
sales dashboard for this quarter."** userMessage across 8 non-LGP slugs.
Mirrors LGP's `generate_a2ui` outer-emit fixture pattern.
Empirical gap (from `/tmp/staging-journal-diff.md`):
| Shape | Traffic | Status on main | Why |
|---|---|---|---|
| Shape A | model=gpt-4o-mini, no tools key, curl/8.7.1 | 503 | (out of
scope) |
| Shape B | model=gpt-4.1, stream=true, tools=[generate_a2ui],
AsyncOpenAI/Python | **503** | **No fixture matched** (this PR's target)
|
LGP has both `render_a2ui` AND `generate_a2ui` fixtures for this
userMessage → stays 200. The 8 slugs below were missing the
`generate_a2ui` entry → 503.
## Slugs patched
8 file edits, one entry each in
`showcase/aimock/d6/<slug>/gen-ui-declarative.json`:
- llamaindex / built-in-agent / ag2 / langroid / claude-sdk-typescript /
claude-sdk-python / ms-agent-dotnet / ms-agent-python
Each entry mirrors LGP's sales-dashboard outer entry — match by
`userMessage` + `context: "<slug>"`, response is a `generate_a2ui`
toolcall with no args and a per-slug unique toolCallId.
## Red-Green Proof (local aimock against
`ghcr.io/copilotkit/aimock:latest`)
Replayed the Shape-B production request body (model=gpt-4.1,
stream=true, tools=[generate_a2ui], X-AIMock-Context per slug) against
local aimock booted with unmodified `main` fixtures (RED) then this
branch's fixtures (GREEN).
**RED (baseline main, all 8 slugs):** `HTTP=404 ERROR=no_fixture_match`
(aimock's 404 `no_fixture_match` is what surfaces as 503 on prod
aimock-staging.)
**GREEN (this branch, all 8 slugs):** `HTTP=200 tool=generate_a2ui
id=call_d6_decl_dash_outer_<slug>_001`
**LGP regression (baseline → fix branch):** `HTTP=200` (unchanged).
Logs: `/tmp/genfix-RED-baseline.log`, `/tmp/genfix-value-test.log`,
`/tmp/genfix-lgp-regression.log`.
## Aimock fixture validation
`pnpm --filter @copilotkit/showcase-scripts test aimock-fixtures` →
**737/737 tests pass**.
## Scope
Intentionally narrow per CLAUDE.md "scope PRs to originally-flagged
findings":
- Closes Shape-B `tools=[generate_a2ui]` 503s for ONE userMessage.
- Out of scope: Shape-A 503s (no `tools` key, `curl/8.7.1` probe
traffic) and other userMessage gaps — separate follow-ups.
## Test plan
- [x] Local aimock RED→GREEN proof across all 8 slugs (Shape-B request
body)
- [x] LGP regression check (unchanged)
- [x] Aimock fixture validation suite (737/737)
- [ ] CI green
- [ ] Post-deploy staging replay (8 slugs → 200)
Mirrors LGP's `generate_a2ui` outer-emit fixture into 8 non-LGP slugs to close
the production 503 gap on the "Show me my sales dashboard for this quarter."
userMessage. Per /tmp/staging-journal-diff.md, aimock-staging returns 503 on
shape-B traffic (model=gpt-4.1, stream=true, tools=[generate_a2ui],
UA=AsyncOpenAI/Python) for all 8 slugs because no fixture matched.
Slugs patched (one entry each in d6/<slug>/gen-ui-declarative.json):
- llamaindex
- built-in-agent
- ag2
- langroid
- claude-sdk-typescript
- claude-sdk-python
- ms-agent-dotnet
- ms-agent-python
Each entry matches `userMessage` + `context: "<slug>"` and emits a
`generate_a2ui` toolcall with no args, mirroring LGP's sales-dashboard outer
entry. Per-slug unique toolCallId.
Red-green proof (local aimock, ghcr.io/copilotkit/aimock:latest):
RED (baseline main, 8 slugs): HTTP=404 no_fixture_match
GREEN (this branch, 8 slugs): HTTP=200 tool=generate_a2ui id=call_d6_decl_dash_outer_<slug>_001
LGP regression (baseline+fix): HTTP=200 (unchanged)
aimock fixture validation: 737/737 tests pass.
PR scope is intentionally narrow per CLAUDE.md "Scope PRs to flagged
findings": this closes ONE userMessage gap. Shape-A 503s (no tools key) and
other userMessage gaps remain as separate follow-ups.
## The gap
Staging strict-mode replay against `aimock-staging.up.railway.app`
confirmed
**ag2** was the only integration missing a fixture for:
- `userMessage: "Open Excalidraw and sketch a system diagram..."`
- `toolName: "create_view"`
- `context: "ag2"`
All 11 other contexts that share this userMessage (`langgraph-python`,
`google-adk`, `ms-agent-{dotnet,python}`, `strands`, `llamaindex`,
`built-in-agent`, `claude-sdk-{typescript,python}`, `langroid`,
`mastra`)
return 200 with `tools=[create_view]` declared. Only `ag2` returns
`{"code":"no_fixture_match"}`.
Evidence: a staging-only audit on the originating workstation
(`/tmp/aimock-staging-fixture-verify.md`).
## The fix
One fixture entry added to `showcase/aimock/d6/ag2/mcp-apps.json`,
mirroring the LGP gold-standard at
`showcase/aimock/d6/langgraph-python/tool-rendering-reasoning-chain.json`
(same fixture id `call_d5_mcp_apps_create_view_001`, same arguments
payload,
same `chunkSize: 9999`). The entry sits next to the existing
`hasToolResult: true` turn-2 sibling for the same userMessage.
## Red-green proof
**RED (pre-fix, against staging):**
```
$ curl -sS -X POST https://aimock-staging.up.railway.app/v1/chat/completions \
-H "X-AIMock-Context: ag2" -H "X-AIMock-Strict: true" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Open Excalidraw and sketch a system diagram..."}],"tools":[{"type":"function","function":{"name":"create_view","parameters":{"type":"object","properties":{}}}}]}'
{"error":{"message":"Strict mode: no fixture matched","type":"invalid_request_error","param":null,"code":"no_fixture_match"}}
```
**GREEN (post-fix, against local iso9 aimock with the new fixture
mounted):**
```
$ curl -sS -X POST http://localhost:6010/v1/chat/completions \
-H "X-AIMock-Context: ag2" -H "X-AIMock-Strict: true" \
-H "Content-Type: application/json" \
-d '{ ...same payload... }'
200 OK
content: "Sketching a client → server → database diagram in Excalidraw."
tool_calls[0].id: "call_d5_mcp_apps_create_view_001"
tool_calls[0].function.name: "create_view"
```
Local aimock `/__aimock/journal` confirms the match keys:
```
status=200 fixture={"userMessage":"Open Excalidraw and sketch a system diagram","toolName":"create_view","context":"ag2"}
```
**LGP regression check:** the same probe with
`X-AIMock-Context: langgraph-python` against the local iso9 aimock
returns 200
with the same canonical tool_call payload — LGP unaffected.
## Scope
Intentionally narrow per "scope PRs to the originally-flagged findings".
This
PR closes ONLY the one genuine fixture gap identified by the staging
verification audit. The 9 other 503s in that audit (sales-dashboard
across 9
slugs + google-adk Excalidraw) are NOT fixture gaps — staging replays
show
those fixtures DO load and DO match when `tools=[create_view]` is
declared.
The 503s come from the backend omitting the tool name from the outbound
`/v1/chat/completions` request's `tools` array, a separate effort.
## Post-merge verification
After `showcase_deploy` rebuilds `aimock-staging`, the same staging
probe
above will be re-run; it must return 200 (not 503).
Staging strict-mode replay confirmed ag2 was the only integration missing a
fixture for `Open Excalidraw and sketch a system diagram...` + `create_view`
(the D5 mcp-apps probe's turn-1). All 11 other contexts (langgraph-python,
google-adk, ms-agent-{dotnet,python}, strands, llamaindex, built-in-agent,
claude-sdk-{typescript,python}, langroid, mastra) returned 200 against
aimock-staging with the same payload; ag2 returned 503
`{"code":"no_fixture_match"}`.
Add the missing turn-1 entry to ag2/mcp-apps.json, mirroring the LGP
gold-standard at langgraph-python/tool-rendering-reasoning-chain.json (same
fixture id `call_d5_mcp_apps_create_view_001`, same arguments payload, same
chunkSize). Match keys: userMessage + toolName + context.
Local GREEN proof: iso9 aimock (port 6010) with new fixture mounted returns
200 with the canonical tool_call payload for the exact staging-replay request
body. LGP regression check (same probe with X-AIMock-Context: langgraph-python)
stays green.
Scope intentionally narrow per "scope PRs to the originally-flagged findings":
this PR closes only the one genuine fixture gap identified by the staging
verification audit. The 9 other 503s in that audit (sales-dashboard across 9
slugs + google-adk Excalidraw) are NOT fixture gaps — they are backend
tool-array forwarding issues and are tracked separately.
## Summary
Aligns showcase integration dependencies to current released minor
versions for the **1.60.2** cycle. Closes the version-coherence gap that
was blocking the `react-core@1.60.2` resume-path / gen-ui-interrupt
fixes from taking effect on staging.
## Package families bumped
| Family | From | To | Scope |
|---|---|---|---|
| `@copilotkit/{a2ui-renderer, react-core, react-ui, runtime, shared,
sdk-js, voice}` | `1.59.4` (18 integrations) / `1.57.2`
(ms-agent-harness-dotnet) | **`1.60.2`** | 19 integrations |
| `@ag-ui/{client, core, encoder}` | `0.0.55` | **`0.0.57`** | 15
integrations |
| `@ag-ui/mastra` | `0.2.1-beta.2` | **`0.2.4`** | mastra only |
`@ag-ui/mastra@1.0.x` (major) **deliberately not** bumped — major jump
held back per the broad-scope dep-bump policy.
## Integrations covered (19/19)
`ag2`, `agno`, `built-in-agent`, `claude-sdk-python`,
`claude-sdk-typescript`, `crewai-crews`, `google-adk`,
`langgraph-fastapi`, `langgraph-python`, `langgraph-typescript`,
`langroid`, `llamaindex`, `mastra`, `ms-agent-dotnet`,
**`ms-agent-harness-dotnet`** (newly added — was missed by the prior
18-integration staging and jumps two minor lines), `ms-agent-python`,
`pydantic-ai`, `spring-ai`, `strands`.
`built-in-agent`, `langgraph-fastapi`, `langgraph-python`,
`langgraph-typescript` carry no `@ag-ui/*` deps directly (the langgraph
trio gets the protocol via `@copilotkit/runtime`'s nested resolution,
which has been verified at `0.0.57` post-install).
## Coherence note
`@copilotkit/react-core@1.60.2` does not declare a hard peer-dep on
`@ag-ui/core` at the package-manifest level; it bundles its own copy via
nested `node_modules`. Lockfile inspection confirms nested
`@copilotkit/{react-core,runtime,shared}/node_modules/@ag-ui/client`
resolved at `0.0.57` across every integration that ships them,
satisfying the `feedback_agui_client_bump_scope` rule (`@ag-ui/core >=
0.0.48`).
## Reconciliation method
Per-integration `npm install --package-lock-only --legacy-peer-deps` (no
`node_modules` mutation). The `--legacy-peer-deps` flag is required to
step past a **pre-existing** `cmdk@0.2.1` ↔ `react@^19` peer conflict
that long predates this bump; lockfile contents are otherwise unchanged
in shape (only dep-version touchups). 30 files in the visible diff
because 8 of the 38 changed files were identical between staged-index
and working-tree.
## Red / Green proof
**RED** (pre-merge staging):
- `gen-ui-interrupt` cells on the langgraph trio (LGP / LGTS /
LG-FastAPI) and across the broader integration matrix are RED on the
showcase dashboard pending the `@copilotkit/react-core@1.60.2`
resume-path fix landing on every integration container.
**GREEN** (expected post-merge):
- `showcase_deploy` will rebuild every integration container on push to
`main`. The dashboard `gen-ui-interrupt` + `resume-path` cells should
flip GREEN once those containers redeploy.
- Per-cell empirical value-test (`bin/showcase test
<slug>:gen-ui-interrupt --d6 --direct`) on ≥3 cells across LGP / LGTS /
LG-FastAPI is queued for the post-merge verification window.
## Out of scope
- No source-code changes (TS/Python/.NET/Java/Go).
- No fixture changes.
- No frontend page changes.
- No `packages/`, `examples/`, `showcase/shell-*` touched.
- No Railway worker restart (separate effort).
- No langgraph-typescript backend agent changes (separate effort).
## Test plan
- [ ] CI green on this PR (lint / format / build / publint / attw on the
affected workspaces)
- [ ] Admin-merge once cr-loop converges to zero findings
- [ ] Post-merge: confirm `showcase_deploy` rebuilds integration
containers
- [ ] Post-merge value-test: `gen-ui-interrupt` + `resume-path` cells
flip GREEN on staging dashboard for LGP, LGTS, LG-FastAPI (≥3 cells)
## Summary
- New `.github/workflows/showcase_promote_notify.yml` — Slack notify
workflow (workflow_dispatch only) for promote results. Posts initiation
+ threaded reply to `#team-showcase`; cross-posts to `#oss-alerts` on
partial/total failure.
- New `showcase_promote_notify.dry-run.sh` — local render-logic mirror
(no Slack API calls); validates payload + emits expected Slack messages
for manual review.
- New `showcase/test-fixtures/promote-notify/` — three canonical
fixtures (success / partial / total-failure), a strict schema validator,
and a README.
- New `docs/runbooks/showcase-promote-notify-pr1-checklist.md` —
pre-merge runbook with Slack-membership checks, dispatch-fixture
commands, and schema-mismatch test.
## Why this is PR1
PR1 lands the notify workflow with no callers. The CLI (`bin/railway
promote --notify`) lands in PR2. Splitting is required because `gh
workflow run` resolves the workflow file from the repo's default branch
— PR2's CLI cannot dispatch a workflow that doesn't yet exist on `main`.
## Test plan
- [ ] Pre-merge checklist passes (see
`docs/runbooks/showcase-promote-notify-pr1-checklist.md`)
- [ ] Required `SLACK_BOT_TOKEN` secret is set with scopes `chat:write`,
`chat:write.public`, `users:read.email`
- [ ] Bot is a member of `#team-showcase` AND `#oss-alerts` (or scope
sufficient for public posts)
- [ ] All 3 canonical fixtures dispatched via `gh workflow run --ref
<pr-branch>` produce the expected Slack messages
- [ ] Schema-mismatch test (step 5 of runbook) produces `::warning::`
annotation with NO Slack API call
## Follow-up
A bucket(d) list of defensive-hardening items was deferred to a
follow-up PR (notify workflow defensive hardening). The CR loop (4
rounds, 7 unbiased agents each) identified ~50 items across categories:
silent-Slack-failure exit codes, validator strictness gaps (ISO-8601
fractional seconds, enum constraints), dry-run/workflow parity
(lookup-failure simulation, permalink-empty fallback), payload
defensive-validation (cross-field, run_id source-of-truth). Per cr-loop
convergence-audit: these are PR1-adjacent but their own subject; they'll
land in a focused follow-up PR.
Additionally, local `actionlint` (not currently in CI for this workflow)
flags one SC2034 (`failed_count` retained for workflow/dry-run parity
but not echoed) and two SC2016 (intentional single-quoted Slack-mrkdwn
backticks in `trigger_label`). Both fold into the bucket(d) follow-up.
## CR rounds
4 rounds × 7 agents = 28 reviews. Final convergence: ALL bucket(a)
findings fixed; bucket(b)/(c)/(d) preserved for follow-up. Integration
HEAD: `b65b8452d9b847f73c885854c146547ce82c3706`.
- showcase-canonical-pins.json: bump canonicalCopilotKitVersion 1.59.4 -> 1.60.2;
remove ms-agent-harness-dotnet override (caught up to canonical in prior commit).
- fail-baseline.json: re-ratchet validatePinsFailCount 39 -> 38 and hash to match
the one-item drop (ms-agent-harness-dotnet override no longer counted).
- @ag-ui/mastra: revert 0.2.4 -> 0.2.1-beta.2. 0.2.4 imports
'@mastra/core/runtime-context' which the pinned @mastra/core@1.41.0 does not
export, breaking 'next build' (failing mastra build-check in CI). Holding
@ag-ui/mastra at the prior pin until a coordinated @mastra/core upgrade lands.
Aligns dependency versions across all 19 showcase integrations to current
released minor versions for the 1.60.2 release cycle.
Package families:
- @copilotkit/{a2ui-renderer, react-core, react-ui, runtime, shared, sdk-js, voice}
1.59.4 -> 1.60.2 (18 integrations already staged; ms-agent-harness-dotnet
catches up from 1.57.2)
- @ag-ui/{client, core, encoder} 0.0.55 -> 0.0.57
- @ag-ui/mastra 0.2.1-beta.2 -> 0.2.4 (stable on 0.x; 1.0.x major held back)
Includes the previously-missed ms-agent-harness-dotnet integration in the
@copilotkit/* bump, plus the @copilotkit/web-inspector override pin.
Lockfile-only reconciliation via npm install --package-lock-only
--legacy-peer-deps (cmdk@0.2.1 pre-existing react^18 peer-dep is unaffected).
Pre-merge checklist for the promote-notify workflow: Slack-membership
checks (#team-showcase + #oss-alerts), SLACK_BOT_TOKEN scope verification,
gh workflow run dispatch commands for all three canonical fixtures, and
a schema-mismatch test that verifies the workflow emits a ::warning::
annotation with NO Slack API call.
Adds three canonical fixtures under showcase/test-fixtures/promote-notify/:
success.json (29 services green), partial.json (26 green / 3 red with
mixed exit codes + categories), and total-failure.json (29 red fleet
abort with truncation-suffix sentinel).
Includes validate.sh — strict schema enforcement (rc propagation, enum +
regex assertions for run_id, pre_staging, abort_reason, category, exit
codes). And a README documenting the fixture contract + how to dispatch
them via gh workflow run.
Adds .github/workflows/showcase_promote_notify.yml — workflow_dispatch-only
Slack notify workflow for promote results. Posts initiation + threaded reply
to #team-showcase; cross-posts to #oss-alerts on partial/total failure.
Also adds showcase_promote_notify.dry-run.sh — a local render-logic mirror
that decodes the same payload and prints the messages the workflow would
send to Slack, without making any Slack API calls. Used by the pre-merge
runbook and by future CI fixture tests.
Throw when a catalog definition has no matching renderer so createCatalog
satisfies strict check-types under Angular's tsconfig.
Co-authored-by: Cursor <cursoragent@cursor.com>
Modern @a2ui/web_core and @angular/cdk subpath exports require bundler
resolution so angular:check-types can resolve the new A2UI imports.
Co-authored-by: Cursor <cursoragent@cursor.com>