## Summary
The showcase drift detection workflow has been failing on every
scheduled run since Apr 9 (issue #3689) because Git LFS fetch fails:
```
error: failed to fetch some objects from 'https://github.com/CopilotKit/CopilotKit.git/info/lfs'
```
The workflow runs Playwright against deployed Railway backends — it
doesn't need any LFS blobs. Changing `lfs: true` to `lfs: false` fixes
the checkout and restores drift detection.
## Impact
Every 6-hour drift detection run has been failing, firing Slack alerts
to #oss-alerts and masking actual drift. This is urgent.
Closes#3689
## Test plan
- [ ] Next scheduled run completes successfully
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- Remove `sed` build-time patches and `grep` verification lines from 6
`Dockerfile.app` files
- Delete `crewai-crews/docker-route-override.ts` (no longer needed)
- Source `route.ts` files now read `process.env.AGENT_URL` natively
(merged in #3737)
These patches were added when building the starter e2e smoke tests
(#3698) because the source code hardcoded `http://localhost:8000`. Now
that the source reads from `AGENT_URL`, the Docker build-time
workarounds are redundant.
## Test plan
- [x] Verified all 6 starters have `AGENT_URL` in source route.ts on
main
- [ ] CI starter smoke tests pass (same behavior, fewer moving parts)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Source route.ts files now read process.env.AGENT_URL natively (PR #3737),
so the build-time sed patches and verification greps are no longer needed.
Also removes crewai-crews docker-route-override.ts and its COPY line,
since that starter's route.ts already has AGENT_URL support.
## Summary
- Enable 9 new oxlint rules for better TypeScript/React code health
- Enforce `import type` for type-only imports
(`typescript/consistent-type-imports` + companions)
- Enforce self-closing JSX, `.slice()` over `.substring()`, optional
catch binding, and more
- Bump `typescript/no-unnecessary-type-assertion` from warn to error
- Auto-fix all 1,400+ existing violations across 55 files
## Test plan
- [ ] CI passes (lint + format)
- [ ] No runtime behavior changes (all fixes are syntactic)
## Summary
Comprehensive optimization of all 25 GitHub Actions workflows. Net
reduction of ~900 lines.
**Analysis & details:** [CopilotKit GitHub Workflows: Analysis &
Optimization](https://www.notion.so/33e3aa38185281ffbe35e34ff2ac867f)
### Changes
**Path filters & waste reduction**
- `publish-commit.yml`: Added `packages/**` path filter — was firing on
every push to every branch with no filters at all
- Upgraded `actions/checkout` from @v2 to @v4, added
`--frozen-lockfile`, added pnpm cache
**Concurrency controls**
- Added `cancel-in-progress: true` concurrency groups to: `test_unit`,
`e2e_dojo`, `e2e_examples`, `test_runtime-servers`, `static_quality`
- Prevents superseded CI runs from piling up on rapid pushes
**Consolidation**
- Merged standalone `static_commitlint.yml` into `static_quality.yml` as
a 4th parallel job
- Same triggers, same setup — no reason for a separate workflow
**Bug fixes**
- `static_danger.yml`: Fixed phantom `matrix.node-version` reference (no
matrix was defined), upgraded actions from @v2/@v3 to @v4, added timeout
- `test_unit-python-sdk.yml`: Removed `poetry lock` in CI (was
regenerating lockfile instead of using committed one), added concurrency
+ timeout
**Auto-fix on PRs**
- Format job now auto-runs the formatter and commits fixes back to the
PR branch
- Commitlint posts a helpful PR comment with valid prefixes and rebase
instructions on failure
**showcase_deploy.yml matrix refactor**
- Replaced 23 structurally identical build jobs with a single dynamic
matrix job
- **1,193 → ~230 lines** — service config encoded as JSON, matrix
generated from path-filter results
- Special cases (shell LFS, build-args, custom Dockerfile) handled via
matrix properties
- Railway deploy step conditionally runs only for services with a
Railway service ID
**Action version standardization**
- Upgraded `actions/checkout` and `actions/setup-node` to @v4 across all
modified workflows
- Added pnpm cache configuration where missing
### Files changed
- 10 workflow files modified, 1 deleted (`static_commitlint.yml`)
- Net: -900 lines
## Test plan
- [ ] Verify `publish-commit` no longer fires on docs/examples changes
- [ ] Verify concurrency cancels superseded runs on a PR with rapid
pushes
- [ ] Verify `static_quality` runs all 4 jobs (format, oxlint,
package-quality, commitlint)
- [ ] Verify format auto-fix commits back to PR on formatting violations
- [ ] Verify commitlint posts helpful comment on bad commit messages
- [ ] Verify `showcase_deploy` matrix correctly builds only changed
services
- [ ] Verify `showcase_deploy` workflow_dispatch with specific service
works
- [ ] Verify `showcase_deploy` Railway deploy triggers for services with
IDs
- [ ] Verify `static_danger` no longer has empty node-version
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Replaced 23 identical build jobs with one parameterized matrix job.
1200 lines to ~250. Service config as JSON, matrix generated from
path-filter results. Shell special cases (LFS, build-args, custom
Dockerfile) handled via matrix properties and dedicated prep step.
Merged standalone commitlint into static_quality as 4th parallel job.
Upgraded all actions to v4, added concurrency group. Format job now
auto-fixes and commits back to same-repo PR branches. Commitlint
posts helpful PR comment with valid prefixes on failure.
publish-commit had no path/branch filters — fired on every push.
Restricted to packages/**, upgraded checkout to v4, added pnpm
cache and --frozen-lockfile to both publish workflows.
## Summary
When the backend emits `RunErrorEvent` via the AG-UI protocol, several
frontend components did not handle it, causing the UI to show an
infinite spinner after backend errors.
## Changes
### Bug fixes (4 components)
- **`ProxiedCopilotRuntimeAgent`** (`packages/core/src/agent.ts`):
`isRunning` stayed `true` after `RUN_ERROR`, causing
`data-copilot-running` to never transition to `"false"`
- **`StateManager`** (`packages/core/src/core/state-manager.ts`):
`activeRun` entries never cleaned up, `runFinished` flag never set —
stale state on subsequent runs
- **`CopilotKitAgent` (Angular)** (`packages/angular/src/lib/agent.ts`):
Same `isRunning` bug as the proxy agent
- **`useAgentNodeName`**
(`packages/react-core/src/hooks/use-agent-nodename.ts`): Node name stuck
at last step instead of `"end"` after error
### Tests
- Added `emitRunErrorEvent` to `EventEmittingMockAgent` test helper
- Added 2 tests verifying StateManager properly tracks state on error
and resets for subsequent runs
### Key distinction
`onRunErrorEvent` handles **protocol-level** `RUN_ERROR` events from the
backend (e.g., unhandled exception in agent flow). `onRunFailed` handles
**local exceptions** (network errors, deserialization failures). Both
need to reset `isRunning` but fire under different conditions.
## Context
Found while adding e2e tests for CrewAI's `ErrorFlow` in
ag-ui-protocol/ag-ui#1478. The error flow test (`errorFlowPage.spec.ts`)
timed out because `data-copilot-running` never transitioned to `"false"`
after `RunErrorEvent`.
## Test plan
- [x] All 337 existing tests pass (including 2 new StateManager tests)
- [x] Formatted with oxfmt
- [ ] Verify error flow e2e test in ag-ui passes after this fix is
released
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Verify that:
- State is tracked when a run errors (getStateByRun returns error state)
- A new run after an error works correctly (runFinished flag resets,
second run tracked independently)
When the backend emits RunErrorEvent via the AG-UI protocol, several
components did not handle it:
- ProxiedCopilotRuntimeAgent: isRunning stayed true, causing
data-copilot-running to never transition to false (infinite spinner)
- StateManager: activeRun entries were never cleaned up, runFinished
flag never set (stale state on subsequent runs)
- Angular CopilotKitAgent: same isRunning bug as the proxy agent
- useAgentNodeName: node name stuck at last step instead of "end"
onRunErrorEvent is distinct from onRunFailed — the former handles
protocol-level RUN_ERROR events from the backend, the latter handles
local exceptions (network errors, deserialization failures).
## Summary
- **Root cause**: `CopilotChatMessageView` deduplicated messages with
`new Map(messages.map(m => [m.id, m]))`, which keeps only the *last*
occurrence of each ID. During streaming, when an agent fires multiple
tool calls in one turn, the same message ID appears multiple times:
first with text content, then with empty content + tool calls appended.
The last entry wins, wiping the text.
- **Fix**: Replace "keep last" with a **merge** strategy for assistant
messages — recover non-empty `content` from any earlier occurrence while
keeping the latest `toolCalls` (which accumulate). All other message
roles retain "keep last" behavior.
- **Test**: Added a regression test covering the three-occurrence
scenario (text → first tool call → second tool call) to confirm the
merged message renders both the original text and the final tool call
set.
## Test plan
- [x] New test `"preserves assistant text content when later duplicate
has empty content (multi-tool-call scenario)"` passes
- [x] Existing dedup tests (single duplicate, order preservation,
activity rendering) all pass unchanged
- [x] Full `@copilotkit/react-core` test suite: **950 passed, 0 failed**
(baseline was 949)
Closes#3470🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- **Fix multimodal content in TanStack AI converter**:
`convertInputToTanStackAI` was silently dropping all array content
(images, audio, video, documents) by setting `content: null`. Added
`convertUserContent()` that properly maps AG-UI parts to TanStack AI
`ContentPart` format, including legacy `binary` backward compat.
- **Switch react-router example to resource routes**: Replaced the Hono
server + `createCopilotEndpoint` setup with a React Router resource
route (`api.copilotkit.$.tsx`) that uses the pure
`createCopilotRuntimeHandler` fetch handler — showcasing that the
runtime plugs into any framework with standard `Request`/`Response`.
- **Both agents use factory pattern**: The example now has two
`BuiltInAgent` factories (AI SDK via `streamText` + TanStack AI via
`chat`) instead of mixing factory + classic modes.
## Test plan
- [x] 13 new tests in `multimodal-tanstack.test.ts` covering all content
part types, mixed content, empty arrays, missing sources, legacy binary
compat, and user-vs-assistant distinction
- [x] All 294 existing agent tests pass with zero regressions
- [ ] Manual: send an image in CopilotChat with TanStack AI agent
selected — verify the model describes it
- [ ] Manual: toggle to AI SDK agent and verify the same works
- Fix convertInputToTanStackAI silently dropping multimodal
content (images, audio, video, documents) by converting
AG-UI content parts to TanStack AI ContentPart format
- Switch react-router example from Hono server to a React
Router resource route using createCopilotRuntimeHandler
- Both agents now use BuiltInAgent factory pattern
- Remove hono/react-router-hono-server deps from example
## Summary
Adds **factory mode** to `BuiltInAgent`, enabling backend-agnostic LLM
integration without introducing a new class. Users provide a factory
function that creates the LLM stream — CopilotKit handles lifecycle
events, stream-to-AG-UI conversion, error handling, and
abort/cancellation.
### What's included
- **Three backend types**: `aisdk` (Vercel AI SDK), `tanstack` (TanStack
AI), and `custom` (raw AG-UI events)
- **Stream converters**: `convertAISDKStream` and
`convertTanStackStream` extract AG-UI events from backend-native streams
- **Input helpers**: `convertInputToTanStackAI` and
`convertMessagesToVercelAISDKMessages` for message/prompt conversion
- **State management**: Automatic `STATE_SNAPSHOT`/`STATE_DELTA` event
extraction from tool results
- **Reasoning support**: Auto-close logic for providers that never emit
`reasoning-end` (e.g. `@ai-sdk/anthropic`)
- **273 tests** across 16 test files covering all backend types,
converters, state tools, multimodal, and edge cases
- **Docs**: Comprehensive factory mode guide with 16 examples covering
tools, reasoning, state, forwardedProps, structured output
- **Example app**: React Router example migrated to factory mode
### Architecture
Factory mode is a discriminated union on `BuiltInAgentConfiguration`:
```typescript
const agent = new BuiltInAgent({
type: "aisdk", // or "tanstack" | "custom"
factory: ({ input, abortSignal }) =>
streamText({ model: openai("gpt-4o"), messages: ..., abortSignal }),
});
```
No new public classes — `BuiltInAgent` handles both simple mode (model
string) and factory mode (user-owned LLM call).
## PR feedback addressed
### Critical fixes
- **C1/M3**: Fixed TOCTOU window — `abortController` now set
synchronously before Observable creation in classic `run()`, with
concurrent run guard
- **C2/M2**: Classic `tool-result` now checks both `output` and `result`
property names, with `try/catch` on `JSON.stringify`
- **C3**: Error case safely handles `undefined` `p.error` with proper
fallback chain
### Important fixes
- **I2**: `RUN_ERROR` events now include `threadId` and `runId` in
classic mode (matching factory mode)
- **I3**: State tool events guarded against `undefined` snapshot/delta
before emitting
- **I4**: `JSON.stringify` wrapped in try/catch with descriptive
fallback message
- **M1**: `assignToolsToAgents` now skips factory-mode agents (prevents
config corruption)
- **M5**: TanStack converter's dropped event types documented with clear
limitation note
### Suggestions addressed
- **S1**: `@ts-expect-error` on `clone()` middlewares documented with
rationale
- **M4**: `AgentFactoryContext.abortController` kept (needed by TanStack
AI) but JSDoc added discouraging direct `.abort()` calls
## Test plan
- [x] 273 unit tests pass across all 3 backend types
- [x] Build succeeds
- [x] React Router example compiles
- [x] Docs render correctly (Vercel preview)
Two fixes:
1. **False positive drift alerts**: Skip the image drift check if the
latest main commit is <20 minutes old — the deploy workflow is probably
still building images. Prevents every push from triggering a wall of
stale warnings.
2. **Literal \\n in Slack message**: The drift alert was rendering a
literal \\n instead of a newline between the header and body. Fixed by
using printf + jq --rawfile instead of string interpolation.
## Summary
Adds a `/health` GET endpoint returning `{"status": "ok"}` to all 11
starter integration examples. This is a best practice for any deployed
service — health checks enable platform health monitoring, load balancer
probes, and container lifecycle management.
**11 starters updated:**
- adk, agent-spec, agno, crewai-crews, crewai-flows, langgraph-fastapi,
llamaindex, ms-agent-framework-dotnet, ms-agent-framework-python,
pydantic-ai, strands-python
**1 skipped:**
- langgraph-python — uses `langgraph dev` which provides its own `/ok`
endpoint
All `/health` endpoints are registered before any catch-all mount to
ensure they take priority (lesson learned from the pydantic-ai showcase
bug).
## Test plan
- [ ] Each starter builds successfully
- [ ] `curl localhost:8000/health` returns `{"status": "ok"}` on each
## Summary
- **Configurable agent URL**: All starter `route.ts` files now read
`AGENT_URL` from environment with localhost fallback, enabling
deployment to non-localhost environments without code changes. Applied
to: pydantic-ai, adk, agno, llamaindex, ms-agent-framework-python,
ms-agent-framework-dotnet, crewai-crews.
- **crewai-crews version bump**: Bumped `ag-ui-crewai` from pinned
`==0.1.3` to `>=0.1.5` to pick up the `MethodExecutionFinishedEvent`
dict state fix from ag-ui-protocol/ag-ui#1478.
- **`.env.example` files**: Added `.env.example` with `OPENAI_API_KEY`
and `AGENT_URL` (correct default port per starter) to all affected
starters.
## Starters NOT touched (already configurable or different pattern)
- strands-python (uses `STRANDS_AGENT_URL`)
- langgraph-python / langgraph-js (use `LANGGRAPH_DEPLOYMENT_URL`)
- langgraph-fastapi (has docker-route-override)
- mastra (monolith, no separate agent URL)
## Test plan
- [ ] Verify each starter builds with `next build`
- [ ] Verify `AGENT_URL` env var is respected when set
- [ ] Verify localhost fallback works when `AGENT_URL` is unset
- [ ] Verify crewai-crews agent installs with `ag-ui-crewai>=0.1.5`
The starter pinned ag-ui-crewai==0.1.3 which crashes on crew completion:
'dict' object has no attribute 'messages' in MethodExecutionFinishedEvent
handler. Fixed upstream in ag-ui-protocol/ag-ui#1478, released in 0.1.5.
Every starter's route.ts now reads process.env.AGENT_URL as the
primary agent endpoint, with framework-specific vars as fallback:
- AGENT_URL || "http://localhost:8000" (pydantic-ai, adk, agno, crewai,
ms-agent-python, ms-agent-dotnet, strands-python)
- AGENT_URL || LANGGRAPH_DEPLOYMENT_URL || "http://localhost:8123"
(langgraph-python, langgraph-js)
- AGENT_URL || "http://localhost:9000" (llamaindex)
Also adds .env.example with AGENT_URL to all starters.
Next.js 16 (Turbopack) rejects having both `/api/copilotkit/route.ts`
and `/api/copilotkit/[[...slug]]/route.ts`. The Dockerfile copies our
override to `route.ts` but didn't remove the catch-all first. Now
removes it before copying.
Two fixes:
1. **E2E install**: removed `|| true` from `pnpm install` in
drift-detection workflow. It was hiding install failures, causing
`@playwright/test` module not found errors.
2. **Image drift**: added `DOCKER_CLI_EXPERIMENTAL=enabled` env var.
`docker manifest inspect` requires experimental mode on some Docker
versions. Without it, every manifest inspect silently fails
(`2>/dev/null || true`), so no drift is ever detected.
## Summary
- Scaffold `shell-dojolike` alternative frontend for the showcase
platform
- Reuses the same registry/demo data as the existing shell, adds a
dojo-style single-page layout
- Sidebar with integration dropdown, Preview/Code view toggle, demos
grouped by feature category
- Switching integrations preserves the selected demo when available
(cross-framework navigation)
- Styled to match the AG-UI dojo using the CopilotCloud design system
- Added `private-agents.md` convention (gitignored per-developer agent
instructions)
- Added Internal Skills section to AGENTS.md pointing to
CopilotKit/internal-skills
## Related
- CopilotKit/internal-skills#8 — the `copilotkit-ui-theme` skill
extracted from this work
## Test plan
- [ ] Run `shell-dojolike` locally on port 3001 and verify it matches
the dojo styling
- [ ] Verify integration switching preserves selected demo
- [ ] Verify code view shows syntax-highlighted source with file tabs