Commit Graph

4085 Commits

Author SHA1 Message Date
Jordan Ritter 82f79b211f fix(showcase): unify resolveD5Row strict-missing + honest stale badge row
resolveD5Row now mirrors cell-model.ts resolveD5's STRICT missing-sub-row
handling: a mapped multi-key D5 family is credited green only when EVERY
sub-row is present. A missing sub-row collapses a present green/degraded
fold to no-data (gray), while a present red still dominates. Previously a
family with one present-green and missing siblings rendered a false-green
badge while buildCellModel's D5 rendered gray.

buildBadge now returns the EFFECTIVE (downgraded) row so badge.row.state
agrees with badge.tone — a stale-green badge had tone amber but
row.state green, a latent false-green for any consumer reading .row.state.
The spread preserves fail_count/first_failure_at/observed_at/signal so
drilldown metadata is unaffected; only .state becomes honest.
2026-05-30 21:01:44 -07:00
Jordan Ritter 79a22fb18d test(showcase): cover computeColumnTallyDetail health-dimension branch
Add cases for green-D3+D4+red-D5 and green-D3+red-D4, both landing in the
red bucket with dimension 'health'. No production change.
2026-05-30 20:52:07 -07:00
Jordan Ritter c506d8badf fix(showcase): deriveDepth D4 uses worst-state-wins instead of OR
A present red chat/tools row now pulls D4 down even if its sibling is green,
matching cell-model resolveD4. Single-side present-green cases still achieve
D4 (absent sibling is skipped).
2026-05-30 20:50:45 -07:00
Jordan Ritter afd3bdba36 fix(showcase): require emitted data above achievedDepth for isRegression
A cell is a regression only when the next rung above achievedDepth has
emitted data (exists && status !== null). Drops the no-data-D5 false
positive (mapped but unemitted D5) while keeping red-below-ceiling and
D3-red regressions.
2026-05-30 20:49:11 -07:00
Jordan Ritter 8c61713d66 fix(showcase): downgrade stale-green rows in resolveCell + resolveD5Row
Fold each contributing row's stale-green to degraded before tone/rollup
derivation so a frozen-green driver no longer reads as healthy. Per-badge
windows: e2e/d5/d6 6h, health/d2/smoke 45m. Thread co-render now from
cell-matrix gaps filter and cells-view stats.
2026-05-30 20:46:32 -07:00
Jordan Ritter d0da6e357d refactor(showcase): extract shared staleness helper into lib/staleness.ts
Move the three staleness windows and the byte-identical isStale/isRowStale
duplicate out of cell-model.ts and depth-utils.ts into one lib/staleness.ts.
Type-only import of StatusRow avoids a runtime cycle so live-status can also
consume it. No behavior change.
2026-05-30 20:41:25 -07:00
Jordan Ritter 91069e826e fix(showcase/harness): exclude bare-named infra services from aimock-wiring probe
The probe's EXCLUDE_SERVICES set listed all infra services with a
`showcase-` prefix (e.g. `showcase-harness`, `showcase-shell`,
`showcase-aimock`), but the actual deployed Railway service names in
the production project are BARE (`harness`, `shell`, `dashboard`,
`docs`, `dojo`, `pocketbase`, `webhooks`, `aimock`). The exact-match
`isExcluded` check therefore failed to exclude them, the non-LLM
infra services were counted as unwired (correctly missing
`*_BASE_URL` overrides), and the probe stayed red on staging/prod
even though aimock IS correctly wired for every real LLM-calling
integration service.

Fix `isExcluded` to also match the `showcase-`-prefixed form of a
bare input (`isExcluded("harness")` matches `showcase-harness` in the
set). Both naming conventions now resolve to the same canonical
entry, no parallel lists to drift. Added `showcase-webhooks`,
`showcase-dashboard`, `showcase-docs`, `showcase-dojo` to round out
the infra roster. Hypothetical pinger-style names like
`showcase-aimock-pinger-mock-for-test` still correctly surface as
unwired (existing regression test continues to pass).

Driver wrapper (`drivers/aimock-wiring.ts`) delegates entirely to the
probe and needed no change.

Red-green: two new tests assert bare names (`harness`, `shell`,
`dashboard`, `docs`, `dojo`, `pocketbase`, `webhooks`, `aimock`,
`ms-agent-harness-dotnet`) are excluded; failed pre-fix, pass
post-fix. Full file: 27/27 probe + 16/16 driver tests pass.
2026-05-30 20:41:14 -07:00
Jordan Ritter dd1495257a fix(showcase): add missing shadcn UI primitives for CST D6 page-mirror
The CST D6 page-mirror imported LGP demo pages but not the shared UI primitives they depend on, causing prod build failures (`Module not found: '@/components/ui/button'`, etc.). Copy LGP's `src/components/ui/` (24 primitives) and `src/lib/utils.ts` verbatim into CST so the mirrored pages resolve. Deps were already present in CST's package.json; regen lockfile after npm install.
2026-05-30 20:26:08 -07:00
Jordan Ritter bd4424bdf6 Merge branch 'blitz/d6-parity/maf-dotnet' into blitz/d6-parity/integration 2026-05-30 20:17:57 -07:00
Jordan Ritter c968445f36 Merge branch 'blitz/d6-parity/maf-python' into blitz/d6-parity/integration 2026-05-30 20:17:57 -07:00
Jordan Ritter 7fc395c3e4 Merge branch 'blitz/d6-parity/pyd-pages' into blitz/d6-parity/integration 2026-05-30 20:17:56 -07:00
Jordan Ritter 811afa9025 fix(showcase): ms-agent-dotnet D6 residuals (toolCallId strip, fixtures, hitl pages)
Address the four residual D6 failures on the ms-agent-dotnet integration
(baseline was 177/5).

route.ts toolCallId strip completeness
- Extend stripReplaySafeToolCallIdsFromMessage to also clean the
  snake_case `tool_calls[].id` array and any nested OpenAI-style
  `function.tool_call_id`. AG-UI canonical uses `toolCalls`, but some
  runtime / message-converter paths emit the OpenAI shape and the
  replay-safe `__ck_run_<uuid>` suffix was leaking through to aimock on
  those paths. Apply the same coverage in applyToolResultDecisionSuffix
  so decision-suffix routing (`__approved` / `__rejected` /
  `__cancelled`) lands on every tool-call shape.
