The old "Showcase: Build & Deploy" workflow used a single concurrency group
that cancelled in-flight builds on every push to main. When multiple PRs
merged in quick succession, most service builds got cancelled and never
deployed.
Split into two workflows:
1. showcase_build.yml ("Showcase: Build & Push") - triggered on push to main,
builds Docker images and pushes to GHCR. Has NO concurrency group so every
run completes. Railway auto-update picks up the new :latest tag.
2. showcase_deploy.yml ("Showcase: Verify Deploy") - triggered by workflow_run
from the build workflow. Polls Railway to verify each service picked up the
new image and is healthy. Uses cancel-in-progress since verification is
idempotent. Posts results to showcase-harness via webhook.
Also updates showcase_capture-previews.yml to trigger from the renamed build
workflow.
## Summary
Reverts PR #4469 which replaced SpringAIAgent (streaming) with SyncAgent
(synchronous).
The fix changed the nature of the demo — spring-ai should demonstrate
streaming, not synchronous calls.
The proper fix needs to make SpringAIAgent.stream() correctly
auto-execute tool callbacks.
## Test plan
- [x] Revert verified — restores original SpringAIAgent code
## Summary
- Replace `SpringAIAgent` (streaming `.stream()`) with custom
`SyncAgent` (synchronous `.call()`) for all spring-ai showcase
controllers
- Add `JacksonConfig` to handle unknown AG-UI message roles (activity,
reasoning) gracefully
- Fix the root cause of spring-ai D5 timeouts on tool-rendering,
hitl-steps, gen-ui, mcp-apps, and shared-state features
## Why
Spring AI's streaming mode does NOT auto-execute tool callbacks. The
model returns `tool_calls` in the SSE stream but the tools are never
invoked. The AG-UI event stream emits `TOOL_CALL_START/ARGS/END` without
`TOOL_CALL_RESULT`, causing the CopilotKit runtime to re-invoke the
agent in an infinite loop (and eventual timeout).
By using `ChatClient.call()`, Spring AI's internal tool-execution loop
runs tools automatically. `SyncAgent` wraps each backend tool callback
to capture AG-UI events during auto-invocation, and registers frontend
tools as pass-through callbacks.
The `JacksonConfig` addresses a secondary issue: the AG-UI Java SDK's
`MessageMixin` only maps 5 roles (assistant, developer, user, system,
tool). Messages with roles like "reasoning" cause
`InvalidTypeIdException` during deserialization. The config overrides
the mixin with `defaultImpl = UnknownMessage.class` so unknown roles
deserialize harmlessly.
## Test plan
- [ ] Docker build succeeds (verified locally)
- [ ] D5 tool-rendering passes
- [ ] D5 subagents passes
- [ ] D5 hitl-steps passes
- [ ] D5 mcp-apps passes
- [ ] D5 shared-state passes
SpringAIAgent from the AG-UI Java SDK uses streaming (.stream()) for
Spring AI's ChatClient. In streaming mode, Spring AI does NOT auto-execute
tool callbacks -- the model returns tool_calls but the tools are never
invoked. This causes the CopilotKit runtime to see incomplete tool cycles
(TOOL_CALL_START/ARGS/END without TOOL_CALL_RESULT) and re-invoke the
agent in an infinite loop, producing the D5 timeouts on tool-rendering,
hitl-steps, gen-ui, and mcp-apps features.
SyncAgent uses ChatClient.call() instead, which runs Spring AI's internal
tool-execution loop synchronously. Backend tools are auto-invoked and
their results are captured as AG-UI events (TOOL_CALL_START/ARGS/END/
RESULT). Frontend tools from the CopilotKit runtime are registered as
pass-through callbacks that emit start/args/end events without a result,
letting the runtime handle frontend execution.
Also adds JacksonConfig to handle unknown message roles (activity,
reasoning) that the AG-UI Java SDK doesn't model. Without this, Jackson
throws InvalidTypeIdException on deserialization, causing request failures
before any controller logic runs.
Changes:
- Add SyncAgent.java: LocalAgent subclass using .call() with event-
capturing tool callback wrappers
- Add JacksonConfig.java: lenient MessageMixin with defaultImpl for
unknown roles
- Update AgentConfig to return SyncAgent bean instead of SpringAIAgent
- Update AgentController to inject SyncAgent instead of SpringAIAgent
- Update A2uiFixedSchemaController to build SyncAgent per request
- Update AgentConfigController to build SyncAgent per request
Previous harness deploy was cancelled by CI concurrency. This no-op
triggers a rebuild to pick up PRs #4460 (pool starvation fix), #4461
(pool logging), and #4465 (comprehensive harness logging).
## Summary
- Fixes `ERR_MODULE_NOT_FOUND: Cannot find package 'tsx'` crash in the
local langgraph-typescript container
- Root cause: the host bind mount `src:/app/src` clobbers
`src/agent/node_modules` that was installed during the Docker build
- Fix: anonymous volume `/app/src/agent/node_modules` preserves the
image's copy through the bind mount overlay
- google-adk D5 intermittent failures are pool starvation (already
addressed by #4460); no code change needed
## Test plan
- [x] Verified the compose container crash-loops before the fix
- [x] Verified the container starts healthy after the fix
- [x] Confirmed the production GHCR image works correctly (the issue
is compose-local only)
The prepack script ran `pnpm run build` during `pnpm publish`, duplicating
the build that prerelease.ts/publish-release.ts already do across the
workspace. With tsdown's `exports: true` regenerating package.json's
exports field on every build, the second rewrite happened mid-publish
and pnpm's pack step failed with ENOENT on the staging tarball.
No other monorepo package has prepack — bringing a2ui-renderer in line.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The host bind mount `src:/app/src` in docker-compose.local.yml overlays
the container's /app/src directory, clobbering src/agent/node_modules
that was installed during the Docker build. This causes `node --import tsx`
to fail with ERR_MODULE_NOT_FOUND at container startup.
Add an anonymous volume for /app/src/agent/node_modules so Docker
preserves the image's copy even when the parent directory is bind-mounted.
## Summary
- Introduces `FixedAGUIChatWorkflow` in the llamaindex hitl-in-chat
agent to work around 3 upstream bugs in `llama-index-protocols-ag-ui`
v0.2.2:
1. **Missing `parentMessageId`** on `ToolCallChunkWorkflowEvent` —
patched to propagate parent message ID
2. **Duplicate tool calls** from both `MESSAGES_SNAPSHOT` and
`TOOL_CALL_CHUNK` events — deduplicates by tracking emitted tool call
IDs
3. **Wrong role on tool results** — tool result messages sent as
`role="user"` instead of `role="tool"` — patched to correct the role
## Test plan
- [ ] D5 probe for `llamaindex / hitl-text-input` passes
- [ ] No regressions in other llamaindex features
The LlamaIndex AGUIChatWorkflow has three bugs that caused the
hitl-text-input test to fail:
1. ToolCallChunkWorkflowEvent emitted without parent_message_id,
causing the AG-UI client to create a duplicate assistant message
(one from MESSAGES_SNAPSHOT, one from dechunked TOOL_CALL_START).
The duplicate produced 8 time-picker buttons instead of 4.
2. _snapshot_messages embeds toolCalls in the MESSAGES_SNAPSHOT AND
emits separate TOOL_CALL_CHUNK events, so the client registers
the same tool call twice on the same message.
3. ag_ui_message_to_llama_index_message converts AG-UI ToolMessages
to ChatMessage(role='user') instead of role='tool', so the second-
leg OpenAI API call has no tool-result message and aimock's
hasToolResult matcher fails.
Fix: subclass AGUIChatWorkflow (FixedAGUIChatWorkflow) that:
- Assigns a stable ID to assistant messages and passes it as
parent_message_id on ToolCallChunk events
- Strips toolCalls from MESSAGES_SNAPSHOT (letting TOOL_CALL_CHUNK
be the sole mechanism)
- Corrects tool-result message role from 'user' to MessageRole.TOOL
so the OpenAI API receives proper tool-result messages
## Summary
- Log pool stats when abort signal force-releases a browser back to the
pool
- Provides visibility into starvation events for diagnosis in Railway
logs
Follow-up to PR #4460 which added the abort-release mechanism but no
telemetry.
## Test plan
- [x] 1372 harness tests pass
Log pool stats and context counts when the abort signal fires and
force-releases a browser, so pool starvation events are visible in
Railway logs for diagnosis.
## Summary
- Wire abort signal through to `createPooledE2eDeepLauncher` so pooled
browsers are force-released when the invoker's timeout fires
- Track open browser contexts and close them before releasing on abort
to prevent orphaned contexts
- Guard against double-release with a `forceReleased` flag so the
driver's finally block is a no-op after abort
## Root cause
When `executeOne`'s `Promise.race` timeout fires, it returns the timeout
result but the driver promise continues running with a pooled browser
acquired. The browser is not released back to the pool until the driver
eventually finishes (up to 5 minutes later). Since pool size (4) equals
`max_concurrency` (4), one orphaned browser means the next probe run (15
minutes later) can only acquire 3 browsers. Orphans accumulate across
ticks, causing cascading starvation that manifests as D5 probe flapping
in production.
## Changes
- `E2eDeepBrowserLauncher` type now accepts an optional `AbortSignal`
- `createPooledE2eDeepLauncher` registers an abort listener that closes
open contexts and releases the browser back to the pool immediately
- Driver's `run()` passes its abort signal to the launcher
- 5 new tests covering: abort release, context cleanup on abort,
pre-aborted signal, full starvation scenario with pool recovery, and
backwards-compat no-signal path
## Test plan
- [x] 5 new unit tests for the pooled launcher abort behavior (all pass)
- [x] Full harness test suite: 1372 tests pass, 0 failures
- [x] TypeScript typecheck clean
When the probe invoker's timeout fires, Promise.race returns the timeout
result but the driver promise keeps running with a pooled browser held.
The browser is not released back to the pool until the driver eventually
finishes (up to 5 minutes later). Since pool size (4) equals
max_concurrency (4), orphaned browsers accumulate across ticks and cause
cascading starvation -- each 15-minute probe run has fewer available
browsers until the pool is completely exhausted.
The fix wires the abort signal through to createPooledE2eDeepLauncher.
When abort fires, the launcher force-closes open browser contexts and
releases the browser back to the pool immediately. A forceReleased flag
prevents the driver's finally block from double-releasing.
Iterate on the image attachment rendering based on review feedback:
- Reduce thumbnail size to 80x80 (down from 300x300) so attachments
read as compact thumbnails like Claude's chat UI
- Render attachments above the message text instead of below, and lay
multiple attachments out in a horizontal row (flex-row + flex-wrap +
justify-end) instead of stacking vertically
- Add a muted background so transparent images stay readable
- Extract Lightbox + useLightbox into a shared module so the rendered
attachment can reuse the same click-to-zoom modal as the queue preview;
clicking a thumbnail now opens it in a fullscreen lightbox with a
view-transition morph
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Constrain image attachments to a 300x300 max size with object-cover and
12px rounded corners so they appear as small thumbnails in chat instead
of filling the message width. Applies to both the v2 renderer (Tailwind)
and the legacy react-ui renderer (CSS).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
- **Restores the eval system** accidentally deleted by PR #4449 ("D5
all-green" session removed it as "superseded by D5 depth probes" while
unaware #4448 had just merged)
- **Per-test JSON granularity** — eval runner now injects
`--reporter=list,json` + `PLAYWRIGHT_JSON_OUTPUT_NAME` when spawning
test subprocesses, reads JSON file after exit for per-test results,
falls back to exit-code when absent
- **Slug-keyed baselines** — `transformHarnessResponse` restructured to
key by integration slug (via `summary.services[]`) instead of probe ID,
matching `collectResults` format so `computeRegressions` works against
harness-prod baselines
## Context
PR #4448 added the eval system (5 CR rounds, 8 bugs fixed, 35+ agent
reviews). PR #4449 (a concurrent session) deleted it. This PR restores
the CR-converged code and adds two follow-up improvements from the eval
plan.
## Test plan
- [x] `npx nx run @copilotkit/showcase-harness:test` — 1375 tests pass
(70 files)
- [x] All 8 CR fixes from #4448 verified present in restored code
- [ ] CI green
## Summary
- Add `showcase-aimock` to the Build & Deploy workflow matrix so fixture
file changes in `showcase/aimock/` auto-deploy on merge to main
- Create `showcase/aimock/Dockerfile` -- thin overlay on
`ghcr.io/copilotkit/aimock:latest` that bakes fixture files into the
image
- Add local-vs-production parity comment in `docker-compose.local.yml`
explaining that new fixture files must be added to both the volume
mounts and the Dockerfile
- Add "Aimock Fixture Deployment" section to `showcase/RUNBOOK.md` with
manual build command and new-fixture checklist
## Why
Production showcase-aimock was running a week-old image with no D5
fixtures because fixture file changes did not trigger a CI rebuild. This
caused D5 probe failures that required manual intervention to resolve.
## Test plan
- [ ] CI workflow parses correctly (no YAML syntax errors)
- [ ] `workflow_dispatch` with `service=showcase-aimock` triggers the
aimock build
- [ ] Push to `main` touching `showcase/aimock/**` triggers the aimock
matrix leg
Production showcase-aimock was running a week-old image because fixture
file changes in showcase/aimock/ did not trigger a CI rebuild. This adds
showcase-aimock to the Build & Deploy workflow matrix so it auto-deploys
on merge, creates a thin Dockerfile that bakes fixture files into the
image, documents the local-vs-production parity requirement in
docker-compose.local.yml, and adds an aimock fixture deployment section
to the RUNBOOK.
Runner: inject --reporter=list,json and PLAYWRIGHT_JSON_OUTPUT_NAME
when spawning test subprocesses; read JSON file after exit for per-
test granularity; fall back to stdout then exit-code.
Baseline: restructure transformHarnessResponse to iterate
summary.services[] and produce slug-keyed results with _status
sub-keys matching collectResults format. Worst-status-wins when
same slug appears in multiple probes.
PR #4449 removed the eval tier system while unaware #4448 had just
merged. Restoring orchestrator, matrix, scope, config, GHA workflow,
and CLI wiring with all 8 CR fixes intact.
Heater shield with CopilotKit kite logo in magenta on dark
background, matching the sub-property icon style across
copilotkit.dev properties. Wires up Next.js metadata for
title, description, icons, and openGraph.
## Summary
- Remove `from __future__ import annotations` from
`agent_config_agent.py` which causes the AG2 agent server to crash on
startup with `PydanticInvalidForJsonSchema: Cannot generate a JsonSchema
for core_schema.CallableSchema`
- The import turns `ContextVariables` type hints into string ForwardRefs
that AG2's pydantic TypeAdapter can't resolve at tool registration time
- This fix was previously applied (cb2ef941) but reverted as part of the
broader D2 revert (5a28b778)
## Test plan
- [x] Built Docker image locally and confirmed crash reproduction on
current main
- [x] Applied fix, rebuilt, confirmed agent starts successfully (`Agent
started (PID: 8)` vs previous `ERROR: Agent failed to start`)
- [x] Verified the current deployed GHCR image also crashes
(pre-existing since the D2 revert)
The `from __future__ import annotations` import turns the
`ContextVariables` type hint on the @tool-decorated
`get_current_config` function into a string ForwardRef that AG2's
pydantic TypeAdapter cannot resolve at registration time, raising
`PydanticInvalidForJsonSchema: Cannot generate a JsonSchema for
core_schema.CallableSchema` and crashing the entire FastAPI agent
server on startup.
This fix was previously applied (cb2ef9411) but reverted as part of
the broader D2 revert (5a28b778c). The file uses no PEP 585/604
syntax, so the import was purely harmful.
## Summary
- Add `@copilotkit/voice` to mastra and google-adk package.json
- Mastra build was failing in CI; google-adk resolved via hoisting but
adding explicitly
## Test plan
- [x] Audit confirmed these are the only two packages missing this dep
The voice demo route handler imports @copilotkit/voice which was not
declared in these two package.json files. Other integrations either
had it or resolved it via hoisting, but mastra's Docker build failed.
## Summary
- Adds 4 missing dependencies to
`showcase/integrations/mastra/package.json`:
- `@json-render/core` (0.18.0)
- `@json-render/react` (0.18.0)
- `@radix-ui/react-checkbox` (^1.3.3)
- `@radix-ui/react-separator` (^1.1.8)
## Why
The `byoc-json-render` and `beautiful-chat` demos import these modules,
but they were never added to mastra's package.json. Every other
integration that ships these demos (langgraph-python, agno, etc.)
already has them. The omission causes webpack `Module not found` errors
during `next build`, blocking the mastra Docker deploy.
## Test plan
- [ ] CI builds mastra showcase successfully
- [ ] Docker build no longer fails on missing module errors
The byoc-json-render demo imports @json-render/core, @json-render/react,
@radix-ui/react-checkbox, and @radix-ui/react-separator, but these were
never added to mastra's package.json (they exist in langgraph-python and
all other integrations). This causes webpack Module not found errors
during next build in Docker.
## Summary
- Auto-formats 16 files that accumulated formatting drift across recent
PRs
- Fixes the `format` CI check that's been failing on main
## Files
README.md, express.ts, DEBUGGING.md, showcase README, render-red-tick
test, conversation-runner, d5-mcp-subagents, and 9 shell-dashboard
components/config files.
## Test plan
- [x] `pnpm run check-format` passes locally
- [ ] CI format check goes green
## Summary
Adds `showcase eval` — a command to evaluate any PR against the full
showcase D5 integration matrix before merging. Catches cross-integration
regressions (like PR #4358 breaking 13/18 integrations) before they land
on main.
- **`showcase eval --d5`** — auto-detects affected integrations from git
diff, builds + starts them in Docker, runs D5 probes, outputs a colored
matrix with SAFE TO MERGE / REGRESSIONS DETECTED verdict
- **Tiered fail-fast** — Tier 1 (langgraph-python gold standard) runs
first; regressions stop the eval immediately. Tier 2 (key partners) and
Tier 3 (full matrix) follow
- **Baseline comparison** — pulls live D5 results from showcase-harness
API or uses locally captured baselines for regression detection
(pass→fail)
- **`/eval` PR comment trigger** — GitHub Actions workflow on Depot
16-core for async full-matrix runs with results posted as a markdown
table on the PR
- **1363 vitest tests** pass across the harness (47 new eval-specific
tests across 4 modules)
## Architecture
```
showcase/harness/src/cli/eval/
scope.ts — pure: git diff paths → affected slugs
baseline.ts — harness API pull + local capture
matrix.ts — result collection, terminal formatting, verdict
(canonical SlugResult/TestResult types)
runner.ts — parallel execution with tier support
index.ts — commander subcommand + orchestrator pipeline
```
## What was fixed during CR (5 rounds, 35 agent reviews)
- ESM `require("node:fs")` replaced with proper import
- GHA scope_flag whitespace from user-typed comma-space slugs
- Tier resolution included out-of-scope slugs (inflated fail count)
- Teardown `console.log` corrupted `--json` stdout in CI
- Logger `LOG_LEVEL=warn` wasn't reloaded (cached at import time)
- Synthetic status mapping dropped "pass" to "fail" for empty-test slugs
- Zero-affected-slugs path wrote plain text in `--json` mode
- Duplicate SlugResult types unified (runner imports from matrix)
- Worktree cleanup cwd was inside the worktree being deleted
- `normalizeStatus` fallthrough returned raw string instead of "error"
## Known v1 limitations
- Per-test granularity is slug-level only (exit-code-based); Playwright
JSON reporter integration is future work
- Harness-prod baselines key by probe ID, not slug — regression
detection works with locally captured baselines only
- CI workflow Docker setup (building 17 integration images) is Phase 3
work
## Test plan
- [x] `npx nx run @copilotkit/showcase-harness:test` — 1363 tests pass
(70 files)
- [x] `tsc --noEmit` — zero type errors
- [ ] `showcase eval --help` — displays all flags
- [ ] `showcase eval --d5 --scope all` — full matrix against local
Docker stack
- [ ] `/eval` comment on a test PR — GHA workflow triggers and posts
results
1328 test assertions across 4 test files covering scope classification,
baseline pull/load/capture, matrix aggregation/formatting/regressions,
and tiered parallel runner with concurrency control.