## Summary
- Wires `useAgent` to sync `agent.threadId` from
`CopilotChatConfigurationProvider` when the caller marked the threadId
as explicit. AbstractAgent's constructor was auto-minting a UUID, so
`ProxiedCopilotRuntimeAgent` was shipping that random UUID in
`/agent/run`, `/agent/connect`, `/agent/stop` instead of the value the
caller passed via `<CopilotKit threadId={x}>` — breaking thread
persistence and causing 404s on lookup.
- Fixes#5041 (V1 `<CopilotChat>` path) and #4739 (headless `useAgent`
path), which share the same root cause.
## Why this regressed
PR #3525 originally solved this with per-thread agent cloning. That
cloning was reverted in commit `762370a4e5` (May 2026) because it wiped
state on tool calls. The revert restored the explicit `agent.threadId =
resolvedThreadId` assignment only in the V2 `CopilotChat` component —
the V1 chat hook (`use-copilot-chat_internal.ts`) and headless
`useAgent` paths were left without an equivalent sync.
`use-agent-thread-isolation.test.tsx` (333 lines) was deleted in the
revert and never replaced for those surfaces, which is why this
regressed silently.
The fix lives in `useAgent` rather than the V1 chat hook because V1 chat
already routes through `useAgent` for agent acquisition, and a headless
`useAgent` user (issue #4739) gets the sync for free. The gate on
`hasExplicitThreadId` matches V2 `CopilotChat`'s gating so a
`ThreadsProvider`-minted placeholder doesn't overwrite the agent's
auto-minted UUID before a real thread exists.
## Test plan
- [x] 4 new unit tests in `use-agent-threadid-sync.test.tsx` covering:
explicit sync, the non-explicit no-op path, threadId change re-sync, and
the no-provider headless case.
- [x] 2 new integration tests in `v1-explicit-threadid-bridge.test.tsx`
covering the customer's exact scenario: `<CopilotKit threadId={x}>` →
`useAgent` → `agent.threadId === x`.
- [x] All 1177 existing `@copilotkit/react-core` tests still pass.
- [x] `@copilotkit/react-core:build` succeeds.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The previous regression (#5041, shared root cause with #4739) slipped through
because the original coverage (use-agent-thread-isolation.test.tsx) lived
next to the per-thread-cloning feature and was deleted alongside it when
cloning was reverted. The invariant outlived the feature but the tests didn't.
Relocate to packages/react-core/src/__tests__/ and rename as a contract test
so future implementation swaps (cloning, effect, prop drilling, context) keep
it in scope. Tightened the header docstring to spell out the invariant and the
reason for the placement.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useAgent now syncs agent.threadId from CopilotChatConfigurationProvider when
the caller marked the threadId as explicit. Without this, AbstractAgent's
constructor mints a random UUID and ProxiedCopilotRuntimeAgent ships it in
/agent/run, /agent/connect, /agent/stop — diverging from the threadId app code
reads via useThreads, breaking thread persistence and causing 404s on lookup.
This was originally fixed by per-thread agent cloning in #3525. That cloning
was reverted in May 2026 because it wiped state on tool calls, and the revert
only restored the explicit assignment in V2 CopilotChat — leaving headless
useAgent (issue #4739) and the V1 chat hook path unfixed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Adds the Skills installation section to the shell-docs
`/build-with-agents` page
## Changes
**`showcase/shell-docs/src/content/snippets/shared/guides/build-with-agents.mdx`**
(new)
- Single source of truth for the page content
- Intro paragraph, `## CopilotKit Skills` section (Callout + Steps:
install + starter prompt), `<MCPSetup />` as second section
**`showcase/shell-docs/src/content/docs/build-with-agents.mdx`**
- Replaced inline content with `<BuildWithAgents />` — delegates to the
shared snippet
- Icon updated to `BrainCircuit`, description updated to keyword-rich
copy, `hideTOC: true` removed
**`showcase/shell-docs/src/content/docs/integrations/built-in-agent/build-with-agents.mdx`**
- Replaced `<SharedContent />` with `<BuildWithAgents />` so the skills
section appears here too (built-in-agent uses `docs_mode: authored` and
never falls through to the root page)
**`showcase/shell-docs/src/content/snippets/shared/guides/mcp-server-setup.mdx`**
- `## Overview` → `## MCP Docs Server`
**`showcase/shell-docs/src/lib/docs-render.tsx`** +
**`mdx-registry.tsx`**
- Register `BuildWithAgents` in `SNIPPET_MAP` and component registry
## Acceptance criteria status
- [x] `/coding-agents` redirects to `/build-with-agents` (PR #5023)
- [x] React Native moved to Platforms section (PR #4927 / #5023)
- [x] `npx skills add` flow documented with skills link and starter
prompt
- [x] MCP server install instructions present (existing content, now
labeled correctly)
- [x] Page structure follows Mastra "Build with AI" reference
## Summary
Adds `metadata.internal: true` to
`.claude/skills/showcase-demo-debugging/SKILL.md` so it is hidden from
`npx skills add CopilotKit/CopilotKit` public installs.
## Why
`showcase-demo-debugging` is a contributor-only skill (for working on
showcase demos, fixtures, and CI regressions). It has no value to a user
building a CopilotKit app, but was showing up in the install summary.
This brings it in line with `git-hooks` and `copilotkit-demo-parity`
which were fixed the same way in PR #4937.
## Change
```yaml
metadata:
internal: true
```
One field, same pattern as #4937.
Root page was duplicating the snippet content inline. Switch it to
<BuildWithAgents /> so snippets/shared/guides/build-with-agents.mdx
is the one source — root page and built-in-agent both render from it.
built-in-agent uses docs_mode: 'authored' so it loads the per-framework
integrations/built-in-agent/build-with-agents.mdx directly, bypassing
the root page entirely. Other frameworks (generated mode) fall through
to the root page and already show the skills section.
Fix: extract the full page body into a shared snippet
snippets/shared/guides/build-with-agents.mdx
register it as <BuildWithAgents /> in SNIPPET_MAP + mdx-registry, and
switch built-in-agent's page to <BuildWithAgents />. Root page keeps
its inline content (for crawlability). The snippet is the source used
by authored-mode frameworks; root is the source for generated-mode and
crawlers — same pattern as MCPSetup / coding-agents.
Add `metadata.internal: true` so the skill is hidden from
`npx skills add CopilotKit/CopilotKit` public installs.
Same pattern as PR #4937 for git-hooks and copilotkit-demo-parity.
The table was incomplete (6 skills listed, 12 actually install) and has
no CI check to keep it in sync. Replace with a one-line prose summary
and a link to the canonical skills/ directory on GitHub, which is always
accurate.
- Rewrites the root build-with-agents page to document Skills + MCP Docs Server
- Skills section: intro, npx skills add command, skills table, starter prompt
- Removes hideTOC so the new TOC (Skills / MCP Docs Server) renders
- Updates icon to BrainCircuit and description to keyword-rich copy
- Renames mcp-server-setup snippet heading: ## Overview → ## MCP Docs Server
so both sections compose cleanly on the root page without a double Overview
Closes out the last AC on OSS-133: npx skills add flow documented with
table of skills, starter prompt, and page structure matching Mastra reference.
## Summary
- Adds an optional `tool_call_id` / `toolCallId` parameter to
`copilotkit_emit_tool_call` across all three SDK variants (Python
LangGraph, Python CrewAI, JS/TS)
- When provided, the custom ID is used as the `toolCallId` in AG-UI
protocol events; when omitted, a random UUID is generated (backwards
compatible)
- All variants now return the tool call ID (`str` / `Promise<string>`)
instead of `True` / `void`, aligning the LangGraph Python and JS
variants with CrewAI which already returned it
- Introduces `CopilotKitError` base exception class and
`CopilotKitMisuseError` for invalid API usage in the Python SDK
- Adds compensating `TOOL_CALL_END` / `action_execution_end` dispatch
when mid-stream failures occur, preventing stuck client UIs
- Exports all exception types from the `copilotkit` package root
## Motivation
Customer request — enables use cases including:
- **Correlation across agent steps** — emit a tool call with a known ID
and reference it later
- **Idempotency / replay safety** — deterministic IDs prevent duplicate
tool calls on LangGraph checkpoint replays
- **External system linking** — use a job ID, trace span ID, or request
correlation ID as the tool call ID
- **Custom HITL flows** — programmatically approve/reject a specific
tool call by predictable ID
- **Testing** — deterministic IDs make snapshot/integration tests stable
## Behavioral changes
- **JS dispatch errors are no longer wrapped in
`CopilotKitMisuseError`**: Previously, `copilotkitEmitToolCall` wrapped
any `dispatchCustomEvent` failure in a `CopilotKitMisuseError`.
Transport/dispatch errors now propagate as their native error types.
This is more correct — a dispatch failure is not a misuse error — but
callers that specifically caught `CopilotKitMisuseError` for transport
errors will need to update their catch clauses.
- **Python return type changed**: `copilotkit_emit_tool_call` previously
returned `True` (documented as `Awaitable[bool]`). It now returns `str`
(the tool call ID). Code using truthiness checks (`if result:`) is
unaffected; strict equality (`== True`) will break.
## Test plan
- [x] 4 unit tests for LangGraph Python variant (default UUID, custom
ID, return value, explicit None)
- [x] 3 unit tests for CrewAI Python variant (skipped when crewai not
installed)
- [x] 3 integration tests for AG-UI dispatch (custom ID propagates to
all TOOL_CALL events)
- [x] All 26 existing `test_agui_agent.py` tests pass (no regressions)
- [x] JS SDK builds cleanly (`nx run @copilotkit/sdk-js:build`)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Zizmor's ref-version-mismatch audit flags the existing `# v1` comment
because the hash 5f3b3c2e5a00f0093de47f657aeaefcedff27d18 is the
v1.17.0 tag, not the v1 head. Update the trailing comment to match.
No behavior change — the SHA pin is what governs which commit Actions
fetches. This just keeps zizmor green so unrelated showcase-wiring PRs
don't trip on a pre-existing pin annotation.
Three companion workflows duplicate the showcase_build.yml service registry
and were missed in the initial wiring commit. Bring them in sync:
- .github/workflows/showcase_build_check.yml: add ms_agent_harness_dotnet
to the paths-filter and the ALL_SERVICES matrix (mirror of the
production build matrix, used for pre-merge Docker build verification).
- .github/workflows/showcase_deploy.yml: add ms-agent-harness-dotnet to
the workflow_dispatch options and the verification ALL_SERVICES with
railway_id 6343d7f9-6c3f-4c8d-9a6e-79f03d2f1e37 and /api/health.
- .github/workflows/showcase_keep-alive.yml: add ms-agent-harness-dotnet
to the keep-alive ping matrix.
Brings the Microsoft Agent Harness (.NET) integration live on the
showcase Railway project. Integration code itself landed in PR #4982.
Changes:
- Railway service `showcase-ms-agent-harness-dotnet` created
(id 6343d7f9-6c3f-4c8d-9a6e-79f03d2f1e37) with the public domain
showcase-ms-agent-harness-dotnet-production.up.railway.app, image
source ghcr.io/copilotkit/showcase-ms-agent-harness-dotnet:latest,
healthcheck /api/health, and env vars cloned from the sibling
showcase-ms-agent-dotnet service.
- .github/workflows/showcase_build.yml: add ms-agent-harness-dotnet to
workflow_dispatch options, paths-filter, and the ALL_SERVICES matrix
(mirroring the ms-agent-dotnet sibling entry).
- showcase/integrations/ms-agent-harness-dotnet/manifest.yaml: flip
deployed: false -> true so the dashboard surfaces the integration
once the image is live.
Skips test-and-check-packages pre-commit hook locally because
@copilotkit/web-inspector:test has a pre-existing failure on main
(window.localStorage.clear telemetry test setup) unrelated to these
YAML-only changes.
gen-ui-tool-based was in the global FEATURED_DEMO_IDS, so it was promoted
into Featured for all 18 integrations. The Controlled Generative UI product
treatment (reliable sample-data rendering + the "Controlled Generative UI"
pill) is only polished for LangGraph Python and Google ADK so far, so move it
out of the global list and into EXTRA_FEATURED_BY_INTEGRATION for those two,
mirroring the per-integration scoping pattern from #4980 (HITL demos).
The other 16 integrations still expose the demo in its category section; it
just isn't promoted into Featured until the treatment scales to them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "Traffic pie chart" suggestion ("Show me a pie chart of website traffic
by source.") names a subject but supplies no numbers, so the agent asked the
user for data instead of rendering. Add a system-prompt directive (LGP + ADK)
telling the agent to invent illustrative sample values and render on the first
turn, never asking for data. The suggestion copy stays clean — the behavior is
carried by the system prompt, not parenthetical UI hints.
Also retag the gen-ui-tool-based demo (LGP + ADK) from `generative-ui` to
`controlled-generative-ui` so the dojo sidebar pill reads "Controlled
Generative UI" — the established product taxonomy (already a category in
shared/feature-registry.json and the dashboard catalog).
Scoped to LGP and ADK per the ticket; the other 16 integrations keep the old
tag until the taxonomy rolls out wider.
Tests: add D5 aimock fixture entries mirroring all three suggestion chips
(bar/traffic-pie/market-share) so the suggestion-click path has deterministic
coverage. The existing "revenue by category" probe message is preserved, so
the dashboard D5 row stays green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
`MCPAppsActivityContentSchema` (and a couple of sibling schemas) used
the single-argument `z.record(valueType)` form for `toolInput`. **Zod 4
made the key schema mandatory**, so the single-arg form is a
**compile-time error (`TS2554: Expected 2-3 arguments, but got 1`)**
when built against Zod 4. Since `@copilotkit/react-core` declares `zod:
">=3.0.0"`, a downstream consumer that resolves Zod 4 is affected. Fixes
#4295.
This PR switches to the two-argument `z.record(z.string(), z.unknown())`
form (valid and identical under both Zod 3 and Zod 4) and adds a lint
guard so it can't regress.
### Note on the issue's framing
The original issue (and the Linear ticket) described this as breaking
*runtime validation for non-empty records*. I verified against real Zod
4.4.3 in isolation: **runtime `safeParse` is unaffected** under both Zod
majors — the incompatibility is purely **type-level** (`tsc`). That
distinction drove the test/lint decisions below.
## Changes
- **Schemas** — `toolInput` now uses `z.record(z.string(), z.unknown())`
in:
- `packages/react-core/.../MCPAppsActivityRenderer.tsx`
- `packages/vue/.../MCPAppsActivityRenderer.ts`
- **Sibling fix** — same single-arg form corrected in
`packages/react-core/.../defineToolCallRenderer.test.tsx` (a `metadata`
schema).
- **Field-contract test** — added to
`MCPAppsActivityRenderer.e2e.test.tsx`, asserting `toolInput`
round-trips mixed value types. (Honest scope: a runtime test cannot
reproduce a type-level break under either Zod major — see below.)
- **Lint guard** — new `copilotkit/no-single-arg-zod-record` oxlint rule
(with autofix) in the existing `packages/react-ui/oxlint-rules/` plugin,
enabled as `error` for `packages/**` in `.oxlintrc.json`. Because the
break is type-level and the workspace lockfile pins Zod 3, this static
rule is the only thing that actually prevents regressions here.
### Lint rule scope
Scoped to `packages/**` (published surface that declares Zod
4-compatible peers). `examples/` and `showcase/` still contain the
single-arg form in ~80 spots, but those are unpublished apps with pinned
Zod; migrating them is out of scope for this fix and would be a large
mechanical change. `packages/**` is clean after this PR (0 lint errors).
## Test plan
- [x] `nx run @copilotkit/react-core:test` — affected files pass (28/28
incl. new test)
- [x] Full `packages/**` suite + `publint`/`attw` green (pre-commit
hook)
- [x] `oxlint packages/` — 0 errors; new rule fires + autofixes on a
probe, clean on the fixed code
- [x] Isolated Zod 4.4.3 repro confirms: single-arg → `TS2554`; two-arg
→ compiles; runtime parsing unaffected for both
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Post-Slice 3 cleanup. Restores qa + pin-drift + aimock-wiring probes
that broke from path/wiring changes.
- **qa probe**: `manifest.yaml` lookup corrected from
`showcase/packages/` to `showcase/integrations/` (Slice 3 moved files;
probe code and test fixture still referenced the old path)
- **pin-drift probe**: `fail-baseline.json` ENOENT in container — the
5-level `import.meta.url` walk-up from `dist/probes/drivers/` overshoots
`/app` and lands at `/`. Added `PIN_DRIFT_REPO_ROOT=/app` env var and
COPY of the baseline file into the runtime stage
- **aimock-wiring probe**: `showcase-ms-agent-harness-dotnet`
(`deployed: false`) was not in the `EXCLUDE_SERVICES` set, causing it to
be flagged as unwired. Added to the exclude list in both
`aimock-wiring.ts` and `smoke.yml`
## Files touched
- `showcase/harness/src/probes/drivers/qa.ts` — path fix
- `showcase/harness/src/probes/drivers/qa.test.ts` — matching test
fixture fix
- `showcase/harness/Dockerfile` — `PIN_DRIFT_REPO_ROOT` env +
`fail-baseline.json` COPY
- `showcase/harness/src/probes/aimock-wiring.ts` — exclude list addition
- `showcase/harness/config/probes/smoke.yml` — matching exclude list
addition
## Test plan
- [x] `qa.test.ts` — 13/13 pass
- [x] `pin-drift.test.ts` — 26/26 pass
- [x] `aimock-wiring.test.ts` (driver) — 16/16 pass
- [x] `aimock-wiring.test.ts` (probe) — 25/25 pass
- [ ] CI green
No fixture changes. No behaviour changes to probe logic.
- qa probe: manifest.yaml path corrected from showcase/packages/ to
showcase/integrations/ (Slice 3 moved files; probe code and test
fixture still referenced the old path).
- pin-drift probe: fail-baseline.json ENOENT in container — the 5-level
import.meta.url walk-up from dist/probes/drivers/ overshoots /app and
lands at /. Added PIN_DRIFT_REPO_ROOT=/app env var and COPY of the
baseline file into the runtime stage.
- aimock-wiring probe: showcase-ms-agent-harness-dotnet (deployed: false)
was not in the EXCLUDE_SERVICES set, causing it to be flagged as
unwired. Added to the exclude list in both aimock-wiring.ts and
smoke.yml.
agno uses useFrontendTool Strategy B (not LangGraph interrupt()).
Matches google-adk precedent. Lets the D6 probe skip these features
cleanly. Removes the corresponding agno D6 fixtures.
agno uses useFrontendTool (Strategy B) — async Promise handler in the
frontend — rather than LangGraph's native interrupt() primitive. The D5
probe asserts via useInterrupt hook which routes through the LangGraph
interrupt event path. agno doesn't emit those events; the D5 probe
fundamentally cannot pass against agno's HITL architecture without
per-integration probe-code divergence (which would violate the D6
apples-to-apples invariant).
Excluding these two features at the manifest level (matching google-adk
precedent) lets the D6 probe skip them cleanly. Removes the
corresponding D6 aimock fixtures since they're no longer reachable.
If agno gains LangGraph-style interrupt() support, or if aimock gains
AG-UI-event fixture authoring, this exclusion can be reverted.
Slice 3 (#5022) renamed the e2e-deep probe driver to d6-all-pills
(also d4-chat-roundtrip + d5-single-pill in the same family). The
two operator-facing docs still referenced the old names. Fix.
No probe behavior change.
## Summary
Ports the changes from PR #4927 to the live shell-docs app. PR #4927
renamed \`coding-agents\` → \`build-with-agents\` in the legacy
\`docs/\` tree, but \`showcase/shell-docs\` has its own independent
content directory that was never updated.
## Changes
- **11 file renames:** \`coding-agents.mdx\` → \`build-with-agents.mdx\`
(root + 10 integrations: ag2, agno, aws-strands, built-in-agent,
crewai-flows, langgraph, llamaindex, mastra, microsoft-agent-framework,
pydantic-ai)
- **Frontmatter titles:** \`"Coding Agents"\` → \`"Build with agents"\`
in all 11 files
- **Root \`build-with-agents.mdx\`:** replaced 291 lines of hardcoded
content with \`<MCPSetup />\` shared snippet (content had drifted from
the canonical snippet)
- **9 integration \`meta.json\` files:** \`"coding-agents"\` →
\`"build-with-agents"\` in sidebar nav
- **Root \`meta.json\`:** rename entry + add React Native under new
\`---Platforms---\` section (mirrors PR #4927's second commit)
- **\`seo-redirects.ts\`:**
- Update S4 (\`vibe-coding-mcp\`) and S15 (\`mcp\`) subpath destinations
to \`build-with-agents\`
- Add S16: \`coding-agents\` → \`build-with-agents\` subpath rename for
all 13 legacy framework slugs
- Add \`CODING_AGENTS_RENAMES\`: root \`/coding-agents\` + all canonical
framework \`/*/coding-agents\` → \`/*/build-with-agents\` (exact 301s)
- Fix stale destinations in F20, R12, R18, R19
- **\`docs-render.tsx\`:** update \`SUBPATH_TO_COMPONENT\` key
\`"coding-agents"\` → \`"build-with-agents"\` so pages at the new slug
still resolve the shared MCP snippet
## Test plan
- [x] \`/coding-agents\` 301-redirects to \`/build-with-agents\`
- [x] \`/langgraph-python/coding-agents\` 301-redirects to
\`/langgraph-python/build-with-agents\`
- [x] \`/mcp\` and \`/vibe-coding-mcp\` redirect to
\`/build-with-agents\`
- [x] Sidebar shows "Build with agents" entry in each integration
- [x] React Native appears under Platforms section in root sidebar
- [x] All 11 \`build-with-agents\` pages render content correctly (MCP
setup snippet loads)
- [x] Root \`/build-with-agents\` shows correct MCP setup content
Zod 4 made the key schema mandatory for z.record, so the single-argument
z.record(valueType) form is a compile-time error (TS2554) when built against
Zod 4. @copilotkit/react-core declares zod ">=3.0.0", so downstream apps on
Zod 4 are affected; runtime parsing is unaffected under both majors.
- react-core + vue MCPAppsActivityContentSchema: toolInput now uses the
two-argument z.record(z.string(), z.unknown()) form
- react-core defineToolCallRenderer test: same fix for a metadata schema
- add a toolInput field-contract test (round-trips mixed value types)
- add copilotkit/no-single-arg-zod-record oxlint rule (autofix), enabled as
error for packages/**; the incompatibility is type-level, so no runtime
test can guard it while the workspace lockfile pins Zod 3
Closes#4295
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Adds per-integration D6 aimock fixtures for 18 showcase integrations,
completing the D6 probe coverage that #5022 (Slice 3) scaffolded. Each
integration now has the standard set of D5 feature-type fixtures
(agent-config, auth, byoc, gen-ui-*, interrupt-headless, multimodal,
prebuilt-*, tool-rendering-*) under `showcase/aimock/d6/<integration>/`.
## Why
Slice 3 (#5022) shipped the per-integration directory structure + D6
probe driver + harness scoping. The first D6 probe run reported most
integrations RED because most integrations' D6 directories were missing
per-feature fixtures (only langgraph-python had any). This PR fills in
those gaps.
## Authoring rules applied
- Every fixture has `match.context = "<integration>"` for
cross-integration isolation
- toolCall responses use `hasToolResult: false` (or
`toolName`/`toolCallId` gates) to prevent re-match loops
- Conversation turns match the corresponding D5 probe scripts at
`showcase/harness/src/probes/scripts/d5-<feature>.ts`
- Reference shape: langgraph-python's fixtures + each integration's
existing D5/D6 fixture conventions
- Skipped features: each integration's `not_supported_features` list
(e.g., google-adk skips gen-ui-interrupt + interrupt-headless)
## CR
Two rounds of 7-agent unbiased CR converged. R1 surfaced 5 bucket-(a)
findings, all addressed in R2 fix commit. R2 confirmation converged with
no in-scope regressions.
## Test plan
- [ ] Showcase auto-redeploys on merge (path filter matches
`showcase/**`)
- [ ] Next D6 probe cycle on production harness writes
`d6:<slug>/<featureId>` rows for all 18 integrations
- [ ] Dashboard D6 chips reflect real per-integration state instead of
all-gray
Bumps exact-duplicate ceiling 11 → 230 and substring-shadow ceiling
126 → 151. The D6 per-integration fixtures naturally share match keys
with pre-existing demo fixtures in the same context scope, disambiguated
at runtime by the active demo/probe path.
Adds per-integration D6 fixtures for langgraph-python, langgraph-typescript,
and langgraph-fastapi. Each fixture is keyed by match.context for cross-
integration isolation and uses hasToolResult:false on toolCall responses
to prevent re-match loops.
The previous wave-4 fix added `host == "::1"` for IPv6 loopback support,
but verification against `mcr.microsoft.com/dotnet/sdk:9.0` showed that
`new Uri("http://[::1]:8000/").Host` returns `"[::1]"` WITH brackets, not
`"::1"`. The string-equality check therefore never matched and IPv6
loopback detection was silently broken.
Switch to `Uri.IsLoopback`, which is the framework-provided helper that
robustly identifies all loopback variations: `localhost`, the entire
`127.0.0.0/8` range, `::1` in any bracketed form, and IPv4-mapped IPv6
loopback (`::ffff:127.0.0.1`). This sidesteps `Uri.Host`'s bracket-
formatting quirks entirely.
The explicit `0.0.0.0` (unspecified address, kept for back-compat) and
`aimock` (Docker-compose service name) checks are retained because they
are not loopback addresses.
ResolveApiKey and ResolveEndpoint previously cascaded through env var →
configuration sources using the `??` operator, which only short-circuits
on null. When `OPENAI_API_KEY=""` or `OPENAI_BASE_URL=""` was set in the
environment, the empty string was returned by the cascade WITHOUT
consulting the configuration fallbacks (`configuration["OPENAI_API_KEY"]`,
`configuration["GitHubToken"]`, `configuration["OPENAI_BASE_URL"]`),
masking the configured values entirely. The downstream
`IsNullOrWhiteSpace` check would then push the resolver into the
mock-or-throw path even when a valid key was configured.
Replace the `??` chains with a `FirstNonBlank` helper that treats both
null and whitespace-only candidates as absent, so empty env vars fall
through to the configuration fallbacks as intended.