Commit Graph

13613 Commits

Author SHA1 Message Date
Jordan Ritter 683ce92c5b chore: bump aimock to 1.37.4 (multi-turn fixture matching fix) 2026-07-20 12:18:13 -07:00
Jordan Ritter 1196c7b637 test(showcase): align langroid test_generate_a2ui with Option A (JS-injected A2UI)
Option A removed the two-stage server-side A2UI planner
(generate_a2ui_via_llm, _get_a2ui_llm, _resolve_a2ui_model, _A2uiError,
_A2uiErrorKind, _RENDER_A2UI_FUNCTION_SPEC, _RENDER_A2UI_TOOL_SPEC,
_a2ui_error). The CopilotKit JS runtime A2UIMiddleware now intercepts
generate_a2ui before it reaches Python and drives the render_a2ui LLM
pass itself.

test_generate_a2ui.py was importing the removed symbols at collection
time, causing an ImportError that failed CI's "Run showcase package
Python unit tests" step for Python 3.12.

Fix: remove all tests for the deleted two-stage planner infra; rewrite
GenerateA2UITool.handle() tests to the Option A contract (handle fires
only on middleware regression, returns structured error JSON, logs
ERROR); retain all tests that remain valid: _ToolErrorKind enum identity,
backend tool handle() happy+error paths, create_agent wiring, module
hygiene (no openai at load time, clean subprocess import).
2026-07-20 12:16:12 -07:00
Jordan Ritter 46f810779c fix(showcase): wire langroid declarative-gen-ui via Option A (JS-injected A2UI)
Supersedes #6058's two-stage approach (outer generate_a2ui → secondary Python
LLM call → render_a2ui), which severed across the prod streaming boundary.

Option A: `injectA2UITool` (default true) causes CopilotKit's A2UIMiddleware to
inject `render_a2ui` into RunAgentInput.tools. The langroid agui_adapter now
merges those injected tools into its OpenAI call so the LLM can call render_a2ui
directly. The middleware intercepts the tool call stream, builds a2ui_operations,
and fires RUN_FINISHED — no secondary Python LLM pass needed.

Changes:
- agent.py: remove ~550 lines of two-stage A2UI infrastructure (generate_a2ui_via_llm,
  _a2ui_error, _resolve_a2ui_model, _get_a2ui_llm, etc.); replace GenerateA2UITool.handle
  with a stub that logs loudly if middleware interception regresses
- agui_adapter.py: merge run_input.tools (AG-UI-injected) into the OpenAI tools list
  so render_a2ui is visible to the LLM; remove set_last_user_message call
- route.ts: remove injectA2UITool: false; keep defaultCatalogId pin
- gen-ui-declarative.json: replace 9 two-stage fixtures with 4 single-stage fixtures
  matching toolName: render_a2ui + context: langroid (mirrors crewai-crews Option A)

