Commit Graph

4111 Commits

Author SHA1 Message Date
Benjamin Taylor a617fd399e docs(telemetry): document bot SDK telemetry on the /telemetry page
Both files that render at docs.copilotkit.ai/telemetry now describe the @copilotkit/bot anonymous oss.bot.* events, note that bot telemetry is unsampled, and clarify the opt-out (COPILOTKIT_TELEMETRY_DISABLED / DO_NOT_TRACK) disables the bot SDK too. The bot's one-time disclosure already points at this URL.
2026-06-26 16:36:42 -05:00
Jordan Ritter 7b5d47e125 feat(showcase): add 'showcase reap' subcommand to tear down leaked isolated stacks
Dry-run by default (lists the plan, changes nothing); --force executes,
--all ignores TTL/keep, --include-live opts into reaping a live-owner
target, <name|slot> targets one. Identifies harness-owned projects via
the slot-record / run-dir / showcase-iso<N> / self-id-label union, and
never touches the base 'showcase' stack or BuildKit resources. Real
docker bats prove dry-run/--force/--all + the base/buildkit guards.
2026-06-26 12:54:56 -07:00
Jordan Ritter 42e17cf6c3 fix(showcase): reconcile slot liveness against container state, add kept-stack TTL + reap self-id label
A --keep'd isolated stack whose owning process had exited (but whose
containers kept running) was classified 'live' forever and never reaped,
leaking Docker stacks indefinitely. Introduce a start-time-verified
_owner_liveness probe and a new 'kept' state, an ISOLATE_KEEP_TTL (4h,
SHOWCASE_ISOLATE_KEEP_TTL-overridable) that flips an over-age kept slot
to 'stale' so the sweep reclaims it, a com.copilotkit.showcase.isolate
self-id label stamped by apply_isolation, a 'slots --reapable' filter,
and a macOS lsof COMMAND-truncation fix in the own-project port filter.
Real-surface bats cover the liveness false-positive and TTL reaping.
2026-06-26 12:54:55 -07:00
Jordan Ritter 042788270c docs(showcase): document the dashboard staleness trap (red/BE✗ ≠ broken)
A red/BE✗ dashboard cell is often staleness, not a broken feature. The
per-cell BE flag is resolveD4 = worst-of(chat,tools) folded by a staleness
window; a green row older than its window folds to stale-red even when the
app is healthy. Add D5 Strategy 10 with a diagnostic checklist (harness
/api/runs sweep-duration vs window, observed_at age vs *_STALE_AFTER_MS,
numReplicas × HARNESS_POOL_COUNT concurrency) plus an anti-pattern entry.
Evidence from the 2026-06-26 prod incident: D4 sweep (127m d6 / 97m e2e-demos)
blew past the 60m window with a starved worker pool, stale-reddening ~13
integration columns while apps were fine.
2026-06-26 12:01:42 -07:00
Jordan Ritter 988defa612 docs(showcase): require CVDIAG + X-AIMock-Strict forwarding + Dockerfile COPY for new/promoted integrations
Document the requirement that new/promoted integrations forward X-AIMock-Strict, emit CVDIAG boundaries, and COPY src/cvdiag in their Dockerfile.
2026-06-26 11:04:39 -07:00
Jordan Ritter d84069d8a8 fix(showcase): stage src/cvdiag into the strands-typescript Docker runner so the two-process agent boots
Add a COPY of src/cvdiag into the strands-typescript Docker runner image so the two-process agent boots with the vendored cvdiag module present.
2026-06-26 10:48:52 -07:00
Jordan Ritter 28baa7f667 fix(showcase): guard crypto.randomUUID in strands headless chat shells
Guard crypto.randomUUID usage in the strands headless chat shells to avoid runtime failure where it is undefined.
2026-06-26 10:48:51 -07:00
Jordan Ritter 1f36894bc5 feat(showcase): emit CVDIAG backend boundaries agent-side for strands-typescript
Emit CVDIAG backend boundary markers from the agent process for strands-typescript (byteLength fix on sseChunkByteLength), enable the emitter in docker-compose.local.yml, vendor src/cvdiag, and exclude tests from tsconfig.
2026-06-26 10:48:43 -07:00
Jordan Ritter 2ab8514671 feat(showcase): forward inbound X-AIMock-Strict end-to-end through strands-typescript two-process hop
Forward inbound X-AIMock-Strict header through the two-process strands-typescript hop (Next route -> agent -> sub-agent fetch), with null-guard on the forwarding proxy fetch and supporting unit tests.
2026-06-26 10:48:34 -07:00
Jordan Ritter a17635a6c5 fix(showcase): backfill prod harness-workers into SSOT so image rebuilds bounce it (#5715)
## Root cause

Prod's `harness-workers` fleet worker runs a **stale `showcase-harness`
image** because it had **no `prod` env entry** in the railway-envs SSOT.

- The worker (`serviceId c2aa8a0b-350e-4b76-8541-3012dfac41d0`, prod
instance `7c48ee43-6df4-457b-b977-10f1f1ac1680`, `HARNESS_ROLE=worker`)
consumes the shared `showcase-harness` image via `imageOf: "harness"`.
- `expandImageConsumers(names, env)` is **env-aware**: a consumer only
enters an env's redeploy scope if it declares that env
(`redeploy-env.ts:278` — `if (!Object.hasOwn(entry.environments, env))
continue;`).
- Because the worker modeled **staging only**, a rebuilt
`showcase-harness:latest` bounced the prod control-plane but **silently
skipped the prod worker**, which kept its stale **2026-06-19** image.
- That stale image bakes a **1-demo `registry.json`** for
`ms-agent-harness-dotnet` (only `beautiful-chat`). The hourly
`e2e_demos` driver runs on the worker → resolves 1 demo → writes only
`e2e:ms-agent-harness-dotnet/beautiful-chat`. The other 38 feature rows
never exist in prod PocketBase → `resolveD3.exists === false` → `UI`
badge omitted → broken D3 rung collapses the ladder → **D0**.

(d5/d6 populate fully in prod because the D5/D6 drivers enumerate from a
**compiled-in** script registry, not the `registry.json` data file —
only `e2e_demos` is data-driven, which is why only `UI` was affected.)

## Fix

Backfill the live prod worker as a real `prod` env entry in
`scripts/railway-envs.ts` (real serviceInstance ID `7c48ee43…`), flip
`gateIgnore` off, and set `gateValidated: true`. The env-aware `imageOf`
expansion now pulls the prod worker into the **prod** redeploy scope on
every `showcase-harness` rebuild, so it can no longer drift onto a stale
image.

Also regenerates `railway-envs.generated.json` (Ruby/jq boundary
artifact) and the golden behavior-preservation fixture, and updates the
two gate-count assertions (`gateValidated` services 40→41;
`harness-workers` removed from the gateIgnore set).

## Local RED → GREEN proof

Failure surface: the real `expandImageConsumers("harness", "prod")`
against the real SSOT must include `harness-workers`.

**RED** (prod env entry absent from SSOT — the bug):
```
 × includes harness-workers in the PROD redeploy scope when showcase-harness rebuilds
 AssertionError: expected [ 'harness' ] to include 'harness-workers'
   at __tests__/redeploy-env.harness-worker-prod-scope.test.ts:31:19
      Tests  1 failed | 1 passed (2)
```

**GREEN** (after adding the prod `harness-workers` env entry):
```
 ✓ includes harness-workers in the PROD redeploy scope when showcase-harness rebuilds
 ✓ still includes harness-workers in the STAGING redeploy scope (no regression)
      Tests  2 passed (2)
```

Full SSOT-dependent suite (golden snapshot, emit-json, image-ref gate,
promote closure, verify-matrix, redeploy-env): **140 passed**.

## Note (out of scope for this PR)

This SSOT change ensures the prod worker is bounced on **future**
rebuilds. The currently-live prod worker still needs a one-time
redeploy/restart onto the current `showcase-harness:latest` (39-demo
registry) to immediately backfill the 38 missing rows; that is an
operational step, not a code change.
2026-06-26 09:30:55 -07:00
Jordan Ritter f399afbbdc fix(showcase): conform claude-sdk-python, built-in-agent & ms-agent-harness-dotnet auth demos to langgraph-python gold standard (#5716)
## What

Brings the **Authentication demo** of three integrations into 1:1
conformance with the `langgraph-python` (LGP) gold standard, completing
the work started in #5713. The showcase Iron Law: LGP is the reference;
every integration must have (1) identical tests, (2) near-identical
frontends, (3) minimal backends, (4) per-integration fixtures.

A conformance audit against LGP found 3 violators (the other 17
integrations already conform):

| Integration | Violation | Fix |
|---|---|---|
| **claude-sdk-python** | Legacy auth-*first* shape: class
`ChatErrorBoundary` + `lastError`, no `handleAuthError`, missing
`sign-in-card.tsx`, divergent banner/hook | Ported
`page.tsx`/`use-demo-auth.ts`/`auth-banner.tsx` **byte-identical** to
LGP + new `sign-in-card.tsx`; added the shared shadcn primitives it
lacked (`lib/utils.ts`, `components/ui/{button,card}.tsx`) +
`radix-ui@^1.4.3` (matching the claude-sdk-typescript peer) |
| **built-in-agent** | Distinct legacy variant:
`ChatErrorBoundary`→`auth-demo-chat-boundary`, local 401-regex
`onError`, auth-first hook | Normalized error-handling shape + hook to
LGP; **preserved** the forced `<CopilotKitProvider>` (default-agent) +
raw-Tailwind divergences (documented in a new `README.md`) |
| **ms-agent-harness-dotnet** | Missing `tests/e2e/auth.spec.ts` (rule
1) | Added LGP's spec **byte-identical** (sha256 `603a68e5…`) |

After this PR, all auth `page.tsx`/hook files are byte-identical to LGP
except documented, forced per-integration wiring; all `auth.spec.ts`
share LGP's sha256.

## Red–green proof (per integration, on the real probe surface)

The shared `d5-auth.ts` probe accepts *either* `auth-demo-error` *or*
`auth-demo-chat-boundary`, so it passes leniently on the legacy shape —
the **discriminating gate is the byte-identical `auth.spec.ts`**
(asserts unauth-first `SignInCard` + `auth-authenticate-button` +
post-sign-out `auth-demo-error`):

- **claude-sdk-python:** legacy frontend → `auth.spec.ts` **6/6 FAIL**
(timeout on `auth-sign-in-button`); conformed → **6/6 PASS** (`next
build` clean).
- **built-in-agent:** legacy → 6/6 FAIL; conformed → 4 conformance
assertions flip FAIL→PASS incl. unauthenticated-send surfaces
`auth-demo-error` (`next build` clean).
- **ms-agent-harness-dotnet:** spec absent (coverage gap) → added →
`--d5 --isolate` green, full real-browser auth flow passes.

## Review

7-agent CR round + mandatory 7-agent confirmation round → **converged to
zero findings** (correctness, conformance, types/build, deps/lockfile,
tests, silent-failures, cross-integration regressions). 2 P2 conformance
nits found and fixed (import-style alignment; restored `DEMO_TOKEN` so
built-in-agent's hook is byte-identical to LGP).

## Known limitation (non-blocking, pre-existing infra)

The GHA workflow `test_e2e-showcase-on-demand.yml` runs Playwright only
for slugs with a Python agent, so the **built-in-agent /
ms-agent-harness-dotnet auth specs are not executed in PR CI**. This is
a pre-existing infra gap (those integrations have no Python agent), not
introduced here. Coverage **does** exist post-merge: the Railway staging
**d6 harness** enumerates services language-agnostically and runs the
auth probe against live `/demos/auth` for both — verified, and it's what
drives their dashboard cells green at D6. A follow-up to add a
non-Python e2e execution path is warranted.

## Notes (pre-existing, not introduced)

- `npm ci`/`npm install` in `showcase/integrations/claude-sdk-python`
shows a micromark/unified desync and a zod/openai ERESOLVE peer conflict
— both reproduce identically at the base commit `ab85b939ac`
(independent of the `radix-ui` add); handled by the existing
`--legacy-peer-deps` path.

Ref: #5713 (original post-sign-out auth rejection fix).
2026-06-26 09:29:24 -07:00
Ran Shem Tov a7bc444814 fix(showcase): bump @ag-ui/langgraph 0.0.39 -> 0.0.42 for lg-ts recovery render
getA2UITools changed signature: 0.0.39 is getA2UITools(model, options) (positional),
0.0.42 is getA2UITools(params) (single object). The agent code (recovery-agent.ts
and graph.ts) calls the single-object form, but the override pinned 0.0.39, so the
whole params object was treated as the model -> e.bindTools undefined -> the tool
returned {"error":"Provided model does not support bindTools"} and the render
sub-agent never ran. Bumping the override to 0.0.42 aligns the dep with the API the
code uses; verified the recovery graph now emits a healed a2ui_operations surface
(invalid seq0 -> valid seq1) and fires the render_a2ui sub-agent.
2026-06-26 17:09:22 +02:00
Ran Shem Tov ab8b39a9bc fix(showcase): register a2ui_recovery graph in langgraph-typescript agent server
The lg-ts agent serves graphs from a hardcoded graphSpec in src/agent/server.mjs
(mirrors langgraph.json). The a2ui_recovery graph was added to langgraph.json but
not graphSpec, so the langgraph server returned 404 on its runs and the demo
never dispatched. Add a2ui_recovery to graphSpec.

NOTE: this fixes graph REGISTRATION. The lg-ts recovery render does not yet fire
(getA2UITools 0.0.39 returns from generate_a2ui without invoking the render
sub-agent); tracked separately, likely needs @ag-ui/langgraph >= 0.0.42.
2026-06-26 16:58:45 +02:00
Ran Shem Tov b985449e50 feat(showcase): add A2UI Error Recovery demo for langgraph + strands
Port the google-adk a2ui-recovery demo to langgraph (python, fastapi,
typescript) and aws-strands (python, typescript). Each ships a dedicated
recovery agent, route, demo page/chat/suggestions, manifest entry, aimock
d6 fixtures, e2e spec, and QA doc.

Backend-owned recovery on langgraph via get_a2ui_tools / getA2UITools
(injectA2UITool=false); auto-inject recovery on the strands adapter path.
Heal stages an invalid-then-valid render via aimock sequenceIndex (the
toolkit validate->retry loop rejects the whole surface, so a single-pass
parse_and_fix heal is ADK-specific and does not apply here). Recovery
prompts are unique per framework and the fixtures carry no context match
field, so they fire for real browser (dojo) traffic, not just the harness.

Also harden the strands declarative-gen-ui composition guide to name the
exact catalog component (Metric, not MetricTile) and update the
generate-catalog + aimock-fixtures test expectations.
2026-06-26 16:17:58 +02:00
Jordan Ritter ce0ea8bd20 test(showcase): update SSOT tests for dual-env prod harness-workers
The prod harness-workers backfill (e88d01a) inverts the old
"harness-workers is staging-only" invariant. Update the 8 stale
assertions across 3 test files that still encoded staging-only,
deriving the new expected values from the SSOT (railway-envs.ts) and
the regenerated railway-envs.generated.json:

- healthcheckPathFor/emit healthcheckPath: prod now /health (was undefined/omitted)
- repoNameFor(prod): now resolves showcase-harness (was throw)
- envsFor: now [prod, staging] (was [staging])
- the worker-shape test: dual-env, domainless+probe-disabled in BOTH
  envs, gateValidated:true / gateIgnore dropped (per SSOT)
- computePromoteClosure: harness-workers now Tier-1 promoted, not
  skipped; the always-Tier-1 set no longer filters it out
- expandImageConsumers(prod) / default prod redeploy scope (39->40):
  the dual-env worker now joins the prod showcase-harness redeploy scope
2026-06-25 23:05:18 -07:00
Jordan Ritter c04318b193 fix(showcase): align auth conformance import style + restore DEMO_TOKEN to match gold 2026-06-25 23:04:08 -07:00
Jordan Ritter 2983bbc69d fix(showcase): conform claude-sdk-python auth demo to langgraph-python gold standard
claude-sdk-python was the last integration still on the legacy auth-first
shape: an authenticated-on-load page guarded by a class-based
`ChatErrorBoundary`, a `useDemoAuth` exposing `authenticate`/`authenticated`,
an `auth-banner` with an `onAuthenticate` prop and bespoke buttons, and NO
`sign-in-card`. The byte-identical `auth.spec.ts` (which asserts an
unauthenticated-first `SignInCard` with `auth-sign-in-button` /
`auth-demo-token`) therefore failed all six cases against it.

Port the four auth files verbatim from the langgraph-python gold standard
(adapting nothing — the per-integration wiring, `agent="auth-demo"` and
`runtimeUrl="/api/copilotkit-auth"`, was already identical):
- use-demo-auth.ts: unauth-first, localStorage-backed, exposes
  `isAuthenticated`/`hasEverSignedIn`/`signIn`/`signOut`.
- page.tsx: render `SignInCard` until first sign-in, then keep `<CopilotKit>`
  mounted across the sign-out cycle; shared `handleAuthError` on BOTH the
  provider and agent-scoped `<CopilotChat onError>`; clear-on-auth effect;
  amber `auth-demo-error` surface.
- auth-banner.tsx: shared `<Button>`, `onSignIn`/`onSignOut` props.
- sign-in-card.tsx: new, ported from the gold standard.

Add the shared shadcn primitives the gold-standard frontend depends on and
which claude-sdk-python was missing (`src/lib/utils.ts`,
`src/components/ui/button.tsx`, `src/components/ui/card.tsx`) plus the
`radix-ui` dependency they require, matching the claude-sdk-typescript peer.

Red/green on the real surfaces: against the legacy frontend `auth.spec.ts`
fails 6/6 (every test times out waiting for `auth-sign-in-button`); against
the rebuilt frontend it passes 6/6 and the `--d5 --isolate` auth probe is
green.
2026-06-25 22:50:30 -07:00
Jordan Ritter 2d451e3d66 fix(showcase): conform built-in-agent auth demo to langgraph-python gold
built-in-agent was the lone integration left on the legacy auth variant
when 5057efce1a brought the other 19 into conformance ("built-in-agent
already passes via its ChatErrorBoundary"). It rendered the post-sign-out
401 via a React ChatErrorBoundary (auth-demo-chat-boundary) + a local
401-regex onError, and defaulted to authenticated on first paint — so the
byte-identical auth.spec.ts (the CI conformance gate) failed every
unauth-first assertion.

Normalize to the langgraph-python gold shape:
- use-demo-auth.ts: unauth-first hook (hasEverSignedIn/signIn/signOut,
  localStorage-backed token, isAuthenticated/authorizationHeader).
- page.tsx: drop ChatErrorBoundary/lastError/local-401-regex; wire a shared
  handleAuthError onto BOTH <CopilotKitProvider onError> and the agent-scoped
  <CopilotChat onError>; clear-on-auth useEffect keyed off authError alone;
  unauth-first SignInCard gate; amber [data-testid="auth-demo-error"] surface.
- auth-banner.tsx / sign-in-card.tsx: align prop contract to gold
  (onSignIn, onSignIn(token)).

Forced divergences preserved: built-in-agent IS the built-in agent, so it
keeps <CopilotKitProvider> (runtime registers the agent under the default
key) rather than <CopilotKit agent="auth-demo">, and uses raw Tailwind
elements (no shadcn @/components/ui in this integration). The error-handling
shape, auth hook, and testid contract match gold exactly.

Proven RED->GREEN on the byte-identical auth.spec.ts (the discriminating
surface; the --d5 probe accepts both shapes and was green for the legacy
frontend): all unauth-first conformance assertions flip FAIL->PASS, and the
canonical built-in-agent:auth --d5 --isolate probe is green.
2026-06-25 22:50:30 -07:00
Jordan Ritter 9cb62acf94 fix(showcase): add byte-identical auth e2e spec to ms-agent-harness-dotnet
The Authentication demo frontend conforms to the langgraph-python gold
standard but was missing its tests/e2e/auth.spec.ts (conformance rule 1:
e2e tests must be byte-identical to LGP). Add the LGP auth.spec.ts verbatim
(sha256 match) so the auth flow is e2e-covered. Verified green via
showcase test ms-agent-harness-dotnet:auth --d5.
2026-06-25 22:50:30 -07:00
Jordan Ritter e88d01a99f fix(showcase): backfill prod harness-workers into SSOT so image rebuilds bounce it
The prod `harness-workers` fleet worker (serviceId
c2aa8a0b-350e-4b76-8541-3012dfac41d0, instance
7c48ee43-6df4-457b-b977-10f1f1ac1680) runs the shared `showcase-harness`
image (`imageOf: "harness"`) but had NO `prod` env entry in the
railway-envs SSOT. `expandImageConsumers` is env-aware — a consumer only
joins an env's redeploy scope if it declares that env — so a rebuilt
`showcase-harness:latest` bounced the prod control-plane but SILENTLY
SKIPPED the prod worker, leaving it pinned to a stale 2026-06-19 image.

That stale worker image carries a 1-demo `registry.json` for
`ms-agent-harness-dotnet` (only `beautiful-chat`), so the hourly
`e2e_demos` driver running on it produced only 1 of 39 `e2e:` rows in
prod PocketBase. The other 38 feature rows were absent → `resolveD3`
exists=false → `UI` badge omitted → broken D3 rung → D0.

Backfill the live prod worker as a `prod` env entry (real
serviceInstance ID), flip `gateIgnore` off, and set `gateValidated:
true` so the env-aware `imageOf` expansion now pulls the prod worker
into the prod redeploy scope on every `showcase-harness` rebuild.
Regenerate the emitted JSON + golden fixture and update the two
gate-count assertions accordingly.
2026-06-25 22:46:32 -07:00
Jordan Ritter 5057efce1a fix(showcase): render post-sign-out auth rejection across showcase integrations
The auth demo capped at D4 across integrations because the post-sign-out
rejection banner never rendered. The post-sign-out `agent_run_failed` is
delivered only on the agent-scoped `<CopilotChat onError>` channel — never the
provider-level `<CopilotKit onError>` the demos listened on — so the D5/D6 auth
probe's rejection-surface assertion failed and the cell was capped at D4.

Fix (applied to all 19 integrations whose auth demo reproduced the bug): wire a
stable `handleAuthError` onto the agent-scoped `<CopilotChat onError>` (keeping
the provider handler), key the error surface off auth-error STATE alone with a
clear-on-auth effect (removing the `&& !isAuthenticated` cross-slice race), and
harden the rejection-banner message fallback against nullish error events.

Scope: 19 of 20 integrations. built-in-agent already passes (renders via its
ChatErrorBoundary); claude-sdk-python adapted to its legacy/error-boundary shape.
2026-06-25 20:34:01 -07:00
Tyler Slaton b1ca211bb7 docs(teams): rewrite Microsoft Teams guide for @copilotkit/bot-teams (#5615)
Rewrites the Microsoft Teams guide for the new `@copilotkit/bot-teams`
adapter added in #5497.

The existing guide documented an older API (`createTeamsAgentBot`, a
local "Teams DevTools" bridge) that shipped through copilotkitnext and
no longer matches the package. This rewrites it to mirror the Slack
guide:

- Quickstart with `createBot` + the `teams()` adapter, verified in the
M365 Agents Playground (no Microsoft account)
- Interactive Adaptive Cards with inline `onClick` handlers
- A human-approval gate via `thread.awaitChoice`
- Splitting the bot from its agent over AG-UI
- Azure sideloading into real Teams (tunnel, Entra app, Azure Bot,
manifest)

Also refreshes the frontend picker summary (Playground, not DevTools).

### Merge ordering

This depends on #5497. The Teams guide is an `earlyAccess` page, so it
should not go live until `@copilotkit/bot-teams` actually publishes.
**Merge this after #5497 ships the package.**
2026-06-25 15:18:13 -07:00
github-actions[bot] e9fce5b8bb style: auto-fix formatting 2026-06-25 21:07:23 +00:00
Jordan Ritter 4396f71ab0 docs(showcase): document promoting a staging-only integration to production
Adds a "Promoting a Staging-Only Integration to Production" section to
showcase/RAILWAY.md — the staging-first -> promote-later procedure that was
undocumented and caused the strands-typescript D6 false-red (PR #5705).

The procedure previously survived only as a comment inside the SSOT
(railway-envs.ts); there was no human-facing SOP. The new section is a
start-to-finish checklist grounded in the PR #5705 worked example:

- When it applies (gateValidated:false, gateIgnore:true, staging-only env map,
  legacyJsonCompat prod placeholder).
- The critical gotcha up front: the promote pipeline (showcase_promote.yml /
  bin/railway promote) only moves digests to a prod service that ALREADY
  exists; it does NOT provision a new prod serviceInstance. Until that instance
  exists, D6 false-reds the whole column (404 -> empty backendUrl ->
  goto-error on every cell).
- Ordered steps: provision the prod serviceInstance out-of-band
  (environmentStageChanges + environmentPatchCommitStaged, mirroring a peer
  prod TS service); edit the SSOT (add prod env block, gateValidated:true, drop
  gateIgnore, remove legacyJsonCompat); regenerate derived artifacts
  (emit-railway-envs-json.ts, golden fixture, sync-promote-service-options.ts)
  and run the gate (verify-railway-image-refs.ts + vitest); prod secrets via
  the prod env var set / aimock (no inline secrets); verify GREEN (/api/health
  200, prod PocketBase health record, D6 flips on the next hourly :40 tick).

Cross-links INTEGRATION-CHECKLIST.md §B (single-shot bring-up) both ways.
Leaves a precise TODO that §B.3 still names the stale showcase_deploy.yml for
the build matrix (the RAILWAY.md references were already corrected on main).
2026-06-25 14:06:04 -07:00
Jordan Ritter 27dcf4a404 fix(showcase): promote strands-typescript to production (dual-env SSOT)
The showcase-strands-typescript integration was staging-only: it had no
production Railway serviceInstance, so the prod D6 dashboard column showed
a uniform false-red (every cell errorClass=goto-error, backendUrl="") —
the probe navigated a bare relative path because the harness had no prod
health record / backendUrl to discover.

Provisions the prod serviceInstance (8a50728e-6119-43c4-b59c-d9535b6717a4,
domain showcase-strands-typescript-production.up.railway.app, healthcheck
/api/health, image pinned to the GHCR @sha256 digest, OPENAI_BASE_URL at
prod aimock) and brings the SSOT to the dual-env showcase-strands shape:

- railway-envs.ts: add the prod env entry with the real instanceId,
  gateValidated:true, drop gateIgnore, remove the legacyJsonCompat
  prod-domain placeholder.
- railway-envs.generated.json: regenerated (prod instanceId/domain, probe.prod
  true, prod healthcheck; moved into the promote closure, tier 2).
- railway-envs.golden.json: regenerated to include the new prod (service,env)
  pair (intentional behavior change, not a refactor regression).
- showcase_promote.yml: dropdown regenerated to list strands-typescript.
- verify-railway-image-refs.test.ts / redeploy-env.test.ts: update the
  gateValidated/scope counts (39->40 gate targets, prod default 38->39) and the
  now-stale staging-only comments.

RED->GREEN (live prod): BEFORE /api/health 404, prod PocketBase
health:strands-typescript totalItems:0, the 3 named D6 cells all
errorClass=goto-error backendUrl="". AFTER /api/health 200, prod PocketBase
health:strands-typescript present (status:200, valid url),
verify-railway-image-refs OK 80 instances.
2026-06-25 13:22:31 -07:00
Jordan Ritter a6c22de23f docs(showcase): complete Depot-runner footnote (all depot workflows + showcase/eval -16 runner) 2026-06-25 10:38:42 -07:00
Jordan Ritter 154b0d98bd docs(showcase): address adversarial-review findings (QA count 19, single --fixtures ref, Depot runtime, autoUpdates schedule) 2026-06-25 10:33:29 -07:00
Jordan Ritter 3755ef34a7 docs(showcase): correct factual drift in TESTING.md + RAILWAY.md docs (claim audit) 2026-06-25 10:22:07 -07:00
Ran Shem Tov 24a93672f1 feat(showcase): bump CopilotKit 1.61.1 -> 1.61.2 and adopt A2UI catalog auto-inject (#5611)
Bump the canonical CopilotKit pin across all showcase integrations + shell
to 1.61.2 (canonical-pins.json, every package.json + package-lock.json),
which carries CopilotKit#5611: passing a catalog to the provider
(`<CopilotKit a2ui={{ catalog }}>`) now auto-enables A2UI and defaults tool
injection on, so the runtime no longer needs an explicit `a2ui` config.

Demonstrate the feature on the A2UI dynamic (declarative-gen-ui) demos by
removing the now-redundant runtime `a2ui` block (`injectA2UITool: true` +
`defaultCatalogId`) from:
  - langgraph-python, langgraph-fastapi, langgraph-typescript
  - strands, strands-typescript
  - google-adk

The forwarded catalog supplies its own catalogId (sdk-js A2UI middleware
auto-derives `defaultCatalogId` from it), so the previous "Catalog not found"
fallback no longer applies.

Verified: validate-pins drift ratchet unchanged (38 / same hash);
langgraph-python D6 `gen-ui-declarative` green end-to-end (no Catalog-not-found).
2026-06-25 14:03:36 +02:00
Jordan Ritter d501d233b0 fix(showcase): make built-in-agent declarative-gen-ui paint its D6 surface
The secondary-LLM prompt was far thinner than the canonical generation guidelines,
so it emitted trees that (correctly) failed the renderer's paint gate → surface-missing.
Port the canonical generation rules into the prompt, add output validation, add catalog
parity (DataTable + info-row), ground the planner with sales-context, and record
multi-turn aimock fixtures. Includes CR fixes: two-arg z.record for the DataTable rows
schema (zod@4 API), index-based DataTable row key, and Metric trendValue rendering for
neutral trend.
2026-06-24 20:25:16 -07:00
Jordan Ritter bbdcb01440 fix(showcase): resolve a2ui-fixed-schema React #31 via Zod-3 catalog defs
The showcase authors A2UI catalog defs with root zod@4, but @a2ui/web_core's
GenericBinder schema scraper inspects Zod-3 internals (_def.typeName==='ZodUnion').
A zod@4 union reports _def.typeName===undefined → misclassified STATIC → the raw
{path} binding object reaches render → React error #31. Author this demo's catalog
with a zod-v3 (npm:zod@3.25.76) alias so the binder resolves bindings. Includes CR
hardening of the shared a2ui factory validation (plain-object data guard, unique-id
check, fail-loud on non-string secondary-LLM return).
2026-06-24 20:25:16 -07:00
Jordan Ritter b305394b5c docs(showcase): correct gen-ui-agent PARITY_NOTES — it is GREEN, not react-core-blocked (#5693)
## Summary

Doc-only single-file correction to
`showcase/integrations/built-in-agent/PARITY_NOTES.md`. The
`gen-ui-agent` D6 cell already passes end-to-end locally, but its
PARITY_NOTES entry still documented it as RED/blocked on a `STATE_DELTA
→ useAgent` gap in `@copilotkit/react-core`. That premise is stale and
is now refuted by local D6 runs. This rewrites the entry to
GREEN/reclaimed and rescopes the surrounding section header to the
remaining A2UI render-layer demos.

## RED → GREEN proof (from the work log)

The "RED" here is documentary, not behavioral: the cell **passes**
despite the stale RED doc.

Local RED baseline (`bin/showcase test built-in-agent:gen-ui-agent --d6
--direct --isolate`):
```
[conversation-runner] turn 3/3 — assertions passed
[conversation-runner] conversation completed successfully { turnsCompleted: 3 }
  ✓ d6:built-in-agent green (44.1s)
  1 passed (44.1s)
```

Local GREEN value-test (same command, `--repeat 3`):
```
  3 passed (130.1s)
✓ Tests passed for built-in-agent:gen-ui-agent
```
3/3 stable — not a flake. The doc correction changes no runtime
behavior.

Why it works: the backend `set_steps` server-tool result is converted to
a `STATE_DELTA` `[{op:"add", path:"/steps", value:steps}]` in
`src/lib/factory/tanstack-factory.ts` (`add`, not `replace`, so
`@ag-ui/client@0.0.57` doesn't drop it as
`OPERATION_PATH_UNRESOLVABLE`). `@ag-ui/client` applies the patch and
fires `onStateChanged`; the core state-manager fans it to subscribers;
`useAgent` re-renders off `agent.state.steps`. Fully wired in published
1.61.1 — no react-core change needed.

## Key finding: there was NO config quarantine

`gen-ui-agent` was never in the manifest `not_supported_features`, never
excluded in `shared/constraints.yaml`, and
`shared/feature-registry.json` has no per-feature status field. The
harness already routes it into `runnable` and grades it green. The
**stale doc was the only artifact** — there was no executable quarantine
to lift, so this is a pure doc correction.

## The two genuinely-RED demos are out of scope

`a2ui-fixed-schema` (React #31 crash from an unresolved `{path}` A2UI
binding) and `declarative-gen-ui` (surface never paints; secondary-LLM
op-shape) are real bugs, but both belong to `@copilotkit/a2ui-renderer`
/ showcase — **not** `@copilotkit/react-core`. They are being addressed
in a separate spec and are intentionally left untouched here.

## Test plan

- [x] `oxfmt --check` on the changed file — passes (correctly formatted)
- [x] `oxlint` on the changed file — 0 warnings, 0 errors
- [x] `commitlint` on the commit message — passes (`docs(showcase):`)
- [x] D6 cell passes locally, 3/3 stable
- [ ] CI green
2026-06-24 15:35:40 -07:00
Jordan Ritter a478692b58 docs(showcase): correct gen-ui-agent PARITY_NOTES (it is GREEN, not react-core-blocked)
The built-in-agent gen-ui-agent D6 cell already passes end-to-end locally;
the PARITY_NOTES entry that documented it as RED/blocked on a STATE_DELTA
to useAgent gap in @copilotkit/react-core was stale. The set_steps to
STATE_DELTA {op:"add", path:"/steps"} workaround merged in
tanstack-factory.ts closed that gap: @ag-ui/client applies the patch and
fires onStateChanged, the core state-manager fans it to subscribers, and
useAgent re-renders off agent.state.steps. No react-core change is needed.

Rewrites the gen-ui-agent entry to GREEN/reclaimed and rescopes the
section header to the remaining A2UI render-layer demos (a2ui-fixed-schema,
declarative-gen-ui), whose fixes belong to @copilotkit/a2ui-renderer, not
react-core. Doc-only; no config quarantine existed (gen-ui-agent was never
in manifest not_supported_features), so the cell stays a counted green.

Local RED baseline: cell passes (1 passed) despite the stale RED doc.
Local GREEN value-test: --repeat 3 => 3 passed (130.1s), stable.
2026-06-24 15:12:45 -07:00
Mark Fogle 56b51aeaef fix(showcase/shell-dojo): runtime-derive preview backend URL
The dojo's preview iframe built its src from `integration.backend_url`,
which generate-registry.ts bakes into registry.json at Docker BUILD time
(default `showcase-{slug}-production.up.railway.app`). So the staging
dojo iframed PROD integration backends — the exact staging->prod leakage
the shell's SU-13 runtime-derivation fix already prevents, but which was
never ported to shell-dojo.

Port the `backendHostPattern` slice of SU-13:
- copy shell's backend-url.ts verbatim (resolveBackendUrl + the
  NEXT_PUBLIC_LOCAL_BACKENDS local-dev override); a scripts drift-guard
  test keeps it byte-identical to the shell's and pins the default
  pattern across backend-url.ts and generate-registry.ts.
- add `backendHostPattern` to shell-dojo's RuntimeConfig (server reads
  SHOWCASE_BACKEND_HOST_PATTERN at request time; client carries the SSR
  sentinel) — the existing layout injection picks it up automatically.
- page.tsx derives previewUrl via resolveBackendUrl at request time,
  gated on a `mounted` flag so the SSR-phase sentinel host never reaches
  an iframe src (shell-dojo loads the registry synchronously, so unlike
  the shell it has no data-loading guard to defer the read past
  hydration).

Staging dojo's SHOWCASE_BACKEND_HOST_PATTERN is set to
`showcase-{slug}-staging.up.railway.app`; prod stays unset (= default
prod pattern), so prod behavior is byte-identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 21:37:34 +00:00
Jordan Ritter eb3bb6af98 docs(showcase): correct stale aimock fixture/Dockerfile claims in RAILWAY.md (#5683)
## Summary

`showcase/aimock/RAILWAY.md` described the aimock fixture infrastructure
as it worked during an earlier migration phase, not as it works today.
The stale claims actively misled debugging. This PR corrects them to
match the real, verified infra.

## Before → After

| Claim | Before (stale) | After (correct) |
| --- | --- | --- |
| Fixture delivery (§4/§5) | Three fixtures fetched at boot from
GitHub-raw URLs: `d5-all.json`, `smoke.json`, `feature-parity.json` |
Fixtures are **baked into the image** at build time under
`/fixtures/{shared,d4,d6}`; no remote fetch |
| `d5-all.json` | Authoritative D5 bundle, re-bundled from
`harness/fixtures/d5/*` | **Gone** — a one-time migration source split
into the per-slug `showcase/aimock/d6/<slug>/` tree (the source of
truth) |
| startCommand `--fixtures` (§5) | 3 `raw.githubusercontent.com` URLs |
`--fixtures /fixtures/shared --fixtures /fixtures/d4 --fixtures
/fixtures/d6` (the baked-in dirs). Added a warning that a single
`--fixtures /fixtures` parent loads nothing |
| Runtime image (§3) | Bare `ghcr.io/copilotkit/aimock:<version>` pulled
directly | `showcase-aimock` image, `FROM
ghcr.io/copilotkit/aimock:latest` + baked fixtures, built by
`showcase_build.yml` |
| Dockerfile (§8) | "Dead code / legacy wrapper, safe to remove" |
**LIVE** — `showcase_build.yml` builds it (`dockerfile:
showcase/aimock/Dockerfile`) and it bakes `shared/`, `d4/`, `d6/` into
`/fixtures/`. Do not remove |
| Env vars (§6), references (§9) | Referenced public GitHub-raw fixture
URLs and ~5 min raw-edge cache propagation | Local baked dirs;
propagation is via image rebuild + Railway deploy |

Also documents the working Railway mutation auth path by mechanism only
(§2): account-scoped `RAILWAY_TOKEN` from the DevOps `showcase`
1Password item via GraphQL `Authorization: Bearer` — the CLI session
token is not authorized for showcase-project mutations. No token value
is in the doc.

## Verification

- `showcase/aimock/d5-all.json` does not exist; the tree is `shared/`,
`d4/`, `d6/`.
- `showcase/aimock/Dockerfile` is `FROM
ghcr.io/copilotkit/aimock:latest` + `COPY shared|d4|d6 ->
/fixtures/...`.
- `.github/workflows/showcase_build.yml` matrix entry `showcase-aimock`
builds `dockerfile: showcase/aimock/Dockerfile`, context
`showcase/aimock`.

Docs-only change. Do not merge / do not enable auto-merge.
2026-06-24 13:28:26 -07:00
Jordan Ritter 1db78ee902 fix(showcase): re-author pydantic-ai gen-ui-declarative aimock fixture (kill 503 no_fixture_match) (#5690)
## Summary
Corrects the `pydantic-ai` `gen-ui-declarative` aimock fixture. PR #5661
merged it mis-templated from `ms-agent-dotnet`: the inner secondary-LLM
blocks gated on `_design_a2ui_surface` (ms-agent's tool) instead of
pydantic-ai's actual **`render_a2ui`**, and only **1 of the 4**
declarative pills was covered (the rest were dead KPI/pie/bar/status
blocks that match no real pill). So the cell still 503'd.

Re-authored 1:1 from the canonical `langgraph-python` fixture: inner
tool **`render_a2ui`**, all **4 pills** (sales-dashboard, rep-vs-quota,
at-risk, biggest-account) as outer `generate_a2ui` + inner `render_a2ui`
+ tool-result triplets, `context: "pydantic-ai"`. Dead blocks removed.

## Effect (real-surface proven via `bin/showcase test
pydantic-ai:declarative-gen-ui --d6 --direct`)
- `no_fixture_match` 503 count **6 → 0** — the 503 is fully gone.
- **Necessary, not sufficient:** the cell still can't paint (advances
503 → `surface-missing`) due to **separate pydantic-ai integration
gaps** — missing `DataTable` renderer, `InfoRow` lacking the
`declarative-info-row` testid, and the `injectA2UITool: false` A2UI
delivery path. Filed as a follow-up (Slack `#` showcase alerts); out of
scope here.
- No-regression: `multimodal` cell still green; fixture-only change
(+187/-336, one file).

## Test plan
- [x] Real-surface red-green: `no_fixture_match` 6 → 0
- [x] Schema valid (`validateFixtures` 0 issues), no fixture shadowing
(zero delta)
- [x] Inner `render_a2ui` arguments byte-identical to canonical
langgraph-python
- [ ] CI green
2026-06-24 13:23:11 -07:00
Jordan Ritter ffe50c1a96 fix(showcase): re-author pydantic-ai gen-ui-declarative aimock fixture
PR #5661 mis-templated this fixture from ms-agent-dotnet: the inner
secondary-LLM blocks used toolName "_design_a2ui_surface" (never matches
pydantic-ai, whose inner tool is render_a2ui) and only the sales-dashboard
pill had any block, so the other 3 declarative pills matched nothing and
the cell 503'd with no_fixture_match on turn 1.

Re-author 1:1 from the canonical langgraph-python fixture (identical inner
tool render_a2ui + per-pill surfaces), adapted for pydantic-ai: context
"pydantic-ai" on the outer generate_a2ui + narration entries, inner
render_a2ui entries matched by toolName alone (the agent's inner OpenAI()
client does not forward x-aimock-context). All 4 pills (sales-dashboard,
team-performance, at-risk, top-account) now get both an outer
(generate_a2ui) and inner (render_a2ui) block whose component payloads meet
each pill's probe assertion. Dead KPI/pie/bar/status blocks removed.

Real-probe proof: with this fixture the inner render_a2ui call matches and
the outer narration renders ("Here's your Q2 sales dashboard.") — the cell
advances from "Strict mode: no fixture matched / 503" to all LLM calls
matched. Remaining surface-missing failure is a non-fixture frontend/agent
A2UI delivery gap (see PR description), out of scope for this fixture fix.
2026-06-24 12:28:01 -07:00
Jordan Ritter 5f9875a6a2 fix(showcase): ship shared-state-read D6 cell for strands(+TS)
Add the shared-state-read demo entry to the strands and strands-typescript
manifests, mirroring the gold-standard langgraph-python entry. The fleet
enumerates D6 cells only from manifest demos that have both an id and a
route; shared-state-read was declared as a feature (and is not in
not_supported_features) but had no demo entry, so it resolved to status
unshipped and never ran on staging.

This makes the aimock fixture fix from #5673 actually take effect on the
fleet: both integrations now enumerate and run the shared-state-read cell
green.
2026-06-24 12:23:23 -07:00
Jordan Ritter 4a04b59d1c docs(showcase): correct stale aimock fixture/Dockerfile claims in RAILWAY.md
The showcase-aimock RAILWAY.md described fixtures as fetched from GitHub-raw
URLs at boot and called the Dockerfile dead code. Both are false and actively
misled debugging: fixtures are baked into the image at build time under
/fixtures/{shared,d4,d6}, the Dockerfile is the live image builder driven by
showcase_build.yml, and the d5-all.json bundle no longer exists (split into
the per-slug d6/ tree). Corrects sections 3, 4, 5, 6, 7, 8, 9 to match the
real infra, fixes the startCommand to load the three baked-in subdirectories,
and documents the account-scoped RAILWAY_TOKEN mutation path by mechanism.
2026-06-24 11:51:30 -07:00
Sam Julien 93ce311cfb docs: move Threads into chat UI docs (#5653)
## Summary

- Moves the canonical `/threads` guide into the **Build Chat UIs** nav
group, immediately after prebuilt components
- Keeps `/premium/threads-explained` under **Intelligence Platform** as
the architecture/persistence explanation
- Adds contextual cross-links between the Threads guide, Threads
architecture page, and relevant prebuilt chat UI docs
- Shows `Threads` in the authored framework sidebars next to their chat
UI basics

## Why

Threads are primarily discovered by developers adding saved
conversations, history, and thread switching to a chat UI. The
implementation guide belongs with chat UI docs, while the platform page
remains the deeper explanation of persistence, realtime sync, and
Enterprise Intelligence Platform backing.

## Screenshots

**Root docs navigation: `/threads` now appears with the chat UI basics,
immediately after Prebuilt Components.**

![Root docs Threads
navigation](https://raw.githubusercontent.com/CopilotKit/CopilotKit/92e68e787ec0e137124460637549b0df33929389/pr-5653/root-threads-build-chat-uis-nav.png)

**Authored framework navigation: framework-specific docs now show
Threads next to Prebuilt Components too.**

![Authored framework Threads
navigation](https://raw.githubusercontent.com/CopilotKit/CopilotKit/92e68e787ec0e137124460637549b0df33929389/pr-5653/authored-langgraph-threads-nav.png)

**Intelligence Platform navigation: the architecture page stays in the
platform section.**

![Threads architecture in Intelligence Platform
navigation](https://raw.githubusercontent.com/CopilotKit/CopilotKit/92e68e787ec0e137124460637549b0df33929389/pr-5653/threads-architecture-intelligence-nav.png)

## Validation

- `git diff --check origin/main...HEAD`
- `git diff --check`
- `npm run typecheck` from `showcase/shell-docs`
- Local route smoke checks for `/threads`, `/premium/threads-explained`,
`/prebuilt-components`, and `/prebuilt-components/chat` returned 200
- Authored framework route smoke checks returned 200
2026-06-24 11:11:33 -07:00
Sam Julien 5507c75d2d docs: add framework-scoped Threads callouts (#5651)
## Summary

- Adds a `thread_persistence_pattern` manifest flag so shared docs can
render selected-framework Threads guidance.
- Marks LangGraph Python, LangGraph TypeScript, LangGraph FastAPI, and
Google ADK with the appropriate thread persistence pattern.
- Extends `WhenFrameworkHas` support so the shared Threads guide can
show LangGraph-only and ADK-only callouts.
- Clarifies that `useThreads` manages Enterprise Intelligence Platform
thread records, not native framework stores.
- Adds framework-selected callouts to the root/shared Threads guide
without adding a third setup path.

## Notes

The new callouts intentionally avoid claiming external store listing,
lifecycle sync, migration/import tooling, or durable ADK sessions by
default. Those remain product/runtime follow-ups tracked separately.

## Validation

- `git diff --check`
- `npm run pretypecheck` in `showcase/shell-docs`
- `npm run lint` in `showcase/shell-docs` (passes with existing
warnings)
- `npm run typecheck` in `showcase/shell-docs`
- `npm run build` in `showcase/shell-docs` (passes with existing
Turbopack/NFT warning)
- Local route smoke checks:
  - `/threads` hides framework callouts
  - `/langgraph-python/threads` shows LangGraph callout only
  - `/langgraph-typescript/threads` shows LangGraph callout only
  - `/langgraph-fastapi/threads` shows LangGraph callout only
  - `/google-adk/threads` shows ADK callout only
2026-06-24 11:11:22 -07:00
Ran Shemtov 311c47f002 Merge branch 'main' into claude/reverent-black-6ba1b9 2026-06-24 20:04:00 +02:00
Ran Shem Tov d6f618dc03 fix(showcase): make default redeploy scope env-aware for staging-only services
The strands-typescript SSOT entry is ciBuilt:true but staging-only (prod
instance not yet provisioned). redeploy-env.ts's default scope was the full
CI_BUILT_SERVICES set for BOTH envs, so a staging-only ciBuilt service would
wrongly enter the prod default scope and fail a manual `redeploy-env.ts prod`
(no prod instance). Filter the default scope by env declaration (explicit
--services stays unfiltered, preserving the contract-pin that an operator can
force a named service in an env it does not declare). imageOf expansion was
already env-aware; this extends the same invariant to the base scope.

Update the inventory-lock test counts for the new service (total 40->41,
CI_BUILT 38->39, staging default scope 39->40; prod default scope stays 38 as
the staging-only service is now correctly excluded).
2026-06-24 19:54:49 +02:00
Ran Shem Tov d779f71468 feat(showcase): deploy strands-typescript integration to staging
Wire the strands-typescript showcase integration for staging deployment,
mirroring how the Python strands integration is deployed.

- manifest: flip deployed: true so the shell lists it in the integration menu
- railway-envs.ts: add showcase-strands-typescript SSOT entry (staging-only
  for now: prod instance not yet provisioned, so it omits the prod env and is
  gateIgnore'd until promoted dual-env); regenerate railway-envs.generated.json
- showcase_build.yml + showcase_build_check.yml: add the strands-typescript
  build matrix entry, change-detection filter, and dispatch option (railway_id
  is the new Railway service id)
- golden fixture + image-ref-gate inventory tests updated for the new service

Railway staging service showcase-strands-typescript provisioned
(showcase-strands-typescript-staging.up.railway.app, health /api/health,
OpenAI-via-aimock env). Prod is added later via the promote pipeline.
2026-06-24 19:39:36 +02:00
Ran Shemtov 9319a5f57a Merge branch 'main' into claude/strands-d6-green 2026-06-24 19:26:12 +02:00
Ran Shemtov 54ef8027f6 Merge branch 'main' into claude/adk-d6-non-a2ui 2026-06-24 19:25:24 +02:00
Jordan Ritter b507054f51 fix(showcase): map AG-UI multimodal content to native pydantic-ai types (fix assert_never) (#5675)
## Summary
The pydantic-ai showcase integration crashed with `assert_never` in
pydantic-ai's `_map_user_prompt` whenever AG-UI multimodal
`InputContent` (images / documents / binary, data: and url: sources)
reached the model — AG-UI content types were never normalized to the
native pydantic-ai types (`str` / `ImageUrl` / `BinaryContent` /
`DocumentUrl`) the mapper requires.

## What changed
- **`_MultimodalFlattenModel(WrapperModel)`** normalizes content at the
**model-call boundary** (overriding `request` / `request_stream` /
`count_tokens`) — deliberately NOT a `history_processor`, because that
hook persists its return into `message_history` and would leak flattened
content back to the UI.
- **Supported-type gating + degrade centralized at the single
native-type emission choke point** (instead of scattered
per-content-branch): unsupported image subtypes (HEIC/SVG/TIFF/BMP),
audio/video, and non-fetchable url attachments degrade to a text
placeholder rather than emitting a native type the OpenAI Responses
vision API rejects (which would fail the turn).
- **Mime normalized once** — strips RFC-2045 params/whitespace,
lowercases, and aliases the common non-canonical `image/jpg` →
`image/jpeg` before the allow-list (png/jpeg/gif/webp) test, so real
JPEGs are no longer silently dropped.
- Identity-based (`is`) no-op detection replaces fragile structural
`==`.

## Why it matters
Unblocks multimodal turns in the pydantic-ai showcase integration;
eliminates the `assert_never` crash and stops valid `image/jpg` JPEGs
and url-borne documents from being silently mishandled.

## Testing
- **43 unit tests** (clean pinned venv, pydantic-ai 1.0.18), red-green
proven per gap: `image/jpg` forwarded as a supported JPEG; url-media /
non-PDF-doc degrade instead of emitting an unconditional `DocumentUrl`;
parameterized mime forwarded; state-leak guard (`request_stream`
forwards flattened, not raw); `count_tokens` override; `assert_never`
provably unreachable (all flatten paths return a native type or raise).
- 9 rounds of code review (7 agents/round) to a clean confirmation round
(zero blocking findings).

## Test plan
- [ ] CI green
2026-06-24 09:57:21 -07:00
Jordan Ritter b00817d534 fix(showcase): record pydantic-ai generate_a2ui D6 fixture (staging 503 flap) (#5661)
## Summary
- The pydantic-ai `generate_a2ui` declarative D6 turn was missing an
aimock fixture, producing HTTP 503 `no_fixture_match` on staging
(pydantic-ai 503 vs ms-agent-dotnet 200 for the same turn) — a source of
dashboard flapping.
- Adds the canonical mirror fixtures (outer `generate_a2ui` + matching
inner `_design_a2ui_surface`) to
`showcase/aimock/d6/pydantic-ai/gen-ui-declarative.json`. These are
deterministic canonical mirrors matching the langgraph-python convention
— **not** a non-deterministic real-LLM recording — preserving the
mandatory LGP 1:1 parity.

## Red-green proof
- **RED:** exact failing request (`POST /v1/responses`, gpt-4.1, "Show
me my sales dashboard for this quarter.", tools=[`generate_a2ui`],
header `x-aimock-context: pydantic-ai`, strict) against the pre-fix
fixture set → **HTTP 503 `no_fixture_match`** (reproduces staging
exactly; confirmed live on staging too).
- **GREEN:** same request against the new set → **HTTP 200** SSE
emitting the `generate_a2ui` tool call; the inner `_design_a2ui_surface`
turn also returns 200 with the dashboard surface.
- Independently re-verified. `validate-on-load` clean (no fixture
shadowing); existing pydantic-ai D6 turns (KPI/pie/bar/status) still
match identically — no regression.

## Notes
- No credentials in the committed fixture — the OpenAI key was never
even resolved (canonical mirror, not a recording). Credential scan of
the diff + full blob: zero matches.

## Test plan
- [ ] CI green
- [ ] After deploy, confirm the `generate_a2ui` declarative D6 cell
flips red→green on staging
2026-06-24 09:57:17 -07:00
Jordan Ritter 6b04bb08a9 fix(showcase/harness): reliable data-copilot-running turn-done signal (kill probe false-red flaps) (#5649)
## Summary

Makes the showcase harness probe's turn-done signal **reliable**,
killing the dominant class of dashboard false-red flaps without ever
hiding a real failure.

`waitForTurnComplete` previously relied on a fragile SSE fetch-counter
conjunct that false-reds healthy demos whenever the page-side fetch
wrapper missed the runtime URL/transport. This change makes the
**`data-copilot-running` DOM attribute** (driven directly by the agent
run lifecycle, `RUN_STARTED`→true / `RUN_FINISHED`→false,
transport-independent) the **PRIMARY** done-signal, with the SSE counter
demoted to a **headless-only fallback** (headless demos never render
`CopilotChatView`, so the attribute is absent).

Design (all three preserved — no false-green, no false-red, hangs still
red):
- **Primary signal** = the `data-copilot-running` true→false
**transition** with a **stayed-stopped quiescence window** (a stop must
persist on the same run-start count for `settleMs`; a new sub-run resets
it) — so it cannot complete on an intermediate stop in a multi-step
turn.
- **SSE counter** = headless fallback only; never an OR-trigger when the
DOM signal is present.
- **`done-signal-missing` backstop** (gated on `attrPresent===true` +
`runningNow!==true`) reds a genuine painted-but-never-finished DOM turn
before the hard timeout; headless turns use their full timeout for their
only signal.

## How it was reviewed

A full 4-round `cr-loop` (7 unbiased agents/round + confirmation rounds
+ a Procedure-3 promotion audit) caught and fixed **5 distinct
correctness defects** in the implementation before merge:
- **F1** — SSE OR-trigger could complete a multi-step turn early on an
intermediate stop (false-GREEN), in both the loop and the post-loop
classifier.
- **F2** — the run-start baseline was captured *after* the message send,
killing the primary signal on fast turns (false-RED).
- **F3** — non-atomic double `surfaceReady` read per poll (latent hazard
+ wasted round-trip).
- **F4** — the surface-mount (`completeOnMount`) path had no quiescence
window (false-GREEN on intermediate stop + false-RED on a still-running
gen-UI turn).
- **F5** — the early backstop false-redded slow-but-healthy **headless**
turns (now gated on the DOM signal).

Bidirectional red-green tests for F1–F5 plus a systematic `{DOM,
headless} × {completes, lagging-recovers, genuine-hang} × {text,
surface}` completion/backstop matrix. Full harness unit suite: **3173
passed / 18 skipped / 0 failed**; `tsc --noEmit` clean; lint 0 errors;
build clean.

## Known follow-ups (NOT in this PR — pre-existing / non-blocking)

- **Theoretical edge (not reachable on real or realistically-streamed
turns):** if a run completed within a single synchronous microtask
(zero-duration), the page-side MutationObserver could miss the true edge
while `attrPresent===true` → false-red. Real LLM turns and aimock
realistic-streaming hold the attribute true across many event-loop
ticks, so the observer reliably latches it. A naive "re-add SSE fallback
for DOM-present" fix would reintroduce F1's multi-step false-green, so
it's intentionally not done here.
- **Recommended quick follow-up (latency only, no wrong verdict):**
capture `baselineBannerText` pre-`sendTurnMessage` (mirroring the
run-start/count baselines) so a fast-erroring cold-start turn fast-fails
(#5142) instead of burning the full timeout.
- **Pre-existing sse-interceptor capture/counter internals** (none
load-bearing for the new done-signal; verified STAY_IN_C by the
Procedure-3 audit): page-side counter soft-nav/multi-capture reset,
`__hk_fetchWrapped` pattern reuse + hardcoded fallback, g/y-flag
stateful RegExp, TextDecoder end-of-stream flush, bare-catch
reader-error swallow, framenav payload discard/TOCTOU,
CDP-wallTime-vs-Date.now TTFT, addInitScript/close-listener
re-registration accumulation.

## Test plan

- [x] `pnpm test` (harness) — 3173 passed / 18 skipped / 0 failed
- [x] `tsc --noEmit` exit 0, lint 0 errors, build exit 0
- [ ] Verify on staging that auth / prebuilt-sidebar / claude-sdk-tools
(and other previously-flapping cells) stop false-redding while
genuinely-broken cells stay red

Please review the replay/primary-signal approach. Not auto-merging.
2026-06-24 09:57:13 -07:00
Jordan Ritter 27b5e79a00 test(showcase): cover pydantic-ai multimodal content mapping + degrade paths 2026-06-24 09:16:07 -07:00