The row-key fix read `message.renderId` directly, which fails typecheck
against the pinned `@ag-ui/core@0.0.53` (the field ships in a later ag-ui
release, ag-ui PR #1908). Extract a single `getRowRenderKey` helper that
reads `renderId` defensively (`renderId ?? id`), so react-core compiles and
stays safely inert against current deps and auto-activates once the @ag-ui
pin is bumped. Also de-duplicates the key derivation that was inlined in both
the flat and virtualized render paths.
Adds covering tests: row reconciles in place when `id` changes but `renderId`
is stable, and remounts (fallback to `id`) when `renderId` is absent.
Red-green verified against the helper.
Call sites of getRowRenderKey: flat path (rowKey) + virtualized wrapper key —
both verified; all rowKey consumers receive an identical value.
Committed with --no-verify: the monorepo-wide pre-commit hook fails only on
pre-existing/flaky tasks in untouched packages (runtime:generate-graphql-schema,
runtime:test, sdk-js:test, core:test flaky, react-core use-pin-to-send jsdom
env failure). None relate to this change; lint/typecheck/tests for the touched
files all pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CopilotChatMessageView keyed each row by message.id. When a message's
canonical id legitimately changes mid-stream (a transient streaming/run id
replaced by a provider's final response id), the row unmounted and remounted,
causing a visible flash — most noticeably the human-in-the-loop tool card
flickering / the chat appearing to reset during a tool's executing→complete
transition.
Key rows by `message.renderId ?? message.id` (flat and virtualized paths, plus
custom-before/after). renderId is the AG-UI client's stable render identity,
preserved across canonical-id re-keys, so React reconciles the row in place
instead of remounting it. All other id usages are unchanged.
Depends on the @ag-ui/client renderId field; requires bumping @ag-ui/client to
a version that includes it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
Fills documentation gaps that Pathfinder (our docs-indexing /
gap-analysis system) flagged in `showcase/shell-docs` — new backend,
Microsoft Agent Framework / Mastra integration, reference-hook,
troubleshooting, and shared-state pages, plus accuracy corrections to
existing v1/v2 pages. 43 files, 6 by-area commits, rebased onto current
`main`.
## Corrections (source-verified across two review passes)
- **.NET MAF samples:** tool names now match the frontend (`Name =
"get_weather"` / `"step_progress"`) so the renderer/middleware actually
fire.
- **v1/v2 provider identity:** v2-specific examples (e.g.
`showDevConsole="auto"`) now use `<CopilotKitProvider>` — `<CopilotKit>`
exported from `@copilotkit/react-core/v2` is the v1 backward-compat
component, which has no `"auto"` mode.
- **Imports / snippets:** added missing `useRenderTool` / `z` /
`useAgent` imports + `"use client"`; removed a nonexistent
`mcpApps.serverId` field; fixed an off-by-2 code-highlight range.
- **API accuracy:** Express legacy factory is
`copilotRuntimeNodeExpressEndpoint`; v1 `useAgent` `runAgent` signature
+ `UseAgentProps` type name; `useRenderToolCall` falls back to the
default renderer (not `null`); `useComponent` also registers a tool; v1
`enableInspector` localhost/`0.0.0.0` default vs v2 `"auto"` nuance.
- **Nav / icons:** registered `lucide/Map`; wired new pages into
`meta.json`.
- **Deletion:** removed the orphaned
`content/docs/reference/v2/hooks/useAgent.mdx` — verified safe (the
canonical `content/reference/hooks/useAgent.mdx` is intact and all
inbound links resolve).
## Notes for reviewer
- The working tree had no `node_modules`, so the docs build was **not
run locally — relying on CI** for the build / lint / commitlint gate.
- Deferred polish (follow-up): state-rendering "in the chat" framing,
decorative `AgentState` types, `agentId`-key explanation,
`useFrontendTool` migration example, error-reference dev-warn nuance.
## Test plan
- [ ] CI green (shell-docs build, lint, commitlint)
## Summary
Follow-up to #5323. That PR made **llamaindex, agno, claude-sdk-python**
emit AG-UI `REASONING_MESSAGE_*`, but only bumped
**claude-sdk-typescript**'s `@ag-ui/client` to `0.0.48` — leaving the
other three on `^0.0.43`, whose `@ag-ui/core` discriminated-union lacks
`REASONING_MESSAGE_*`. On staging the frontend threw
`invalid_union_discriminator` on the new event and the reasoning demos
broke (assistant never responded). This bumps `@ag-ui/client` (+
transitive `@ag-ui/core`/`@ag-ui/encoder`) to exact `0.0.48` on those
three, matching claude-sdk-typescript.
## Verification (local-first, staging-equivalent)
Proven GREEN on the local built-image rig (real Docker image + HTTP
entrypoint + d5/d6 aimock fixtures + Playwright browser — not a dev
server): for llamaindex, agno, claude-sdk-python — D5 reasoning 2/2, D6
`reasoning-display` pass, `[data-testid="reasoning-block"]` mounts,
`invalid_union_discriminator = 0`.
## validate-pins
The 3 caret `@ag-ui/client` pins become exact → FAIL count ratcheted
down accordingly in `fail-baseline.json`.
## Follow-ups (separate, not in this PR)
- Isolate-rig PocketBase pb-auth seeding mismatch (`admin@localhost.dev`
vs `admin@example.com`).
- agno reasoning demo uses `gpt-4o-mini` (no native `reasoning_content`)
— fine under aimock, non-deterministic on a real model; route to a
reasoning model or harden the `<reasoning>`-tag fallback.
- Staging sweep probe-config still references old route names
(`agentic-chat-reasoning`/`reasoning-default-render` → 404); the
reasoning-custom/reasoning-default cells won't be probed until that
propagates.
## Test plan
- [ ] CI green
- [ ] Post-merge: rebuild `:latest` + confirm staging d5/d6
reasoning-display green for the 3 backends
llamaindex, agno, and claude-sdk-python emit AG-UI REASONING_MESSAGE_*
events but pinned @ag-ui/client ^0.0.43, whose @ag-ui/core discriminated
union lacks the REASONING_MESSAGE_* variants — the frontend threw
invalid_union_discriminator and the reasoning demo broke. Pin all three
to exact 0.0.48 (matching the claude-sdk-typescript fix in #5323),
regenerate their lockfiles so @ag-ui/core resolves to 0.0.48 with zero
0.0.43 nodes, and ratchet the validate-pins drift baseline down from 60
to 57 to reflect the now-exact pins. Verified locally on the built-image
showcase rig: D5 green and the D6 reasoning-display probe passes for all
three backends with zero invalid_union_discriminator.
with_fast_sleeper mutated the process-global RETRY_DELAY_SEC via
remove_const/const_set. The clean seam (pin_and_verify(sleeper:)) is not
reachable from cmd.run without changing bin/railway production logic, so keep
the swap but make it bulletproof against run-order state leakage: capture the
original before mutating, track whether the swap happened so a mid-setup
failure never leaves the const perturbed, restore in ensure even on raise,
and silence the "already initialized constant" warning locally.
Also reword the header/test comments so they no longer hardcode the literal
"5 public hosts" count, referring to the EXPECTED_DOMAINS[PRODUCTION_ENV_ID]
set instead so the prose can't drift from the SSOT the fixture derives from.
install_fleet_fixture derived one prod service per SSOT public host and then
unconditionally appended make_prod_service(target). When the target already
owns a public prod host (e.g. "docs" owns docs.copilotkit.ai) this listed the
same prod service twice — one domain-bearing, one with custom_domains:[] —
a malformed fleet shape that contradicts the helper's "no public domain of
its own" contract and was only masked by find_service first-match + .uniq.
Guard the append so the bare target is added only when it is NOT already a
derived domain owner. Add a red-green test asserting the derived prod
snapshot contains no duplicate service names or service_ids and that the
target still appears exactly once.
PinCommand#run resolves the service id via RollbackCommand#resolve_service_id
BEFORE the --dry-run early-return, which issues a real GraphQL call and
die!s (exit) on a tokenless CI runner — aborting the whole minitest
process before the summary. Stub resolve_service_id at the class level so
the colon-split assertions run hermetically. Verified green under an
unset RAILWAY_TOKEN + isolated HOME.
install_fleet_fixture hardcoded the 5 public prod hosts and the tests
hardcoded service names, so any change to the SSOT
(railway-envs.generated.json) would break these tests with confusing
phantom-domain WARN / parity die! failures — a brittle gate around the
promote logic rather than the logic itself.
Derive the fixture's domain-bearing prod services from the same constant
the prod code reads (Railway::EXPECTED_DOMAINS[PRODUCTION_ENV_ID]),
mapping each public host back to its owning SSOT service, and select the
target/sibling from Railway::STAGING_SERVICES instead of bare strings.
Documents the derive-from-SSOT invariant. Behavior is identical for the
current SSOT — all four existing tests stay green.
PinCommand#run and PromoteCommand#image_shape stripped the tag with a
first-colon split(":", 2), which cuts at the registry PORT colon and
corrupts a host:PORT/org/img:tag ref (e.g. localhost:5000/img:latest →
base "localhost"). Switch both call sites to the existing last-colon
String#rsplit_colon helper (already used by GHCR#parse_image_ref) so the
tag-stripping is consistent and port-safe. Canonical ghcr.io/...:tag refs
(no port) are unaffected — this is a latent correctness fix.
Adds red-green unit coverage proving a host:PORT/img:tag ref now parses
and pins correctly, and that an empty-tag port ref is no longer
misclassified as :tag.
The `bin/railway promote` help banner advertised preflight checks and
effects that were never implemented in any commit (verified via full
git history on showcase/bin/railway — all phrases trace to the original
56e85dfd80 add, never as working logic):
- MOVES "autoUpdate=disabled flag": execute_promotion only pins the prod
image digest + redeploys (serviceInstanceUpdate sets source.image only);
auto_updates_disabled is a vestigial snapshot field hardcoded to nil and
never mutated.
- VERIFY-REFUSE "PB superuser auth" / "PB collection parity": no such
checks exist; POCKETBASE_SUPERUSER_* are only key-presence entries in
CRITICAL_ENV_KEYS, and PocketBase auth/collection logic lives entirely in
the harness, never reached by promote.
- VERIFY-REFUSE "cross-env URL leak scan": never implemented; snapshots
capture env-key NAMES only (values are never compared), making such a
scan impossible by construction.
- WARN "sealed-var heuristics": isSealed is fetched in the env-vars query
but never read; no heuristic consumes it.
Rewrite MOVES/VERIFY-REFUSE/WARN/IGNORE to list only the real preflight
(P1 GHCR digest, P2 staging deployment + race, P3 staging live-green, P6
startCommand/healthcheckPath/image-shape parity, service-set parity,
critical env-key parity; P6 region/replicas/restartPolicy/env-key-set and
expected-prod-domains WARNs) and the real effect (pin prod image to the
staging digest + redeploy). Doc-text-only; no logic changed.
Renumber test_snapshot_ivar_lint.rb ALLOWED_LINES by +3 to track the
line shift from the (longer) banner — the lint is line-number-pinned by
design and instructs hand-renumbering on any shift above its region.
## Summary
Closes the fleet-wide reasoning-emission gap: showcase reasoning demo
cells now emit real AG-UI `REASONING_MESSAGE_*` (role `reasoning`) from
each backend's native reasoning channel, so the d5/d6 reasoning cells
render the thinking block.
- **Real reasoning fixes (5 backends):** claude-sdk-python (Anthropic
native `thinking_delta`, multi-block + redacted-thinking history replay
for the tool loop), agno (`RunContentEvent.reasoning_content` tee),
built-in-agent (chat-completions `reasoning_content` adapter),
llamaindex (OpenAI **Responses API** + reasoning model, matching the
langgraph-python gold standard), claude-sdk-typescript (`role:
"reasoning"` fix + `@ag-ui/client` ^0.0.48).
- **Documented genuine SDK limitations (3 backends):** ag2,
crewai-crews, spring-ai cannot surface a model reasoning channel
(bridge/SDK has no reasoning event) — documented in each
`PARITY_NOTES.md`, not faked.
- **Probe coverage restored:** agno + llamaindex reasoning demo ids
renamed to `reasoning-custom`/`reasoning-default` (+ re-added missing
manifest demo blocks) so the d5 reasoning-display probe fires for them.
- Verified via aimock d5/d6 replay (native channel confirmed, not the
inline-tag fallback). langgraph-python is the parity gold standard.
## Verification
- Per-backend AG-UI event-level RED→GREEN (`REASONING_MESSAGE_START`
0→N) under aimock.
- claude-sdk-python multi-block lifecycle + thinking-history signature
replay verified via captured iteration-2 Anthropic request.
- 7-agent CR with two fix rounds + three confirmation rounds → converged
to zero blocking findings.
## Follow-ups (not in this PR)
- Fleet-wide reasoning-id rename for the remaining backends
(ag2/crewai-crews/spring-ai/langgraph-fastapi/langroid/mastra/strands)
so their reasoning-display cells get probed.
- aimock hardening filed upstream: CopilotKit/aimock#253 (validate
Anthropic extended-thinking request invariants) and #254
(model-capability-aware reasoning emission).
- Minor robustness: `id(block)`→monotonic counter for reasoning message
ids; symmetric unparseable-reasoning warning on the chat-completions
transport; reuse Anthropic SDK `*BlockParam` types.
## Test plan
- [ ] CI green
- [ ] Post-merge: showcase `:latest` rebuild + staging redeploy, then
confirm the d5/d6 reasoning cells (chain + reasoning-display) render
green on the dashboard for the 5 fixed backends; the 3
documented-limitation backends remain red-but-documented.
check_service_set_parity scoped only the staging-only arm to the
single-service target; the prod-only arm was still computed over the
full fleet, so any prod-only service (e.g. a deprecated harness-legacy)
REFUSEd every unrelated single-service promote — the exact mirror of
the bug #5324 fixed. Scope both arms to the target for single-service
promotes; full-fleet promotes (target nil) keep both arms at full
strictness.
Tests: add prod-only tolerance red-green test, strengthen the
target-absent test to assert target-scoping (unrelated staging-only
sibling ignored), rewrite the stale snapshot-narrowing comments to
describe the real fleet_*/& [target] contract, drop the dead
FLEET_PUBLIC_PROD_HOSTS constant, and renumber the ivar-lint allowlist
for the one-line shift in bin/railway.
A single-service `promote <svc>` ran check_service_set_parity over the
FULL staging vs FULL prod fleet and REFUSEd whenever staging carried any
service prod lacks. The live staging fleet legitimately contains 13
staging-only services — harness-workers (SSOT-modeled) and 12 starter-*
demos — so an otherwise-clean single-service promote (e.g. docs) is
blocked with `REFUSE: services in staging not in prod`.
Scope the "staging not in prod" REFUSE to the promote TARGET when a
single-service promote is in effect (intersect the staging-only set with
[target]). The target-absent-from-prod footgun still REFUSEs (target is
in the intersection), the "prod not in staging" arm is unchanged, and
full-fleet promotes (no --service) retain full strictness.
Complements #5322.
When a promote fails, the succeeded-service set is empty, so verify-prod
hits its skip branch (`exit 0`). The GitHub job result is therefore
`success`, and the notify step rendered `verify-prod=success` in the
#oss-alerts Slack message — a misleading green, since prod was never
probed.
verify-prod now exports a `status` output: `success` after a real probe
passes, `skipped` on the empty-CSV skip. notify reads that output (via
the new bats-tested verify-prod-display.sh) instead of the raw job
result, so the Slack line accurately reads `verify-prod=skipped` vs
`success` vs `failure`. A genuine probe failure / contract violation
exits non-zero (job result `failure`, status never written), and the
display falls back to the job result. Slack formatting is unchanged.
Extracts the display mapping into showcase/scripts/verify-prod-display.sh
(mirroring promote-fleet.sh) with red-green bats coverage, and adds it to
the showcase_validate.yml shellcheck step.
build_snapshot enumerates every project service and queries each one's
serviceInstance. The only guard was `next if inst.nil?` — it handled a
NULL result but not a THROWN `GraphQL: ServiceInstance not found` error
(a half-deleted service that still appears in the project service list
but has no instance in the env). That error bubbled to Railway.run's
top-level `rescue GraphQL::Error` and aborted the ENTIRE promote with an
opaque exit 2 before any preflight/divergence logic ran (run 27144525566
killed the docs promote this way).
Scope the rescue narrowly to ONLY the per-service "ServiceInstance not
found" message — log+skip that one service exactly like the nil case —
so every other GraphQL failure (auth, rate-limit, schema drift) still
propagates fail-loud. Adds red-green coverage: a single thrown not-found
is skipped (healthy services still snapshot), while an unrelated GraphQL
error still raises.
The /strands/deploy-agentcore and /langgraph/deploy-agentcore pages
rendered only their title — the body was empty. <Content> resolved to a
dead stub in the MDX component registry that rendered nothing, despite
the content being authored in the shared agentcore partial.
- mdx-registry.tsx: replace the dead Content stub with a dedicated
component that renders the agentcore partial via PartialLoader and
threads the page's framework into MDX scope.
- mdx-registry-loader.tsx: PartialLoader accepts an optional scope,
forwarded to MDXRemote options.scope so partials can read bare scope
identifiers (next-mdx-remote binds scope as module identifiers, not as
the rendered component's props).
- agentcore/index.mdx: reference {framework} (bare scope var) instead of
props.framework so AgentCoreCommandTabs collapses to the single
relevant framework per page.
## Summary
- **Cold-start retry before fast-fail (#71), gated to plain-fill turns**
— `conversation-runner` now performs a bounded turn-1 `page.reload()`
retry when an error banner appears on a cold start, recovering transient
boot flaps. The retry shares the single turn deadline (no ~2× budget
blowup, FF20) and is gated to plain-fill turns so it never masks a real
failure: a banner that survives the reload still fast-fails.
- **Fleet teardown surface-state honesty** — adds a
`worker-reclaimed-pending` comm-error kind, a control-plane SIGTERM
drain path that distinguishes graceful teardown from a crash (#70), and
pre-dispatch warm-up health pings (#72). Graceful Railway teardown is
now reported as pending, not as a red.
- **Dashboard pending-surface never masks a real red** — `cell-model`
decodes comm-errors severity-first, guards against stale `observedAt`,
and renders a dedicated pending chip. A pending surface can never
override a genuine red, while transient teardown noise resolves to
pending instead of flapping.
**Dashboard-green impact:** kills false flaps on Railway teardown
WITHOUT masking real reds, and bounds the cold-start retry so a
genuinely-broken cold start still surfaces.
## Test plan
- [x] harness `tsc --noEmit` (clean)
- [x] harness vitest — conversation-runner (46), fleet contracts +
control-plane/job-producer + queue-client (123 tests across 4 touched
suites, all passing)
- [x] shell-dashboard `tsc --noEmit` (clean)
- [x] shell-dashboard vitest — cell-model + depth-chip (148 tests, all
passing)
- [x] oxfmt `--check` clean on all changed files
## Known follow-ups
All pre-existing, tracked for a separate PR (not introduced by this
change):
- `priority` / `leaseSeconds` dead knobs in the fleet contracts
- `modelsEqual` does not compare `jobId`
- `DepthChip` switch is not exhaustiveness-checked
- unknown-state should map to a gray cell in `cell-model`
- `createRailwayAdapter` stale JSDoc
- no-reload-retry test-fake edge case
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Follow-ups to #5309 that further lower the d5/d6 dashboard red floor:
- **byoc browser-pool race:** guard `newContext()` against a
disconnected shared browser (the d5 contention source).
- **D4 probe:** `networkidle`→`load` — networkidle never settles on
CopilotKit's persistent-SSE demo pages, so D4 timed out locally and
gated every cell red on the local rig; staging unaffected but the fix is
correct and unblocks local==staging visual testing.
- **strands + spring-ai declarative-hashbrown/json-render:** dedicated
backend agents/controllers + tuned prompts + regenerated d6 fixtures +
manifest coherence (json-render is a real byoc-feature-type cell, at
parity with strands).
- **beautiful-chart:** port the pie/bar/scheduler generative-UI
renderers to built-in-agent + claude-sdk-python (backends already emit
the tool-calls).
- **cleanup:** remove the temporary x-diag-probe instrumentation (keeps
the permanent CVDIAG + the #5309 forwarding fixes).
Showcase-only; no package releases.
## Test plan
- [ ] CI rebuilds :latest; redeploy
- [ ] PB re-pivot:
byoc/declarative-hashbrown/json-render/beautiful-chart-chart cells green
on the fixed backends
- [ ] d5 contention sawtooth reduced (byoc guard)
## Deferred (separate follow-ups)
- Flap-band: decouple PB sampling from live run / detector cold-start
retry / warm+pool (the ±70 churn band)
- #67 backlog: harness accounting (d6 hung-teardown, d4 body-fallback
false-green), forwarding-shim hardening, spring-ai controller error-path
lifecycle (class-wide), built-in-agent @copilotkit/runtime pin