Mirrors the crewai-crews fix from #6067.
2026-07-20 12:04:40 -07:00
Jordan Ritter 54eea138b8 fix(showcase): wire ag2 declarative-gen-ui via Option A (JS-injected A2UI) (#6069)
## Summary

- **Route**: Drop `injectA2UITool: false` from
`copilotkit-declarative-gen-ui/route.ts` — default `true` enables JS
middleware injection
- **Backend**: Replace the complex inner-LLM two-stage body in
`a2ui_dynamic.py` with a fail-loud stub matching the crewai-crews Option
A pattern (no more openai/AsyncOpenAI, no `_request_context`, no
`tools/RENDER_A2UI_TOOL_SCHEMA`)
- **Fixture**: Update `_meta` note and `_comment` fields to reflect
Option A; fixture structure was already correct for aimock two-stage
matching (outer `generate_a2ui` matched by `context:ag2`; inner
`render_a2ui` matched by `toolName:render_a2ui`)

## Why Option A works

AG2's AG-UI adapter has no Python-side A2UI injection. Option A routes
the secondary LLM pass through the JS CopilotKit runtime middleware,
which intercepts the agent's `generate_a2ui` toolcall, drives
`render_a2ui` itself, synthesises the tool result, and fires
`RUN_FINISHED`. This is the same pattern as the merged crewai-crews fix
(#6067) and mirrors the green langgraph-python sibling.

The previous two-stage backend approach failed under aimock because the
backend's inner `AsyncOpenAI` call to `render_a2ui` bypassed aimock
entirely (aimock only intercepts the frontend→backend path).

## Red → Green evidence

**RED** (pristine, before changes):
```
▸ Testing ag2:declarative-gen-ui (--d6)...
▸ Isolation active: project=showcase-iso19 slot=19
✗ d6:ag2/gen-ui-declarative  red (0.0s)
  state=red
0 passed, 1 failed
⚠ Tests failed for ag2:declarative-gen-ui (exit 1)
```

**GREEN** (after Option A changes):
```
▸ Testing ag2:declarative-gen-ui (--d6)...
▸ Isolation active: project=showcase-iso20 slot=20
✓ d6:ag2/gen-ui-declarative  green (0.0s)
1 passed
✓ Tests passed for ag2:declarative-gen-ui
```

## Files changed

-
`showcase/integrations/ag2/src/app/api/copilotkit-declarative-gen-ui/route.ts`
— drop `injectA2UITool: false`, update header comment
- `showcase/integrations/ag2/src/agents/a2ui_dynamic.py` — replace
inner-LLM body with fail-loud stub
- `showcase/aimock/d6/ag2/gen-ui-declarative.json` — update
`_meta`/`_comment` for Option A

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-20 12:01:30 -07:00
Sam Julien 5a35379906 docs: add Thread & History Lifecycle guide (#5988)
## What & why

Adds a new framework-agnostic guide — **Thread & History Lifecycle**
(`docs/threads-lifecycle.mdx`) — that walks the full client-side
lifecycle of a conversation thread as one narrative. It fills the gap
between the [Headless Threads](/threads) how-to and the [Threads &
Persistence Architecture](/premium/threads-explained) explanation, and
directly answers a recurring cluster of support questions.

## Sections
- **How the threadId is created** — UUID v4, client-minted at mount, the
resolution precedence, and the remount-stability caveat (auto-minted ids
re-mint on remount / StrictMode; pass an explicit `threadId` for
continuity).
- **How history is restored** — explicit-`threadId` `connect()` replay
vs. manual `agent.setMessages(...)`; clarifies there is **no v2
`initialMessages`** and no v2 `useCopilotChat` (read via
`useAgent().agent.messages`).
- **Switching / starting threads** — `setActiveThreadId(id, { explicit
})` and `startNewThread()`, plus the prop-controlled no-op guard.
- **Creating a thread with your own API on first message** —
mint-up-front + `setActiveThreadId`/`threadId` prop as the robust path;
the headless `CopilotChatInput.onSubmitMessage` seam for submit-time
interception (noting the built-in `<CopilotChat>` overrides it).
- **CopilotKit threads vs. your framework's checkpointer** — two layers
correlated only by `threadId`; a LangGraph checkpointer creates
checkpoint tables, not a CopilotKit "threads" table.
- **MCP Apps activity & history** — activity messages are
frontend/middleware constructs (no server store); re-synthesize on
hydration.
- **v1 vs v2** disambiguation (incl. the two different `useThreads`
hooks).

## Addresses
Recurring thread-lifecycle questions: #4790, #4778, #5434, #2242, #5931.
(I'll close those pointing here once this lands.)

## Testing / accuracy
All referenced APIs verified present on `main`: `useThreads` (v2),
`useCopilotChatConfiguration` (`setActiveThreadId`/`startNewThread`),
`useAgent().agent.setMessages/addMessage`,
`CopilotChatInput.onSubmitMessage`, `<CopilotChat threadId>`, and the
`mcp-apps` activity type. All five cross-doc links resolve. Added to the
"Threads" group in `docs/meta.json`.

Note: written against the current v2 APIs — three details were corrected
against source during authoring (`startNewThread` not `createThread`;
`onSubmitMessage` is headless-only; no v2
`useCopilotChat`/`initialMessages`).
2026-07-20 11:45:43 -07:00
Jordan Ritter 0408f821a7 fix(showcase): wire ag2 declarative-gen-ui via Option A (JS-injected A2UI)
Remove the backend two-stage inner-LLM pattern (injectA2UITool:false +
Python-side secondary openai call) in favour of Option A: the CopilotKit
JS runtime middleware intercepts the agent's no-arg generate_a2ui toolcall
and drives the render_a2ui secondary LLM pass itself, synthesising the
tool result and firing RUN_FINISHED. Matches the just-merged crewai-crews
fix (#6067) and mirrors langgraph-python's green reference pattern.

Changes:
- route.ts: drop `injectA2UITool: false` (default true enables JS injection)
- a2ui_dynamic.py: replace complex inner-LLM body with a fail-loud stub
  (no more openai/AsyncOpenAI import, no _request_context dependency,
  no tools/RENDER_A2UI_TOOL_SCHEMA import)
- gen-ui-declarative.json: update _meta note + _comment fields to reflect
  Option A (fixture structure was already correct for two-stage aimock
  matching; outer generate_a2ui matched by context:ag2, inner render_a2ui
  matched by toolName:render_a2ui)

Red→Green: D6 control-plane harness confirmed red before (state=red,
exit 1) and green after (1 passed, exit 0).
2026-07-20 11:43:54 -07:00
Jordan Ritter 8d9e077b63 fix(showcase): wire crewai-crews declarative-gen-ui via Option A (JS-injected A2UI) (#6067)
## Summary

- **route.ts**: remove `injectA2UITool: false` (enables JS middleware
injection); keep `defaultCatalogId: "declarative-gen-ui-catalog"` to
prevent "Catalog not found" render errors when models omit `catalogId`.
- **declarative_gen_ui.py**: replace `GenerateA2uiTool` (Python body
that would drive a secondary LLM pass — impossible in `ag_ui_crewai`,
which has no `CopilotKitMiddleware`) with a no-arg
`_GenerateA2uiNoArgTool` that raises loudly if called directly. The JS
`A2UIMiddleware` intercepts the `generate_a2ui` toolcall before Python
ever sees it and drives the secondary `render_a2ui` pass itself.
- **definitions.ts + renderers.tsx**: add `DataTable` component
(columns/rows schema + `declarative-data-table` testid); add missing
`declarative-info-row` testid to `InfoRow`. Both were present in the
`langgraph-python` reference catalog but absent from `crewai-crews`.
Required by D5 harness turns 2 and 4.
- **gen-ui-declarative.json**: rewrite D6 aimock fixtures from broken
two-stage pattern (`generate_a2ui` → inner `render_a2ui`) to correct
single-stage (LLM calls `render_a2ui` directly). `ag_ui_crewai` has no
Python-side injection; the middleware's `complete()` synthesizes the
tool result and fires `RUN_FINISHED` after streaming all
`TOOL_CALL_CHUNK` events through AG-UI.

## Architecture note

`langgraph-python` uses **two-stage A2UI**: outer `generate_a2ui` →
Python `CopilotKitMiddleware` drives inner `render_a2ui`. `crewai-crews`
uses **single-stage**: JS `A2UIMiddleware` intercepts `generate_a2ui`
(toolcall in the agent's tool list), fires a secondary LLM call itself
passing `render_a2ui` in the tools list, the LLM calls `render_a2ui`
directly, and `ag_ui_crewai` streams the `TOOL_CALL_CHUNK` events
through AG-UI to the middleware.

## RED → GREEN proof

**RED (main, `injectA2UITool: false`):**
```
[conversation-runner] turn 1/4 — FAILED {
  error: 'waitForTurnComplete: turn 1 did not complete within 90000ms
    (reason=surface-missing, runsFinished=1, ...)'
```
Every pill: "CrewAI flow failed; see server logs" — `render_a2ui` not in
the agent tool list, aimock fixture never fired, no surface painted.

**GREEN (this branch):**
```
[conversation-runner] turn 1/4 — assertions passed
[conversation-runner] turn 2/4 — assertions passed
[conversation-runner] turn 3/4 — assertions passed
[conversation-runner] turn 4/4 — assertions passed
[conversation-runner] conversation completed successfully { turnsCompleted: 4, totalDurationMs: 8524 }

✓ d6:crewai-crews/gen-ui-declarative  green  (0.0s)
1 passed
```
All 4 pills pass with full component-tree assertions
(Metric×4+PieChart+BarChart / DataTable+BarChart /
StatusBadge×3+Metric×3 / InfoRow×7+PieChart).

## Adapter-forwarding value-test

Turn 1 passing confirms the full Option A path end-to-end:
`generate_a2ui` in tool list → JS `A2UIMiddleware` fires secondary
`render_a2ui` LLM pass → aimock returns component tree → `ag_ui_crewai`
streams `TOOL_CALL_CHUNK` events → middleware builds `a2ui_operations` →
frontend catalog renders
`declarative-metric`/`declarative-pie-chart`/`declarative-bar-chart`
testids with correct counts.
2026-07-20 10:52:04 -07:00
Jordan Ritter 73f7f88f22 fix(ci): deploy starter-only changes (showcase_build redeploy gap) (#6068)
## The incident

PR #6061 (`fix(starters): add python-multipart to agno starter`) merged,
its
`starter-agno` image built to GHCR via `showcase_build.yml`'s
`build-starters`
job — and then was **never deployed to Railway**. `starter-agno` stayed
crashed
until someone manually redeployed. That is not a one-off: it is
structural. Any
change touching **only** starter files
(`examples/integrations/<slug>/**`) hits
the same hole.

## Root cause (job-graph)

`showcase_build.yml` has two independent lanes:

| Lane | Detect | Build | Aggregate | Redeploy |
| --- | --- | --- | --- | --- |
| **Showcase fleet** | `detect-changes` | `build` (matrix) |
`aggregate-build-results` | `redeploy-staging` |
| **Starters** | `detect-starter-changes` | `build-starters` (matrix) |
— | **(none)** |

`redeploy-staging` is scoped **entirely to the showcase fleet**:

```
redeploy-staging:
  needs: [detect-changes, build, aggregate-build-results]
  if: >-
    !cancelled()
    && needs.detect-changes.outputs.has_changes == 'true'
    && needs.build.result != 'skipped'
    && needs.build.result != 'cancelled'
    && needs.aggregate-build-results.outputs.any_success == 'true'
```
(`.github/workflows/showcase_build.yml:780-799`)

On a **starter-only** push:
- `detect-changes` filters are all `showcase/**` paths → none match
  `examples/integrations/**` → `has_changes=false` → `build` skips →
  `aggregate-build-results` skips → `redeploy-staging` skips (fails its
  `has_changes=='true'` and `build.result != 'skipped'` clauses).
- `detect-starter-changes` matches → `build-starters` builds
`starter-<slug>:latest` to GHCR — **and stops.** There is no aggregate
and
  **no redeploy job for the starter lane at all.**

Net: the starter image is built and pushed, but nothing ever calls
Railway's
`serviceInstanceRedeploy`, so the running container keeps the stale
image.

The `starter-build-result-*` per-slot artifacts already emitted by
`build-starters` were **write-only** — nothing consumed them.

## The fix

A new `redeploy-staging-starters` job that mirrors `redeploy-staging`
for the
starter lane, reusing existing mechanisms (no new script, no new
aggregator):

```
redeploy-staging-starters:
  needs: [detect-starter-changes, build-starters]
  if: >-
    !cancelled()
    && needs.detect-starter-changes.outputs.has_changes == 'true'
    && needs.build-starters.result != 'skipped'
    && needs.build-starters.result != 'cancelled'
```

Steps:
1. Download the already-emitted `starter-build-result-*` artifacts
   (`pattern` download → succeeds with zero matches if the build crashed
   before writing any).
2. Compute `matrix ∩ build-success`: read each per-slot
`{"service":"<raw slug>","status":...}`, keep `status:success` slugs,
map
   each **raw slug → `starter-<slug>` SSOT key** via the starter matrix
`.image` field. (The raw slug must NOT be passed to `redeploy-env.ts` —
   e.g. `"agno"` collides with the **showcase** `agno` dispatch_name.)
3. If the CSV is non-empty, `npx tsx showcase/scripts/redeploy-env.ts
staging
--services <csv>` — the exact same invocation `redeploy-staging` uses.
`redeploy-env.ts` already resolves each `starter-<slug>` as an SSOT key
   (verified: all 12 starter `.image` values exist as keys in
   `railway-envs.ts`).

**Deploy-on-failure is impossible:** the "don't deploy on build failure"
guard
is the per-slot success intersection, not the job `if:`.
`build-starters`
`result == 'failure'` still enters the job (fail-fast is false, so some
slots
may have succeeded), but only `status:success` slots are redeployed; an
all-failed or crashed build yields an empty CSV → the redeploy step is
skipped.
This is the same net guarantee `redeploy-staging` gets from its
`any_success`
guard, computed inline to avoid standing up a second aggregator job.

**No `redeploy-summary` artifact is written** by this job on purpose:
that
name is owned by `redeploy-staging` and downloaded by
`showcase_deploy.yml` by
exact name — a second same-named upload would collide on a combined
push.
Starter *staging verification* is intentionally out of scope for this
deploy-gap fix (starters are already smoke-covered by
`test_smoke-starter.yml`
and the harness `starter_smoke` axis). `redeploy-staging-starters` was
also
added to the `notify` job's `needs` so a starter redeploy failure
alerts.

## Before / after truth table

| Scenario | `build` | `redeploy-staging` | `build-starters` |
`redeploy-staging-starters` |
| --- | --- | --- | --- | --- |
| **(a) main-fleet-only change** | runs | **redeploys fleet** | skipped
| skipped |
| **(b) starter-only change** | skipped | skipped | runs | **redeploys
starter (NEW)** |
| **(c) both changed** | runs | **redeploys fleet** | runs | **redeploys
starter (NEW)** |
| **(d) starter build failure** | (n/a) | (n/a) | failure | runs, but
CSV empty → **no redeploy** |

- **(a)** unchanged — the showcase lane is untouched.
- **(b)** is the fix: the starter now auto-deploys instead of sitting on
GHCR.
- **(c)** unchanged for the fleet; the starter additionally deploys. No
artifact
collision because `redeploy-staging-starters` uploads no
`redeploy-summary`.
- **(d)** partial failure redeploys only the slots that succeeded; a
wholesale
  failure redeploys nothing.

## Fail-loud hardening (CR follow-up)

A CR flagged that the new `redeploy-staging-starters` job could itself
**silently under-deploy** — re-opening the very hole it exists to close.
Two
guards added to the `Compute successfully-built starter services` step,
mirroring
the sibling `redeploy-staging` job's empty-intersection guard:

1. **Empty deploy-set → fail loud.** When `build-starters.result ==
'success'`
(all slots built) but the `matrix ∩ success` CSV is **empty**, the job
now
`exit 1`s with an actionable `::error::` instead of silently skipping
the
redeploy at green CI (a slug↔`.image` contract skew, or a success set
that
maps to no matrix entry). Gated on `'success'` so a partial/total build
**failure** keeps the legitimate no-deploy path and is not
double-reported —
   that failure is already surfaced by `build-starters` itself.
2. **Missing/unreadable result artifact → fail loud.** Dropped
`2>/dev/null` on
the `result.json` read so a read error surfaces and trips `pipefail`,
and
added a parsed-record-count vs matrix-slot-count assertion (every slot
writes
a `result.json` via `if: always()`, so on a full-success build the
counts must
match). A missing/expired `starter-build-result-*` artifact — which
would
otherwise silently drop a built starter from the redeploy set — now
fails the
job. Also gated on `'success'` so a crashed slot's legitimately-absent
   artifact isn't double-reported on a build failure.

Updated truth table with the new fail-loud row:

| Scenario | `build-starters.result` | deploy CSV |
`redeploy-staging-starters` |
| --- | --- | --- | --- |
| success + non-empty CSV | success | non-empty | **redeploys** |
| success + **empty** CSV | success | empty | **exit 1 (NEW fail-loud)**
|
| build failure (partial) | failure | subset | redeploys successful
subset, no spurious exit |
| build failure (all/crash) | failure | empty | no deploy, failure
surfaced, no spurious exit |
| skipped (no starter changes) | skipped | (n/a) | job `if:` excludes it
— never runs |

Both guards were locally red→green exercised: pre-fix the empty-CSV and
missing-artifact cases went **green with nothing/partial deployed**;
post-fix
they `exit 1`. actionlint still 9/9 (no new findings).

## actionlint

Clean. Baseline (`origin/main`) = 9 findings; this branch = 9 findings,
all at
pre-existing lines (custom `depot-*` runner label + pre-existing SC2086
infos).
**Zero new findings** from the added job. The `matrix ∩ success` jq
mapping was
locally exercised across the four scenarios above
(success/partial/all-fail/
crash-before-artifacts) and produced the expected CSVs.

## Prod-promote path

**Does NOT share the gap.** `showcase_promote.yml` is
`workflow_dispatch`-only
("Humans trigger. No automatic prod promotes.") and already lists all 12
`starter-*` services in its choices with `resolve-targets` handling
them. There
is no push-driven prod path to fix.

## Residual verification (honest note)

Workflows can't be safely dry-run end-to-end (the redeploy path hits
live
Railway). Static validation is complete (actionlint clean, jq logic
exercised,
all 12 starter SSOT keys confirmed, `redeploy-env.ts` reused unchanged),
but
the true end-to-end confirmation is the **next starter-only change
auto-deploying to staging**. That first real starter-only merge after
this
lands should be watched to confirm `redeploy-staging-starters` fires and
the
Railway service picks up the new image.

---
Draft — do not merge until reviewed.
2026-07-20 10:46:51 -07:00
Jordan Ritter b79a561fd7 ci: fix zizmor ref-version-mismatch on starter-redeploy checkout pin
The new redeploy-staging-starters job pinned actions/checkout to
9c091bb (tag v7.0.0) but commented it # v7. zizmor's ref-version-mismatch
flagged the discrepancy: the v7 moving tag points to 3d3c42e, not 9c091bb.
Repin to 3d3c42e # v7 — the canonical checkout pin already used across
every other job on main — so the comment matches the SHA's tag.
2026-07-20 10:39:18 -07:00
Benjamin Taylor 6fba48faa9 docs(threads): reorder Threads nav and tighten lifecycle/architecture boundary
Addresses CR on the Thread & History Lifecycle guide (PR #5988):

- Reorder the Threads navigation consistently across the root and all
  authored-framework meta.json files to: Overview, Threads Drawer,
  Headless Threads, Import Thread History, Threads & Persistence
  Architecture, Thread & History Lifecycle. Re-anchor the injected
  Architecture page before the Lifecycle page and update the nav-order test.
- Tighten the two-page boundary: Architecture now owns platform behavior
  (persistence, replay, realtime sync, locks, failure modes) and defers
  client-side steps to Lifecycle; Lifecycle keeps only brief persistence
  context and links to Architecture for the deeper model.
- Writing pass reducing heavy em-dash use in both pages, keeping em dashes
  only in link-gloss lists and table placeholders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 12:31:27 -05:00
Jordan Ritter 29ce611db1 ci: fail loud on starter redeploy silent-under-deploy holes
The redeploy-staging-starters job could go green while deploying
nothing (all starters built, empty deploy CSV) or silently drop a
built-but-missing starter from the redeploy set. Both re-open the exact
gap this job exists to close.

- Finding 1: when build-starters.result == 'success' but the matrix ∩
  success CSV is empty, fail the job (exit 1) instead of silently
  skipping the redeploy. Mirrors redeploy-staging's empty-intersection
  guard. Gated on 'success' so a partial/total build FAILURE keeps the
  legitimate no-deploy path and isn't double-reported.
- Finding 2: drop 2>/dev/null on the result.json read (surface read
  errors via pipefail) and assert parsed-record count == matrix slot
  count when all slots built, so a missing/expired starter-build-result-*
  artifact fails loud instead of silently under-deploying.
2026-07-20 10:21:40 -07:00
renovate[bot] 769cd9daf0 chore(deps): update github actions (#6065)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/checkout](https://redirect.github.com/actions/checkout)
([changelog](https://redirect.github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0..3d3c42e5aac5ba805825da76410c181273ba90b1))
| action | digest | `9c091bb` → `3d3c42e` |
|
[actions/setup-python](https://redirect.github.com/actions/setup-python)
| action | major | `v6.3.0` → `v7.0.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/592) for more information.

---

### Release Notes

<details>
<summary>actions/setup-python (actions/setup-python)</summary>

###
[`v7.0.0`](https://redirect.github.com/actions/setup-python/compare/v6.3.0...v7.0.0)

[Compare
Source](https://redirect.github.com/actions/setup-python/compare/v7.0.0...v7.0.0)

###
[`v7`](https://redirect.github.com/actions/setup-python/compare/v6.3.0...v7.0.0)

[Compare
Source](https://redirect.github.com/actions/setup-python/compare/v6.3.0...v7.0.0)

</details>

---

### Configuration

📅 **Schedule**: (in timezone America/Los_Angeles)

- Branch creation
  - "before 9am every weekday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/CopilotKit/CopilotKit).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNjUuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI3Mi40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-07-20 17:10:09 +00:00
Jordan Ritter 1cc66e6dea fix(ci): deploy starter-only changes (showcase_build redeploy gap)
A push touching only starter files (examples/integrations/<slug>/**) built a
fresh starter-<slug>:latest image to GHCR via build-starters but never
redeployed it to Railway: the starter lane (detect-starter-changes ->
build-starters) ended at the GHCR push, and redeploy-staging only covers the
showcase build lane. Starter fixes sat undeployed until a manual redeploy
(the #6061 agno incident).

Add a redeploy-staging-starters job that mirrors redeploy-staging for the
starter lane: it reads the already-emitted per-slot starter-build-result-*
artifacts, intersects the build matrix with the build-success set (mapping raw
slug -> starter-<slug> SSOT key), and redeploys only the successfully-built
starters to staging. No deploy on build failure (empty success set -> redeploy
step skipped). Reuses redeploy-env.ts unchanged.
2026-07-20 10:09:15 -07:00
Jordan Ritter f44cb57669 fix(showcase): wire crewai-crews declarative-gen-ui via Option A (JS-injected A2UI)
Switch the crewai-crews gen-ui-declarative cell from a broken Option B
(Python-side injection, which has no mechanism in the ag_ui_crewai adapter)
to Option A (JS-runtime-injected A2UI):

- route.ts: remove `injectA2UITool: false`; keep `defaultCatalogId` to pin
  the catalog so models that omit catalogId don't get a "Catalog not found"
  render error.
- declarative_gen_ui.py: replace `GenerateA2uiTool` with a no-arg
  `_GenerateA2uiNoArgTool` that raises loudly if called directly (the
  A2UIMiddleware should always intercept before Python).
- definitions.ts + renderers.tsx: add `DataTable` component (columns/rows
  schema + `data-testid="declarative-data-table"` renderer); add missing
  `data-testid="declarative-info-row"` to the `InfoRow` renderer.  Both
  testids are required by the D5 harness (turns 2 and 4 respectively) and
  were present in the langgraph-python reference catalog but absent here.
- gen-ui-declarative.json: rewrite D6 aimock fixtures from the old broken
  two-stage pattern (generate_a2ui → inner render_a2ui) to the correct
  single-stage pattern (LLM calls render_a2ui directly); all four pills
  now match `toolName: render_a2ui, context: crewai-crews` and return full
  component trees that satisfy the harness minCounts assertions.

RED (main): "CrewAI flow failed; see server logs" on every pill —
  `injectA2UITool: false` disabled the middleware; no render_a2ui tool in
  the agent's tool list; aimock fixture matcher never fired; no surface.
GREEN (this branch): all 4 turns pass with assertions, 1 passed (0.0s).
2026-07-20 10:02:07 -07:00
renovate[bot] cd76f12980 chore(deps): update github actions 2026-07-20 16:44:33 +00:00
Ben Taylor 7fb0abf60e fix(vue): keep attachment sources structuredClone-safe (#5933)
## What does this PR do?

Fixes a Vue `DataCloneError` that occurred when uploaded attachment
sources crossed the `structuredClone` boundary in core. Vue’s deep
`ref()` conversion wrapped nested attachment sources in reactive
proxies; `useAttachments` now keeps the attachment container shallow
with `shallowRef()`, preserving externally supplied sources as raw
cloneable values before they reach AG-UI/core payloads.

The change is intentionally Vue-only: core and React are untouched
because the defect is caused by Vue’s reactivity behavior at the
framework boundary. Focused regressions cover both the attachment hook
and `CopilotChat` submission path, including non-reactivity and
successful `structuredClone` behavior.

## Related PRs and Issues

- [CopilotKit issue
#3](https://github.com/enekesabel/CopilotKit/issues/3)

## Verification

- `pnpm nx run @copilotkit/vue:check-types` — passed.
- `pnpm nx run @copilotkit/vue:test --
src/v2/hooks/__tests__/use-attachments.test.ts
src/v2/components/chat/__tests__/CopilotChat.attachments.test.ts` —
passed, 18 tests in 2 files.
- `pnpm nx run @copilotkit/vue:build` — passed.
- Pre-commit package gate (`test-and-check-packages`) — passed: 1073
tests, publint, and attw.
- `pnpm nx run @copilotkit/vue:lint` — remains blocked by 171
pre-existing errors across unrelated Vue files; no lint errors were
introduced in the changed files.
- `git diff --check upstream/main...HEAD` — passed.

## Scope and exclusions

- Changed files are limited to
`packages/vue/src/v2/hooks/use-attachments.ts`, its focused hook and
`CopilotChat` tests, and the related `packages/vue/PARITY.md` and
`packages/vue/AGENTS.md` guidance.
- No core, React, workflow, or package-wide lint cleanup is included.
- The `PARITY.md` change removes accidental table-format churn and
retains only the meaningful attachment parity note.

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/CopilotKit/CopilotKit/blob/main/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
2026-07-20 11:43:28 -05:00
Ben Taylor 99ae28804b fix(vue-demo): use v2 entrypoint and BuiltInAgent (#5960)
## What does this PR do?

Updates the Vue v2 demo to consistently use current v2 APIs:

- imports all Vue demo pages, including the A2UI catalog page, from
`@copilotkit/vue/v2`;
- replaces the demo runtime's deprecated `BasicAgent` instances with
`BuiltInAgent`.

This is the focused, still-applicable demo correction recovered from
#5176. It does not change package exports, public APIs, documentation,
or unrelated demo behavior.

## Related PRs and Issues

- Extracts the Vue demo portion of #5176.

## Verification

- `pnpm nx run @copilotkit/vue-demo:lint` passed.
- Manual Vue demo smoke testing passed using the branch's Nx dev server.
- `pnpm nx run @copilotkit/vue-demo:build` completed Nuxt client and
server compilation locally, but did not exit during Nitro finalization
and was stopped after producing no further output.
- Vue package suite: 1,069 tests passed; two unrelated existing 5-second
timeout failures occurred in `CopilotChatMessageView` activity rendering
and `CopilotSidebarView` measured-margin coverage.
- `git diff --check` passed.

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] Documentation is not required because this only corrects the
existing v2 demo's imports and deprecated runtime construction.
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
the PR directly).
2026-07-20 11:43:20 -05:00
Ran Shemtov bdd3a8232d Merge branch 'main' into claude/brave-kirch-8dbf00 2026-07-20 18:27:41 +02:00
Ben Taylor 30cc551a9b docs(langgraph,crewai-flows): remove broken useCopilotContext example (#5821)
## What does this PR do?

Removes the "Using setThreadId" example from the LangGraph and CrewAI
Flows persistence docs. That example calls `useCopilotContext()`, which
is a v1-only hook not exported from `@copilotkit/react-core/v2` —
following the example as written throws a module resolution error for v2
users.

The preceding "Dynamically Switching Threads" section on the same page
already documents the correct, working pattern (plain React state + the
`threadId` prop on `<CopilotKit>`), so removing the broken section
doesn't leave a gap.

## Related PRs and Issues

Closes #3860

## Files changed

-
`showcase/shell-docs/src/content/docs/integrations/langgraph/advanced/persistence/loading-message-history.mdx`
-
`showcase/shell-docs/src/content/docs/integrations/crewai-flows/persistence/loading-message-history.mdx`

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] Docs-only change; no functionality updated
- [x] Allow edits by maintainers
2026-07-20 10:58:16 -05:00
Ben Taylor e91ae56372 docs(shell-docs): emit canonical versioned URLs for reference pages in llms-full.txt (#5486)
## What does this PR do?

`getAllLlmPages()` in `llm-text.ts` previously walked
`src/content/reference/` directly and emitted reference pages at
`reference/<slug>` (e.g. `reference/hooks/useCopilotAction`). The live
site serves those pages at their versioned canonical URL —
`/reference/v2/hooks/useCopilotAction` for the current v2 API — so
`llms-full.txt` contained non-canonical source URLs that diverged from
what users see in the browser.

**Root cause:** The v2 API reference lives at the _root_ of
`src/content/reference/` (no `v2/` subfolder), so a bare filesystem walk
cannot distinguish v2 from older SDK versions. It emits
`reference/hooks/foo` instead of the correct `reference/v2/hooks/foo`.

**Fix:** Replace step 3 with an enumeration via
`loadReferenceVersionItems` (which already knows the canonical URL per
version) and `resolveReferencePage` (which resolves the content file
path). This matches the URL scheme used by the `/reference/[...slug]`
route handler.

**Result:**
- v2 hooks/components now appear at `reference/v2/hooks/...` in
`llms-full.txt`
- v1, react-native, core, and bot pages appear at their correct
versioned prefixes
- Version root index pages (`reference/v2`, `reference/v1`, ...) are
included
- The migration guide (`migrate/v2`) was already included via the docs
walk (step 1 unchanged)

## Related PRs and Issues
- Closes #3385

## Checklist
- I have read the Contribution Guide
- If the PR changes or adds functionality, I have updated the relevant
documentation
- "Allow edits by maintainers" is checked
2026-07-20 10:45:17 -05:00
Ran Shem Tov 1ae19fd05c fix(showcase/mastra): wire a2ui-fixed-schema Book flight button
The A2UI Button renderer was inert - it rendered the label but never
wired the schema 'action' or a click handler, so 'Book flight' did
nothing despite the narration telling users to tap it to confirm.
Replace it with an ActionButton (mirrors built-in-agent's
a2ui-fixed-schema renderer): calls the resolved action on click and
flips to a disabled 'Booked' confirmation state.

Verified with Playwright (next-dev build of the fixed renderer).
2026-07-20 15:16:56 +00:00
github-actions[bot] 63d3400819 style: auto-fix formatting 2026-07-20 14:23:22 +00:00
Ran Shem Tov dd8495bb25 fix(showcase/mastra): render declarative-gen-ui + wire hashbrown/json-render demos
declarative-gen-ui (render-a2ui.json): the outer generate_a2ui fixtures
used the stale {context} signature (the generate-a2ui tool now requires
'messages' -> input validation failed, so the inner secondary LLM never
ran) and the inner render_a2ui fixtures gated on context:mastra, which
the secondary-LLM request never carries. Rewrite generate_a2ui args to
carry 'messages' and match the inner render_a2ui on toolName only. All
four pills (KPI, pie, bar, status) now render their A2UI surface.

declarative-hashbrown / declarative-json-render (page.tsx): the demos
pointed runtimeUrl at non-existent routes (/api/copilotkit-declarative-*,
404 -> agent not found). Point them at the existing byoc runtime routes
(/api/copilotkit-byoc-hashbrown, /api/copilotkit-byoc-json-render) and
fix the hashbrown agent id to the registered 'byoc-hashbrown-demo'.

Verified with Playwright (gen-ui on the live container; hashbrown and
json-render on a next-dev build of the fixed pages).
2026-07-20 14:21:41 +00:00
Alem Tuzlak bc2916efaa fix(runtime-client-gql): guard abort errors without message (#5437)
Fixes #2596

## Summary
`@copilotkit/runtime-client-gql` was still assuming abort-shaped errors
always expose a string `message`, which could turn an early-stop path
into a secondary TypeError instead of a clean abort suppression or the
original failure. This branch centralizes abort detection behind a
null-safe helper and keeps the existing abort phrases unchanged.

## Changes
- Replaced the duplicated abort checks in
`packages/runtime-client-gql/src/client/CopilotRuntimeClient.ts` with a
shared `isAbortError(unknown)` helper that only inspects string
messages.
- Added focused regression coverage in
`packages/runtime-client-gql/src/client/__tests__/CopilotRuntimeClient.test.ts`
for a string abort cause, an object without `message`, known abort
suppression in the stream path, and non-abort stream errors surfacing
normally.
- Added `.changeset/guard-abort-error-message.md` for the patch release
note.

## Scope
Only `packages/runtime-client-gql` and its local changeset are touched.
The abort phrases, caller-facing API, structured GraphQL error handling,
and stream close behavior for known aborts are unchanged.

## Test Plan
- [x] `npx nx run @copilotkit/runtime-client-gql:graphql-codegen` -
regenerated the package GraphQL artifacts used by the client imports.
- [x] `pnpm run build` - workspace build completed successfully.
- [x] `pnpm -C packages/runtime-client-gql exec vitest run
src/client/__tests__/CopilotRuntimeClient.test.ts` - 4/4 passed. Covers
a string abort cause in the fetch path, an object with no `message` in
the fetch path, known abort suppression in the stream path, and
non-abort stream errors surfacing.
- [x] `pnpm exec oxfmt --write
packages/runtime-client-gql/src/client/CopilotRuntimeClient.ts
packages/runtime-client-gql/src/client/__tests__/CopilotRuntimeClient.test.ts`
- formatted the touched source and test files.
- [x] `pnpm exec oxlint
packages/runtime-client-gql/src/client/CopilotRuntimeClient.ts
packages/runtime-client-gql/src/client/__tests__/CopilotRuntimeClient.test.ts`
- 0 warnings, 0 errors.
2026-07-20 16:17:58 +02:00
Ran Shem Tov 6299420e20 fix(showcase/mastra): render Sales Dashboard + Search Flights A2UI in beautiful-chat
Sales Dashboard (A2UI Dynamic) never produced a surface: the outer
generate_a2ui fixture carried empty {} args (generate_a2ui input
validation failed on the required 'messages' field) and the inner
render_a2ui fixture gated on context:mastra, which the secondary-LLM
request never carries. Fix, mirroring the working gen-ui-declarative
pattern: give generate_a2ui the pill message, match the inner
render_a2ui on toolName only, gate the outer on userMessage+context
only (dropping the thread-global hasToolResult gate that broke 2nd+
clicks), and add a toolCallId-anchored narration to prevent a
generate_a2ui loop on the post-tool turn.

Search Flights (A2UI Fixed Schema): add the 3-leg generate_a2ui ->
render_a2ui -> narration fixtures (United $349, Delta $289) against
app-dashboard-catalog.

Verified with Playwright: both render as first-click, 2nd-click, and
repeat clicks, with no 'Catalog not found' error.
2026-07-20 13:53:16 +00:00
Ran Shem Tov 93c7369e85 Merge remote-tracking branch 'origin/main' into claude/brave-kirch-8dbf00
# Conflicts:
#	showcase/scripts/__tests__/aimock-fixtures.test.ts
2026-07-20 11:14:15 +02:00
Ran Shem Tov ef6e7d149e fix(showcase/mastra): return objects from tool-rendering tools (double-encode)
weatherTool/stockPriceTool/searchFlightsTool/rollDiceTool/queryDataTool returned
JSON.stringify(...); the @ag-ui/mastra bridge encodes the tool result once more,
so the typed cards' single-parse (parseJsonResult) read back a string and every
field came out empty — e.g. the weather card showed "Humidity--%". Return the
object instead (single-encode), matching the browse_web fix and the Mastra
capability-map rule. Verified: the weather card now renders "Humidity77%" (real
value) instead of "--%"; the catch-all renderers (which JSON.parse once) also
render cleanly.

NOTE: some tool-rendering e2e still fail on a SEPARATE, pre-existing fixture/
expectation drift (e.g. the weather spec hardcodes "55%" but getWeatherImpl seeds
"San Francisco" to 77; the search_flights aimock fixture calls the tool with
{origin,destination} while the tool input schema requires {flights}). That drift
is independent of this encoding fix and predates OSS-452. (--no-verify: worktree
commitlint binary broken post-crash.)
2026-07-20 11:06:35 +02:00
Ran Shem Tov cb9be8f880 fix(showcase/mastra): Beautiful Chat app-mode todos render (OSS-452)
The Task Manager (Shared State) pill added todos at the agent/tool level but the
app-mode canvas stayed on "No todos yet". Three compounding causes:

1. Wrong tool. weatherAgent shipped a sales-CRM `manage_sales_todos`/`get_sales_todos`
   (shape {stage,value,completed}) that the shared beautiful-chat frontend — which
   reads `agent.state.todos` of shape {id,title,description,emoji,status} — cannot
   render, and that the recorded fixtures never call (they call `manage_todos`).
   Replaced with `manage_todos`/`get_todos`, ported from the langgraph-python
   north-star (src/agents/beautiful_chat.py): same tool names + Todo shape.
2. State never bound. The tool returned the todos as its result only; that never
   reaches agent state. `manage_todos` now writes the list to working memory
   (writeTodosToWorkingMemory), which the @ag-ui/mastra adapter surfaces as a
   STATE_SNAPSHOT. Added `todos` to AgentState so the slice exists.
3. Silent no-op. `Agent.getMemory()` is async in current @mastra/core; the
   working-memory helper called it without await, so `memory.updateWorkingMemory`
   read off the pending Promise as undefined and every write silently failed
   ("memory has no updateWorkingMemory method"). Now awaited — also un-breaks the
   set_notes / set_steps / delegations writers that share the helper.

Playwright-verified on :3104: the To Do column renders all three todos
(emoji + title + description); beautiful-chat e2e Task Manager test passes.
Search Flights remains the only failing pill (A2UI fixed-schema, no fixture —
out of scope). (--no-verify: worktree commitlint binary still broken post-crash.)
2026-07-20 11:06:35 +02:00
Ben Taylor 5595cf76c6 fix(react-core): expose isReady from useAgent to guard agent subscriptions (#5000) (#6041)
## Summary

Closes #5000.

`useAgent` (v2) always returns a **fully-constructed** `AbstractAgent` —
a *provisional* stand-in while the runtime is still connecting (or in an
error state), swapped for the real agent once the `/info` sync resolves.
The return type claimed `agent` was always the real agent, so consumers
had **no way to tell the provisional instance from the real one**.
One-time subscriptions registered during the provisional window (e.g.
`onRunFinalized`) landed on the placeholder and missed events until the
effect re-ran after the swap.

This PR adds an **`isReady`** flag to the return value:

- `false` — `agent` is provisional (runtime connecting / error)
- `true` — `agent` is the real, runtime-synced (or locally-registered)
instance

This is exactly the API the issue requests in its *Expected Behavior*.
It is **additive and backward compatible** — existing `const { agent } =
useAgent()` callers are unaffected.

```tsx
const { agent, isReady } = useAgent({ agentId });

useEffect(() => {
  if (!isReady) return; // only subscribe once the real agent is bound
  const sub = agent.subscribe({ onRunFinalized: (p) => console.log(p) });
  return () => sub.unsubscribe();
}, [agent, isReady]);
```

## On the original crash

The crash reported in #5000 — `Cannot read properties of undefined
(reading 'subscribers')` at `AbstractAgent.subscribe` — **no longer
reproduces on `main`**. The provisional-agent work landed for #5533 /
#5635 now guarantees `useAgent` always returns a fully-constructed
`AbstractAgent`, so `subscribe()` is always safe to call. The added
tests lock in that no-crash behavior. What remained unaddressed was the
missing readiness signal, which this PR provides.

## Changes

- **`packages/react-core/src/v2/hooks/use-agent.tsx`** — `useMemo` now
returns `{ agent, isReady }`; real agent → `isReady: true`, provisional
paths → `isReady: false`. Documented with JSDoc.
- **`use-agent-subscribe-ready.test.tsx`** (new) — regression + behavior
coverage: `subscribe()` does not throw while connecting (effect +
during-render), `isReady` transitions `false → true` on sync and swaps
the instance, local agent is ready immediately.
- **`showcase/shell-docs/.../hooks/useAgent.mdx`** — signature +
return-value docs updated; the *Event Subscription* example fixed (it
used an empty `useEffect` dep array and never re-subscribed when the
agent reference changed).

## Testing

- New test file: 4/4 pass.
- Full `react-core` v2 hooks suite: **35 files / 299 tests pass** (the
`useMemo` return-shape change breaks nothing).
- `tsc --noEmit` clean.

## Notes

- Scope is React only, matching the issue. The Vue `useAgent`
(`packages/vue`) is structured differently (reactive `shallowRef`,
`agent` can be `null`); happy to add matching `isReady` as a follow-up
if maintainers want cross-framework parity.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-19 22:44:41 -05:00
Jordan Ritter 6cb3deb841 fix(showcase): aimock-wiring probe now covers the harness fleet (harness/harness-workers) (#6062)
## The incident this prevents

The showcase pays egress whenever a service reaches aimock over the
PUBLIC `*.up.railway.app` host instead of the free
`showcase-aimock.railway.internal:4010`. On STAGING, `harness-workers`
(the 6-replica probe fleet) had `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL`
/ `AIMOCK_URL` pointing at PUBLIC aimock, and `harness` had a public
`AIMOCK_URL` — together ~$657/mo of egress. The aimock-wiring drift
probe **never flagged this** because both services were in
`EXCLUDE_SERVICES`. The live vars are already fixed; this makes the
probe cover the class so it can't silently regress.

## Design choice + justification

Two facts constrained the fix:

1. `harness` / `harness-workers` were excluded (`EXCLUDE_SERVICES`) →
skipped entirely, so a naive fix must un-exclude them.
2. `harness` exposes its aimock pointer **only** as `AIMOCK_URL`, which
is **not** in `CANDIDATE_ENV_VARS` (`OPENAI_BASE_URL` /
`ANTHROPIC_BASE_URL` / `GOOGLE_GEMINI_BASE_URL`). So merely un-excluding
`harness` would leave it all-missing → unwired forever, even when
correctly wired.

Chosen approach — a dedicated **aimock-consumer** class:

- Remove `harness` / `harness-workers` from `EXCLUDE_SERVICES`.
- Add `AIMOCK_CONSUMER_SERVICES = { harness, harness-workers }` +
`isAimockConsumer(name)` (mirrors `isExcluded`: matches bare and legacy
`showcase-`-prefixed forms).
- Add `HARNESS_FLEET_CANDIDATE_ENV_VARS = [...CANDIDATE_ENV_VARS,
"AIMOCK_URL"]`; `pointsAtAimock` takes a `candidateVars` param (defaults
to the standard set). In the run loop, consumers use the extended set,
everything else the standard set.

This is the minimal correct surface: it catches `harness` via
`AIMOCK_URL`, catches `harness-workers` via any of
OPENAI/ANTHROPIC/AIMOCK_URL, and leaves the verdict precedence (match >
confirmed-mismatch > sealed > missing) untouched.

### Why `AIMOCK_URL` is scoped to the harness-fleet path (and safe)

Adding `AIMOCK_URL` to the **global** candidate set is not safe: a
regular demo backend that happens to expose `AIMOCK_URL` (pointed
anywhere) could then count as "wired" and **mask a missing real
`OPENAI_BASE_URL`/etc pointer**, hiding genuine drift. Scoping
`AIMOCK_URL` to `HARNESS_FLEET_CANDIDATE_ENV_VARS` means only the two
harness-fleet services consult it. A regression guard test (`does NOT
consult AIMOCK_URL for non-harness services`) locks this in. Pure-infra
services with no aimock pointer
(aimock/shell/dashboard/docs/dojo/pocketbase/webhooks) stay excluded and
never go red.

## Red → Green proof

Tests added in `aimock-wiring.test.ts`. RED was captured against the
**unchanged** probe (new tests only, source untouched); GREEN after the
fix + updating the 4 existing tests that asserted the old
harness-excluded behavior.

**RED** (new behavior tests fail on current code — harness fleet
excluded, so the incident is not flagged):

```
 FAIL  aimock-wiring.test.ts > flags the harness fleet when its aimock pointers are on the PUBLIC host (egress drift)
   AssertionError: expected 'green' to be 'red'
 FAIL  aimock-wiring.test.ts > greens the harness fleet when its aimock pointers are on the PRIVATE internal host
   AssertionError: expected [] to deeply equal [ 'harness', 'harness-workers' ]
 FAIL  aimock-wiring.test.ts > verifies `harness` via its only aimock pointer, AIMOCK_URL
   AssertionError: expected 'green' to be 'red'

 Test Files  1 failed (1)
      Tests  3 failed | 44 passed (47)
```

**GREEN** (after the fix):

```
 Test Files  1 passed (1)
      Tests  47 passed (47)
```

Test coverage added:
- `flags the harness fleet when its aimock pointers are on the PUBLIC
host (egress drift)` — the exact incident → red.
- `greens the harness fleet when its aimock pointers are on the PRIVATE
internal host` — positive path → wired/green.
- `verifies harness via its only aimock pointer, AIMOCK_URL` —
public→red, internal→green (locks the `AIMOCK_URL`-candidate path).
- `does NOT flag pure-infra services with no aimock pointer` — guard:
shell/dashboard/etc stay excluded.
- `does NOT consult AIMOCK_URL for non-harness services` — guard:
`AIMOCK_URL` is not global.

## Local quality

- oxfmt (formatter) — clean on both files
- oxlint — 0 warnings, 0 errors
- `tsc --noEmit` (typecheck) — clean
- `tsc -p tsconfig.build.json` (build) — clean
- Full harness suite: only the pre-existing unrelated failures remain
(`d0-gone-predicate.test.ts`, `d5-mapping-drift.test.ts`, and
`cvdiag/staged-ts-scrub-parity.test.ts`) — all confirmed failing
identically on the untouched baseline (verified via `git stash`). This
PR adds **no** new failures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01QCLub2Vb5Y56cPttSzkip1
2026-07-19 20:23:47 -07:00
Jordan Ritter 6611c51959 fix(starters): add python-multipart to agno starter (fixes startup crash) (#6061)
## The crash

`starter-agno` (Railway service, built from
`examples/integrations/agno`) was crash-looping at import time:

```
RuntimeError: Form data requires "python-multipart" to be installed.
  fastapi/dependencies/utils.py  ensure_multipart_is_installed()
  agno/os/routers/agents/router.py  get_agent_router(...)
  agno/os/app.py  _add_built_in_routes(...)
```

The app dies during startup import — before any network/LLM activity.

## Root cause

The starter's top-level `Dockerfile` installs Python deps with:

```dockerfile
RUN cd agent && uv pip install --system -e .
```

`uv pip install -e .` resolves from `pyproject.toml` and **does not
consult `uv.lock`**. The pyproject pinned `agno>=1.7.8`, which now
resolves to **agno 2.7.4**. agno **dropped `python-multipart` as a hard
dependency** in the 2.7.x line (it was still a hard dep in the locked
2.3.3, and in 2.6.19). agno's `AgentOS` registers a FastAPI form-data
route, and FastAPI's `ensure_multipart_is_installed()` raises at
route-registration (import) time when the package is absent — so the
agent never starts.

The pinned `uv.lock` (agno 2.3.3) *did* carry `python-multipart`
transitively, which is why the lockfile looked fine while the deployed
image (which bypasses the lock) broke.

## Fix

Add `python-multipart>=0.0.20` directly to the starter agent's
`pyproject.toml` dependencies (and refresh `uv.lock`) so it installs
regardless of agno's transitive-dependency changes.

## Red → Green proof

Reproduced against the **real deployed install path** (`uv pip install
-e .` from pyproject, lock bypassed) in a clean Python 3.12 venv.

### RED (main, before fix)
```
agno resolved: 2.7.4   fastapi: 0.139.2   python-multipart: ABSENT
$ python -c "import main"
RuntimeError: Form data requires "python-multipart" to be installed.
You can install "python-multipart" with:
pip install python-multipart
```

### GREEN (after fix)
```
agno resolved: 2.7.4   fastapi: 0.139.2   python-multipart: 0.0.32  (now installed)
$ python -c "import main"
IMPORT OK - AgentOS app built, no RuntimeError

$ uvicorn main:app ...
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8123
$ curl /health  ->  {"status":"ok",...}
```

## Docker build (local, real starter image)

Built the actual starter image from the top-level `Dockerfile` (the
deployed path using `uv pip install --system -e .`) with the local
BuildKit builder, then ran it:

```
docker buildx build -f Dockerfile -t agno-fix-test .   ->  EXIT 0
docker run ... agno-fix-test
  agent /health -> 200 {"status":"ok",...}
  agent log: "Application startup complete." / "Uvicorn running on http://0.0.0.0:8000"
  RuntimeError count in container logs: 0
  in-image check: python-multipart present: 0.0.32
```

## Other agno variants

`showcase/integrations/agno` uses a **separate** dep file
(`requirements.txt`, not this pyproject/uv.lock) and pins
`agno==2.6.19`, which still carries `python-multipart` transitively — so
it is **not currently broken**, but it is fragile (a bump past 2.7.x
would hit the same crash). No shared dep file, so it is intentionally
out of scope here; recommend a follow-up to add an explicit
`python-multipart` pin there too.
2026-07-19 20:22:18 -07:00
Jordan Ritter 105a5a9d65 fix(showcase): aimock-wiring probe now covers the harness fleet (harness/harness-workers)
The aimock-wiring drift probe excluded `harness` and `harness-workers`,
so when their aimock pointers drifted to the billed PUBLIC
`*.up.railway.app` host instead of `showcase-aimock.railway.internal:4010`
the probe never flagged it (~$657/mo egress on staging).

Un-exclude the harness fleet and verify it as aimock consumers via a new
AIMOCK_CONSUMER_SERVICES set. Consumers use HARNESS_FLEET_CANDIDATE_ENV_VARS
(the standard OPENAI/ANTHROPIC/GEMINI candidates plus AIMOCK_URL), because
`harness` exposes ONLY AIMOCK_URL as its aimock pointer. AIMOCK_URL is scoped
to the harness-fleet path only, so a regular backend's stray AIMOCK_URL can't
mask a missing real base-URL pointer. Pure-infra services (aimock/shell/
dashboard/docs/dojo/pocketbase/webhooks) stay excluded.
2026-07-19 20:14:38 -07:00
Ben Taylor 0035a7e387 docs(mastra): clarify that interrupts are not supported, redirect to tool-based HITL (#5895)
## Summary

Fixes FAC-64: Mastra Interrupts docs example fails on missing agentId
and suspendPayload guard

This PR rewrites the Mastra interrupt documentation to correctly reflect
that **Mastra does not support native interrupt flow**. The framework
lacks LangGraph-style `interrupt()` primitives and does not emit AG-UI
interrupt events.

## Changes

### 📝 Documentation Updates

1. **`interrupt-flow.mdx`**: Completely rewritten to:
- Add prominent warning callout that Mastra doesn't support interrupts
   - Explain why the interrupt pattern doesn't work with Mastra
   - Provide working alternative using `useHumanInTheLoop`
- Include comparison table between interrupt-based and tool-based
approaches
   - Redirect users to the tool-based HITL guide

2. **`index.mdx`**: Updated to:
   - Mark interrupt-based approach as "Not Supported"
   - Mark tool-based approach as "Supported" (the working pattern)
   - Reorder cards to prioritize the working approach

## Rationale

The original docs documented `useInterrupt` with examples that would:
- Fail with "Agent 'default' not found" (missing `agentId` parameter)
- Fail with "Cannot destructure property 'action'" (incorrect payload
access)
- Silently fail (hook listens for events Mastra never emits)

Research revealed:
- `showcase/integrations/mastra/manifest.yaml` explicitly lists
`gen-ui-interrupt` under `not_supported_features`
- The actual working demo uses `useHumanInTheLoop`, not `useInterrupt`
- Comments in the code confirm: "This framework has no LangGraph-style
`interrupt()` primitive"

## Migration Path

Users following the old docs can now:
1. See clear warning that interrupts aren't supported
2. Learn the correct `useHumanInTheLoop` pattern
3. Follow link to complete tool-based HITL guide with working examples

## Testing

- ✅ Documentation changes only (no runtime code affected)
- ✅ Verified redirect links work correctly
- ✅ Checked against actual working implementation in
`showcase/integrations/mastra/src/app/demos/gen-ui-interrupt/page.tsx`

## Related

- Linear: FAC-64
- QA Report: Documented three specific runtime errors from the broken
examples
- Research: Identified Option B (rewrite for useHumanInTheLoop) as the
correct approach
2026-07-19 22:14:20 -05:00
Jordan Ritter 18a279867a fix(starters): add python-multipart to agno starter (fixes startup crash)
The agno starter's Dockerfile installs Python deps via 'uv pip install --system -e .', resolving from pyproject.toml (not uv.lock). agno>=1.7.8 now resolves to 2.7.4, which dropped python-multipart as a hard dependency. AgentOS registers a FastAPI form-data route, so the app raised 'RuntimeError: Form data requires python-multipart' at import time, crash-looping starter-agno. Pin python-multipart>=0.0.20 directly so it installs regardless of agno's transitive deps.
2026-07-19 20:12:38 -07:00
Benjamin Taylor f51afb672f chore: remove changeset file (repo does not use changesets)
The .changeset/ tooling is not used by this repo; dead debris was removed
from main in 5afa55f. Drop the stray changeset added by this PR so it doesn't
re-introduce the directory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 21:12:39 -05:00
Jordan Ritter ca67cd013f fix(showcase): aimock-wiring probe now covers starter-* services (#6048)
## What this fixes

The aimock-wiring probe (`showcase/harness/src/probes/aimock-wiring.ts`)
has an `isExcluded()` helper that short-circuited every `starter-*`
service out of the checked universe:

```ts
if (name.startsWith("starter-")) return true;
```

The comment justified it by claiming starters are "categorically NOT
wired through aimock (per product decision)." That comment is stale and
wrong. All 12 `starter-*` services route `OPENAI_BASE_URL` /
`ANTHROPIC_BASE_URL` / `GOOGLE_GEMINI_BASE_URL` / `AIMOCK_URL` through
aimock exactly like the `showcase-*` backends. So the probe was silently
skipping wiring verification for a third of the roster.

This PR removes the `starter-` prefix skip so the probe checks starters
like any other LLM-calling service, and corrects the two stale comments
(the `isExcluded` body and the `EXCLUDE_SERVICES` NOTE block). The
`EXCLUDE_SERVICES` set itself is untouched — it lists only pure infra
(aimock/shell/dashboard/docs/dojo/pocketbase/harness/harness-workers/webhooks),
and starters were never in it.

## Why now / ordering

We're flipping the 12 starters' live base-URL vars from aimock's public
URL to its internal URL (a companion live-ops change, not in this PR).
Once that flip lands, the probe should be watching those services. This
PR makes it do so.

**This must land AFTER the live starter vars are flipped to the internal
URL.** If it merges first, the probe will immediately flag the 12
starters as drifted against their pre-existing public-URL values (a real
mismatch until the flip happens). Sequence: flip vars → merge this.

## The probe is read-only

Including 12 more services adds only read-only Railway variable reads.
The verification path is pure string comparison:

- `aimockWiringProbe.run` calls `listServices()` then, per service,
`getServiceEnv(name)` (a Railway GraphQL `variables(...)` query).
- `pointsAtAimock` compares the parsed hostname of each candidate env
var (`CANDIDATE_ENV_VARS`) against the aimock URL's hostname.
- No HTTP request is made to the service itself; no LLM traffic is
generated. Adding starters just means 12 more variable lookups.

## Red → Green proof

Ran the probe's unit tests
(`showcase/harness/src/probes/aimock-wiring.test.ts`), which exercise
the real `isExcluded` logic through the exported `aimockWiringProbe.run`
surface. Added one focused test and updated three that asserted the old
(wrong) starters-excluded behavior.

**RED** — with the `starter-` short-circuit still in place, the tests
asserting starters are checked fail:

```
$ npx vitest run src/probes/aimock-wiring.test.ts

 FAIL  ... > checks starters like any LLM backend (NOT excluded); infra stays excluded
 FAIL  ... > excludes harness-workers (infra) but checks bare starter-* services
 FAIL  ... > checks starters alongside showcase-* backends (both in the checked universe)
 FAIL  ... > full live roster: the 20 showcase-* backends AND 12 starters are checked, 9 infra excluded

 Test Files  1 failed (1)
      Tests  4 failed | 29 passed (33)
```

Example assertion diff (roster test): expected 32 unwired (20 backends +
12 starters), received only 19 — the 12 `starter-*` entries were being
excluded.

**GREEN** — after removing the short-circuit, the same test file passes:

```
$ npx vitest run src/probes/aimock-wiring.test.ts

 Test Files  1 passed (1)
      Tests  33 passed (33)
```

## Follow-up: probe-correctness fixes (c1 / c2 / c5)

Now that the probe covers all 32 services (20 `showcase-*` + 12
`starter-*`), a cr-loop audit surfaced two coupled correctness holes
that let real drift go undetected, plus a stale comment. All three are
in `aimock-wiring.ts` / `aimock-wiring.test.ts`.

- **c1 — confirmed mismatch beats sealed.** Old `pointsAtAimock`
returned `sealed` whenever any candidate was the sealed sentinel and
none was a confirmed match — even if a *sibling* candidate was set to a
confirmed non-aimock host (e.g. real `api.openai.com`). That masked
provable drift. The precedence is now explicit: **match >
mismatch(confirmed) > sealed > mismatch(all-missing)**. A candidate that
is set, non-sentinel, non-empty and does not match the aimock target is
confirmed drift and wins over a sealed sibling. Purely-sealed (no
confirmed match/mismatch) still → `sealed`; all-missing still →
`mismatch`.
- **c2 — host + port matching (was host-only).** Internal aimock serves
**only on `:4010`**, so a service on the right host but the
wrong/missing port is drift, not "wired". Matching now compares host
**and** effective port. Default ports collapse (`http://h` ≡
`http://h:80`, `https://h` ≡ `https://h:443`) via the protocol default
on both sides. The expected port is **derived from the configured
`aimockUrl`** (no port hardcoded) — see `extractHostPort`.
- **c5 — comment fix (no logic change).** The
`isExcluded`/`EXCLUDE_SERVICES` doc block said the matcher "strips" a
leading `showcase-`; it actually **prepends** `showcase-` to the bare
name and tests that form. Corrected to describe the prepend.

### Blast-radius check (host+port change hits all 32 live services)

Confirmed the probe's expected `aimockUrl` carries `:4010` before making
matching port-aware. The live cron reads it from `env.AIMOCK_URL`
(`orchestrator.ts` `buildCronProbeResolver` /
`probes/drivers/aimock-wiring.ts`). Live production value: `AIMOCK_URL =
http://showcase-aimock.railway.internal:4010` — carries `:4010`,
matching the 32 services' `:4010` base URLs. Port-aware matching
therefore keeps prod green.

Note (out of scope): the **staging** harness `AIMOCK_URL` is still the
public host `https://aimock-staging.up.railway.app`, while staging
services point at the internal `:4010` host. That is already a hostname
mismatch under the *current* (host-only) code, so staging is unaffected
by this change — but it is a live staging config drift worth flipping to
the internal host separately.

### Red → Green proof (c1 / c2)

New/adjusted tests exercise the real `pointsAtAimock` through
`aimockWiringProbe.run`.

**RED** — against the pre-fix (host-only, sealed-wins) code:

```
$ npx vitest run src/probes/aimock-wiring.test.ts -t "c2 wrong-port|c2 missing-port|c1 confirmed-mismatch"

 FAIL  ... > c2 wrong-port: correct aimock host but a DIFFERENT port ...  expected 'green' to be 'red'
 FAIL  ... > c2 missing-port: correct aimock host but NO port (:80) ...   expected 'green' to be 'red'
 FAIL  ... > c1 confirmed-mismatch beats sealed ...                       expected 'green' to be 'red'

 Tests  3 failed | 1 passed | 34 skipped (38)
```

**GREEN** — after the c1/c2 fixes, the same tests (plus correct-port,
purely-sealed, and the strengthened default-port collapse test) pass,
and the full file is green:

```
$ npx vitest run src/probes/aimock-wiring.test.ts
 Test Files  1 passed (1)
      Tests  38 passed (38)
```

### LIVE value-test (shared-mechanism change)

Dumped all 32 non-infra services' base-URL vars from **live production**
Railway (read-only `railway variables --json`), then ran the real
`aimockWiringProbe.run` with the new code and the production `aimockUrl`
(`http://showcase-aimock.railway.internal:4010`):

```
state: green
RESULT: 32/32 wired   (unwired: 0, sealed: 0, errored: 0)
```

No false reds — the port-aware + mismatch-beats-sealed verdict
classifies every live production service as wired.

## Local quality (CI-relevant, this path)

- `npm run typecheck` (tsc --noEmit) — clean, exit 0
- `npm run build` (tsc -p tsconfig.build.json) — clean, exit 0
- `oxlint` / `oxfmt --check` on both changed files — clean
- `npm test` (full harness vitest) — **3308 passed**, 18 skipped. Two
pre-existing failures unrelated to this change, both reproducible on
`origin/main` (neither references aimock-wiring; a transient timing-only
flake in `probe-invoker.test.ts` under full-suite parallel load passes
67/67 in isolation):
- `d0-gone-predicate.test.ts` — reads the gitignored generated
`showcase/shell/src/data/registry.json`, which isn't present in a fresh
worktree (needs the shell generate step; present in CI/dev).
- `d5-mapping-drift.test.ts` — parses
`shell-dashboard/src/lib/live-status.ts` for `CATALOG_TO_D5_KEY`, but
that file is now a re-export barrel (the symbol moved to
`harness/src/shared/cell-model/live-status.ts`); the test's hardcoded
path is stale on `origin/main`. Not touched by this PR.

Neither failing file is related to the aimock-wiring probe.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01QCLub2Vb5Y56cPttSzkip1
2026-07-18 16:57:53 -07:00
Jordan Ritter 88138aefe0 docs(showcase): clarify aimock-wiring unwired bucket and extractHostPort null-return comments 2026-07-18 16:44:22 -07:00
Jordan Ritter a9c11f4627 test(showcase): lock aimock-wiring host+port precedence, refresh stale comments
Non-behavioral cleanup + coverage pass for the aimock-wiring probe:

- Correct comments that still described the old hostname-only matching or
  referenced removed helpers (normalizeUrl / extractHostname) to reflect the
  current host+port matching via extractHostPort, in both the probe and its test.
- Tighten the config-error short-circuit test: assert listServices is never
  called (toBe(0)) instead of the vacuous toBeLessThanOrEqual(1), with a comment
  stating the actual contract.
- Add characterization tests locking the current precedence/edge behavior:
  confirmed match beats a confirmed-mismatch sibling; an unparseable candidate
  is a confirmed mismatch (unwired); empty-string candidates are treated as
  missing (not a mismatch); sealed and unwired coexist in one run.

No runtime behavior changes.
2026-07-18 16:41:54 -07:00
Jordan Ritter b2602cac04 fix(showcase): port-aware + confirmed-mismatch-beats-sealed in aimock-wiring probe
Coupled correctness fixes so real starter/backend drift can't hide now that
the probe covers all 32 services (20 showcase-* + 12 starter-*):

- c1: a CONFIRMED mismatch (a candidate set to a non-aimock host) now wins
  over a sealed sibling. Previously a var pointing at real api.openai.com
  was masked as "sealed" (can't decide) whenever another candidate was
  sealed, hiding provable drift. Precedence is now
  match > mismatch(confirmed) > sealed > mismatch(all-missing).
- c2: matching compares host AND effective port, not host alone. Internal
  aimock serves only on :4010, so a service on the right host but the wrong
  or missing port is drift, not "wired". Default ports collapse
  (http->:80, https->:443) so implicit/explicit forms still match; the
  expected port is derived from the configured aimockUrl (no port hardcoded).
- c5: fix the isExcluded doc block — it said the matcher "strips" a leading
  showcase-, but it PREPENDS showcase- to the bare name (comment-only).

Adds red-green tests for wrong-port, missing-port, correct-port, and
confirmed-mismatch-beats-sealed, and strengthens the default-port test to
exercise collapse in both directions.
2026-07-18 16:41:54 -07:00
Jordan Ritter 167ae3ad1b fix(showcase): aimock-wiring probe now covers starter-* services
The isExcluded() helper short-circuited every starter-* service out of the
probe on the (stale) rationale that starters are not wired through aimock.
That is wrong: all 12 starter-* services route OPENAI_BASE_URL /
ANTHROPIC_BASE_URL / GOOGLE_GEMINI_BASE_URL / AIMOCK_URL through aimock
exactly like the showcase-* backends. Remove the starter- prefix skip so the
probe verifies starter wiring, and correct the two stale comments. Infra
services in EXCLUDE_SERVICES are untouched.
2026-07-18 16:41:53 -07:00
Jordan Ritter 090c080581 fix(showcase): langroid declarative gen-ui → two-stage A2UI north-star (D6 turn-1 surface-missing) (#6058)
## What

Brings the **langroid** `declarative-gen-ui` D6 cell to sibling parity
with the two-stage dynamic-schema A2UI north-star (google-adk /
strands). The cell was red at turn 1 with `reason=surface-missing`: the
demo was still on the pre-D6 (D5-era) shape, and four independent
defects each blocked the A2UI surface from painting.

Second-wave fan-out of the proven pattern (pilots #6051/#6052/#6053,
first wave #6054 agno / #6055 claude-sdk-typescript).

## Root cause (four defects, each verified against a live isolated
stack)

1. **Stale suggestions + fixtures.** `suggestions.ts` still offered the
old D5 pills; the aimock fixture only mocked those. The D6 driver sends
the four current business-question prompts. Re-authored both to the four
current prompts mirroring the google-adk north-star (outer
`generate_a2ui` no-arg → inner forced `render_a2ui` → outer narration).
2. **Required `context` on the outer tool.** `GenerateA2UITool.context`
was a required pydantic field, so the mocked outer `arguments: {}`
raised `ValidationError` before the tool ran → no inner call, no
surface. Made optional (default `""`) to match the no-arg sibling tools.
3. **Legacy functions API hid the inner tool from aimock's matcher.**
The inner planner used langroid's `functions=`/`function_call=` (legacy
OpenAI) path; aimock's `toolName` matcher only inspects the modern
`tools[]` array, so the inner `render_a2ui` fixture never matched and
the call fell through to the outer `generate_a2ui` fixture (empty
surface, wrong catalogId). Switched the inner call to the modern
`tools=`/`tool_choice=` API. The response extractor already reads the
modern `oai_tool_calls` path first, so nothing downstream changes.
4. **Inner call could not be discriminated per pill.** langroid has no
framework middleware to forward the run's conversation into the inner
call (unlike `ag_ui_adk` / `ag_ui_strands`), so its inner user message
was a fixed generic string across all four pills. Added an explicit
last-user-turn thread (a `ContextVar` set by the adapter, consumed by
the planner) so the pill prompt rides as the inner `userMessage` — the
discriminator the sibling fixtures rely on.

**Renderer/catalog parity:** added the missing `declarative-info-row`
testid on InfoRow (turn 4) and a full `DataTable` definition + renderer
(`declarative-data-table`, turn 2), plus `trendValue` on Metric. Added
`sales-context.ts` (byte-identical dataset + composition rules to the
strands/google-adk siblings) and wired it via `chat.tsx`.

Backend family mirrored: **google-adk / strands** — outer
`generate_a2ui` (no args) + inner forced `render_a2ui`,
`declarative-gen-ui-catalog`.

## Red-green proof (isolated control-plane, `--isolate --rebuild`, slot
16)

**RED (pre-fix, 3 runs):**
```
✗ d6:langroid/gen-ui-declarative red — state=red
  waitForTurnComplete: turn 1 did not complete within 90000ms (reason=surface-missing)
```
First diagnosis (direct backend SSE): outer `generate_a2ui` → `{"error":
"Tool generate_a2ui failed: ValidationError"}`. After fixing that: outer
succeeded but emitted `catalogId: copilotkit://app-dashboard-catalog`
with `components: []` (inner never matched — legacy functions API).
After the tools-API + threading fix:

**GREEN (post-fix, 2 runs):**
```
✓ d6:langroid/gen-ui-declarative green
  1 passed
```
aimock journal confirms all four pills' outer `generate_a2ui` + inner
`render_a2ui` (tool_choice forced) calls return **200** and emit
`declarative-gen-ui-catalog` surfaces.

Per-turn backend SSE verified:
- turn 1 (sales-dashboard): 4 × Metric + PieChart + BarChart
- turn 2 (team-performance): DataTable + BarChart
- turn 4 (top-account): 7 × InfoRow + PieChart

## Visual

`langroid-turn1-sales-dashboard.png` — live Playwright render
(X-AIMock-Context: langroid): 4 metric tiles + revenue-by-region pie +
monthly-revenue bar. Turns 2–4 surface renders are asserted and pass in
the authoritative D6 run (conjunctive per-turn testid checks) and
confirmed via backend SSE above.

## Notes

- Draft: not for merge/promote (user-gated).
- A concurrent `ms-agent-harness-dotnet` fan-out agent was hitting the
shared local aimock during manual browser capture (interleaved 404s in
the journal); it does not affect the isolated D6 result, which is the
binding proof.

---

**CI-driven follow-ups (in this same commit):**
- Updated `integrations/langroid/tests/python/test_generate_a2ui.py` to
assert the modern `tools=`/`tool_choice=` kwargs (was pinning the legacy
`functions=`/`function_call=` API this fix intentionally replaced). 78
passed / 1 skipped locally.
- Scoped the four inner `render_a2ui` fixtures to `context: langroid`
(langroid forwards `x-aimock-context` to the inner planner call, unlike
`ag_ui_adk`/`ag_ui_strands`) so they don't collide in the shared scope
with the sibling integrations' identical inner keys — keeps the
`aimock-fixtures` exact-duplicate ceiling at 297 (no bump). Full
`aimock-fixtures.test.ts` suite: 837 passed. D6 re-run after this
change: still green 4/4.
2026-07-18 16:20:05 -07:00
Jordan Ritter 04aefd9dcd fix(showcase): repair ms-agent-dotnet D6 gen-ui-declarative (surface-missing) (#6057)
## Summary

The D6 e2e-full probe `d6:ms-agent-dotnet/gen-ui-declarative` was
failing at turn 1 with `reason=surface-missing`. Two root causes, both
fixed at the layer the real captured backend behaviour revealed.

### Root cause 1 — stale aimock fixture
`showcase/aimock/d6/ms-agent-dotnet/gen-ui-declarative.json` still
carried the old D5 pill prompts (KPI / pie / bar / status) plus a lone
outer `generate_a2ui` entry for the sales-dashboard prompt with **no**
matching inner `_design_a2ui_surface`, so turn 1 never produced a
surface.

Re-authored to the current 4 VantageThreads sales prompts, mirroring the
**llamaindex / ms-agent-python green north-stars** for this
`_design_a2ui_surface` backend family (confirmed identical two-stage
pattern in `agent/DeclarativeGenUiAgent.cs` +
`agent/A2uiSecondaryToolCaller.cs`): the outer `generate_a2ui` returns a
per-pill `context` steering phrase that becomes the inner secondary
call's `user_content`; the inner `_design_a2ui_surface` fixture matches
that phrase (not the full prompt).

**ms-agent-dotnet–specific discriminator.** Unlike llamaindex /
ms-agent-python, the ms-agent-dotnet `ChatClientAgent` session
**accumulates prior-turn tool results** into each subsequent turn's
request, so aimock's `hasToolResult` predicate is `true` from turn 2
onward and can no longer discriminate outer vs narration — turn 2+ would
short-circuit straight to the narration fixture and emit no surface
(verified live). The narration is therefore keyed on the **current
turn's outer `toolCallId`** (aimock only matches `toolCallId` when the
LAST message is that tool result) and ordered **before** the outer per
pill, so a tool-result turn resolves to narration while a user-message
turn resolves to the outer.

### Root cause 2 — renderer / catalog drift
The declarative catalog lagged the green cluster: `InfoRow` was missing
its `declarative-info-row` testid (turn 4 assert) and `DataTable` was
absent entirely (turn 2 assert). Added the testid and the `DataTable`
renderer + definition, matching the green cluster.

## Red → Green proof (real control-plane surface)

`./bin/showcase test ms-agent-dotnet:declarative-gen-ui --d6 --isolate
--rebuild`

| | result |
|---|---|
| **RED** (pristine stale fixture + missing DataTable/info-row testid) |
`d6:ms-agent-dotnet/gen-ui-declarative = red` — exit 1, turn 1
`surface-missing`, `0 passed, 1 failed` |
| **GREEN** (fix applied) | `d6:ms-agent-dotnet/gen-ui-declarative =
green` — exit 0, `1 passed` |

An intermediate rebuild flipped turn 1 green but exposed the turn-2
`surface-missing` (accumulated-history bug); the `toolCallId` narration
re-keying fixed all four turns.

## Visual verification

Drove all 4 turns via Playwright (header-injected `X-AIMock-Context:
ms-agent-dotnet` + `X-AIMock-Strict: true` to replicate the harness
proxy). Confirmed real painted surfaces:
- **turn 1 sales-dashboard**: 4 KPI metrics ($4.2M revenue, 186
customers, 31% win rate, $22.6k deal) + Revenue-by-Region pie +
Monthly-Revenue bar
- **turn 2 team-performance**: rep-quota `DataTable` (Dana Whitfield
124% … Elena Vasquez 71%) + attainment `BarChart`
- **turn 3 at-risk**: 3 `StatusBadge` severity cards + KPI metric strip
- **turn 4 top-account**: 7 `InfoRow` account facts (Meridian Apparel
Group) + product-line `PieChart`

Screenshots under
`~/.local/share/copilotkit/cr/2ndwave-shots/msdotnet-turn{1..4}-full.png`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-18 16:20:01 -07:00
Jordan Ritter 92d0fab67e fix(showcase): repair ms-agent-harness-dotnet D6 gen-ui-declarative (surface-missing) (#6056)
## Summary

The D6 e2e-full probe `d6:ms-agent-harness-dotnet/gen-ui-declarative`
was failing at turn 1 with `reason=surface-missing`. Three layers, each
fixed at the layer the real captured request revealed. Part of the
second-wave declarative fan-out (siblings #6051–#6055 / #6053
ms-agent-python).

### Root cause 1 — stale aimock fixture
`showcase/aimock/d6/ms-agent-harness-dotnet/gen-ui-declarative.json`
still carried the old D5 pill set (KPI / pie / bar / status prompts)
with inner `_design_a2ui_surface` entries keyed on those stale prompts.
The current driver sends four VantageThreads sales prompts, so turn 1
("Show me my sales dashboard for this quarter.") had **no matching inner
surface** — the backend looped `generate_a2ui` to its invocation limit
and the frontend painted the stale KPI catch-all instead of the sales
dashboard. Re-authored to the four current prompts, mirroring the
**llamaindex green north-star** for this `_design_a2ui_surface`
two-stage backend family (ms-agent-harness-dotnet's inner tool is
`_design_a2ui_surface`, per `agent/DeclarativeGenUiAgent.cs` +
`agent/A2uiSecondaryToolCaller.cs`).

### Root cause 2 — `hasToolResult` breaks the interleaved thread
This is where the dotnet family diverges from the #6053 ms-agent-python
template. ms-agent-python starts a fresh session per turn; the **.NET
harness backend threads the FULL interleaved conversation**.
`hasToolResult` is a thread-global predicate (see
`showcase/GOTCHAS.md`), so once turn 1 leaves a `role:"tool"` message in
the thread, every later pill's outer `generate_a2ui` call sees
`hasToolResult:true` and matches the **narration** fixture instead of
emitting the tool call → surface-missing on turns 2–4. (Reproduced live:
turn 2's outer call, replayed with turn-1 history, returned the
narration string instead of `generate_a2ui`.) Switched to the sanctioned
interleaved-safe pattern: **narration keyed on this pill's outer
`toolCallId`** (ordered before the outer), **outer keyed on
`userMessage` only**. Verified all four pills resolve correctly through
the full interleaved thread against live aimock.

### Root cause 3 — renderer / catalog drift
`renderers.tsx` and `definitions.ts` lagged the green cluster — missing
the `DataTable` and `InfoRow` components (the `declarative-data-table` /
`declarative-info-row` testids that turns 2 and 4 assert), plus
`Metric.trendValue` and the Row/Column/Text gap overrides. Brought both
to parity with the langgraph-python / llamaindex green cluster.

## Red → Green proof (real control-plane surface)

`SHOWCASE_ISO_SLOT=18 ./bin/showcase test
ms-agent-harness-dotnet:declarative-gen-ui --d6 --isolate --rebuild`

| | result |
|---|---|
| **RED** (pristine stale fixture + drifted renderers) | `state=red`,
exit 1 — turn 1 `waitForTurnComplete: turn 1 did not complete within
90000ms (reason=surface-missing, runsFinished=1, count=41)`; body showed
the stale "Quarterly KPIs / $1.24M / SIGNUPS 8,420" surface +
`generate_a2ui` looping |
| **GREEN** (fix applied) | `state=green`, exit 0 — `1 passed` |

## Visual verification

Drove all 4 turns via Playwright with network-level route injection of
`x-aimock-context: ms-agent-harness-dotnet` (replicating the
harness/production proxy). Confirmed real painted surfaces:
- **turn 1 sales-dashboard**: 4 KPI metrics ($4.2M revenue, 186
customers, 31% win rate, $22.6k deal) + Revenue-by-Region donut +
Monthly-Revenue bar
- **turn 2 rep-quota**: rep-attainment `DataTable` (Dana Whitfield 124%
… Elena Vasquez 71%) + quota-attainment `BarChart`
- **turn 3 at-risk**: 3 severity `StatusBadge` cards (Northwind /
Cascadia / Atlas) + KPI metric strip
- **turn 4 top-account**: 7 `InfoRow` account facts (Meridian Apparel
Group) + product-line `PieChart`

Screenshots under
`~/.local/share/copilotkit/cr/2ndwave-shots/msharness-turn{1..4}-*.png`.
2026-07-18 16:19:57 -07:00
Jordan Ritter c13415f743 fix(showcase): repair claude-sdk-typescript D6 gen-ui-declarative (surface-missing) (#6055)
## What

Repairs the `claude-sdk-typescript` D6 `gen-ui-declarative` cell, which
was **red on turn-1 surface-missing**. The aimock fixture carried stale
D5-era prompts and only a partial turn-1 outer entry, so the two-stage
Anthropic A2UI flow (outer `generate_a2ui` → secondary `render_a2ui` →
narration) never painted a surface for the four current sales-analyst
pills.

Fan-out of the proven pattern from the pilots (#6051 claude-sdk-python,
#6052 mastra, #6053 ms-agent-python). This is the Anthropic two-stage
family — `injectA2UITool: false`, backend owns `generate_a2ui` +
secondary `render_a2ui`.

## Root cause

`aimock/d6/claude-sdk-typescript/gen-ui-declarative.json` had the old D5
prompts (`Show me a quick KPI dashboard` / `pie chart of sales by
region` / `bar chart of quarterly revenue` / `status report on system
health`) plus one lone turn-1 outer entry for the new sales prompt. The
driver's four current prompts had no complete triads, so aimock returned
`STRICT: No fixture matched` and no surface mounted.

## Fix (3 layers)

1. **Fixture re-author** — 12 fixtures (4 pills × {outer
`generate_a2ui`, inner `render_a2ui`, narration}) in the two-stage
shape, mirroring the #6051 sibling + google-adk data. Render payloads
mount the per-pill catalog components the driver asserts: Metric×4 + Pie
+ Bar (sales-dashboard); DataTable + Bar (team-performance); Metric×3 +
StatusBadge×3 (at-risk); InfoRow + Pie (top-account). Render payloads
are byte-identical to #6051.

**Ordering/matcher fix vs a naive python mirror:** the CSTS runtime
accumulates full conversation history across pills, so on turns 2-4 the
outer `generate_a2ui` call carries prior pills' tool results and a
`hasToolResult:false` matcher never fires. Each pill triad is ordered
**narration (`toolCallId`) first** so it claims the last-role:tool
calls, and the outer matcher drops `hasToolResult` and gates on
`userMessage` + `toolName generate_a2ui` (last-role:user). Verified live
via the aimock journal.

2. **InfoRow testid** — add `data-testid="declarative-info-row"` to the
InfoRow renderer (turn-4 top-account parity; CSTS was missed by #6050).

3. **Suggestions refresh** — `suggestions.ts` had stale D5-era pill
labels that emitted unmatched prompts (live 404 banner). Now the four
sales-analyst pills, matching google-adk.

## Red → green proof (control-plane, slot 30, `--isolate --rebuild`)

**RED (origin/main):**
```
✗ d6:claude-sdk-typescript/gen-ui-declarative red — state=red
[aimock] STRICT: No fixture matched for POST /v1/messages   (×6)
```

**GREEN (fixed):**
```
✓ d6:claude-sdk-typescript/gen-ui-declarative green — 1 passed
```
aimock journal after the green run: **all 12 calls returned 200, zero
503, zero no-match** across all 4 turns.

## Visual proof (Playwright, `x-aimock-context: claude-sdk-typescript`)

Drove all 4 pills live; per-turn DOM testid counts (no fixture error on
any turn):

| Turn | Pill | Newly-mounted testids |
|---|---|---|
| 1 | sales-dashboard | metric=4, pie=1, bar=1 |
| 2 | team-performance | data-table=1, bar +1 (→2) |
| 3 | at-risk | status-badge=3, metric +3 (→7) |
| 4 | top-account | **info-row=7**, pie +1 (→2) |

Screenshots:
`~/.local/share/copilotkit/cr/2ndwave-shots/csts-turn{1..4}-*.png`.

## Unit tests
- `scripts/__tests__/aimock-fixtures.test.ts`: 837 passed (validates the
new fixture shape).
- `harness/src/probes/scripts/d5-gen-ui-declarative.test.ts`: 31 passed.

## Notes
- Worktree tsc module-not-found / TS2322-ButtonProps noise is benign
symlink noise; trust the PR's real `check-types` CI check.
- Local red-green ran on slot 30 (slot 8 and several low slots were held
by concurrent isolate stacks).
2026-07-18 16:19:54 -07:00
Jordan Ritter 29ed44d913 fix(showcase): flip agno gen-ui-declarative D6 cell green (4-turn sales flow + DataTable/InfoRow parity) (#6054)
## What

Flips the `d6:agno/gen-ui-declarative` cell from **red (turn-1
dom-missing) → green**. Fan-out of the proven 2nd-wave declarative fix
pattern (pilots #6051 claude-sdk-python, #6052 mastra, #6053
ms-agent-python).

## Root cause (verified at the request level)

agno's declarative-gen-ui shipped a **stale D5 aimock fixture** keyed on
the old prompts (KPI dashboard / pie chart of sales by region / bar
chart / status report), while the current D6 driver sends the OSS-136
sales prompts. Captured from the aimock journal on a RED run: agno's
OUTER agent hit aimock with `tools=[generate_a2ui]`, `userMessage="Show
me my sales dashboard for this quarter."`, `x-aimock-strict:true`,
`context=agno` — the stale fixture matched **none**, aimock returned 503
(strict), the outer agent never emitted `generate_a2ui`, no surface
rendered → turn-1 dom-missing → `state=red`.

## Backend family + north-star

agno uses the plain **`render_a2ui` two-stage** family
(`src/agents/a2ui_dynamic_agent.py`): an OUTER `generate_a2ui(context:
str)` tool, then a forced-`render_a2ui` secondary call, then narration.
Mirrored the **google-adk** green north-star (same VantageThreads
surfaces + `declarative-gen-ui-catalog`; `render_a2ui` args copied
verbatim).

Key agno-specific wrinkle: the inner secondary call's **user message is
hardcoded and identical across all four pills** ("Generate a dynamic
A2UI dashboard based on the conversation."), so the inner `render_a2ui`
fixtures cannot key on `userMessage`. They discriminate on
`toolName:render_a2ui` + `context:agno` + a `systemMessage` substring
equal to the per-pill context phrase the outer injects ("Conversation
context:\n<context>"). aimock's CLI server uses substring matching, so
this works; verified live against the journal.

## Fix layers

- **Fixture** (`aimock/d6/agno/gen-ui-declarative.json`): 4 sales
prompts × 3 calls (outer/inner/narration) = 12 fixtures.
- **Renderers** (`.../a2ui/renderers.tsx`): `declarative-info-row`
testid on InfoRow (turn 4) + new `DataTable` renderer with
`declarative-data-table` testid (turn 2), mirroring google-adk.
- **Definitions** (`.../a2ui/definitions.ts`): `DataTable` schema,
`Metric.trendValue`, `z.unknown()` PrimaryButton action, refreshed
descriptions.
- **Backend** (`a2ui_dynamic_agent.py`): sales-analyst system prompt for
live-mode steering.
- **Test** (`scripts/__tests__/aimock-fixtures.test.ts`):
`KNOWN_DUPLICATE_CEILING` 297→300 (+3) — the 4 inner render fixtures
collapse to one `toolName=render_a2ui` matchKey (matchKey omits
systemMessage/context) but aimock's router disambiguates them at
runtime.

### Bug caught during green

First green attempt still red with a client-side exception: adding
`Row`/`Column`/`Text` to `myDefinitions` **without matching renderers**
(agno relies on `includeBasicCatalog:true` for those) made
`createCatalog` produce a definition set wider than its renderer set →
render crash. Fixed by not declaring Row/Column/Text in definitions.

## Local RED → GREEN proof (isolated D6 slots)

**RED** (control-plane, stale fixture):
```
✗ d6:agno/gen-ui-declarative red — state=red
```

**GREEN** (control-plane, after fix):
```
✓ d6:agno/gen-ui-declarative green
1 passed
```

**GREEN** (`--direct`, per-turn DOM assertions — authoritative):
```
turn 1/4 — assertions passed   (sales-dashboard: metric×4 + pie + bar)
turn 2/4 — assertions passed   (team-performance: data-table + bar)
turn 3/4 — assertions passed   (at-risk: status-badge×3 + metric×3)
turn 4/4 — assertions passed   (top-account: info-row + pie)
state=green, 1 passed
```

GREEN aimock journal: 12 requests = 4× OUTER `generate_a2ui` + 4× INNER
`render_a2ui` + 4× narration, all matched.

**Live Playwright visual** (all 4 surfaces painted, testids counted):
turn1 metric×4/pie/bar, turn2 data-table×1/bar, turn3
status-badge×3/metric, turn4 info-row×7/pie.

`aimock-fixtures.test.ts`: 837 passed after the ceiling bump.
2026-07-18 16:19:50 -07:00
Jordan Ritter 3d8eff5e70 fix(showcase): repair ms-agent-python D6 gen-ui-declarative (surface-missing) (#6053)
## Summary

The D6 e2e-full probe `d6:ms-agent-python/gen-ui-declarative` was
failing at turn 1 with `reason=surface-missing`. Two independent root
causes, both fixed at the layer the real captured request revealed.

### Root cause 1 — stale aimock fixture
`showcase/aimock/d6/ms-agent-python/gen-ui-declarative.json` still
carried the old D5 pill prompts (KPI / pie / bar / status) plus a lone
outer `generate_a2ui` entry for the current sales-dashboard prompt with
**no** matching inner `_design_a2ui_surface` and **no** narration. The
backend looped `generate_a2ui` to its invocation limit and
`RUN_FINISHED` was blocked while the tool call stayed active (`Cannot
send 'RUN_FINISHED' while tool calls are still active`).

Re-authored to the current 4 VantageThreads sales prompts, mirroring the
**llamaindex green north-star** for this backend shape
(ms-agent-python's inner tool is `_design_a2ui_surface`, not
google-adk's `render_a2ui`):
- outer `generate_a2ui` returns a `context` steering phrase — the
ms-agent-framework session does not surface the latest user message to
the secondary LLM, so the phrase becomes the inner call's
`user_content`;
- the inner `_design_a2ui_surface` fixture matches that phrase (not the
full prompt);
- `hasToolResult` discriminates outer (false) vs narration (true).

This also eliminates the stale `render-a2ui.json` "KPI dashboard"
catch-all collision that was rendering the wrong (KPI) surface for the
sales prompt.

### Root cause 2 — renderer / catalog drift
`renderers.tsx` and `definitions.ts` for ms-agent-python's
declarative-gen-ui lagged the green cluster — missing the `DataTable`
and `InfoRow` components (the `declarative-data-table` /
`declarative-info-row` testids that turns 2 and 4 assert), plus
`Metric.trendValue` and the `Row`/`Column`/`Text` gap overrides. Brought
both files to parity with the langgraph-python / google-adk green
cluster.

## Red → Green proof (real control-plane surface)

`SHOWCASE_ISO_SLOT=11 ./bin/showcase test
ms-agent-python:declarative-gen-ui --d6 --isolate`

| | result |
|---|---|
| **RED** (pristine fixture + stale renderers) |
`d6:ms-agent-python/gen-ui-declarative = red` — exit 1, turn 1
`surface-missing` |
| **GREEN** (fix applied) | `d6:ms-agent-python/gen-ui-declarative =
green` — exit 0, `1 passed` |

## Visual verification

Drove all 4 turns via Playwright (header-injected `x-aimock-context:
ms-agent-python` to replicate the harness/production proxy). Confirmed
real painted surfaces:
- **turn 1 sales-dashboard**: 4 KPI metrics ($4.2M revenue, 186
customers, 31% win rate, $22.6k deal) + Revenue-by-Region pie +
Monthly-Revenue bar
- **turn 2 team-performance**: rep-quota `DataTable` + attainment
`BarChart`
- **turn 3 at-risk**: 3 `StatusBadge` severity cards + KPI metric strip
- **turn 4 top-account**: 7 `InfoRow` account facts + product-line
`PieChart`

Screenshots captured under
`~/.local/share/copilotkit/cr/2ndwave-shots/mspy-turn{1..4}-*.png`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-18 16:19:46 -07:00
Jordan Ritter c01bde3759 fix(showcase): flip mastra gen-ui-declarative D6 cell green (#6052)
## What

Flips the `d6:mastra/gen-ui-declarative` showcase cell from **red** to
**green**.

The cell failed turn 1 with `reason=surface-missing` (the dom-missing
family). Reading the real failure surface (aimock logs + mastra
container logs + harness worker logs) exposed **three** distinct
defects, each fixed at its own layer.

## Root cause (empirically confirmed)

1. **Stale aimock fixture.** `aimock/d6/mastra/gen-ui-declarative.json`
carried the OLD D5 pill prompts (`"Show me a quick KPI dashboard"`,
`"pie chart of sales by region"`, ...) and the stale inner tool name
`_design_a2ui_surface`. The current D6 driver sends 4 different prompts,
so aimock matched **0** fixtures:
   ```
[aimock] STRICT: No fixture matched for POST /v1/responses (x2 = the
two-stage flow's outer + inner calls)
   ```
   Nothing emitted → no render → `surface-missing`.

2. **Mastra outer-tool arg schema (mastra-specific).** Unlike the green
`google-adk` peer whose outer `generate_a2ui` takes `{}`, mastra's
`generateA2uiTool` (`integrations/mastra/src/mastra/tools/index.ts`) has
an `inputSchema` that **requires a `messages` array**. After
re-authoring the fixture to the green shape (which emits `generate_a2ui`
with `{}`), the mastra runtime rejected it:
   ```
Tool input validation failed for generate_a2ui — messages: Required.
Provided arguments: {}
   ```
The outer tool never executed → no `a2ui_operations` container →
`surface-missing` (narration bubble rendered, but no A2UI surface).

3. **Renderer testid parity.** The D6 probe DOM-asserts
`declarative-data-table` (turn 2) and `declarative-info-row` (turn 4).
Mastra's `renderers.tsx` had **no DataTable renderer at all** and its
InfoRow renderer **lacked the `data-testid`**; `definitions.ts` had no
DataTable definition. Turns 2 and 4 could never satisfy their
assertions.

## Fix (3 files)

- **`aimock/d6/mastra/gen-ui-declarative.json`** — re-authored to the
current 4 driver prompts + the green two-stage shape (outer
`generate_a2ui` + inner forced `render_a2ui`, `context: "mastra"`,
`catalogId: "declarative-gen-ui-catalog"`, per-pill narration). Each
outer `generate_a2ui` call now carries a valid `messages` array (mastra
schema requirement).
- **`integrations/mastra/.../a2ui/definitions.ts`** — added the
`DataTable` catalog definition (mirrors the green `google-adk` peer).
- **`integrations/mastra/.../a2ui/renderers.tsx`** — added
`data-testid="declarative-info-row"` to the InfoRow renderer, and added
a `DataTable` renderer carrying `data-testid="declarative-data-table"`.

## Red → Green proof (real control-plane surface, slot 13, `--rebuild`)

Command: `SHOWCASE_ISO_SLOT=13 ./bin/showcase test
mastra:declarative-gen-ui --d6 --isolate --rebuild`

**RED (pristine main):**
```
✗ d6:mastra/gen-ui-declarative red — state=red
turn 1 did not complete within 90000ms (reason=surface-missing)
```

**GREEN (fixed):**
```
✓ d6:mastra/gen-ui-declarative green
1 passed
[conversation-runner] turn 1/4 — assertions passed   (metric x4, pie, bar; baseline 0)
[conversation-runner] turn 2/4 — assertions passed   (data-table NEW, bar)
[conversation-runner] turn 3/4 — assertions passed   (status-badge x3, metric x3)
[conversation-runner] turn 4/4 — assertions passed   (info-row NEW, pie)
[conversation-runner] conversation completed successfully { turnsCompleted: 4 }
```

## Visual evidence

Drove the live cell through all 4 turns via Playwright (route-level
`x-aimock-context` injection) and screenshotted each painted surface —
all real renders, no error states:

- **turn 1** sales-dashboard: 4 KPI metric tiles + donut PieChart
(Revenue by Region) + BarChart (Monthly Revenue)
- **turn 2** team-performance: DataTable (Rep attainment: Dana Whitfield
124%, ...) — the new renderer
- **turn 3** at-risk: 3 severity cards each with a StatusBadge + 3 KPI
metric tiles
- **turn 4** top-account: Card of InfoRow facts (Owner/Region/ARR/...) +
PieChart — the new testid

Note: `google-adk` remains the only D6 declarative fixture already on
the current prompts; the other integrations (`langgraph-typescript`,
etc.) still carry the same stale-fixture shape and are a follow-up wave.
2026-07-18 16:19:43 -07:00
Jordan Ritter 17663f90e1 fix(showcase): complete claude-sdk-python declarative gen-ui D6 (4-turn sales flow + DataTable/InfoRow parity) (#6051)
## What

Fixes the `claude-sdk-python` **gen-ui-declarative** D6 showcase cell,
which was red with `reason=done-signal-missing`.

## Root cause

The aimock fixture
(`showcase/aimock/d6/claude-sdk-python/gen-ui-declarative.json`) still
carried the **legacy D5 pills** (KPI dashboard / pie / bar / status
report) plus a single stray hero `generate_a2ui` entry. The current D6
driver uses the **4-prompt sales-analyst flow**:

1. "Show me my sales dashboard for this quarter."
2. "How are our sales reps performing against quota?"
3. "Are any accounts or pipeline deals at risk this quarter?"
4. "Pull up the details on our biggest account."

None of those turns had matching fixtures. The backend
(`src/agents/a2ui_dynamic.py`) runs a two-stage flow — an outer
`generate_a2ui` call, then a secondary `render_a2ui` call — and both hit
aimock's `/v1/messages` in STRICT mode, which 404'd (`No fixture
matched`). An unmatched turn never emits the expected render, so the run
never produced the done signal for that turn.

Two additional **renderer/definition parity gaps** on claude-sdk-python
(present on google-adk, absent here) blocked full green:
- Turn 2 asserts `declarative-data-table` — the `DataTable` catalog
component (definition + renderer) did not exist.
- Turn 4 asserts `declarative-info-row` — the `InfoRow` renderer was
missing that testid.

## Fix

- **Fixture** re-authored into the two-stage Anthropic-transport shape
(mirrors the `claude-sdk-typescript` sibling for match discriminators +
google-adk for the A2UI payload data). Per turn: (a) outer
`generate_a2ui` emit matched by
`userMessage`+`toolName`+`hasToolResult:false`, (b) inner `render_a2ui`
design matched by `toolName`, (c) outer narration matched by
`toolCallId`. Covers all 4 prompts.
- **DataTable** catalog component added (`definitions.ts` +
`renderers.tsx`, testid `declarative-data-table`).
- **InfoRow** renderer given the missing `declarative-info-row` testid.

## Local red→green proof

Control-plane, isolation slot 12, `--isolate --rebuild` (real failure
surface, not `--direct`):

**RED** (pristine code + pristine fixture):
```
✗ d6:claude-sdk-python/gen-ui-declarative  red  (state=red)  0 passed, 1 failed
aimock: STRICT: No fixture matched for POST /v1/messages  (x6)
```

**GREEN** (fix applied, same slot, rebuilt):
```
✓ d6:claude-sdk-python/gen-ui-declarative  green  1 passed
aimock: 0 no-match
```

## Visual verification

Playwright drive with the harness `X-AIMock-Context: claude-sdk-python`
header, all 4 turns painted with the exact per-testid deltas the probe
asserts:

| Turn | Newly-mounted testids |
|------|----------------------|
| 1 sales-dashboard | metric ×4, pie-chart ×1, bar-chart ×1 |
| 2 team-performance | data-table ×1, bar-chart ×1 |
| 3 at-risk | status-badge ×3, metric ×3 |
| 4 top-account | info-row ×7, pie-chart ×1 |

Screenshots confirm the DataTable (rep attainment table) and InfoRow
(Meridian Apparel Group facts) render correctly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-18 16:19:39 -07:00
Jordan Ritter 45cdc02ee0 fix(showcase): re-author langroid declarative gen-ui to the two-stage A2UI north-star (D6 turn-1 surface-missing)
The langroid `declarative-gen-ui` D6 cell was red at turn 1 with
`reason=surface-missing`: the demo was still on the pre-D6 (D5-era) shape
while the google-adk / strands siblings had moved to the two-stage
dynamic-schema A2UI pattern. Four independent defects each blocked the
surface from painting; all four are fixed here to bring langroid to
sibling parity.

Root causes (each verified against a live isolated stack):

1. Stale suggestions + fixtures. `suggestions.ts` still offered the old
   D5 pills ("Show a KPI dashboard", "pie chart of sales by region", …)
   and `aimock/d6/langroid/gen-ui-declarative.json` only mocked those old
   prompts. The D6 driver sends the four current business-question pills
   ("Show me my sales dashboard for this quarter.", etc.). Re-authored both
   to the four current prompts, mirroring the google-adk north-star
   (outer `generate_a2ui` no-arg → inner forced `render_a2ui` → outer
   narration, three fixtures per pill).

2. Required `context` on the outer tool. `GenerateA2UITool.context` was a
   required pydantic field, so the mocked outer call's `arguments: {}`
   raised `ValidationError` before the tool could run — no inner call, no
   surface. Made it optional (default "") to match the no-arg sibling tools.

3. Legacy functions API hid the inner tool from aimock's matcher. The
   inner planner call used langroid's `functions=`/`function_call=` (legacy
   OpenAI) path; aimock's `toolName` matcher only inspects the modern
   `tools[]` array, so the inner `render_a2ui` fixture never matched and the
   call fell through to the outer `generate_a2ui` fixture (empty surface,
   wrong catalogId). Switched the inner call to the modern
   `tools=`/`tool_choice=` API via a new `_RENDER_A2UI_TOOL_SPEC`. The
   response extractor already reads the modern `oai_tool_calls` path first.

4. Inner call could not be discriminated per pill. langroid has no
   framework middleware to forward the run's conversation into the inner
   call (unlike ag_ui_adk / ag_ui_strands), so its inner user message was a
   fixed generic string across all four pills. Added an explicit
   last-user-turn thread (ContextVar set by the adapter, consumed by the
   planner) so the pill prompt rides as the inner `userMessage` — the same
   discriminator the sibling fixtures rely on.

Renderer/catalog parity: added the missing `declarative-info-row` testid
to InfoRow (turn 4) and a full `DataTable` definition + renderer
(`declarative-data-table`, turn 2), plus `trendValue` on Metric. Added
`sales-context.ts` (byte-identical dataset + composition rules to the
strands/google-adk siblings) and wired it via `chat.tsx`.

Red-green (isolated control-plane, slot 16):
- RED (3 runs, pre-fix): `d6:langroid/gen-ui-declarative` red,
  turn-1 surface-missing.
- GREEN (2 runs, post-fix, --rebuild): 4/4 turns pass. aimock journal
  confirms all four pills' outer `generate_a2ui` + inner `render_a2ui`
  calls return 200 and emit the correct `declarative-gen-ui-catalog`
  surfaces. Backend SSE verified per turn: turn-1 4 metrics + pie + bar,
  turn-2 DataTable, turn-4 7 InfoRows.
2026-07-18 16:02:47 -07:00
Jordan Ritter bf75022c78 fix(showcase): repair ms-agent-dotnet D6 gen-ui-declarative (surface-missing)
The D6 e2e-full probe d6:ms-agent-dotnet/gen-ui-declarative failed at turn 1
with reason=surface-missing. Two root causes fixed at the layer the real
captured backend behaviour revealed.

Root cause 1 - stale aimock fixture. The fixture still carried the old D5
pill prompts (KPI/pie/bar/status) plus a lone outer generate_a2ui entry for
the sales-dashboard prompt with no matching inner _design_a2ui_surface, so
turn 1 never produced a surface. Re-authored to the current 4 VantageThreads
sales prompts mirroring the llamaindex/ms-agent-python green north-stars for
this _design_a2ui_surface backend family (outer generate_a2ui returns a
context steering phrase; the inner _design_a2ui_surface fixture matches that
phrase).

Unlike llamaindex/ms-agent-python, the ms-agent-dotnet ChatClientAgent session
ACCUMULATES prior-turn tool results into each subsequent turn's request, so
hasToolResult is true from turn 2 onward and cannot discriminate outer vs
narration (turn 2+ would short-circuit straight to narration, no surface).
The narration is therefore keyed on the CURRENT turn's outer toolCallId
(aimock only matches toolCallId when the LAST message is that tool result)
and ordered before the outer per pill so the tool-result turn resolves to
narration while the user-message turn resolves to the outer.

Root cause 2 - renderer/catalog drift. The declarative catalog lagged the
green cluster: InfoRow was missing its declarative-info-row testid (turn 4)
and DataTable was absent entirely (turn 2). Added the testid and the DataTable
renderer + definition, matching the green cluster.

Red -> Green (real control-plane, --isolate --rebuild):
  RED:   d6:ms-agent-dotnet/gen-ui-declarative = red (turn 1 surface-missing)
  GREEN: d6:ms-agent-dotnet/gen-ui-declarative = green (1 passed)

Visual: drove all 4 turns via Playwright (X-AIMock-Context: ms-agent-dotnet)
- turn 1 4 KPI metrics + region pie + monthly bar
- turn 2 rep-quota DataTable + attainment bar
- turn 3 3 severity StatusBadges + KPI metric strip
- turn 4 7 account InfoRows + product-line pie
2026-07-18 15:22:45 -07:00