Adds a "Promoting a Staging-Only Integration to Production" section to
showcase/RAILWAY.md — the staging-first -> promote-later procedure that was
undocumented and caused the strands-typescript D6 false-red (PR #5705).
The procedure previously survived only as a comment inside the SSOT
(railway-envs.ts); there was no human-facing SOP. The new section is a
start-to-finish checklist grounded in the PR #5705 worked example:
- When it applies (gateValidated:false, gateIgnore:true, staging-only env map,
legacyJsonCompat prod placeholder).
- The critical gotcha up front: the promote pipeline (showcase_promote.yml /
bin/railway promote) only moves digests to a prod service that ALREADY
exists; it does NOT provision a new prod serviceInstance. Until that instance
exists, D6 false-reds the whole column (404 -> empty backendUrl ->
goto-error on every cell).
- Ordered steps: provision the prod serviceInstance out-of-band
(environmentStageChanges + environmentPatchCommitStaged, mirroring a peer
prod TS service); edit the SSOT (add prod env block, gateValidated:true, drop
gateIgnore, remove legacyJsonCompat); regenerate derived artifacts
(emit-railway-envs-json.ts, golden fixture, sync-promote-service-options.ts)
and run the gate (verify-railway-image-refs.ts + vitest); prod secrets via
the prod env var set / aimock (no inline secrets); verify GREEN (/api/health
200, prod PocketBase health record, D6 flips on the next hourly :40 tick).
Cross-links INTEGRATION-CHECKLIST.md §B (single-shot bring-up) both ways.
Leaves a precise TODO that §B.3 still names the stale showcase_deploy.yml for
the build matrix (the RAILWAY.md references were already corrected on main).
The @copilotkit/core build (tsdown with dts generation) has a working set
of about 3.45GB. Node's default old-space ceiling is roughly 4GB and CI pins
NODE_OPTIONS to 4096, leaving almost no headroom. Under nx run-many the build
competes for CPU with sibling builds, GC falls behind, and the process tips
over the heap limit. nx flags it as a flaky task and pre-commit/CI builds fail
intermittently with a V8 heap OOM.
Bake the ceiling into the core build script with cross-env so every invocation
path gets consistent headroom: local lefthook hooks, nx run-many, CI, and
direct pnpm build, on every platform including Windows. 8192 matches the value
already used by the e2e workflow and gives roughly 2x headroom over the working
set.
Scoped to core only; it is the single package that OOMs.
## What
The `showcase-strands-typescript` integration was **staging-only** — it
had no production Railway serviceInstance. As a result the prod D6
dashboard column was a uniform **false-red**: every cell reported
`errorClass=goto-error` with `backendUrl=""`, because the harness had no
prod health record to hand the probe, so Playwright navigated a bare
relative path (`page.goto("/demos/…")`) and Chromium rejected it as an
invalid URL.
This brings the service into production so the prod D6 column mirrors
**staging's actual pattern** (the false goto-error reds disappear;
whatever staging genuinely passes, prod passes). Fixing staging's ~7
genuinely-broken feature cells is **out of scope**.
## Infra provisioned (production)
- **prod serviceInstance** `8a50728e-6119-43c4-b59c-d9535b6717a4`
created (via `environmentStageChanges` + `environmentPatchCommitStaged`,
mirroring the peer prod TS service `showcase-claude-sdk-typescript`).
- **domain** `showcase-strands-typescript-production.up.railway.app`,
**healthcheck** `/api/health`, image **pinned** to the GHCR `@sha256`
digest, `restartPolicyType` `ON_FAILURE`, auto-updates `minor`.
- **app vars** mirror staging's set with `OPENAI_BASE_URL` repointed at
prod aimock (`https://showcase-aimock-production.up.railway.app/v1`). No
real prod secret needed: the agent routes 100% to aimock and staging's
`OPENAI_API_KEY` is the `sk-aim…` aimock placeholder — so nothing secret
was sourced.
- deployment **SUCCESS**.
## SSOT / code changes
- `railway-envs.ts` — convert the `showcase-strands-typescript` block to
the dual-env `showcase-strands` shape: add the `prod` env entry with the
real instanceId, `gateValidated:true`, drop `gateIgnore`, remove the
`legacyJsonCompat` prod-domain placeholder, update the leading comment.
- `railway-envs.generated.json` — regenerated (real prod
instanceId/domain, `probe.prod:true`, prod healthcheck; service moves
into the promote closure, tier 2).
- `railway-envs.golden.json` — regenerated to include the new prod
`(service, env)` pair (intentional behavior change, not a refactor
regression).
- `showcase_promote.yml` — promote dropdown regenerated to list
`strands-typescript`.
- `verify-railway-image-refs.test.ts` / `redeploy-env.test.ts` — update
the gateValidated/scope counts (39→40 gate targets; prod default scope
38→39) and the now-stale "staging-only" comments.
## RED → GREEN proof (live prod)
| Check | RED (before) | GREEN (after) |
|---|---|---|
| prod `/api/health` | **404** (staging 200) | **200**
`{"status":"ok","integration":"strands-typescript"}` |
| prod PocketBase `health:strands-typescript` | **totalItems:0** |
**present** — `status:200`, `url=…-production…/api/health` |
| `d6:strands-typescript/agentic-chat` | `errorClass=goto-error`,
`backendUrl=""` | _flips on next D6 tick_ (staging: fail_count:0, 3
turns) |
| `d6:strands-typescript/prebuilt-popup` | `errorClass=goto-error`,
`backendUrl=""` | _flips on next D6 tick_ (staging: fail_count:0) |
| `d6:strands-typescript/chat-css` | `errorClass=goto-error`,
`backendUrl=""` | _flips on next D6 tick_ (staging: fail_count:0) |
`verify-railway-image-refs.ts` now reports **✓ 80 env-scoped instances
verified**. The D6 cell flip lands on the prod harness's next hourly
`d6-all-pills-e2e` tick (the harness already discovered the new prod
health record); this comment will be updated with the post-flip
payloads.
## Quality gates
`scripts` vitest: **2110 passing**. `emit --check`,
`sync-promote-service-options --check`, `verify-railway-image-refs.ts`:
all clean. oxlint: 0. tsc: changed files clean (one pre-existing
unrelated error in `generate-search-index.ts` on `main`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
`copilotkit-upgrade` was the one skill the v2 migration skipped. It
routed users to packages that don't exist, so following it produces a
non-installable app.
Root cause: the skill was generated against a non-existent
`packages/v1/*` / `packages/v2/*` layout. The repo is flat and v2 ships
from the `/v2` subpath of the same packages.
## Fix (pure docs)
Corrected every package/import reference across `SKILL.md` plus the
three reference files against the live exports maps:
| Phantom in skill | Real target |
| --- | --- |
| `@copilotkit/react` | `@copilotkit/react-core/v2` |
| `@copilotkit/agent` | `@copilotkit/runtime/v2` (`BuiltInAgent`) |
| bare `@copilotkit/runtime` for v2 APIs | `@copilotkit/runtime/v2` |
| `createCopilotEndpointExpress` (doesn't exist) |
`createCopilotExpressHandler` (`@copilotkit/runtime/v2/express`) |
Also fixed the install commands and regenerated `sources.md` against the
real flat layout.
## Verification
Grep sweep shows zero remaining phantom references, and every v2 symbol
the skill now cites was confirmed to exist in the live source barrels.
The showcase-strands-typescript integration was staging-only: it had no
production Railway serviceInstance, so the prod D6 dashboard column showed
a uniform false-red (every cell errorClass=goto-error, backendUrl="") —
the probe navigated a bare relative path because the harness had no prod
health record / backendUrl to discover.
Provisions the prod serviceInstance (8a50728e-6119-43c4-b59c-d9535b6717a4,
domain showcase-strands-typescript-production.up.railway.app, healthcheck
/api/health, image pinned to the GHCR @sha256 digest, OPENAI_BASE_URL at
prod aimock) and brings the SSOT to the dual-env showcase-strands shape:
- railway-envs.ts: add the prod env entry with the real instanceId,
gateValidated:true, drop gateIgnore, remove the legacyJsonCompat
prod-domain placeholder.
- railway-envs.generated.json: regenerated (prod instanceId/domain, probe.prod
true, prod healthcheck; moved into the promote closure, tier 2).
- railway-envs.golden.json: regenerated to include the new prod (service,env)
pair (intentional behavior change, not a refactor regression).
- showcase_promote.yml: dropdown regenerated to list strands-typescript.
- verify-railway-image-refs.test.ts / redeploy-env.test.ts: update the
gateValidated/scope counts (39->40 gate targets, prod default 38->39) and the
now-stale staging-only comments.
RED->GREEN (live prod): BEFORE /api/health 404, prod PocketBase
health:strands-typescript totalItems:0, the 3 named D6 cells all
errorClass=goto-error backendUrl="". AFTER /api/health 200, prod PocketBase
health:strands-typescript present (status:200, valid url),
verify-railway-image-refs OK 80 instances.
Adds examples/teams: a runnable Teams bot built on @copilotkit/bot-teams.
Demonstrates rendering charts from uploaded CSV files, inbound file
handling over the Graph API, and human-in-the-loop confirmation flows,
plus the Teams app manifest and packaging scripts.
Adds the @copilotkit/bot-teams package: a PlatformAdapter that bridges
the CopilotKit runtime to Microsoft Teams. Renders agent output as
Adaptive Cards (including native <Chart> components via bot-ui), streams
replies with a typing-indicator heartbeat, supports human-in-the-loop
confirmations, and handles inbound files via the Graph API with channel
file support.
ɵsubscribeToMemories mirrored ɵsubscribeToThreads and sent userId in the
body, but the platform's memory routes resolve the app user from the
x-cpki-user-id header (like listMemories/createMemory), not the body — so
POST /api/memories/subscribe returned 401 AUTH_UNAUTHENTICATED. Send the
user via the header instead; the body is now empty.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The memory store client POSTs /memories/subscribe to mint realtime join
credentials, but the runtime router had no such route — the path fell
through to /memories/:id (memories/mutate), so a POST returned 405 Method
Not Allowed. Threads had the full chain (route → handleSubscribeToThreads →
platform); memory was missing the runtime proxy between the client and the
platform's POST /api/memories/subscribe.
Add `memories/subscribe` to the route union, match it before /memories/:id
(and exclude "subscribe" from the :id rule, mirroring threads), add
handleSubscribeToMemories returning { joinToken, joinCode } (memory delivers
the join code here, unlike threads where it rides the thread-list response),
and ɵsubscribeToMemories on the platform client.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Claim-by-claim accuracy audit of the three showcase ops docs surfaced 32
WRONG + 2 STALE factual claims. This PR fixes every actionable one,
surgically and consistent with each doc's tone. Each fix was re-verified
against the repo / live Railway before writing (RED = current text
wrong; GREEN = new text matches reality). UNVERIFIABLE claims were left
unchanged.
**Supersedes #5702** — that PR only corrected the aimock recursion
warning; this PR applies BOTH that correction AND the §5 startCommand
code-block fix.
### showcase/TESTING.md (15 fixes)
- QA file count: ~498 across **20** packages (was "153 files, 17
packages"). `ls showcase/integrations/*/qa/*.md | wc -l` => 498.
- "PASS 17 packages" → "PASS 20 packages" (9 cells). `ls -d
showcase/integrations/*/ | grep -v _shared | wc -l` => 20.
- Per-package E2E: ~33–41 specs/package (was "9"); removed fabricated
`renderer-selector.spec.ts` claim + Known-gap #5.
- Aimock fixtures: removed non-existent `feature-parity.json`; fixed
`smoke.json` → `shared/smoke.json`.
- python-sdk: Python 3.10–3.14 matrix (was 3.12).
- Removed `.changeset` from `test_e2e-dojo` and `test_e2e-legacy-v1`
path filters (not in triggers).
- Corrected `showcase_validate.yml` path filter.
- Removed non-existent `test_smoke-starter-deployed.yml` matrix row +
footnote.
- **test/unit paths-ignore reversal:** `test/unit` does NOT fire on
`showcase/**` or `sdk-python/**` PRs — both ARE in `test_unit.yml`
paths-ignore (the doc claimed the opposite). Added `static/quality` to
the sdk-python PR list; fixed danger path to
`sdk-python/copilotkit/langgraph_agent.py`.
- Classic branch protection is fully **disabled** (API returns 404) —
there is no live `required_status_checks.contexts` array.
- Depot footnote: runner is `depot-ubuntu-24.04-4` and most workflows
(not just dojo) run on Depot.
### showcase/RAILWAY.md (8 fixes)
- **Pending Service Provisioning section was fully stale:**
`built-in-agent` is provisioned with real `railway_id f4f8371a-...` in
the `ALL_SERVICES` matrix in `showcase_build.yml`; `grep
PLACEHOLDER-CREATE-RAILWAY-SERVICE` => 0 matches repo-wide. Rewrote
section, removed the fail-loud paragraph.
- Fabricated `generate-starters.ts` → real `extract-starter.ts` /
`provision-starter-fleet.ts`.
- **autoUpdates 41→24/40:** live prod env (b14919f4) has 40 services,
only **24** with `autoUpdates.type=minor`, 16 with none; minor services
carry an all-day/every-day `schedule` array (not "no schedule").
- **showcase_build-vs-deploy attribution:** `serviceInstanceRedeploy`
(via `redeploy-env.ts`) runs from `showcase_build.yml` ("Build & Push");
`showcase_deploy.yml` ("Verify Deploy") only health-checks it. Fixed in
Auto-Updates section, Known Quirks, and the "Add to ALL_SERVICES matrix"
step.
- `smoke.yml` is auto-discovery driven — no edit needed to add a
`showcase-*` service.
### showcase/aimock/RAILWAY.md (6 fixes)
- §1: runs the `showcase-aimock` wrapper image (not the bare
`@copilotkit/aimock` container).
- §3: published `linux/amd64` only (arm64 not published).
- §4 browser-pool: `BROWSER_POOL_MAX_CONTEXTS=24` (was 40); D5 peak
4×4=16 contexts ~4.8 GB; knob is `FEATURE_CONCURRENCY_D6` in
`d6-all-pills.ts` (no `e2e-deep.ts` exists).
- **§5 startCommand:** both live prod (b14919f4) and staging (8edfef02)
aimock instances run a single `--fixtures /fixtures` (before the
provider flags), not three subdir flags. Replaced the code block + flag
table row and **corrected the recursion warning** (a single `--fixtures
/fixtures` loads the whole baked tree — verified live, healthy).
- §7: verify curl payload `"ping"` → `"Respond with exactly: OK"` (the
actual `smoke.json` match string).
## RED→GREEN evidence (highest-impact)
- **qa count:** `ls showcase/integrations/*/qa/*.md | wc -l` => 498; 20
packages.
- **test/unit paths-ignore:** `test_unit.yml` paths-ignore includes both
`showcase/**` and `sdk-python/**` — directly reverses the doc's
parenthetical.
- **showcase_build vs deploy:** `showcase_build.yml:810 npx tsx
showcase/scripts/redeploy-env.ts staging ...`; `showcase_deploy.yml`
name = "Showcase: Verify Deploy", no redeploy call.
- **autoUpdates 24/40:** live GraphQL on prod env b14919f4 — 40
services, 24 minor, 16 none; minor schedule = 7 days, startHour 0 /
endHour 24.
- **aimock §5 startCommand:** live GraphQL on prod + staging — `node
/app/dist/cli.js --proxy-only --fixtures /fixtures --provider-openai ...
--validate-on-load --host 0.0.0.0 --port 4010`.
Audit ref: claim-by-claim audit @ commit 99f24fa168. Full fix-log
retained out-of-tree at `aimock-docs-fix-log.md`.
## Test plan
- [ ] `oxfmt --check` passes on the three docs (verified locally).
- [ ] Docs-only change; no code/CI behavior affected.
## CopilotDrawer — promote threads management to the SDK
Implements the hardened [CopilotDrawer design (Threads
v2)](https://app.notion.com/p/3883aa3818528190b5d1f9b6ba26dca0).
Promotes thread listing/selection/creation/rename/archive/delete out of
the example apps and into the SDK as a framework-agnostic shadow-DOM web
component plus a React vertical (Angular wrapper included; Vue/RN to
follow).
> Note: assumes
[CopilotKit#5624](https://github.com/CopilotKit/CopilotKit/pull/5624) is
already merged (built on top of its `unarchiveThread` surface).
### What's in this PR (by area)
- **A — `@copilotkit/core`**: thread store with optimistic
add/rename/archive/delete (delete rolls back on reject), session-guarded
results (a superseded session can't leak an error/rollback),
`startNewThread` (non-explicit), `refetchThreads`,
`getServerState`/`getServerSnapshot`, per-store memoized selectors, and
**non-fatal** realtime diagnostics (a channel-join or
metadata-credential failure warns and keeps the already-loaded list
rather than blanking it). Registers a `"threads"` feature for catalog
gating.
- **B — `@copilotkit/web-components` (new package)**: the shadow-DOM
`<copilotkit-drawer>` Lit element — self-contained styles with
**build-time token sync** from react-core's `@theme` (checked-in
`generated-tokens.ts`, drift-guarded), slot projection for custom rows
(`slot="row:{id}"`), license/upsell gating, active/all filtering, and a
mobile modal. The element owns view-state; the host owns domain state.
Multi-format build: ESM/CJS externalize `lit` (peer dep); the **UMD
bundle inlines `lit`** so the advertised `<script>`/CDN path is
self-contained.
- **C/E/F — `@copilotkit/react-core`**: `<CopilotDrawer>` wrapper
(custom-element interop, two-pronged license gate, scoped chat-input
focus-return, registration-gated header launcher);
`CopilotChatConfigurationProvider` gains `drawerOpen` + `<768px`
modal/drawer mutual-exclusion + a **non-explicit active-thread setter**
so a bare `<CopilotDrawer>` connects to the picked thread and shows the
welcome screen on "+ New" with no host wiring; `useThreads` gains an
`{enabled}` gate and a list-only error channel.
- **D — `@copilotkit/angular`**: `injectThreads` signal wrapper —
reactive runtime context (re-dispatches when `wsUrl`/`threadEndpoints`
arrive), enabled-gated single-slot registration, bound result methods,
and error/loading parity with react-core.
### Quality
- Tests: core, web-components (46), react-core (1370), angular (106) —
all green; `tsc --noEmit` clean across all four; full builds pass; oxfmt
clean.
- **Code review:** 4 rounds of an unbiased 7-slot review fleet (15
agents/round) — 3 fix rounds + 1 confirming round that converged with
zero load-bearing findings. The convergence lever was applied per the
requester: the deep focus/overlay-accessibility rework, pagination
wiring, and Angular parity gaps are deferred to the documented
follow-ups below rather than blocking this PR.
### Follow-ups (intentionally deferred)
- **Accessibility (own PR + a11y test harness):** the mobile-modal Tab
trap now runs at the host level and contains the backdrop (fixed in
review); remaining a11y work — focus-restore to the opener on close,
scroll-lock reference-counting, confirm-delete `alertdialog` focus
management, the default-open-on-mobile scroll-lock, and a dedicated a11y
test harness.
- **Pagination wiring:** the element exposes `hasMore`/fetch-more UI but
nothing triggers it and the wrapper doesn't pass a `limit`; also clear a
recovered next-page error and cancel an in-flight next-page on filter
change.
- **Error surfacing:** wire `onError` to a transient toast and separate
the mutation-error channel from the list-load error (the list no longer
blanks on a failed mutation — the rollback restores the row — but
mutation failures are currently silent beyond the rejected promise).
- **Angular parity:** mirror react-core's `startNewThread` config-error
dismissal, add a list-only `listError`, and refresh
intelligence/threadEndpoints in `updateRuntime` on a runtime-URL change.
- **Reach:** Vue + React Native wrappers; de-fork the ~18 example apps
onto the SDK drawer; nested-provider active-thread sharing.
- **Minor:** migrate the react-core/angular wrappers to per-store
`store.selectors`; honor-or-drop `ThreadEnvironment.fetch` (RxJS
`fromFetch` ignores it; both wrappers pass `globalThis.fetch`, so no
behavior change today); `threadId=""` controlled-predicate edge; reveal
animation for slotted rows; empty-string-timestamp coalescing;
`_visibleThreads` recompute; empty-name `aria-label`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The mobile drawer is a modal dialog, but the Tab trap was bound to .root only,
while the backdrop button renders as a sibling OUTSIDE .root — so Tab from the
backdrop (or any out-of-root node) could escape the modal. Move the trap to the
host keydown listener (keydown is composed, so it catches the backdrop too) and
include the backdrop in the focusable cycle; keep initial focus on the first
real control. Adds a regression test for backdrop/outside-root Tab containment.
The UMD bundle inlines lit for a self-contained CDN <script>; inlining lit also
pulls in its runtime deps (@lit/reactive-element, lit-html, lit-element,
@lit-labs/ssr-dom-shim). tsdown flags that transitive bundling as 'unintended'
and promotes the warning to a fatal error under CI, breaking
@copilotkit/web-components:build (and every dependent). Declare all lit packages
in noExternal and set inlineOnly: false to mark the bundling intentional.
Angular signal wrapper over the core thread store: reactive runtime context
(re-dispatches when wsUrl/threadEndpoints arrive), enabled-gated single-slot
registration, bound result methods, and synthesized error/loading parity with
react-core.
Add <CopilotDrawer> (interops with the shadow-DOM element, two-pronged license
gate, scoped chat-input focus return, registration-gated header launcher) and
extend CopilotChatConfigurationProvider with drawerOpen + mobile mutual
exclusion + a non-explicit active-thread setter so a bare drawer connects to the
picked thread and resets on New with no host wiring. useThreads gains an
{enabled} gate and a list-only error channel.
Shadow-DOM Lit element that renders the threads drawer: self-contained styles
with build-time token sync from react-core's theme, slot projection for custom
rows, license/upsell gating, filtering, and a mobile modal. View-state lives on
the element; domain state stays with the host. Multi-format build (ESM/CJS
externalize lit as a peer dep; UMD inlines lit so the CDN <script> path is
self-contained).
Add the thread store (optimistic add/rename/archive/delete with delete
rollback-on-reject, session-guarded results, startNewThread/refetchThreads,
getServerState/getServerSnapshot, per-store memoized selectors, and non-fatal
realtime diagnostics for channel-join and metadata-credential failures) and
register the "threads" feature so the SDK can gate thread management.
## What
Makes the Slack triage agent runtime (`examples/slack/runtime.ts`)
resilient to MCP server failures. A down, misconfigured, or hanging MCP
server no longer takes the whole agent down.
## Why
The factory connected its MCP clients with `Promise.all`, and
`@tanstack/ai-mcp`'s `createMCPClient` connects eagerly. So a single
unreachable server (bad API key, sidecar not running, network hang)
rejected the entire `Promise.all`, the factory threw, and the run
surfaced as a fatal:
```
⚠️ Agent error: Failed to connect to MCP server
```
The bot was effectively dead for **every** request — even ones that need
no MCP at all (plain chat, web search, rendering). This was hit in
practice when `.env` carried a placeholder `LINEAR_API_KEY` / missing
Notion sidecar.
## How
- Connect each MCP server **independently** via `Promise.allSettled`,
each wrapped in an 8s timeout (a hanging server can't stall the turn; a
late post-timeout failure is swallowed so it can't become an unhandled
rejection).
- **Drop** the servers that fail; the agent runs with whatever's left —
web search, card/chart rendering, thread reading, and any MCP that did
connect.
- Inject a per-turn system note listing the unavailable sources,
instructing the model to mention the outage **only if the user's request
needs that source**, and never to invent data or claim a write
succeeded.
- Connections are re-attempted each turn, so a transient outage
**self-heals** on the next message.
## Testing
- `pnpm check-types` on the example — clean for `runtime.ts` (one
pre-existing unrelated error in `app/demo-restart.tsx` re: unbuilt
`@copilotkit/bot-store-redis`).
- Manual: ran the example with a deliberately bad `LINEAR_API_KEY`.
General questions answer normally (Linear dropped, logged `MCP "Linear"
unavailable this turn`); asking for Linear yields a graceful
"temporarily unreachable" instead of a fatal agent error.
The triage runtime connected its MCP clients with Promise.all, so a single
unreachable/misconfigured server (bad key, sidecar down, hang) rejected the
whole run and surfaced as a fatal "⚠️ Agent error: Failed to connect to
MCP server" — the bot was dead for every request, even ones needing no MCP.
Connect each server independently (Promise.allSettled) with an 8s timeout,
drop the ones that fail, and let the agent run with whatever's left (web search,
rendering, thread reading, and any MCP that did connect). A per-turn system note
lists any down sources so the model only tells the user a source is unreachable
if they actually ask for it, and never invents data. Connections are retried
each turn, so a transient outage self-heals.
## What
Replaces the homemade animated `⏳` "thinking"
placeholder (a posted message whose dots cycled via `setInterval`) with
Slack's **native** `assistant.threads.setStatus` "is thinking…"
indicator — and generalizes it from assistant-pane-only to **every
thread-anchored reply**: channel @-mentions, channel threads, DMs, and
the pane.
## Why
The hourglass was an extra post-then-delete message that looked nothing
like the native loading state other Slack agents show. Slack has since
relaxed `assistant.threads.setStatus` to accept the ordinary
**`chat:write`** scope (not just `assistant:write`), specifically so
channel-based apps can show AI loading states in channels and DMs
([docs](https://docs.slack.dev/reference/methods/assistant.threads.setStatus/)).
The adapter already used `setStatus` for the pane — this just extends it
everywhere and deletes the placeholder.
## How
- **`event-renderer.ts`** — delete the hourglass machinery
(`startThinking`/`claimThinking`/`clearThinking`/`setInterval`);
generalize `setPaneStatus`/`clearPaneStatus` →
`setStatus`/`clearStatus`. New `status?: { threadTs, isPane, config }`
arg: `statusMode` drives the thinking indicator; `isPane` only selects
tool-progress surface (pane composer status vs `task_update` timeline /
`🔧` rows — both unchanged).
- **`adapter.ts`** — `createRunRenderer` builds `status` for any target
with a thread anchor (`threadTs ?? statusTs`); `assistant: false` opts
out everywhere.
- **`types.ts` / `slack-listener.ts`** — flat DMs (no `thread_ts`) carry
the inbound message `ts` as the status anchor; replies still post flat.
- **README** — document the broadened status + the `chat:write`
relaxation.
## Testing
- `nx run-many -t test build` for `bot-slack` — **248 tests pass**,
build clean, oxlint 0 errors, oxfmt clean.
- New tests cover non-pane channel-thread status, the `🔧` tool
path staying on non-pane, and the DM `statusTs` anchor.
- Net **−149 lines**.
> Note: the DM path anchors `setStatus` to a non-assistant DM message ts
— the one piece only verifiable against the live Slack API. Channel
@-mentions already carry a real `thread_ts`.
ɵMemoryStore exposes addMemory(input: NewMemory) and
updateMemory(id, changes: MemoryChanges), but those input types were
internal — a thin framework binding (useMemories / injectMemories) had
no way to name them when wrapping the store. Export them alongside the
existing ɵMemory / ɵMemoryKind / ɵMemoryScope.
Replace the homemade animated ⏳ placeholder (a posted
message with setInterval-cycled dots) with Slack's native
assistant.threads.setStatus "is thinking..." indicator on every thread-anchored
reply -- channel @-mentions, channel threads, DMs, and the assistant pane -- not
just the pane. Slack now accepts setStatus under chat:write (not only
assistant:write), so it works for channel-based apps. Flat DMs carry the inbound
message ts as the status anchor. assistant:false opts out everywhere.
## What
Bumps the canonical CopilotKit pin across **all showcase integrations +
shell** from `1.61.1` to `1.61.2`, and adopts the new DX from
[CopilotKit#5611](https://github.com/CopilotKit/CopilotKit/pull/5611) in
the A2UI dynamic demos.
`1.61.1` (published 2026-06-23 21:28) predates the #5611 merge
(2026-06-24 13:36), so the feature could not reach showcase without a
release. `1.61.2` carries it (verified `providerA2UIHasCatalog` in
runtime + `a2uiCatalogAvailable` in react-core).
## Changes
- **Version bump** — `showcase/scripts/showcase-canonical-pins.json`
`1.61.1` → `1.61.2`; every `@copilotkit/*` pin in 22 `package.json` + 22
`package-lock.json` regenerated to match.
- **Feature adoption** — removed the now-redundant runtime `a2ui` block
(`injectA2UITool: true` + `defaultCatalogId:
"declarative-gen-ui-catalog"`) from the **declarative-gen-ui (A2UI
dynamic)** route of:
- `langgraph-python`, `langgraph-fastapi`, `langgraph-typescript`
- `strands`, `strands-typescript`
- `google-adk`
The catalog passed to the provider (`<CopilotKit a2ui={{ catalog }}>`)
now auto-enables A2UI and defaults tool injection on. The forwarded
catalog supplies its own `catalogId` (the sdk-js A2UI middleware
auto-derives `defaultCatalogId` from it), so the old "Catalog not found"
fallback no longer applies. Stale comments updated accordingly.
## Verification
- `validate-pins` drift ratchet **unchanged** (count 38, same hash) —
`@copilotkit/*` pins are not in the FAIL set, so no re-baseline needed.
- **langgraph-python D6 `gen-ui-declarative`: green** end-to-end (real
container, aimock replay) with the runtime `a2ui` config removed — no
"Catalog not found".
- strands D6 (runtime-middleware path) verification in flight; langgraph
siblings + ADK covered by parity (identical edit + middleware path).
The 2 reds in the langgraph-python D6 run (`auth`,
`gen-ui-open-advanced`) are unrelated demos with timing-based flakiness,
not affected by this change.
Memory handlers now forward client-actionable 4xx platform statuses
verbatim (404 not-found, 409 conflict, 422 unprocessable) so a useMemories
consumer can distinguish those from a server error, and map a platform 5xx
(or malformed status) to 502 rather than collapsing everything to 500 — the
runtime is healthy, its dependency failed. This also avoids a Response
RangeError on an out-of-range status.
parseMemoryBody and the platform client's createMemory/updateMemory now
treat scope as optional, deferring the default to the platform.
refresh() now returns a promise that rejects when the re-pull fails
(listFailed) or the store is stopped mid-flight, instead of resolving
silently as if it succeeded — symmetric with the mutation methods. The
error still lands in the error selector for declarative consumers.
toMutationBody no longer defaults scope to "user"; it forwards scope only
when the caller supplies it, so the platform owns the single default.
Bump the canonical CopilotKit pin across all showcase integrations + shell
to 1.61.2 (canonical-pins.json, every package.json + package-lock.json),
which carries CopilotKit#5611: passing a catalog to the provider
(`<CopilotKit a2ui={{ catalog }}>`) now auto-enables A2UI and defaults tool
injection on, so the runtime no longer needs an explicit `a2ui` config.
Demonstrate the feature on the A2UI dynamic (declarative-gen-ui) demos by
removing the now-redundant runtime `a2ui` block (`injectA2UITool: true` +
`defaultCatalogId`) from:
- langgraph-python, langgraph-fastapi, langgraph-typescript
- strands, strands-typescript
- google-adk
The forwarded catalog supplies its own catalogId (sdk-js A2UI middleware
auto-derives `defaultCatalogId` from it), so the previous "Catalog not found"
fallback no longer applies.
Verified: validate-pins drift ratchet unchanged (38 / same hash);
langgraph-python D6 `gen-ui-declarative` green end-to-end (no Catalog-not-found).
Adds POST /memories (create), PATCH /memories/:id (supersede), and
DELETE /memories/:id (retire) to the intelligence runtime, mirroring the GET
read route: CopilotKitIntelligence.{createMemory,updateMemory,removeMemory}
proxy the platform's /api/memories with the identifyUser-resolved user via the
x-cpki-user-id header. Completes the REST surface the client memory store's
addMemory/updateMemory/removeMemory call.
refresh() now resolves once the re-pulled snapshot settles (listSucceeded/
listFailed) for the current session, or immediately when no context is set —
so a useMemories-style binding can expose refresh(): Promise<void>. Mirrors
trackMutation's completion handling.
memories/list fell through to the POST-only default in validateHttpMethod,
so GET /memories returned 405. Add it to the GET-allowed group alongside
threads/list.
Adds a runtime-native memory read endpoint mirroring /threads: route
(memories/list) → handleListMemories → CopilotKitIntelligence.listMemories,
which proxies the platform's GET /api/memories with the project API key and
the user resolved via identifyUser (scoped through the x-cpki-user-id header,
never a client-supplied id). The client memory store's {runtimeUrl}/memories
fetch now resolves out of the box for any intelligence runtime — no per-app
BFF code. Read-only for now (list); mutations to follow.
## Release monorepo v1.61.2
**Scope:** `monorepo` | **Bump:** `patch`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `monorepo` packages to `1.61.2`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `monorepo` packages to npm at version `1.61.2`
- Creates git tag `monorepo/v1.61.2`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
## Release bot-slack v0.1.0
**Scope:** `bot-slack` | **Bump:** `minor`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `bot-slack` packages to `0.1.0`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `bot-slack` packages to npm at version `0.1.0`
- Creates git tag `bot-slack/v0.1.0`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
Bare useAgent() calls inside a CopilotChatConfigurationProvider were falling back to the default agent instead of the provider's resolved agentId. The hook now keeps explicit props first, then chat configuration, then the shared default, with Vue coverage for inheritance and explicit-prop precedence.
Constraint: Vue useAgent owns its own agent resolution path separate from React
Rejected: Threading agentId through every consumer | duplicates provider state and misses bare hook users
Confidence: high
Scope-risk: narrow
Tested: pnpm nx run @copilotkit/vue:test -- src/v2/hooks/__tests__/use-agent.test.ts
Tested: pnpm --dir packages/vue exec eslint src/v2/hooks/use-agent.ts src/v2/hooks/__tests__/use-agent.test.ts
Tested: pnpm nx run @copilotkit/vue:check-types
Tested: pnpm nx run @copilotkit/vue:test
Tested: lefthook pre-commit test-and-check-packages (ran @copilotkit/vue test, build, publint, attw)
Not-tested: pnpm nx run @copilotkit/vue:lint (fails on pre-existing unrelated Vue lint errors)
Related: CopilotKit/CopilotKit#5656
## Summary
Fixes the two RED A2UI D6 demos in the **built-in-agent** showcase
integration. Both are confirmed GREEN locally via the D6 control-plane
probe (`--d6 --direct --isolate`).
### a2ui-fixed-schema — React #31 crash → GREEN
**Root cause:** the showcase authors A2UI catalog defs with root
`zod@4`, but `@a2ui/web_core`'s `GenericBinder` schema scraper inspects
**Zod-3** internals (`_def.typeName==='ZodUnion'`). A zod@4 union
reports `_def.typeName===undefined` → field misclassified STATIC → the
raw `{path}` binding object reaches render → **React error #31**.
**Fix:** author this demo's catalog with a `zod-v3` (`npm:zod@3.25.76`)
alias so the binder resolves bindings (card now renders `SFO → JFK,
$289`). NOT a react-core fix.
### declarative-gen-ui — surface-missing (no paint) → GREEN
**Root cause:** the secondary-LLM prompt was far thinner than the
canonical generation guidelines, so it emitted trees that (correctly)
failed the renderer's `surfaceHasRenderableContent` paint gate. The
paint gate is correct and untouched.
**Fix:** port the canonical generation rules into the prompt, add output
validation, add catalog parity (`DataTable` + info-row), ground the
planner with sales-context, and record multi-turn aimock fixtures (4/4
turns paint).
## Red→green
- a2ui-fixed-schema: RED `Minified React error #31 {path}` → GREEN `✓
d6:built-in-agent green`, card resolves real values.
- declarative-gen-ui: RED `reason=surface-missing` → GREEN
`turnsCompleted:4, 1 passed`, all sales pills paint
(KPI/pie/bar/DataTable/status/info-row).
## Cross-regression
The shared-renderer path was checked across A2UI demos on both runtimes:
gen-ui-agent ✓, beautiful-chat 5/5 ✓, langgraph-python (v1)
a2ui-fixed-schema ✓ — no regressions.
## Code review
2 review rounds (7 agents each). Applied: a2ui factory validation
hardening (plain-object data guard, unique-id check, fail-loud on
non-string secondary-LLM return), two-arg `z.record` (zod@4 API),
index-based DataTable row key, Metric `trendValue` rendering for neutral
trend, doc corrections.
## Follow-ups (not this PR)
- **openai@5 / zod@4 peer ERESOLVE** on fresh `npm install`
(pre-existing; committed lock installs via `npm ci`).
- Cross-integration `sales-context.ts` sync (5 byte-identical copies).
- A2UI tree cycle/reachability enforcement (deferred — not a realistic
LLM-output threat; unique-id IS enforced).
- Non-string component-id edge in the unique-id guard.
- `SYSTEM_PROMPT` catalog enumeration omits `DataTable`.
- `page.tsx` docstring drift; DonutChart legend mirror; Row `align` map;
fixture match-key coupling.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The secondary-LLM prompt was far thinner than the canonical generation guidelines,
so it emitted trees that (correctly) failed the renderer's paint gate → surface-missing.
Port the canonical generation rules into the prompt, add output validation, add catalog
parity (DataTable + info-row), ground the planner with sales-context, and record
multi-turn aimock fixtures. Includes CR fixes: two-arg z.record for the DataTable rows
schema (zod@4 API), index-based DataTable row key, and Metric trendValue rendering for
neutral trend.
The showcase authors A2UI catalog defs with root zod@4, but @a2ui/web_core's
GenericBinder schema scraper inspects Zod-3 internals (_def.typeName==='ZodUnion').
A zod@4 union reports _def.typeName===undefined → misclassified STATIC → the raw
{path} binding object reaches render → React error #31. Author this demo's catalog
with a zod-v3 (npm:zod@3.25.76) alias so the binder resolves bindings. Includes CR
hardening of the shared a2ui factory validation (plain-object data guard, unique-id
check, fail-loud on non-string secondary-LLM return).