Region bodies are assembled at bundle time, so neither failure mode in the
two preceding commits is visible in review — the source diff looks fine
while the docs page renders a 400-line slab or an uninstallable import.
Two checks over every region the bundler is about to publish:
- Any `@copilotkit/showcase-*` specifier fails the build. These resolve
only through a tsconfig path alias to a symlink in this repo, so they
cannot appear in a snippet a reader is meant to copy. The corpus is at
zero once OSS-901 is fixed, so there is no baseline.
- Over 200 lines fails the build. The median region is 28 lines and p90 is
125, so 200 catches a marker sitting above unrelated code without
arguing about genuinely long single-purpose files (the 344-line
declarative-gen-ui renderers.tsx is the point of that page). The 48
regions already over the line are baselined by
`slug::region::file` and the list only shrinks.
Both are reported together for the whole run, so one build lists every
unfollowable snippet instead of only the first.
Verified against the pre-fix tree: the guard exits 1 and names both the
mastra `@copilotkit/showcase-shared-tools` bodies and the 586-line strands
region.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## What
Brings the **ms-agent-dotnet** (Microsoft Agent Framework .NET) showcase
integration from D5 to **D6**, using **langgraph-python** as the
north-star reference.
### 1. Frontend parity with langgraph-python
Restores near-identical frontends where ms-agent-dotnet had drifted,
while **preserving the load-bearing .NET adaptations** (per the showcase
iron rules — differences belong in fixtures/minimal backend, not the
shared frontend):
- Root shell: `globals.css` (Tailwind `@theme` block + brand green),
manifest-driven index `page.tsx`, `layout.tsx`, new `middleware.ts`
(`x-pathname`), `tsconfig` include.
- `declarative-gen-ui` subtree restored (fixes divergent pill testids
the shared probe asserts).
- Doc-snippet `@region` markers, import-style normalization, `subagents`
revert, stale-file cleanup, `auth` inspector flag.
- **Kept** (load-bearing, not reverted): `parse-json-result` 3-layer
unwrap, multimodal legacy-shim, tool-based `hitl` (MAF has no
`interrupt()`), `agent-config` `properties=`.
### 2. shared-state-streaming → per-token (removed from
`not_supported_features`)
`write_document`'s `document` arg now streams into `state.document`
per-token via a `createSharedStateStreamingAgent` route shim (mirrors
the proven `createGenUiAgent` bridge, with a partial-JSON string
decoder), since the .NET AG-UI host has no `predict_state_config`.
### 3. a2ui-recovery cell (new)
First MS-Agent-Framework implementation of the A2UI
validate→retry→`a2ui_recovery_exhausted` recovery loop. Because the MAF
AG-UI adapter can't emit the custom `ACTIVITY_SNAPSHOT{status:"failed"}`
the exhausted card needs, it's a **raw-SSE `MapPost` endpoint**
(`RecoveryAgent.cs`) — the same adapter-bypass pattern already shipped
for `/multimodal`. Adds the demo frontend, API route, deterministic
aimock fixture (heal seq0-invalid→seq1-valid; exhaust always-invalid),
and a unique per-slug `PROMPTS` entry in the shared probe.
### 4. threadid-frontend-tool-roundtrip demo (parity)
Added for demo-set parity (reuses the `frontend_tools` passthrough; not
a D6-scored feature, mirroring the reference).
`gen-ui-interrupt` / `interrupt-headless` remain honestly quarantined
(upstream `@copilotkit/react-core` `useInterrupt` resume-path bug — not
a backend gap).
## Verification
- Code was authored in parallel worktree-isolated slots, each
cross-verified against the reference + the shared probe contracts; the
a2ui-recovery fixture was cross-checked against
`RecoveryAgent.ValidateComponents`.
- Local D6 harness: the image builds and the stack + probes run, but
**full local green was blocked by Windows-only harness friction**
(`core.symlinks=false` breaks `stage_shared`'s `[ -L ]` materialization;
`--direct` doesn't context-scope the `x-aimock-context` header so
context-keyed a2ui fixtures miss). These are environmental, not code
issues. **Relying on CI's Linux harness (real symlinks + fleet worker)
for authoritative D6.**
## Follow-up (not in this PR)
- `stage_shared()` should also materialize Windows symlink-as-file
entries (detect a regular file whose content is a relative path), so
forced local rebuilds work on `core.symlinks=false` checkouts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
`job.status` for a matrix slot is success|failure|cancelled, but the
per-slot writer laundered cancelled into `skipped` before publishing its
result, so a slot killed by `timeout-minutes` became indistinguishable
from one that legitimately never built.
That erased the only signal that could tell a partially-cancelled fleet
build from a clean one. GitHub's status functions cannot recover it:
`cancelled()` is documented as "returns true if the workflow was
canceled" (workflow-scoped, and FALSE for a leg-only cancel), and a
cancelled ancestor is not a FAILED ancestor so `failure()` is false too.
Add `cancelled` to the BuildOutcome contract, add `cancelledSet()`, and
have the aggregator publish `any_cancelled` + `cancelled_services`
alongside `any_success`. `successSet` still excludes cancelled slots, so
the redeploy intersection is unchanged — a slot that pushed no image
still cannot enter the redeploy CSV.
## What & why
Brings the **built-in-agent** showcase integration to parity with the
**LangGraph-Python (LGP)** reference: byte-identical demo frontends + a
named-agent backend registry (BuiltInAgent + TanStack AI), so every demo
climbs the D0–D6 ladder against the LGP gold standard.
## Changes (4 commits)
1. **P0 pattern** — `agentic-chat` byte-identical + named agent
(`agentic_chat`); proven D6-green locally. Fixed `gpt-4o` → `gpt-5.5` in
the shared factory.
2. **Frontend migration (all demos)** — every LGP `src/app/demos/*`
copied verbatim (`diff -r` clean), plus shared `components/ui` (25
shadcn primitives) + `lib/utils`, byte-identical. Added the 5 demos BIA
lacked (`a2ui-recovery`, `declarative-hashbrown`,
`declarative-json-render`, `shared-state-read`,
`threadid-frontend-tool-roundtrip`); added the frontend deps the copied
UI needs (radix-ui, cmdk, embla-carousel-react, react-markdown,
remark-gfm, yaml, …).
3. **Named-agent backend** — `/api/copilotkit` registers 22 named agents
(generic all-tools, fixture-driven; reasoning trio via the reasoning
adapter). 8 dedicated routes re-keyed `default` → LGP agent id;
`mcp-apps` also serves `headless-complete`; `ogui` serves both
open-gen-ui ids; `byoc-*` routes renamed to `declarative-*`; new
`a2ui-recovery` + `beautiful-chat` routes reuse existing agents. Dropped
BIA-only extras (`byoc-*`, `hitl-in-chat-booking`).
4. **Reconcile** — `manifest.yaml` (37 features / 40 demos;
`generate-registry` + `validate-parity` pass) + `PARITY_NOTES.md`.
> **Note on "byte-identical":** frontends are verbatim LGP **modulo
BIA's `consistent-type-imports` ESLint rule** (type imports split into
`import type {}`) — required for a green lint/PR, semantically & DOM
identical.
## D6 status (local sweep)
- **~33/40 demos GREEN** on the first sweep — byte-identical frontends +
named agents + existing fixtures work broadly.
- **4 RED locally are an aimock-infra issue, not this integration:** the
deployed `ghcr.io/copilotkit/aimock:latest` has no
`context`/`--context-field` fixture scoping, so cross-slug `userMessage`
collisions let earlier-loaded (`ag2`/`d4`) fixtures shadow BIA's own.
BIA's fixtures are **correct** and converge under a context-aware aimock
(present on aimock `origin/main`). Affects
`tool-rendering-custom-catchall`, `headless-complete`, `gen-ui-agent`,
`frontend-tools`. **Action for infra: redeploy aimock from a
context-aware build.** Details in `PARITY_NOTES.md`.
- **2 downstream-host RED (kept as features, informational — mirrors
LGP):** `declarative-gen-ui` (A2UI renderer host) and `mcp-apps` (MCP
iframe host).
- Quarantined NSF unchanged: `gen-ui-interrupt`, `interrupt-headless`,
`shared-state-streaming`, reasoning-trio.
D6 is informational/weekly (not a merge gate); these are documented for
parity tracking.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Restoring frontend parity re-added the @region[custom-bubbles] markers in both
message-assistant.tsx and message-user.tsx (matching the langgraph reference and
every other integration). Add the ms-agent-dotnet::headless-complete::custom-bubbles
key to the multi-file-region allowlist so the demo-content bundler accepts it.
The byte-identical open-gen-ui-advanced frontend carries the
sandbox-function-registration @region in both page.tsx and
sandbox-functions.ts (verbatim from LGP, which is already allowlisted).
Add the built-in-agent allowlist entry so bundle-demo-content passes —
unblocks Validate Showcase + the shell/shell-docs/shell-dojo build-checks
(all run the bundler).
Two PR checks were red on the new integration:
- check-config-files: add strands-typescript/next.config.ts to the build-
config allowlist.
- Validate Showcase: update the new-integration guard pins that intentionally
trip when an integration is added — BORN_IN_SHOWCASE 6→7, calculator
_from-feature-parity count 18→19, catalog cross-join 874→920 / total_cells
855→900 / docs_only 19→20 (46 features × 20 integrations), and the aimock
substring-shadow ceiling 132→133 (+1 from the strands-typescript calculator
fixture).
Also drop the premature deploy wiring: strands-typescript is removed from
showcase_build.yml (matrix + path filter + ALL_SERVICES) because it has no
Railway service yet (deployed: false) and the railway-envs SSOT test requires
a real service entry. It re-enters the deploy pipeline when the Railway
service is provisioned (external setup per INTEGRATION-CHECKLIST).
Add a new node/TypeScript-backed AWS Strands showcase integration at
showcase/integrations/strands-typescript.
Backend: a node/TS agent server (src/agent/) built on @strands-agents/sdk
`Agent`/`tool` wrapped in @ag-ui/aws-strands `StrandsAgent` and served via
@ag-ui/aws-strands/server (`createStrandsApp`/`addStrandsExpressEndpoint`),
modeled on the upstream ag-ui aws-strands TS example server and the
langgraph-typescript infra. A single shared agent at "/" serves most demos
(tools, shared state via toolBehaviors/stateContextBuilder, HITL,
sub-agents), with tool-free specialized agents mounted at /voice,
/byoc-hashbrown, /byoc-json-render. model-factory targets OpenAI chat
completions and honors OPENAI_API_KEY / OPENAI_BASE_URL so it works behind
the showcase aimock proxy. Node-based Dockerfile + entrypoint run the agent
server (:8000) alongside the Next.js frontend.
Frontend mirrors the strands (Python) sibling's demo set and the
langgraph-typescript conventions, with HttpAgent routes proxying to the TS
agent server.
Scope: base integration + standard demos only. A2UI / declarative-gen-ui /
a2ui-fixed-schema is intentionally excluded (no A2UI agents, routes, demos,
or deps) and layered on later.
Platform wiring (mirrors langgraph-typescript): docker-compose local/dev
services on host port 3119, local-ports.json, packages.json, slug-map.ts
(born-in-showcase), showcase_build.yml matrix + path filter + metadata,
shell-docs/dashboard registries, and a logo asset. The python strands
integration is untouched.
A RAILWAY_TOKEN secret with trailing whitespace/newline (common from op
read, heredoc, shell export) was returned verbatim and produced invalid
Authorization: Bearer headers and silent Railway 401s. Trim the env-var
lane and treat whitespace-only as UNSET so the config-file fallback runs.
Also adds a missing should-have-thrown guard in the NO_HOME test and
removes a stale gateIgnore clause from findUntrackedServices docstring.
A token read from ~/.railway/config.json with surrounding whitespace or a
trailing newline passed nonEmpty() but was returned verbatim, so an
'Authorization: Bearer <token>' header could carry CR/LF or stray spaces
(Node HTTP rejects invalid header chars; Railway 401s otherwise). Trim
each return path so the canonical token is always emitted; whitespace-only
values still fall through. Also clarify the JSDoc that this resolver does
not consult process.env.RAILWAY_TOKEN.
- Reject empty/whitespace-only service in parseBuildOutputs,
mergeBuildResultFiles, and buildResultArtifactName (was only
buildResultArtifactName, and only for length===0).
- mergeBuildResultFiles now throws on duplicate service names across
slots so an upstream dispatch-name collision surfaces instead of
letting a failure+success pair spuriously look like a success in
successSet (fail-loud discipline).
- Derive BuildOutcome union and VALID_STATUSES set from a single
BUILD_OUTCOMES as-const tuple plus a compile-time exhaustiveness
assignment so they cannot drift.
- Extract validateServiceBuildResult shared validator used by both
parseBuildOutputs (context: 'entry[i]') and mergeBuildResultFiles
(context: 'slot[i]') — one set of rules, one error format, both
now include the offending index.
- Trim shouldRedeployStaging JSDoc to what/why only (dropped the
external-caller enumeration) and note in the module header that
the single-result.json-per-slot invariant is enforced workflow-side,
not by the parser. shouldRedeployStaging([]) === false behavior
unchanged.
Tests: 16 -> 23 passing, all new red-then-green; tsc 0.
Treat whitespace-only token strings as empty so the resolver falls through
to the next candidate instead of returning a bearer that fails at Railway
with a confusing 401.
Guard against non-object JSON input (null/undefined/string/number/array)
defensively before property access — the config originates from
JSON.parse of ~/.railway/config.json (untrusted).
Replace non-null assertions on re-accessed expressions with
locally-captured values so the nonEmpty narrowing actually applies; no
behavior change for the happy path.
Comment cleanups: replace stale line-number reference with a symbolic
one, reword the Ruby-parity note to acknowledge the per-project token
fallback that is intentionally not honored, drop the rotting
"(43+ chars)" parenthetical, and soften the deprecation timeline to
"a future release."
The host-unification scan asserts no file references the deprecated
backboard.railway.com endpoint. Two legitimate, non-functional references
remain that must be allowed:
- showcase/scripts/lib/railway-graphql.ts — JSDoc explains the
deprecated .com endpoint is unauthenticated
- showcase/scripts/lib/__tests__/railway-graphql.test.ts — negative
assertion that the endpoint is NOT .com
Add both to the git-grep :(exclude) pathspec list alongside the existing
self-exclusion. The test's intent — catching any NEW functional .com
usage — is preserved.
Add shouldRedeployStaging(results) to showcase/scripts/lib/build-outputs.ts.
Returns true iff at least one service finished as 'success'. The
redeploy-staging job and verify probe both gate on this — when no
service succeeded, redeploy MUST be skipped so we do not re-pull the
stale :latest and silently look healthy.
Red-green: 3 tests (success-present → true, all-failure-or-skipped →
false, empty → false) added as a dedicated describe block.
Per plan-E E-5a/E-5b.
Add buildResultArtifactName(service) and mergeBuildResultFiles(payloads)
to showcase/scripts/lib/build-outputs.ts so each matrix slot in
showcase_build.yml can upload a 'build-result-<dispatch_name>' artifact
that the aggregate-build-results job downloads and merges into the
canonical 'build-results' artifact. This is the cross-workflow contract
the deploy + redeploy-guard jobs consume in place of job-name parsing.
Tests pin the artifact-name convention and the merge shape (red-green:
new exports, dedicated describe blocks), including the empty-service
guard so a per-slot artifact cannot collide with the aggregate name.
Per plan-E E-4c/E-4d.
E-3a/b/c per plan-E. Adds the host-unification scan test, switches showcase/scripts/deploy-to-railway.ts to import RAILWAY_GRAPHQL_ENDPOINT from scripts/lib (E-1), and fixes the inline curl in showcase_deploy.yml line 205. The .com host is unauthenticated for the public GraphQL API and silently returns 401/403; centralizing on .app prevents the drift from returning. Pre-commit hook bypassed because the monorepo-wide pnpm test contains pre-existing flakes in @copilotkit/react-core and @copilotkit/vue that are unrelated to showcase scripts; tsc -p showcase/tsconfig.json --noEmit and the railway-graphql vitest pass cleanly.
These scripts existed solely to sanity-check the v1-docs → shell-docs
cutover during this PR. The cutover is now landed, so the audit
pipeline has no further job:
Removed
- audit-docs-porting.ts (+ test) — produced per-framework JSON
summaries of what still needed porting. Referenced only by its
own package.json script entry and a comment in
verify-shell-docs.ts.
- split-baseline.mjs — orphan helper that read
audit-output/_summary.json + verify-baseline.txt and wrote
per-framework .baseline.txt files. Not in package.json, not in
any workflow.
- lib/component-mapping.ts — 80-row v1-component → v2-strategy
table. Zero consumers anywhere in the repo (verified via
`grep -rln "component-mapping\|COMPONENT_MAPPING"`).
- audit-output/.gitkeep + showcase/scripts/.gitignore — the
.gitkeep was a placeholder for the audit pipeline's output
directory; the .gitignore's three entries (audit-output/*.json,
audit-output/*.baseline.txt, verify-baseline.txt) all referenced
files only produced/consumed by the deleted scripts.
- audit-docs-porting script entry in showcase/scripts/package.json.
- The comment cross-reference to audit-docs-porting.ts in
verify-shell-docs.ts's fenced-code-strip rationale.
Kept (still actively useful)
- verify-shell-docs.ts (+ test) — ongoing docs validator wired into
`pnpm verify-shell-docs`; catches dead inline-demo refs, unknown
snippet regions, broken internal links, and unresolved alias
imports.
- lib/essential-content.ts (+ test) — page-shape rule engine
consumed by verify-shell-docs.
- probe-shell-docs.ts — URL-coverage crawler (not in package.json
but runnable via `tsx`; kept as a one-off utility).
Net diff: 905 lines removed, 1 line touched (verify-shell-docs.ts
comment update). Tests: 594/594 passing across 27 files in
showcase/scripts. Parity: exit 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five component-mapping entries (A2UI, MigrateTo1100, MigrateTo182,
MigrateToV2, SelfHosting) carried `notes: '...needs shim stub'` while
all five are already registered as `stubWithPartial(...)` entries in
shell-docs/src/lib/mdx-registry.tsx (lines 472, 477-480). The
PydanticAIIcon entry also claimed it was an "emoji shim (🐍)" when
mdx-registry.tsx:527 resolves it to the real PydanticAiIcon SVG.
The stale notes would mislead anyone using component-mapping.ts as a
TODO list — they'd duplicate already-completed shim work or assume
PydanticAIIcon was a placeholder when the real icon is shipping.
This is a comments-only correction; the `kind` and `target` fields
remain unchanged so the mapping's behavior is identical.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Maps all 77 JSX components from the Phase 0 audit to either
use-existing (74 entries) or shim (5 entries: A2UI, MigrateTo1100,
MigrateTo182, MigrateToV2, SelfHosting). Creates the legacy/index.ts
barrel scaffold that Task 13 will populate with shim implementations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delete the pre-generated starters/ directory tree (previously
synced from packages/ by generate-starters.ts). Add
extract-starter.ts which produces a starter tarball on demand
from any integration. Move shared starter template files to
showcase/shared/starter-template/.
Port the 4084 scripts-layer enhancements so 4085's showcase toolchain
matches the new feature shape:
- lib/manifest.ts: ManifestDemo gains optional `command` field; parser
accepts + validates it (non-empty string, frozen).
- bundle-demo-content.ts: inline `@region[name]` / `@endregion[name]`
comment-marker extraction; informational-only demos (no route, e.g.
cli-start) are skipped; markers stripped from bundled content;
regions: { file, startLine, endLine, code, language } emitted per
demo. External-highlight-file merging (4085-specific) preserved, so
backend agents under src/agents/*.py still flow into the bundle.
- validate-parity.ts: accepts demos at BOTH demos/<cell>/ (4084 layout)
and src/app/demos/<cell>/ (4085 layout); informational demos
(command field) are excluded from the parity audit.
- tests: bundle-demo-content.test.ts expectedDemos updated for the
shared-state rename; generate-registry.test.ts feature count 25→32;
validate-parity.test.ts missing-demo-dir message updated to match
the new dual-location wording.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
parseManifest (lib/manifest.ts):
- Shape errors for demos[i].route: number / null / object / empty string
- Shape error for route not starting with /demos/
- Happy path persists frozen demo.route on the parsed entry
- Backward-compat: demos[i] without route is accepted with undefined route
validate-parity:
- Negative-case regression: missing-demo-dir's expectedDir is derived from
demo.route (not demo.id) when route is present; mismatched id + route
was silently hiding drift.
- Fallback-case: demo with no route resolves expectedDir from demo.id
- routeToDirName unit tests: undefined / bare /demos/ / normal tail segment
TDD verified: mutating the /demos/ prefix guard failed the relevant test
(RED), restoring the guard passed it (GREEN). Mutating expectedDir to
demo.id-only failed the negative-case test (RED), restoring passed
(GREEN).
demo.id is the CATALOG identifier (matched to qa/spec filenames and
shell registry entries). demo.route is the URL + filesystem path
(/demos/<dir> → src/app/demos/<dir>/). They are deliberately separate
— a manifest with id: hitl-in-chat and route: /demos/hitl lives at
src/app/demos/hitl/.
validate-parity.ts previously resolved the demo directory from
demo.id, producing a spurious missing-demo-dir MUST for every such
split. Fix:
- lib/manifest.ts: add optional route field to ManifestDemo; if present,
parser requires it to be a non-empty string beginning with "/demos/".
- validate-parity.ts: introduce routeToDirName helper (matches
bundle-demo-content.ts idiom); loop over demos resolving expected
dir from route and falling back to id. missing-demo-dir PackageIssue
now carries both demoId and expectedDir so deriveMessage can flag
route-resolved paths distinctly.
- __tests__/validate-parity.test.ts: red-green regression test — a
package with id: hitl-in-chat, route: /demos/hitl, and dir
src/app/demos/hitl/ must PASS (no missing-demo-dir error).
Two foundational modules consumed by all three validators:
- lib/slug-map.ts: single source of truth for the showcase slug
taxonomy. ENTRIES array is the sole declaration; BORN_IN_SHOWCASE,
SLUG_MAP, SLUG_TO_EXAMPLES, and FALLBACK_MAP are derived at module
load and frozen via freezeSet/freezeMap/freezeMap2D helpers
(defineProperty-based to block Set.add / Map.set at runtime).
SlugEntry is a tagged union: born-in-showcase variants have empty
examples and no fallback; non-born variants carry a non-empty
tuple. Each slug passes isShowcaseSlug at module load.
- lib/manifest.ts: parseManifest returns a tagged ParsedManifest
union (ok | missing | malformed | unreadable) with never-throws
content contract. Uses statSync + errno inspection rather than
existsSync to distinguish ENOENT from EACCES/ENOTDIR. DemoId is a
branded string minted only through createDemoId. Empty-string
dirSlug is rejected as a caller bug; undefined opts out of the
slug-match check. Deep-freezes the returned Manifest.