- Add a universal strip middleware in createAgent itself so EVERY
  registered agent (not only the replay-safe ones) clears the suffix
  before the request reaches the backend / aimock. Decision suffixing
  remains scoped to createReplaySafeAgent because the suffix is
  non-idempotent and the inner middleware re-runs the same logic.
- Drop the now-redundant strip calls inside createGenUiAgent,
  createReadonlyContextAgent, createSharedStateReadWriteAgent, and
  createReasoningAgent — the outer createAgent middleware already
  canonicalised inbound messages by the time these run.

chat-slots fixture
- Mirror LGP's turnIndex:1 'Give me a fun fact' entry so the
  chat-slots e2e's second-turn assistant slot has a deterministic
  reply (the bare 'Give me a fun fact' fixture in headless-simple.json
  is turnIndex:0-gated and was never matching chat-slots' turn 1).

HITL reject + interrupt-headless cancel branches
- Add a reject-branch fixture in render-a2ui.json keyed on
  toolCallId `call_d5_generate_steps_001__rejected` so the hitl.spec
  reject-flow's 'will not execute the Mars trip plan' assertion lands
  the right narration. Keep the legacy hasToolResult:true fixture as a
  fallback for paths that don't apply a decision suffix. Add an
  `__approved` variant for symmetry.
- Add cancel-branch fixtures in interrupt-headless.json for both the
  sales-intro and 1:1-with-alice pills, keyed on `__cancelled`
  toolCallIds, returning the Denied/not-booked narration the
  interrupt-headless cancel spec expects.

hitl-in-app + hitl-in-chat demo pages already mirror LGP (only an extra
README.md per directory in this integration), so no page changes were
needed.
2026-05-30 20:17:08 -07:00
Jordan Ritter 2ab5d80334 fix(showcase/harness): emit d6:<slug> aggregate row so dashboard D6 column populates
The showcase dashboard reads integration-scoped aggregate rows keyed
`d6:<slug>` (see shell-dashboard/src/lib/live-status.ts and
shell-dashboard/src/components/depth-utils.ts) to populate the D6
column. The CLI driver path (cli/targets.ts) invokes the e2e-full
driver with `key: d6:<slug>` so its primary return matches that
shape — but the cron probe driver runs with `key:
d6-all-pills-e2e:<name>`, so its primary return never lands under
`d6:<slug>`. The driver only emits per-feature side rows keyed
`d6:<slug>/<featureType>`, never the integration-scoped aggregate.
Result: in staging/prod the D6 column is permanently blank even
though the docstring and probe YAML promise the aggregate row.

Fix: add an `emitAggregate` helper next to the existing `sideEmit`
that writes a `d6:<slug>` row carrying the same
`E2eFullAggregateSignal` shape as the primary return, and call it
at every primary-return point in `d6-all-pills.ts`:

  - post-loop aggregate (placed AFTER the feature loop so a
    per-feature timeout cannot skip it; emits red when any feature
    failed, so the dashboard shows red rather than blank)
  - no-features-declared early return
  - deploy-churn grace-window skip
  - launcher-error early return
  - all-runnable-filtered-out early returns (both the missing-script
    red and the all-filtered green branches)

`emitAggregate` is best-effort — writer errors are logged but never
propagate to the caller, matching `sideEmit` semantics.

Tests: two new regression tests in d6-all-pills.test.ts under
`aggregate d6:<slug> side row (dashboard read contract)` exercise
the cron-shape key (`d6-all-pills-e2e:<slug>`) and assert that the
emitted `d6:<slug>` side row carries the right state (green when
all features pass; red when any fails) and aggregate signal.

Red-green: confirmed both tests fail before the fix and pass after.
Full harness suite (1648 tests / 99 files) and typecheck both clean.
2026-05-30 20:15:40 -07:00
Jordan Ritter 4edcd2f129 fix(showcase): ms-agent-python D6 residuals (toolCallId strip, fixtures, hitl pages, multimodal)
Port the replay-safe toolCallId stripping middleware from the ms-agent-dotnet
sibling route.ts so HITL/interrupt demos match toolCallId-keyed aimock
fixtures across the 2nd-turn request. The strip walks every inbound message
(role=tool, role=assistant.toolCalls[].id, toolCallId, tool_call_id) before
the AG-UI HttpAgent forwards them onto the FastAPI backend, and the outbound
event stream rewrites toolCallId on TOOL_CALL_* events to embed the
deterministic per-run suffix so the next turn's fixture matcher still
keys on the original (suffix-stripped) id.

Wraps the human_in_the_loop, interrupt-adapted, hitl-in-app, and
hitl-in-chat agents with the new replay-safe middleware. Adds gen-ui-agent
set_steps state-snapshot synthesis, readonly-state-agent-context system
message injection, and shared-state-read-write preference-as-system
injection — all ported verbatim from the dotnet sibling so the per-agent
shaping behavior is identical across the MAF runtimes.

chat-slots.json: add the turnIndex:1 "Give me a fun fact" fixture mirrored
from langgraph-python/chat-slots.json so the chat-slots.spec.ts second-
turn assertion ("second assistant turn is also wrapped in the custom
slot") gets a deterministic reply instead of falling through to headless-
simple's fun-fact fixture or the live proxy.

hitl-in-app and hitl-in-chat demo pages were already mirrored from LGP
(only a minor consumerAgentId addition on the in-app suggestions module).
No frontend page changes needed.

The B2 header-forwarding conveyance in src/agents/_header_forwarding.py
is untouched and verified intact (httpx hook + Starlette HTTP middleware
plus ContextVar bridge).

Multimodal diagnosis: the 5-test multimodal.spec.ts timeout is NOT a
routing/wiring gap. The agent_server.py mounts /multimodal FIRST (before
the catch-all "/"), the dedicated Next.js route /api/copilotkit-multimodal
registers the HttpAgent under "multimodal-demo", and the page wires
runtimeUrl + agent correctly. The multimodal fixture's userMessage
"describe the sample image" is the same stale phrasing LGP uses (which
passes at 185/0/2 — the test asserts a /image/i regex on the assistant
transcript, so fallthrough to the proxy still satisfies it). The
remaining suspects are (a) agent_framework_ag_ui's AG-UI -> AF adapter
mishandling inbound `binary` content parts, (b) the dual chat_client
init pattern (multimodal_chat_client built after the global httpx hook
is installed — the hook is idempotent so this should be safe), or
(c) >30s real-OpenAI vision latency under D6 record-replay. Capture
agent_server stderr during a single multimodal run to confirm.
2026-05-30 20:14:36 -07:00
Jordan Ritter 3c8bf11160 feat(showcase): complete CST D6 page-mirror (LGP demos + manifest homepage)
Mirror LGP's demo pages, components, hooks, lib and the manifest-driven
homepage into the CST integration so canonical D6 specs find the
elements they assert on. Prior partial mirror copied specs+fixtures
but left CST's stub pages intact, producing ~106 fails where elements
never render.

Backend swap is limited to runtimeUrl/agent adaptation for the demos
where CST has dedicated routes that LGP does not:
  - headless-complete: LGP /api/copilotkit-mcp-apps -> CST /api/copilotkit-headless-complete
  - reasoning-default, reasoning-custom, tool-rendering-reasoning-chain:
      LGP /api/copilotkit -> CST /api/copilotkit-reasoning
  - shared-state-read-write: LGP /api/copilotkit -> CST /api/copilotkit-shared-state-read-write
  - subagents: LGP /api/copilotkit -> CST /api/copilotkit-subagents

agent_server.ts and the api/* route files are otherwise untouched.

Aligns @copilotkit/* runtime to 1.59.2 (LGP parity) and adds the LGP
companion deps the mirrored pages import (@radix-ui/*, cmdk,
embla-carousel-react, lucide-react, react-markdown, remark-gfm,
tailwind-merge, class-variance-authority, clsx, radix-ui). Lockfile
regenerated with --legacy-peer-deps (cmdk@0.2.1 peers react@^18 but
LGP's lock resolves it the same way).

The beautiful-chat page still references /api/copilotkit-beautiful-chat
which CST does not host; that route exists only in LGP. The page tree
renders but the chat itself will 404 on POST. Adding the dedicated
route is out of scope for this slot (no API-route edits).

--no-verify: pre-commit oxlint binary not installed in this worktree
(missing dev tool, not a code defect).
2026-05-30 20:14:07 -07:00
Jordan Ritter c560df9e68 feat(showcase): complete pydantic-ai D6 page-mirror + fix frontend-tools slug
Mirror manifest-driven homepage from langgraph-python (replacing 59-line stub
with the tag-grouped, features-ordered layout) and add the missing
threadid-frontend-tool-roundtrip demo page. Register the new agent slug in
the runtime route so the demo can talk to the PydanticAI backend via the
default sales agent mount.

Fix the pre-existing frontend-tools slug mismatch: the page declared
agent="frontend-tools" (matching what src/app/api/copilotkit/route.ts
registers) but CopilotSidebar received agentId="frontend_tools" (underscore,
LGP convention). Align agentId to the hyphenated form pydantic actually
registers.

Pin @copilotkit/* deps to 1.59.0 (matching LGP's installed versions), add
the previously-missing @copilotkit/shared, and bump @ag-ui/{client,core}
to ^0.0.52 to satisfy the new @copilotkit/shared peer range. Regenerate
package-lock.json. Python backend (agent.py / agent_server.py / src/agents/)
is preserved untouched.
2026-05-30 20:12:54 -07:00
Jordan Ritter c918051b08 fix(showcase): route browser-pool capacity loss to error/warn + guard reinit
Widen PoolLogger to optional warn/error and route capacity-loss events
(recycle-relaunch-failed, recovery-failed, recycle-failed, new reinit-empty)
to error and per-attempt failures (relaunch-failed, reinit-failed,
close-failed) to warn so Sentry sees real outages instead of buried info.
Add a reiniting concurrent-entry guard so two acquires on an empty pool
launch at most poolSize browsers.
2026-05-30 18:35:08 -07:00
Jordan Ritter 5650004637 fix(showcase): compute CellModel.isRegression from achieved vs ceiling
isRegression is now ceilingDepth > 0 && achievedDepth < ceilingDepth instead
of a hardcoded false. The Coverage regressions filter reads the single source
on the model rather than inlining the same expression.
2026-05-30 18:35:08 -07:00
Jordan Ritter 34ae5667ce fix(showcase): make resolveD5 strict on missing mapped sub-rows
A multi-key D5 family with a missing mapped sub-row is now unverified
(status null), not credited green from the present rows. A present red
sub-row still yields red. Mirrors isD5Green's every(...) so both consumers
agree.
2026-05-30 18:35:08 -07:00
Jordan Ritter 268d12a390 fix(showcase): gate green chip on contiguous D5 and stale-gate D1/D2/D4
A green D6 no longer paints over a red/no-data D5: green requires an
intact verification ladder. Stale-green D1/D2 (45m) and D4 (1h) rows are
downgraded to amber via per-driver windows so frozen drivers can't credit
the depth ladder.
2026-05-30 18:35:08 -07:00
Jordan Ritter 7e6b3b98a9 fix(showcase): update validate-pins baseline hash for copilotkit 0.1.93 bump
FAIL count held at 106; only the rendered text of one FAIL line changed when
langgraph-python bumped copilotkit 0.1.92->0.1.93. Routine hash rotation, no
pin changes.
2026-05-30 18:28:11 -07:00
Jordan Ritter 619be316c4 fix(showcase): stub agents._header_forwarding in crewai-crews python test
The D6 conveyance commit added a top-level _header_forwarding import in
agent_server.py; the test's _stub_heavy_modules fixture omitted it, causing
ModuleNotFoundError at import time. Register the stub submodule with a no-op
hook and a pass-through BaseHTTPMiddleware subclass.
2026-05-30 18:27:56 -07:00
Jordan Ritter 0143b02ae3 WIP: D6 rollout — foundation + per-integration fixtures + conveyance fixes (#5109)
## Status

WIP / not ready to merge. Preserves in-flight D6 work so it isn't lost
mid-rollout. LGP is fixture-complete; other integrations are
mid-rollout.

### Latest banked work

- **langgraph-python — 185 / 0 / 2** (green). Achieved by narrowing a d4
chat matcher that was shadowing the d6 beautiful-chat search_flights
fixture (load order is shared -> d4 -> d6, first-match-wins) plus
refreshing the d6 tool-rendering and tool-rendering-custom-catchall AAPL
fixtures (turnIndex:0 -> hasToolResult:false so the first leg fires in
multi-pill threads). 2 skips are the by-design mcp-apps iframe gap.
- **langgraph-typescript — 185 / 0 / 2** (green). Mirrored the LGP d4
narrowing on the LGT side (3 matchers) and across the
d6/langgraph-typescript suite: replaced fragile turnIndex:0 gates with
hasToolResult:false, fixed em-dash escaping that broke literal matches
in multi-pill threads, and added jsFunctions payloads to the three
sandboxed-ui fixtures (`_from-feature-parity`, `headless-complete`,
`gen-ui-open-advanced`). The final fix removed the chain-tools
`hasToolResult` match gate (which checked the whole thread and made the
chain pill fall through to the broad weather matcher mid-thread),
mirroring LGP.
- **google-adk: 174/7/2 (was 134/52/5)** — conveyance + test-parity +
fixtures + pill-wiring rebuild; 7 residual (6 default-catchall framework
default-renderer testid version question, 1 beautiful-chat fixture).
- **Pill-parity staged across 13 integrations** (ag2, agno, mastra,
pydantic-ai, claude-sdk-python, claude-sdk-typescript, llamaindex,
langroid, strands, spring-ai, built-in-agent, crewai-crews,
langgraph-fastapi). The canonical LGP suggestion pill set is now
mirrored as `src/app/demos/*/suggestions.ts` files in each integration,
with targeted edits to existing `open-gen-ui-advanced` and
`byoc-hashbrown` files. **These new files are currently UNWIRED** — each
integration's `page.tsx` still defines its pill list inline via
`useConfigureSuggestions`. Banked so the canonical source survives; a
follow-up will rewire `page.tsx` to import from `suggestions.ts` and
drop the inline copies.
- **Fleet test-parity sweep**: 576 e2e specs across 15 integrations
aligned to LGP canonical (SHA-verified); 2 orphan specs removed.
- ms-agent-dotnet 177/5/7, ms-agent-python 174/11/2 (post
fixture-mirror); default-catchall green (page-level renderer, not
react-core-gated).

## Scope

### Conveyance (foundation)

Inbound `x-aimock-context` (and friends) must ride along on outbound LLM
HTTP calls so aimock fixture matching sees the inflight test's context.
Without this the call lands on the default project's aimock and silently
picks the wrong fixture. New per-integration
`_header_forwarding.{py,ts}` shim plus matching `agent_server` / route /
factory wiring covers: ag2, agno, built-in-agent, claude-sdk-python,
claude-sdk-typescript, crewai-crews, google-adk, langgraph-fastapi,
langgraph-python, langgraph-typescript, langroid, llamaindex, mastra,
ms-agent-python, pydantic-ai, strands.

For ADK/Gemini the global httpx hook is installed BEFORE any `agents.*`
import (google-genai constructs its client at module-import time).

### langgraph-python — 185 / 0 / 2

Fixture-complete via the conveyance shim + refreshed d6/langgraph-python
fixtures + copilotkit 0.1.93 bump + the latest d4-matcher-narrowing fix
(see banked work above).

### Per-integration fixtures

Mid-rollout snapshot of d6 fixtures across the cohort plus narrowing of
`aimock/shared/common.json`'s generic 'hello' fixture to 'hello world'
so it no longer shadows D6 pills whose prompts contain 'hello' as a
substring.

### Harness `--isolate` patch

`scripts/cli/_common.sh apply_isolation` now rewrites compose-file
relative paths to absolute (build/context/dockerfile/volumes/env_file),
enforces the docker compose `[a-z0-9_-]` project-name rule, and exports
`SHOWCASE_COMPOSE_FILE` / `SHOWCASE_INFRA_PORT_OFFSET` plus offset host
URLs. The TS harness CLI (`aimock-rebuild` / `config` / `doctor` /
`lifecycle`) honors the new env so concurrent isolated stacks stop
reporting each other's services as healthy.

## Lockfile decision flagged

`showcase/integrations/langgraph-python/pnpm-lock.yaml` was deleted in
this branch. Decision: keep the deletion. Rationale:

- 03bed3b76 (fix(showcase): regenerate 18 lockfiles in isolation; switch
to npm ci) migrated all showcase integrations off pnpm onto npm ci.
- The integration's Dockerfile uses `npm ci --legacy-peer-deps`.
- Every sibling integration committed only `package-lock.json` after
03bed3b76.
- The orphan pnpm-lock.yaml only risks tooling drift.

If anyone wants it restored: `git checkout origin/main --
showcase/integrations/langgraph-python/pnpm-lock.yaml`.

## Commits

- feat(showcase): D6 conveyance — forward x-aimock-context headers to
LLM clients
- feat(showcase/langgraph-python): D6 conveyance shim + copilotkit
0.1.93 bump
- feat(showcase/langgraph-typescript): D6 conveyance — propagate request
headers into ChatOpenAI
- feat(showcase/built-in-agent): D6 conveyance — header-forwarding shim
+ factory wiring
- feat(showcase/harness): support concurrent --isolate runs
- test(showcase): D6 langgraph-python fixtures — drive to 180/5/2
- test(showcase): D6 per-integration aimock fixtures + shared narrowing
- docs(showcase): GOTCHAS entry for D6 conveyance + --isolate notes
- feat(showcase): D6 conveyance — wire header-forwarding shims into
remaining entrypoints
- fix(showcase): unblock LGP D6 beautiful-chat + custom-catchall via d4
matcher narrowing
- fix(showcase): narrow LGT D6 d4 shadows + wire sandboxed-ui
jsFunctions
- chore(showcase): copy LGP canonical suggestion pills into 13
integrations
- fix(showcase): forward x-aimock-context per-request in google-adk
routes
- test(showcase): align google-adk e2e specs to langgraph-python
canonical
- fix(showcase): align google-adk D6 fixtures to LGP contract
- fix(showcase): wire google-adk default-catchall to shared 4-pill
suggestions
2026-05-30 16:32:19 -07:00
Jordan Ritter 66cefcf77c fix(showcase): add missing shadcn primitives + fix chat-slots bundling for pydantic-ai D6 mirror
The D6 parity mirror sweep copied LGP demo pages into pydantic-ai but did
not copy the shadcn UI primitives + lib/utils they import, nor reconcile
the manifest highlight paths against actual mirrored file layouts.

Caused two CI build-check failures on PR #5109:

1. pydantic-ai build-check: Next webpack could not resolve
   '@/components/ui/{button,card,avatar,...}' or '@/lib/utils'.
   Fix: copy LGP shadcn primitives (avatar, badge, button, card, input,
   scroll-area, select, separator, spinner, textarea) + lib/utils.ts;
   add 'radix-ui' (umbrella package the primitives import from) and
   'yaml' (used by demos/layout.tsx) to package.json + lockfile.

2. shell-dojo build-check: bundle-demo-content.ts rejected
   pydantic-ai::chat-slots highlight path
   'src/app/demos/chat-slots/custom-welcome-screen.tsx' (does not exist).
   Fix: align pydantic-ai manifest highlight paths for chat-slots,
   headless-complete, tool-rendering-reasoning-chain, and
   gen-ui-interrupt with LGP-canonical paths that match the mirrored
   file layout (slot-wrappers.tsx, chat/hooks/tools subdirs,
   _components/time-picker-card.tsx; drop nonexistent README.md ref).

Local verification: npm run build in pydantic-ai succeeds;
bundle-demo-content.ts bundles 695 demos including all four previously
broken pydantic-ai entries with no errors.
2026-05-30 16:24:09 -07:00
Jordan Ritter 0c8ae8a4f9 fix(showcase): remove dangling shared-state-write nav cards (CST + pydantic) 2026-05-30 16:10:01 -07:00
Jordan Ritter 430dbecc09 feat(showcase): apply D6 parity sweep to pydantic-ai (mirror LGP demos + specs + fixtures)
pydantic-ai had never received the fleet D6 parity sweep — its e2e specs and demo pages
were a pre-sweep, integration-specific set (only 3/26 suggestion files; missing canonical
demos; non-canonical byoc-*/agentic-chat-reasoning/reasoning-default-render variants).
Sitting at 69/109/2.

This change mirrors langgraph-python's canonical frontend (demos + specs + aimock
fixtures) into pydantic-ai, preserving pydantic-ai's Python backend untouched. The
per-demo agent.py files that pydantic-ai carries inside demo directories are preserved.

Changes:
- tests/e2e/: rsync LGP canonical 37-spec set over pydantic-ai (byte-identical). Removes
  non-canonical byoc-hashbrown.spec.ts, byoc-json-render.spec.ts, shared-state-write.spec.ts.
  Adds canonical declarative-hashbrown.spec.ts, declarative-json-render.spec.ts,
  reasoning-custom.spec.ts, reasoning-default.spec.ts.
- src/app/demos/: rsync LGP demos over pydantic-ai. Removes non-canonical demos
  (byoc-hashbrown, byoc-json-render, agentic-chat-reasoning, reasoning-default-render,
  shared-state-write). Adds canonical demos (declarative-hashbrown, declarative-json-render,
  reasoning-default, reasoning-custom) and the _shared/ helpers + demos/layout.tsx
  pydantic-ai was missing. Restores pydantic-ai-specific agent.py files into the 9 demo
  dirs that survived the mirror.
- src/app/demos/frontend-tools/page.tsx: patched agent slug from "frontend_tools" (LGP)
  to "frontend-tools" (matches pydantic-ai's main route.ts registry).
- src/app/api/copilotkit-byoc-{hashbrown,json-render}/ renamed to copilotkit-declarative-*
  to match the canonical frontend wiring. Internals still use HttpAgent against the
  pydantic backend's /byoc_hashbrown/ + /byoc_json_render/ mounts (Python backend
  untouched per scope). copilotkit-declarative-hashbrown/route.ts updates the registered
  agent slug from "byoc-hashbrown-demo" to "declarative-hashbrown-demo" to match the
  canonical demo. copilotkit-declarative-json-render/route.ts updates only the endpoint
  path string (the agent slug "byoc_json_render" is the canonical LGP convention).
- src/app/api/copilotkit/route.ts: renamed reasoning agent registrations from
  agentic-chat-reasoning + reasoning-default-render to reasoning-custom + reasoning-default
  to match canonical demo slugs. Both still proxy to the same /reasoning/ backend mount.
- manifest.yaml: features[] + demos[] updated to reflect the canonical demo set
  (byoc-* + agentic-chat-reasoning + reasoning-default-render removed; declarative-* +
  reasoning-default + reasoning-custom added).
- aimock/d6/pydantic-ai/: added gen-ui-custom.json (mirrored from LGP with
  context-swap + copiedFrom marker, per established fixture convention). Removed
  orphan gen-ui-open-advanced.json (no LGP counterpart in the canonical set).

The Python backend (agent.py / src/agent_server.py / src/agents/) is unchanged.
Some pydantic-ai backend mounts continue to exist that the mirrored frontend no longer
references (e.g. /reasoning/ remains, the deleted demos' agent slugs are still
registered in route.ts but harmlessly orphaned) — these are intentional carry-overs
to avoid touching Python backend code per scope.
2026-05-30 16:05:40 -07:00
Jordan Ritter d06ef78bed chore(showcase): regen claude-sdk-typescript lockfile for yaml dep 2026-05-30 16:05:40 -07:00
Jordan Ritter 95b438ea64 feat(showcase): apply D6 parity sweep to claude-sdk-typescript (mirror LGP demos + specs + fixtures)
CST never received the fleet D6 parity sweep — its e2e specs and demo
pages were a pre-sweep, integration-specific set. This commit mirrors
the canonical langgraph-python frontend into CST:

Demos added (copied from LGP, runtime URL + agent slugs adapted to CST):
- declarative-hashbrown (replaces byoc-hashbrown — same machinery,
  canonical name + heading)
- declarative-json-render (replaces byoc-json-render — same machinery,
  agent slug renamed declarative_json_render)
- reasoning-default + reasoning-custom (paired demos that share CST's
  dedicated /api/copilotkit-reasoning runtime so Claude extended-
  thinking deltas flow as AG-UI REASONING_MESSAGE_*)
- _shared/ helpers + demos/layout.tsx (title prefix adapted)

Demos removed (non-canonical CST originals):
- byoc-hashbrown, byoc-json-render (replaced by declarative-*)
- agentic-chat-reasoning, reasoning-default-render (replaced by
  reasoning-default + reasoning-custom)
- shared-state-write (not part of canonical LGP set)

Specs: replaced byoc-hashbrown.spec.ts, byoc-json-render.spec.ts,
shared-state-write.spec.ts with the canonical LGP specs for
declarative-hashbrown, declarative-json-render, reasoning-default,
reasoning-custom (byte-identical — assertions click pills + assert
rendered cards).

Backend touches (allowed by the parity-sweep brief to remap copied
frontend slugs onto CST's existing agent topology):
- src/app/api/copilotkit/route.ts: replaced byoc/byoc_json_render/
  agentic-chat-reasoning/reasoning-default-render agent registrations
  with declarative-hashbrown-demo, declarative_json_render,
  reasoning-default, reasoning-custom.
- src/app/api/copilotkit-byoc-{hashbrown,json-render}/ renamed to
  copilotkit-declarative-{hashbrown,json-render}/; endpoint + agent
  names updated to match canonical demo expectations.
- src/app/api/copilotkit-reasoning/route.ts: registered reasoning-
  default + reasoning-custom on the existing extended-thinking
  pass-through, replacing the old agentic-chat-reasoning/
  reasoning-default-render entries.

Agent server (/reasoning endpoint, /byoc-hashbrown, /byoc-json-render)
is untouched — these are still the underlying Claude backends that the
renamed Next.js routes proxy to.

manifest.yaml: features list + per-demo entries refreshed to mirror
the canonical demo IDs and names.

package.json: added 'yaml' dep used by the copied demos/layout.tsx for
manifest-driven page titles.
2026-05-30 16:05:40 -07:00
Jordan Ritter c9892aaf48 fix(showcase): wire ms-agent-python hitl suggestions + verify conveyance + reconcile e2e specs
Mirror the gold-standard langgraph-python (LGP) hitl pill-wiring pattern by
extracting the inline useConfigureSuggestions call from hitl/page.tsx into a
dedicated useHitlSuggestions hook in hitl/suggestions.ts. Pill text and
prompts are byte-identical to LGP's hitl/suggestions.ts so the canonical
hitl spec matches without per-integration assertion drift.

Preserves all MAF-specific backend wiring untouched (inline StepSelector /
StepsFeedback components, useHumanInTheLoop registration, the deliberate
omission of useLangGraphInterrupt — MAF has no interrupt() primitive).

Conveyance check (B2 shim): src/app/api/copilotkit/route.ts is already pure
pass-through (no header reading, no slug synthesis), matching the canonical
Python integration pattern (pydantic-ai, langgraph-fastapi). Backend
src/agents/_header_forwarding.py mirrors the canonical x-* prefix-only
forwarding shim. No change required.

E2E spec set: ms-agent-python/tests/e2e is already byte-identical to LGP's
canonical 37-spec set (comm -3 returns empty). No stray specs to delete.
threadid-frontend-tool-roundtrip is not present in LGP either, so skipped
per orchestrator instructions. gen-ui-interrupt spec retained (known
cross-integration useInterrupt 2nd-interrupt issue, not in scope here).
2026-05-30 16:05:40 -07:00
Jordan Ritter d9c8cf35ab fix(showcase): wire ms-agent-dotnet hitl suggestions + reconcile canonical e2e specs
Extracts the ms-agent-dotnet hitl demo's inline useConfigureSuggestions call
into a dedicated suggestions.ts that mirrors langgraph-python's canonical
hitl/suggestions.ts (identical pill titles and prompts), then wires the new
useHitlSuggestions() hook into hitl/page.tsx in place of the inline block.
This matches the gold-standard wiring shape the canonical D6 hitl assertions
expect.

Spec reconciliation: ms-agent-dotnet/tests/e2e/hitl.spec.ts and
interrupt-headless.spec.ts are not present in langgraph-python's canonical
suite, but both exercise MAF-specific behavior with no LGP equivalent
(plain /demos/hitl reject branch using the Simple plan pill; MAF's
frontend-tool adaptation of /demos/interrupt-headless). They are kept and
expected to not count toward the 185 LGP-parity floor.
2026-05-30 16:05:39 -07:00
Jordan Ritter 3998f7fbdb fix(showcase): self-heal browser pool + flag stale-green e2e rows (#5112)
## Summary

Two confirmed bugs in the showcase harness + operator dashboard that
together produced a false-green D3 on the dashboard while the e2e probe
pipeline was actually dead.

### Bug 1 — Browser-pool death spiral
(`showcase/harness/src/probes/helpers/browser-pool.ts`)

When a chromium process crashed, `recycleSlot` relaunched it; if that
launch threw, the slot was permanently removed via `slots.splice`. A
single transient launch failure (OOM spike, fd exhaustion) thus
monotonically shrank the pool to empty with no self-heal, after which
every e2e probe failed with `launcher-error` / `BrowserPool acquire
timeout`.

Fix:
- The recycle retries the relaunch with bounded backoff (3 attempts,
doubling backoff).
- On exhaustion the slot is **kept** (capacity preserved) and parked as
`relaunchPending`; the next `acquire()` lazily re-attempts the launch.
- `acquire()` re-initializes the pool as a backstop when every slot has
been lost (`slots.length === 0`).
- Existing acquire/release/recycle semantics and logging style
preserved.

### Bug 2 — Stale-green e2e rows read as healthy
(`shell-dashboard/src/lib/cell-model.ts`,
`src/components/depth-utils.ts`)

When the e2e driver stops writing `e2e:<slug>/<feature>` rows, the last
row freezes. A green row then reads as a healthy D3 forever, so the
depth ladder shows a false-green D3 that masks a dead probe pipeline
instead of surfacing it.

Fix:
- `buildCellModel` and `deriveDepth` treat a green e2e row whose
`observed_at` is older than `E2E_STALE_AFTER_MS` (6h, matching the
original stale-window model) as degraded/amber, so it no longer credits
D3.
- Only green is downgraded — a stale red/degraded row already signals a
problem and is left as-is.
- An unparseable timestamp is treated as not-stale, so staleness is
never inferred from bad data.

## Test plan

- [x] New unit test: pool recovers after a transient relaunch failure
(does NOT shrink to 0) and re-inits when emptied — confirmed red→green.
- [x] New unit tests: a stale green `e2e:` row downgrades to amber (not
green) and does not credit D3; a fresh green row stays green; a stale
red row stays red — confirmed red→green.
- [x] Existing fixtures with hardcoded old `observed_at` updated to
default to a recent timestamp so legitimate green rows aren't treated as
stale.
- [x] `showcase/harness` full suite: 1646 tests pass.
- [x] `shell-dashboard` full suite: 630 pass (1 env-gated build-spike
skipped).
- [x] `packages/**` test graph: 17 projects pass.
- [x] typecheck clean (both packages); oxlint 0 errors; oxfmt clean.

Note: there is a separate operational lever (reduce `BROWSER_POOL_SIZE`
/ e2e concurrency, raise harness memory) handled outside this PR; this
is the code fix only.
2026-05-30 13:07:09 -07:00
Jordan Ritter db663fe2b9 fix(showcase): guard concurrent browser-pool slot recovery
Re-validate each pending slot inside relaunchPendingSlots before claiming it so
two concurrent invocations can't double-launch a slot (leak + double-publish).
Contain recycle throws with a logged catch and gate recycleSlot entry on shutdown.
2026-05-30 11:12:56 -07:00
Jordan Ritter 4c4e16a630 docs(showcase): correct resolveD5 comment on missing-row divergence from isD5Green
resolveD5 skips missing sub-rows and folds only present rows, so it does not
match isD5Green's every-key-present requirement; clarify staleness IS mirrored.
2026-05-30 11:05:28 -07:00
Jordan Ritter 8e6a0274d2 fix(showcase): remove fragile deferred-relaunch timer from browser-pool
The deferred-relaunch background timer produced a new race in three review
rounds. Remove it entirely and rely on lazy, on-demand recovery: a failed
recycle parks the slot relaunchPending (never evicts), and the next acquire
re-attempts the launch via relaunchPendingSlots, serving any queued waiter
through handOff. The no-eviction + lazy-recovery design already prevents the
original drain-to-0 bug without a timer. Also clamp stats().inUse to >= 0.
2026-05-30 11:05:27 -07:00
Jordan Ritter 5ab0c8e49b fix(showcase): apply D5 staleness per sub-row before the worst-state fold
A stale-green D5 sub-row was masked when a fresh-green sibling won the
all-green tie, since green is the lowest rank and staleness was checked
only on the post-fold winner. Downgrade each green-but-stale sub-row to
degraded before folding so any stale-green forces amber, matching
depth-utils isD5Green per-key semantics regardless of map order.
2026-05-30 10:52:40 -07:00
Jordan Ritter db7aec1a63 fix(showcase): harden browser-pool slot-recovery concurrency
Re-arm the deferred relaunch when a waiter parks after a recycle has
already failed with no waiter queued, so it is not stranded to timeout.
Make scheduleDeferredRelaunch idempotent via deferredRelaunchActive so
the acquire-kick and recycle-kick cannot spawn two timer loops. Guard
recycleSlot entry and the relaunchPending filter with a single
isSlotBusy predicate covering both recyclingSlots and relaunchingSlots,
so a late disconnect for a pending slot's old browser cannot double
launch and leak a process. Strengthen the double-publish test to fail
loud and pin exact-once, and add red-green tests for both fixes.
2026-05-30 10:43:38 -07:00
Jordan Ritter 993289cdfd fix(showcase): dedupe identical green-and-fresh depth helpers
Consolidate isE2eGreenAndFresh into isGreenAndFresh; both had identical
staleness semantics. Single helper now serves D3/D5/D6, removing a drift
hazard. No behavior change.
2026-05-30 10:30:55 -07:00
Jordan Ritter b7b95b4dfd fix(showcase): correct browser-pool recovery docs and contain recovery throws
Reconcile the stale stats()/reinit()/acquire() comments: all-pending recovery
goes through relaunchPendingSlots, not the size-0 reinit backstop (which is the
truly-empty never-launched case). Rename the misleading reinit test to assert
the actual relaunchPendingSlots mechanism. Chain track()'s cleanup so a future
recovery throw cannot float as an unhandled rejection, and log a -1 sentinel
slotIndex for the never-added reinit close. Fix the self-contradicting
drainMicrotasks JSDoc.
2026-05-30 10:30:55 -07:00
github-actions[bot] 38c381c48c style: auto-fix formatting 2026-05-30 17:23:55 +00:00
Jordan Ritter 9871d06671 refactor(showcase): rename getRuntimeConfigEdge to getRuntimeConfigForMiddleware
Clarify the wrapper's role (it forces noStore:false because unstable_noStore is
unavailable in middleware/Edge). Pure rename across shell, shell-docs, and
shell-dashboard: definitions, middleware call sites, and tests. No behavior
change.
2026-05-30 10:22:35 -07:00
Jordan Ritter f2f3f6d803 refactor(showcase): memoize railway token and fix GHCR username fallthrough
Memoize getToken so the Railway config is not re-read and the deprecation
warning is not re-emitted on every GraphQL request; failures are never cached.
Resolve the GHCR username with || so an empty GHCR_USERNAME falls through to
GITHUB_ACTOR, and use a truthy cache-hit guard so an empty token is never
memoized.
2026-05-30 10:22:33 -07:00
Jordan Ritter d6519cbf60 test(showcase): fail loud on malformed or schema-drift aimock fixtures
loadAllFixtures silently skipped files lacking a fixtures array and let
JSON.parse throw without the path; throw a clear error naming the file in both
cases and attach the caught error as cause. Export the helper and add red-green
tests covering both throw branches.
2026-05-30 10:22:32 -07:00
Jordan Ritter f94a0f7843 test(showcase): pin railway promote snapshot-accessor invariants
Add a regression spec that fails if a fleet-scoped check reads the narrowed
single-service snapshot (the historic spurious-WARN/REFUSE bug), an executable
lint banning direct @*_snapshot reads outside the sanctioned accessors, and
harden PopenSpy with UnexpectedPopen + honest exit-code stamping (raise only on
spawn failure, not on a configured non-zero exit).
2026-05-30 10:22:32 -07:00
Jordan Ritter 68b1799930 feat(showcase): harden asHost validator and brand Host with a unique symbol
Reject ASCII control chars, a colon (port suffix), and any char outside the
DNS-label charset; switch the Host brand from a string-literal __brand to a
non-exported unique symbol so a stray `as Host` cast from outside the module
is a type error. asHost stays the sole runtime constructor. Adds positive +
negative test coverage incl. interior-tab rejection.
2026-05-30 10:22:31 -07:00
Jordan Ritter e19159e3ce fix(showcase): downgrade stale-green D5/D6 dashboard rows like D3
Extend the e2e staleness downgrade to resolveD5/resolveD6 in cell-model and to
depth-utils so a frozen-green pipeline no longer credits D5/D6 as healthy. Also
correct misleading comments/test-doc nits flagged in review.
2026-05-30 10:21:33 -07:00
Jordan Ritter 81159ec286 fix(showcase): serve waiters and guard re-entry on browser-pool slot recovery
relaunchPendingSlots only pushed recovered slots to `available` and never resolved
queued waiters, so the deferred relaunch scheduled by a failed recycle (scheduled
precisely because waiters were queued) left the waiter stranded for its full 30s
acquire timeout while a live browser sat idle. It also had no re-entry guard, so an
acquire()-driven call racing the deferred timer could both launch a browser for the
same pending slot — leaking one process and/or publishing the slot to `available`
twice (handing the same browser to two probes).

- Extract a single `handOff` helper (waiter-first, then `available` with an includes
  guard) and use it in reinit, the recycle success path, and relaunchPendingSlots so
  all three share one correct invariant.
- Add a `relaunchingSlots` re-entry guard: a slot is marked before its launch await
  and skipped by concurrent invocations, cleared in finally.
- Replace the one-shot deferred relaunch with a self-rescheduling timer (bounded
  backoff) that retries while pending slots and waiters coexist, so a waiter is no
  longer stranded when a single deferred attempt also fails. Stops on shutdown.
- Track reinit/relaunch launch promises so shutdown() drains them, closing the leak
  where a launch resolved after shutdown awaited the in-flight set.
- Route failure and close logging through the injected logger (with slot index)
  instead of console.error / swallowed catches, so failures reach the harness pipeline.
- stats().size now counts only live (non-pending) capacity, making the empty-pool
  backstop observable (size 0 when every slot is pending).

Tests: fix the vacuous reinit test (failAtCalls now exhausts all retries for every
slot and asserts size 0 before recovery), and add deferred-waiter recovery and
no-double-publish tests. Full harness suite green (1648 tests), typecheck green.
2026-05-30 10:21:32 -07:00
Jordan Ritter 47d3b594ce fix(showcase): align ms-agent-python D6 fixtures to LGP (152/33 → 174/11)
Same pattern as ms-agent-dotnet: full d4 chat.json rewrite + d6 mirrors. Dropped stale
turnIndex gates and broad shadow matchers. Default-catchall now green via page-level
shadcn-catchall-renderer. 11 residual: declarative-gen-ui charts, multimodal conveyance,
tool-rendering, reasoning-chain.
2026-05-30 09:53:35 -07:00
Jordan Ritter afa4cf0cf2 fix(showcase): align ms-agent-dotnet D6 fixtures to LGP (155/27 → 177/5)
Full d4 chat.json rewrite + d6 mirrors. Dropped stale turnIndex gates and broad shadow
matchers that no longer reflect LGP-canonical conveyance. Default-catchall now green via
page-level shadcn-catchall-renderer (not react-core-gated). 5 residual: chat-slots, hitl,
interrupt-headless, readonly-state.
2026-05-30 09:53:25 -07:00
Jordan Ritter dff840b76a fix(showcase): downgrade stale-green e2e rows to degraded in the dashboard
When the e2e driver stops writing e2e:<slug>/<feature> status rows, the last
row freezes. A green row then reads as a healthy D3 forever, so the depth
ladder shows a false-green D3 that masks a dead probe pipeline (a wedged
browser pool, an outage) instead of surfacing it.

buildCellModel (cell-model.ts) and deriveDepth (depth-utils.ts) now treat a
green e2e row whose observed_at is older than E2E_STALE_AFTER_MS (6h, matching
the original stale-window model) as degraded/amber, so it no longer credits
D3. Only green is downgraded — a stale red/degraded row already signals a
problem and is left as-is. An unparseable timestamp is treated as not-stale so
staleness is never inferred from bad data.

Existing test fixtures that hardcoded old observed_at timestamps now default
to a recent value so green rows are not treated as stale; the new staleness
tests pass explicit timestamps.
2026-05-30 09:53:01 -07:00
Jordan Ritter f7e88dd671 fix(showcase): recover browser pool after relaunch failure instead of draining to 0
When a chromium process crashes, recycleSlot relaunches it; if that launch
threw, the slot was permanently removed via slots.splice. A single transient
launch failure (OOM spike, fd exhaustion) thus monotonically shrank the pool
to empty with no self-heal, after which every e2e probe failed with
launcher-error / BrowserPool acquire timeout.

The recycle now retries the relaunch with bounded backoff; on exhaustion it
keeps the slot (capacity preserved) and parks it as relaunchPending for a lazy
retry on the next acquire. acquire() also re-initializes the pool as a backstop
when every slot has been lost (slots.length === 0), so the pool always
self-heals rather than wedging.
2026-05-30 09:52:15 -07:00