- New Platforms entry: /platform/slack quickstart — manifest-based app
creation, Socket Mode tokens, minimal createBot bot run with tsx,
interactive JSX with inline onClick, slash commands, production split
- New "Bots" SDK tab in the reference picker with per-symbol pages for
@copilotkit/bot, @copilotkit/bot-ui, and @copilotkit/bot-slack
(Components / Functions / Classes / Types)
- Rename reference picker labels to React (V2) / React (V1)
- Remove the retired /reference/sdk pages (LangGraph/CrewAI SDK,
Remote Endpoints); search/sitemap/llms indexes derive from the
content tree, so they de-index with the deletion
- Retarget the one inbound link to its /reference/v1 copy
Co-Authored-By: Claude <noreply@anthropic.com>
Closes [OSS-299](https://linear.app/copilotkit/issue/OSS-299).
Follow-up to #5248 (already merged).
## Problem
The `hero_command_copied` PostHog event added in #5248
(`showcase/shell-docs/src/components/hero-start-commands.tsx`) carries
no surface discriminator. `HeroStartActions` renders on **both** the
home hero and **every** framework landing hero:
- The **create** card embeds the framework in `command` (`--framework
langgraph-js`), so it's recoverable.
- The **onboard** card's command (`npx copilotkit@latest skills
onboard`) is byte-identical on every page — so onboard copies **cannot**
be attributed to a surface from the event alone.
Every sibling event in shell-docs already carries a "where" property —
`cli_command_copied` → `location: window.location.pathname`, the nav
events → `location`, `markdown_copied`/`open_in_llm_clicked` → `path`.
`hero_command_copied` was the only one without one.
## Fix
Add `location: window.location.pathname` to the `hero_command_copied`
payload, mirroring the `cli_command_copied` event the global
`<CopyTracker>` already emits for the same copy (verified: it
monkeypatches `navigator.clipboard.writeText`, which the hero calls).
The two paired events now join cleanly on the same dimension. Guarded
for SSR (`typeof window !== "undefined"`) to match the sibling.
## Test
Adds a colocated source-assertion guard test. shell-docs vitest runs in
the `node` environment (no jsdom/RTL), so this follows the suite's
existing convention (`readFileSync` + assertions, like
`brand-nav.test.tsx`) rather than introducing a behavioral render
harness.
```
✓ src/components/__tests__/hero-start-commands.test.tsx (3 tests)
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Pin alignment fixes 9 validate-pins FAILs; ratchet the drift baseline
count and hash accordingly. Also tighten the _comment: document the exact
hash recipe (SHA-256 of the stderr-only [FAIL] lines, LC_ALL=C sort -u)
and correct baselineDemoCount semantics (exact expected demo count per
package; deviation either direction warns).
Align showcase integration requirements.txt files (strands,
langgraph-fastapi, langgraph-python, pydantic-ai, google-adk,
crewai-crews) to the fleet pin standard, including an accurate
typing_extensions comment in crewai-crews and a trailing newline in
langgraph-python.
Replace floating "beta" dist-tags with exact versions for @ag-ui/mastra,
@mastra/{client-js,core,libsql,memory}, and mastra in both the examples
and showcase mastra packages. Showcase mastra also raises its zod floor
^3.24.0 -> ^3.25.0. The examples mastra package additionally carries the
fleet-wide @ag-ui/client 0.0.55 bump and single-tree overrides here, since
its manifest mixes both changes.
The hero_command_copied event fired by the landing-hero command cards carried
no surface discriminator. HeroStartActions renders on both the home hero and
every framework landing hero; the "onboard" card's command is byte-identical
on every page, so onboard copies could not be attributed to a surface from the
event alone (only the "create" card embeds the framework in `command`).
Add `location: window.location.pathname` to the payload, mirroring the
`cli_command_copied` event the global <CopyTracker> already emits for the same
copy so the two paired events join on the same dimension. Guarded for SSR to
match the sibling.
Adds a source-assertion guard test in the shell-docs node-env convention.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What
Replaces the landing-page CTA with **three entry points**, framed by
situation, and renders the **identical action block on the home hero and
every framework landing hero**:
| | action |
|---|---|
| **New project** | `npx copilotkit create` |
| **Existing project** | `npx copilotkit skills onboard` |
| **Guided walkthrough** | **Quickstart** button (preserved from the
previous hero) |
- **Unified `<HeroStartActions>` block**: two equal-weight command cards
plus a quickstart row beneath, shared verbatim by the home hero and the
framework landing heroes (per review: the two surfaces previously
diverged).
- **Quickstart preserved** in its original accent treatment. On the home
hero it is the framework-picker dropdown (`<HeroQuickstartDropdown>`,
restored); on framework pages it links straight to that framework's
quickstart guide. The home hero also keeps the "Learn more about
building with agents" link in the same row.
- **Framework landing heroes** (e.g. `/langgraph-typescript`): the
create command **pre-fills the framework** via the CLI's `--framework`
flag (e.g. `--framework langgraph-js`).
**Framework-flag mapping**: docs slug to CLI `--framework` value,
verified against the CLI's `AGENT_FRAMEWORKS` enum
(`langgraph-typescript`→`langgraph-js`,
`langgraph-python`→`langgraph-py`, `google-adk`→`adk`,
`strands`→`aws-strands-py`,
`ms-agent-dotnet`→`microsoft-agent-framework-dotnet`, identical for
`mastra`/`pydantic-ai`/`llamaindex`/`agno`/`ag2`). Slugs with **no** 1:1
CLI template fall back to a bare `npx copilotkit create`, notably
`crewai-crews` (the CLI ships *CrewAI Flows*, not Crews), plus
`langgraph-fastapi`, `claude-sdk-*`, `langroid`, `spring-ai`,
`agent-spec`, `deepagents`. `skills onboard` has no framework flag, so
it is identical everywhere. Frameworks with bespoke setup (`a2a` `git
clone`, `ms-agent-dotnet`) keep the pre-cards layout: quickstart button
plus their own copy-command chip.
**Responsive, with all text always visible.** Commands **wrap, never
truncate**:
- Wraps happen at spaces only; every token is non-breaking, so
`--framework` can never split into a dangling `-` at a line edge.
- `text-wrap: balance` splits multi-line commands evenly, typically
right at the flag boundary (`npx copilotkit@latest create` /
`--framework langgraph-js`).
- The block caps at 740px with 12px mono, the narrowest cap where both
home commands fit one line with enough headroom to survive platform
mono-font width differences.
- Cards sit two-up from `sm` and stack below it; the grid (`min-w-0`,
`items-stretch`) keeps long commands inside their track and the card
pair equal-height.
## Screenshots
**Home**: two cards, quickstart dropdown, learn-more link

**Home, quickstart dropdown open** (framework picker preserved)

**Framework landing (LangGraph)**: same block, framework pre-filled,
create command balanced across two lines, quickstart links to the guide

**Worst case (Microsoft Agent Framework, Python)**: longest CLI flag
value, three balanced lines, fully readable

**Bespoke setup (A2A)**: quickstart button plus own command chip
(pre-cards layout preserved)

**Mobile (375px)**: cards stack, quickstart goes full-width
| home | framework |
|---|---|
| 
| 
|
## Telemetry
Both hero copy buttons are now explicitly instrumented: each click
captures **`hero_command_copied`** (`command_id`: `create` | `onboard`,
full `command` string, `clipboard_blocked`), so create-vs-onboard
funnels are queryable per landing page. The pre-existing global
`cli_command_copied` (fired by `CopyTracker` on any clipboard copy)
still fires for volume metrics; the new event uses a different name so
that funnel is not double-counted. Validated locally against a live
PostHog client: each click POSTs both events (plus `$autocapture`) to
`/ingest/e` with HTTP 200.
## Notes
- Both cards equal weight; accent only on hover. Copy rows copy on click
with `aria-live` feedback plus a clipboard-blocked fallback; cursor is
`pointer`.
- Removes `agent-start-prompt.tsx` and `hero-command-copy.tsx`.
`hero-quickstart-dropdown.tsx` is back (restored unchanged after review
feedback).
## Summary
- Live staging redeploy evidence (2026-06-10 16:37Z): 2/6 workers
completed the full SIGTERM → abandon → deregister sequence in **under 1
second**, while 4/6 were SIGKILLed mid-browser-teardown because
Railway's ~10s stop grace is shorter than the old 25s drain budget —
leaving 4 stale roster rows and a reclaim splash on every deploy.
- This PR makes abandon + deregister the **guarded, sub-second critical
path** and demotes teardown to best-effort within a composed <10s
budget, so a platform kill mid-teardown is harmless.
## Design
- `drainFleetWorker` ordering: drain → `registration.stop` → bounded
deregister → graced `worker.stop` → always-run pool shutdown, with
stop-error precedence (a pool-shutdown failure can never mask the stop
error).
- `DRAIN_DEREGISTER_TIMEOUT_MS` (3s) bounds the **whole registration
write chain**, so a hung—not failing—PocketBase cannot consume the kill
window; timeout degrades to the documented crash-path reclaim.
- `safeLog` guards every loop/stop/drain-path log: a throwing logger can
neither reject the worker loop's done-promise nor skip the roster delete
or teardown (abort-before-log in `requestDrain`; structural-caller
guards in `drainFleetWorker`).
- Drain-aware lease renewal (an abandoned job's lease lapses instead of
being re-extended), mid-drain claim skip (a claim won after the drain
decision is never run), and mid-report precision (a run that began
reporting is never logged as abandoned).
- Never-throws loop closure: loop-crash logging via `done.catch` +
`/health` 503, heartbeat and idle-poll sleep hardening with a non-busy
pacing floor, aggregate-key protocol-violation wrap.
- `WORKER_DRAIN_GRACE_MS` default 25s → 6s; the composed 3+6 < 10s
budget is **pinned by a test**; present-but-invalid overrides warn;
overrides at/above ~7s are documented as forfeiting the composed budget.
- Boot-failure teardown catches now log (no silent chromium stranding).
## Review
- 6 unbiased 7-agent CR rounds + 5 fix rounds; every behavioral change
red-green or mutation-proven; `Promise.race` loser semantics empirically
pinned by test.
- ~30 pre-existing harness findings deferred to the flap-fix follow-up
backlog (top of the next fleet-robustness PR: lease-renew
retry-on-throw, empty-registry guard/dispatch mismatch,
`registered`-flag refresh, worker `/health` async bind race, queue fetch
timeouts).
## Test plan
- [x] 2176/2176 vitest (32 new tests)
- [x] `tsc --noEmit` both configs
- [x] oxfmt clean
- [ ] CI green on this PR
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Hardens the `--isolate` showcase verification flow across three areas:
**1. XDG state migration.** Isolate slot registry and per-run
rewritten-compose scratch dirs move off `/tmp` (wiped on reboot,
world-writable) to
`${XDG_STATE_HOME:-$HOME/.local/state}/copilotkit/showcase/` (`slots/` +
`runs/<name>/`). `/tmp` clearing silently destroyed a kept stack's
compose file and slot, making `--keep` unreliable. Run dirs are keyed by
the finalized project name (not PID) so a kept run is locatable for
manual teardown.
**2. Slot reaping + registry concurrency.** Since the state dir is now
persistent, slots are reaped by compose-project liveness (`docker ps
--filter label=com.docker.compose.project=<name>`), with PID/age
heuristics as fallback. The registry is made safe under concurrent
claimers: a sweep lock with heartbeat updates, own-pid lock release, and
tombstones; a claim-then-verify duplicate-name guard closing the TOCTOU
window; crash-safe reap ordering with compose-down of reap remnants and
a path-traversal guard. Failed `--isolate` setup no longer tears down
the default stack; half-initialized state is cleaned up on the way out.
Teardown uses `--volumes` everywhere, and a failed compose-down
preserves state for diagnosis. `--isolate` names are validated (must
start with lowercase letter/digit; `showcase` is reserved — it aliases
the default stack), and a fail-loud warning precedes pre-down of an
existing stack.
**3. `--keep` now actually persists an isolated stack.** Previously the
unconditional `trap restore_isolation EXIT` tore the stack down
regardless of `--keep`. Teardown is now gated on the keep flag
(`ISOLATE_KEEP` promoted to a global so it survives `cmd_test` return
into the trap scope): the slot + run dir are retained and a survival
notice prints the project name, the three offset host ports, and the
exact `docker compose -p <name> down` command — no silent port/slot
leak. A kept stack's live containers keep its slot from being reaped.
Shell-only — confined to `showcase/scripts/cli/_common.sh` +
`cmd-test.sh`; the harness TS only reads the env vars the shell exports
(unchanged). Follows up the `--keep` caveat documented in #5346.
## Review hardening
The branch went through an 8-round, 7-agent code-review loop with
red-green-verified fixes — that loop produced the state-machine
hardening commit (trap-scope fix, default-stack guards, registry
concurrency/teardown robustness, name validation) and grew the test
suite to pin every fix. A live end-to-end `--keep` verification run is
what surfaced the trap-scope bug (`--keep` silently not honored),
driving the `ISOLATE_KEEP` global fix.
## Test plan
- [x] `showcase/scripts/__tests__/isolate.bats` — 41 isolate tests
(red→green): XDG path resolution (+`XDG_STATE_HOME` override,
`~/.local/state` fallback, `runs/<name>`), liveness-based reaping (dead
project reaped/reclaimed, live project preserved), real-trap-path
`--keep` tests (no simulated-trap shortcuts), sweep/lock/tombstone race
pins (heartbeat resurrection, lock takeover, duplicate-name TOCTOU),
reap-order probe pinning live-slot protection, root/PID-reuse/DST
guards, and sentinel anti-vacuity discipline so trap tests cannot pass
vacuously.
- [x] Full `bats showcase/scripts/__tests__/` green, matching CI's Shell
script tests invocation.
- [x] shellcheck: no new warnings.
- [x] Live end-to-end: `bin/showcase test <slug> --d6 --isolate <name>
--keep` persists the stack under `~/.local/state/copilotkit/showcase`,
survival notice + manual teardown work, follow-up run reaps the stale
slot.
## Summary
- PR #5352's worker-side flap fixes never reached staging automatically:
`harness-workers` runs the same `showcase-harness` image as the
`harness` scheduler, but the SSOT's `ciBuilt: false` conflated "owns a
build slot" with "should be redeployed when its image is rebuilt" — so
main merges redeployed only the scheduler and the workers silently kept
running a stale image (a manual redeploy was required to ship the
fixes).
- This adds an `imageOf` field to the Railway SSOT so a rebuilt image
redeploys **all** of its consumers: the CI redeploy scope is now built
slots ∪ their `imageOf` consumers that declare the target env.
- Staging default scope becomes 27 (26 ciBuilt + `harness-workers` via
expansion); prod is unchanged at 26 (the worker is staging-only and the
expansion is env-aware).
## Design
- `imageOf: "<ssot-key>"` on consumer entries (`harness-workers` →
`harness`), enforced by a module-load invariant
`assertImageConsumersValid`: dangling targets, non-ciBuilt producers,
consumer chains, and consumer envs not a subset of the producer's all
fail loud at import; lookups are prototype-safe (`Object.hasOwn`).
- `expandImageConsumers` in `redeploy-env.ts` performs the env-aware,
single-level expansion and fails loud on unnormalized env names
(synonyms like `production` must go through `resolveEnv`) — the first
real consumer of `ENV_ID_BY_NAME`.
- Service-name resolution (`resolveTargetServices`/`runRedeploy`) now
rejects inherited `Object.prototype` keys with the proper
Unknown-service operator error.
- The explicit `--services` passthrough (a named service is attempted
even in an env it does not declare) is documented and contract-pinned by
a test.
## Review
- 5 unbiased 7-agent CR rounds plus a diff-attribution triage; every
diff-authored finding fixed with red-green proofs.
- ~30 pre-existing script-hygiene findings (env-registry consolidation,
accessor leniency, fetch timeout, parseArgs edges, coverage gaps in
`makeLiveRedeploy`/summary-JSON, etc.) deferred to the flap-fix
follow-up backlog.
## Test plan
- [x] 82/82 vitest (13 new tests: expansion, env-awareness, invariants
incl. prototype keys and env-subset, contract pins)
- [x] `tsc --noEmit -p showcase/scripts/tsconfig.json` clean
- [x] oxfmt clean on all changed files
- [ ] CI green on this PR
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The webhooks SSOT comment claimed the push-driven default scope is
"guaranteed" to leave webhooks untouched — false: a push touching the
build workflow files trips the workflow_config paths-filter disjunct,
which selects every matrix slot (webhooks included; its skip_build slot
still reports success and enters the redeploy CSV). Reworded to state
the actual behavior. imageOf doc now states the enforced NON-EMPTY
subset constraint; serviceEnvPairs doc now truthfully says it has no
consumers yet; file header notes the probe flag default and
bin/railway's Ruby-only "stage" synonym.
Test hygiene: drop the stale bin/railway line-number citation from a
test name, the _envConfigTypeAnchor (EnvironmentConfig is genuinely
referenced by the shape-compile test), a dead eslint-disable, and a
dead `as never` cast (env is an open string); align the webhooks
dispatch-name pin regex with the extraction regex's whitespace
tolerance.
assertEnvRegistryConsistent gains four clauses: (iv) a key present in
both ENV_IDS and ENV_ID_BY_NAME must carry the same env-id (ENV_IDS.prod
drifted to the staging id previously passed every clause while
resolveEnv("prod") silently returned staging); (v) every ENV_IDS env-id
must be carried by a canonical name (was only caught lazily in
resolveEnv); (vi) registry keys must be trim().toLowerCase()-normalized
(resolveEnv lowercases input, so a non-lowercase spelling is registered
but unreachable); (vii) no registry key may be an Object.prototype
property name.
expandImageConsumers' per-entry env skip-check becomes an own-property
test (Object.hasOwn) for uniformity with every other lookup in the file.
REDEPLOY_SUMMARY_JSON is trimmed before the set-but-empty branch so a
whitespace-only value hits the loud warn path instead of attempting a
JSON write against a garbage path.
- ciBuilt field doc: pocketbase IS showcase-CI-built — only webhooks
remains out-of-band; keep the MUST-NOT-touch claim for webhooks only.
- gateValidated field doc: true for every service EXCEPT the two
gateIgnore entries (harness-workers, harness-legacy), not "every
service".
- CI_BUILT_SERVICES comment: also names the excluded non-CI-built
harness-workers and harness-legacy alongside webhooks.
- legacyJsonCompat doc: it is bin/railway's EXPECTED_DOMAINS derivation
that filters out *.up.railway.app hosts (no "parity test rejects"
claim contradicting the placeholder data below).
- webhooks entry comment: a manual service=all build dispatch MAY
bounce webhooks staging (the skip_build slot still reports success,
entering the matrix ∩ success-set redeploy scope); only the
push-driven default scope is guaranteed to leave webhooks untouched.
- railway-envs: route envsFor/instanceIdFor/domainFor/probeEnabled (and
repoNameFor) through shared getEntry/getEnvCfg own-property helpers so
inherited Object.prototype keys on either axis produce the curated
error (or probeEnabled's contract false) instead of raw TypeErrors,
silent undefined, or a spurious probe=true.
- railway-envs: two new module-load invariants (synthetic-map
injectable): assertEnvRegistryConsistent (per-service env keys are
registered canonical names; ENV_ID_BY_NAME env-ids unique; every
canonical name has an ENV_IDS spelling) and
assertServiceAndInstanceIdsUnique (serviceId unique per entry,
instanceId globally unique).
- redeploy-env: invert the exit-code policy to fail-loud by default —
any env except the documented staging carve-out exits non-zero on
per-service failure (a future preview/canary env inherits fatal
semantics instead of silently swallowing failures).
- redeploy-env: sanitize per-service THROWN error messages through
sanitizeErrorBody before recording; flatten bare \r in the summary
table escape.
- redeploy-env: warn on set-but-empty REDEPLOY_SUMMARY_JSON; reject
flag-like --services CSV parts (both forms) and a flag-like first
argument (missing env); derive usage env lists from ENV_IDS.
- tests: prototype-key sweep across all accessors, invariant
positive/negative coverage, third-env exit-code pin, sanitization and
CLI-guard coverage, makeLiveRedeploy !res.ok and non-true mutation
branches.
Cross-session review fixes for the --isolate machinery (one concern:
source + test + docs).
1) Reaper reserved-name guard (critical): _reap_isolate_slot trusted
slot records — a record naming 'showcase' (corrupt, or written by an
older CLI version before apply_isolation reserved the name) passes
the charset regex, so the reap ran `docker compose -p showcase down
--remove-orphans --volumes` against the LIVE default stack,
destroying the PocketBase named volume. The reserved name now gets
the same treatment as the path-traversal guard: warn (naming the
record and why it is dangerous) and leave the slot intact for manual
inspection — no compose-down, no state removal.
Call-site enumeration: _reap_isolate_slot's sole caller is
_sweep_isolate_slots, at 3 sites (dead-PID reap, project-recorded/
no-owner reap, age-fallback reap), all passing
"$slot_entry" "$slot_proj" — all three flow through the new guard
identically.
Red-green: the new bats test ("a slot whose project record reads the
RESERVED 'showcase' is left intact...") was run against the UNFIXED
code first and FAILED — the sweep logged "Attempting to reclaim
stale slot 0 (project showcase has no live containers and no
recorded owner)" and reaped the slot. It passes with the guard.
2) .iso-bak restore race: two concurrent runs can both see a stale
backup; the loser's mv is the FINAL command of its `[ -f ] && mv`
AND-list, so its failure trips set -e and kills the CLI pre-claim
with a raw error. Both mv's now carry `2>/dev/null || true` — the
survivor's restore wins, the loser proceeds with restored originals.
3) Keep-test absence regexes greped only the `--project-name <name>
down` spelling; the reaper's own downs use `-p <name> down`, so a
keep-branch regression via the -p form passed undetected. Both keep
absence assertions now match `(--project-name|-p) <name> down`.
Mutation-verified: a temporary -p-form compose-down added to the
keep branch made BOTH broadened tests FAIL; reverted, suite green.
(All other absence assertions use the word-matched generic
`compose ... down` regex, which already covers both spellings.)
4) RUNBOOK.md/DEBUGGING.md contradicted shipped code: the manual
teardown was quoted without --volumes plus notes claiming
`down --remove-orphans` leaves named volumes (the shipped survival
notice and every teardown path include --volumes), and the name rule
was documented as `[a-z0-9_-]+` (actual: starts with [a-z0-9], then
[a-z0-9_-], uppercase normalized with a warn, 'showcase' reserved).
Both updated to the shipped semantics; the now-redundant separate
`down --volumes` snippets removed.
Verification: full `bats showcase/scripts/__tests__/` green (60 tests);
shellcheck on _common.sh shows no new warnings vs baseline
(pre-existing SC2034/SC2115 only, line-shifted).
Confirmation-CR bucket-(a) fixes for PR #5353 — one coherent concern:
env-name resolution has exactly ONE authority (the ENV_IDS /
ENV_ID_BY_NAME registries) and SSOT accessors fail loud instead of
silently returning wrong values.
- runRedeploy: resolve envId via ENV_ID_BY_NAME with an Object.hasOwn
guard + fail-loud throw listing the registered envs. Removes the
hardcoded `prod`/`staging` pair check and PRODUCTION/STAGING ternary
that contradicted the SSOT's documented open-env contract ("a new env
needs only a registry entry"); the registry lookup subsumes it.
- resolveEnv: derive resolution entirely from the registries (ENV_IDS
spellings -> env-id -> canonical ENV_ID_BY_NAME name) instead of its
own hardcoded synonym chain. Behavior identical for
prod/production/staging; still throws on unknowns, and now also
throws on a mis-wired registry (a spelling whose env-id has no
canonical name).
- serviceForDispatchName: fix the docstring's false "CI-built service"
claim — it does no ciBuilt filtering and tests pin the unfiltered
behavior (the non-CI-built webhooks resolves).
- repoNameFor: fail loud (consistent with instanceIdFor/domainFor)
instead of silently echoing the service name — the exact
silently-wrong-GHCR-name class this PR's hardening targets. Throws on
unknown service, on an env not registered in ENV_ID_BY_NAME
(unnormalized synonyms like "production"), and on a registered env
the service does not declare. Keeps the documented default (the
service name) for declared envs without an override.
Call-site enumeration confirming nothing relies on the old fallback:
- verify-railway-image-refs.ts:523 — iterates the entry's DECLARED
environments keys, registry-filtered, SSOT-matched service names
- __tests__/railway-envs.golden.test.ts:81 — iterates envsFor(name)
(declared envs only) over real SSOT keys
- railway-envs.test.ts repoNameFor cases — dual-env services,
prod/staging only
- __tests__/verify-railway-image-refs.test.ts:264 — FIVE_NEW keys,
all dual-env
- resolveTargetServices: throw when an explicitly-provided services
list resolves to zero entries (whitespace-only programmatic input)
instead of letting runRedeploy exit 0 having redeployed nothing; the
default undefined -> full CI-built scope is unchanged.
- makeLiveRedeploy: add signal: AbortSignal.timeout(30s) so a hung
Railway API records a per-service FAIL instead of stalling CI, and
pass GraphQL errors[].message through sanitizeErrorBody for
consistency with the HTTP-error path. Exported for direct unit tests.
Red-green: 11 new tests (open-env registry resolution incl. a
runtime-registered hypothetical env, repoNameFor negatives,
empty-resolution throw, abort-signal presence, GraphQL error
sanitization) all failed against the old code; full showcase/scripts
suite green (50 files, 1807 tests) + tsc --noEmit clean.
## Summary
Combined landing of three browser-verified showcase work waves (37
commits, per-integration grouping preserved), plus a full multi-round
code review with all mandatory fixes folded in.
**Wave 1 — @ag-ui currency (9/9 integrations).** Bump `@ag-ui/*`
frontend deps to exact `0.0.55` for: llamaindex, agno,
claude-sdk-python, pydantic-ai, strands, claude-sdk-typescript,
ms-agent-python, ms-agent-dotnet, google-adk. Exact pins land across 13
`package.json` files.
**Wave 2 — reasoning emission ports (4/4, ref #76).** Port
reasoning-message emission to ag2, crewai-crews, langroid, and spring-ai
(each also bumped to `@ag-ui` 0.0.55). Adds per-integration
`reasoning_agent` (Python) / `ReasoningController` (spring-ai Java) and
wires the copilotkit route.
**Wave 3 — google-adk demo parity (3/4).** Port `hitl`,
`threadid-frontend-tool-roundtrip`, and `gen-ui-interrupt` demos to
google-adk for parity with the gold reference, plus d6/aimock fixtures.
The `interrupt-headless` demo is intentionally kept `not_supported`
(needs aimock `customEvents` support + an ADK interrupt route — tracked
as a known upstream gap).
## Code review
A 5-round unbiased multi-agent CR loop ran against the combined diff and
converged at zero mandatory findings; the bucket-(c) promotion audit
came back clean. Key fixes folded into the branch:
- **Client error-leak hardening** in the crewai, langroid, and spring-ai
reasoning routes — internal exception details no longer leak to the
client, with `errorId` correlation between client response and server
logs (also applied to google-adk).
- **Protocol-correct reasoning error paths** — on failure the emitters
now close any open frames, emit a generic `RUN_ERROR`, and never emit
`RUN_FINISHED` after `RUN_ERROR`; verified against `@ag-ui/client`
`verifyEvents` semantics, with red-green tests.
- **`x-aimock-context` propagation** across spring-ai's async hop, so
fixture replay stays correct through the thread boundary.
- **Bounded reasoning executor** (no unbounded thread growth) and
**parse-failure observability** (failures surface in logs instead of
being swallowed).
- **Full multi-turn history threading** in the reasoning agents — prior
turns are now forwarded to the LLM, with single-turn byte-equality
preserved so existing aimock fixtures stay valid; covered by tests.
- **Phantom deps declared** — `@copilotkit/shared`/`@ag-ui` deps that
were imported but undeclared in claude-sdk-python and
ms-agent-dotnet/python are now in their `package.json`.
- **`@copilotkit/core` `"latest"` overrides pinned** to `1.59.4`.
- **Reasoning parity token-identity test** across the 3 ported Python
integrations (ag2/crewai-crews/langroid) so their reasoning streams
can't silently drift.
- **Ratchets tightened/updated**: shadow-collision ceiling ratcheted
down 151 → 123 (actual count), duplicate-fixture ceiling 288 → 290 (new
google-adk fixtures reuse standard prebuilt-probe pills,
runtime-disambiguated by demo route), and the `validate-pins` baseline
drops 57 → 48 (currency bumps retire 9 exact-pin FAIL lines).
The final commit is the `style: auto-fix formatting` bot commit
(formatting on the new Python test files).
## Test plan
- [x] Per-integration browser verification (each touched integration's
demos exercised end-to-end) with aimock fixtures added/updated.
- [x] crewai pytest — 113/113 pass.
- [x] `showcase/scripts` vitest — 1780/1780 pass.
- [x] `validate-parity` MUST checks — 19/19 pass, 0 fail.
- [x] `validate-pins` ratchet — exits 0 against the updated baseline
(FAIL-set hash matched).
- [x] D6 `gen-ui-custom` cells verified live (google-adk + agno).
- [x] CI fully green — per-integration Depot Docker `build-check`,
Python unit tests (3.10 + 3.12), commitlint, format, oxlint, Validate
Showcase, production-pinning lint.
## Follow-ups
Consolidated follow-up ledger (bucket c/d items):
https://www.notion.so/copilotkit/37b3aa38185281e5b871d0b907aaef71
_extract_user_input in the ag2, crewai-crews, and langroid reasoning agents
documented a str return but passed AG-UI message content straight through.
Multimodal content can be a list of parts, which would flow unmodified into
the single caller in each file (_run_reasoning_agent ->
messages=[{"role": "user", "content": user_input}]) sent to the OpenAI
chat-completions API. Coerce: str passes through, a list joins its text
parts (dict or attr form), anything else falls back to str().
Callers (one per file):
- ag2/src/agents/reasoning_agent.py:104 -> chat message at :119
- crewai-crews/src/agents/reasoning_agent.py:108 -> chat message at :123
- langroid/src/agents/reasoning_agent.py:103 -> chat message at :118
Add a dedicated Spring/Java ReasoningController (/reasoning/) that
reimplements the ag2 reasoning_agent.py BEHAVIOR: it makes a direct
streaming chat-completions call, reads the native delta.reasoning_content
channel (with a <reasoning>...</reasoning> regex fallback), and emits
RUN_STARTED -> REASONING_MESSAGE_START/CONTENT/END -> TEXT_MESSAGE_*
-> RUN_FINISHED so the CopilotKit reasoning slot mounts
[data-testid="reasoning-block"].
Spring AI's ChatClient drops delta.reasoning_content and the AG-UI Java
SDK has no REASONING_MESSAGE_* event types (only THINKING_*, which
@ag-ui/client drops), so the controller manages its own SseEmitter and
writes the reasoning frames as raw JSON matching the @ag-ui/client 0.0.55
wire schema. Header forwarding (x-aimock-context) rides the existing
WebClientConfig exchange filter. Wire route reasoning-custom/-default (plus
legacy aliases) to /reasoning/, mirroring ag2's reasoningAgentNames, and
bump @ag-ui/client ^0.0.43 -> 0.0.55 for REASONING_MESSAGE_* decode support.
(cherry picked from commit 4d183371c489013f0a7bcce1a447078164974aef)