12090 Commits

Author SHA1 Message Date
Ran Shemtov 505749b75a feat(showcase/google-adk): auto-inject declarative a2ui, un-red recovery demo (#5671)
## What

Brings the google-adk showcase **A2UI** demos to D6 parity with the
langgraph-python / AWS Strands gold standard.

- **Auto-inject**: `declarative-gen-ui` switches from backend-owned
(`get_a2ui_tool`, `injectA2UITool: false`) to runtime auto-injection
(`injectA2UITool: true`, plain agent). The `ag-ui-adk` 0.7.0 adapter
auto-injects `generate_a2ui` via `plan_a2ui_injection`, matching how
langgraph-python and Strands wire the same demo. First time ADK's
adapter auto-injection is exercised in the showcase.
- **Recovery un-red**: removes the "known-failing on purpose" mark from
the `a2ui-recovery` heal e2e and the OSS-374 inner-render-disambiguation
notes (spec, fixture `_meta`, QA doc, agent docstring, route comment).
That premise was stale: on `ag-ui-adk` 0.7.0 the adapter forwards the
run conversation into the inner `render_a2ui` call, so each pill's last
user turn is its own prompt and aimock selects the right per-pill
fixture. Confirmed against the aimock journal.

`a2ui-recovery` stays backend-owned (the only path that surfaces the
recovery loop) and remains ADK-only (OSS-375 tracks langgraph-python
parity).

## Verification

Canonical D6 driver (`showcase test google-adk --d6 --isolate`), per
pill:

- `declarative-gen-ui`: all 4 pills pass under auto-inject
(sales-dashboard metric>=4 + pie + bar; team-performance data-table +
bar; at-risk status-badge>=3 + metric>=3; top-account info-row + pie)
- `a2ui-fixed-schema`: pass
- `beautiful-chat` (5 sub-pills): pass
- `a2ui-recovery` Playwright spec: 3/3 (page-load, heal, exhaust)
- aimock journal confirms `generate_a2ui` was adapter-injected (the
agent declares no tools) then drove the inner `render_a2ui`.

## Scope and honest status

This is **A2UI D6 parity**, not full-slug D6. The google-adk D6
aggregate is still red (33/39): six **non-A2UI** cells fail and are
untouched by this PR: `shared-state-write`,
`tool-rendering-custom-catchall`, `subagents`,
`tool-rendering-reasoning-chain`, `gen-ui-interrupt`,
`gen-ui-headless-complete`. They are pre-existing (D6 is not CI-gated
for google-adk) and out of scope here.
2026-06-24 15:55:53 +02:00
Ran Shem Tov 22a93a05d6 fix(showcase): green strands(+TS) D6 shared-state-read + multimodal cells
Takes d6:strands and d6:strands-typescript from 32/35 to 34/35.

- shared-state-read: the turn-2 fixture leg wrongly pinned turnIndex:0, so the
  aimock matcher skipped it on turn 2 -> 404 -> turn-2 sse-missing. Drop
  turnIndex to mirror the langgraph-python gold-standard fixture.
- multimodal: sample.png/pdf/wav shipped as git-LFS pointers, so deploy/test
  environments without 'git lfs pull' served the ~130-byte pointer text as the
  upload -> the run never started (runsFinished=0). Ship them as regular
  binaries via a per-integration .gitattributes lfs-unset + real bytes,
  mirroring langgraph-python's convention.

Remaining red (gen-ui-declarative) is a Strands A2UI-dynamic run-completion bug
(reproduces on real-LLM staging too): the surface paints but generate_a2ui
never completes, so the run hangs 'Running'. Tracked separately.
2026-06-24 15:49:22 +02:00
Ran Shemtov 4e7adf9103 Merge branch 'main' into claude/adk-a2ui-d6 2026-06-24 15:44:41 +02:00
Benjamin Taylor f90231f4fd feat(core,react-core): add unarchiveThread to thread store and useThreads
Restores an archived thread via the existing generic PATCH /threads/:id
update path with { archived: false } — the same mechanism example apps
already use for restore — so no new runtime route is required. Mirrors
archiveThread across the core thread store and the v2 useThreads hook.
2026-06-24 08:44:40 -05:00
Ran Shemtov 8bfb155f87 feat(a2ui): passing a catalog to the provider auto-enables A2UI and tool injection (#5611)
## What

A catalog passed to the React provider is now enough to use A2UI end to
end:

```tsx
<CopilotKit a2ui={{ catalog }}>
```

Previously you also had to set `a2ui: { injectA2UITool: true }` on the
runtime. Now that second step is gone: ask the agent to render A2UI and
it works, with no other A2UI-related code anywhere.

## How

- **Provider**
([CopilotKitProvider.tsx](packages/react-core/src/v2/providers/CopilotKitProvider.tsx)):
A catalog flips A2UI active locally (renders surfaces + mounts the
catalog context regardless of the runtime flag), and forwards an
`a2uiCatalogAvailable` signal per run via `properties`.
- **Runtime**
([handle-run.ts](packages/runtime/src/v2/runtime/handlers/handle-run.ts)):
parses the run body before configuring middleware (the request is
single-read), derives `providerA2UIHasCatalog` from
`forwardedProps.a2uiCatalogAvailable`, and passes it on.
- **Decision spot**
([agent-utils.ts](packages/runtime/src/v2/runtime/handlers/shared/agent-utils.ts)):
A2UI is enabled when the runtime enables it OR a catalog was forwarded,
and `injectA2UITool` defaults to `true` when a catalog is present.

The downstream backend chain (inject flag -> agent tool awareness) is
unchanged.

## Opt-outs respected

This only fills a default, it never overrides a deeper decision:
- explicit `a2ui: { injectA2UITool: false }` stays `false` (via `??`)
- explicit `a2ui: { enabled: false }` stays disabled (short-circuit)

## Tests

TDD at the runtime layer plus a provider forwarding test:
- catalog forwarded, no `a2ui` config -> middleware applied with
`injectA2UITool: true`
- catalog forwarded + `injectA2UITool: false` -> respected
- catalog forwarded + `enabled: false` -> not applied
- catalog forwarded + `enabled: true` (no flag) -> defaults to `true`
- no catalog, no `a2ui` -> not applied (unchanged)
- provider forwards `a2uiCatalogAvailable` only when a catalog is
present, preserving user properties

Verified: runtime 1538/1538, react-core provider + a2ui suites green,
typecheck clean, build green, oxfmt clean.
2026-06-24 15:36:29 +02:00
Ran Shemtov 5d31ebbfb2 Merge branch 'main' into claude/stupefied-northcutt-12d382 2026-06-24 15:29:16 +02:00
Ran Shem Tov cad17c77b9 feat(showcase/google-adk): auto-inject declarative a2ui, un-red recovery demo
Switch declarative-gen-ui to runtime auto-injection (injectA2UITool: true, plain
agent), matching the langgraph-python / AWS Strands gold standard. The ag-ui-adk
0.7.0 adapter auto-injects generate_a2ui via plan_a2ui_injection. Verified under
the canonical D6 driver: all four declarative pills pass (sales-dashboard,
team-performance, at-risk, top-account), plus a2ui-fixed-schema and
beautiful-chat.

Remove the stale known-failing mark from the a2ui-recovery heal e2e and the
OSS-374 inner-render-disambiguation notes (fixture _meta, QA doc, agent
docstring, route comment). Verified against the aimock journal: ag-ui-adk 0.7.0
forwards the run conversation into the inner render_a2ui call, so each pill
matches its own fixture by userMessage and both heal and exhaust pass. The
recovery demo stays backend-owned (the only path with the recovery loop) and
ADK-only (OSS-375 tracks langgraph-python parity).
2026-06-24 15:29:08 +02:00
Alem Tuzlak db68be6e35 feat(bot): surface parity — reactions, ephemeral messages, modals (#5618)
## Bot surface parity — reactions, ephemeral messages, modals

Brings the three bot adapters (`@copilotkit/bot-slack`,
`@copilotkit/bot-discord`, `@copilotkit/bot-telegram`) up to surface
parity for **reactions**, **ephemeral messages**, and **modals**, with a
shared, capability-gated API in `@copilotkit/bot` and a new modal view
vocabulary in `@copilotkit/bot-ui`. Implements the approved *Bot surface
parity* spec (2026-06-22).

Design invariants held throughout: **degrade, never throw** ·
**capability-gated like `postFile`** · **honest per-platform matrices**.

### D.1 — Reactions
- `bot.onReaction(handler)` + `ReactionEvent`; `thread.react(emoji)` /
`thread.unreact(emoji)` egress.
- Cross-platform emoji normalization table (`EmojiValue`, `emoji`,
`toCanonicalEmoji`/`toPlatformEmoji`/`normalizeEmoji`), tolerant of the
U+FE0F variation selector on both ingress and egress.
- `IngressSink.onReaction`; per-platform ingress + egress:
- **Slack** — `reaction_added`/`reaction_removed`, threads the reply to
the reacted message, skips the bot's own reactions.
- **Discord** — `GuildMessageReactions` + `DirectMessageReactions`
intents and `Message`/`Reaction` partials; shared add/remove handler
skips the bot's own reactions by id, VS16-tolerant cache lookup.
- **Telegram** — `message_reaction` ingress (with `allowed_updates`),
`setMessageReaction` egress, skips the bot's own reactions.

### D.2 — Ephemeral messages
- `thread.postEphemeral(user, ui, { fallbackToDM })` → `EphemeralResult
| null`.
- **Slack** — native `chat.postEphemeral`. **Discord / Telegram** — DM
fallback when `fallbackToDM` is set.

### D.3 — Modals
- New `bot-ui` modal view vocabulary: `Modal`, `TextInput`,
`ModalSelect`, `ModalSelectOption`, `RadioButtons` (distinct from the
message-level `Input`/`Select`).
- `bot.onModalSubmit` / `bot.onModalClose`; `ctx.openModal(...)` on
action + command contexts (gated on `triggerId`).
- **Slack** — Block Kit modals via `views.open`;
`view_submission`/`view_closed` ingress with `response_action`
validation errors.
- **Discord** — text-input modals via `showModal` (≤5 inputs;
selects/radios rejected); pending-interaction registry races the ~3s ack
window; safe modal-submit ack.
- **Telegram** — unsupported (capability advertised `false`).

All optional adapter methods are capability-gated exactly like
`postFile`: absent capability → the call degrades to a no-op/`null`
rather than throwing.

### Tests & quality
- 636 tests across the five packages (bot-ui 21, bot 51, bot-slack 230,
bot-discord 187, bot-telegram 147).
- Pre-push gate clean: per-package typecheck ✓, tests ✓, build ✓, lint 0
errors, lockfile in sync.

### Follow-up backlog (out of subject — not blocking this PR)
Recorded during the review loop as bucket (c)/(d); intentionally
deferred:
- HITL waiter robustness; `getMessages` ordering guarantees;
`resolveUser` caching.
- Slack stream behavior on shared channels; Slack update/delete
empty-ref guard.
- Run-loop error cast; Telegram `setMessageReaction` replace-set
semantics.
- `removeReaction` ok-on-absent normalization (recurring minor;
Discord/Telegram).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-24 14:36:13 +02:00
Alem Tuzlak 16c546b87e fix(bot-discord): resolve reactions against the conversation target channel
addReaction/removeReaction resolved the channel from messageRef.channelId only,
unlike Slack and Telegram which fall back to the target channel. The shipped
example reacts with `{ id }` (no channelId) — the channel is meant to come from
the thread's reply target — so on Discord fetchSendable("") threw, the call
returned { ok: false }, and the 👀/✅/⚠️ acks silently never fired (contradicting
the example's "works on Slack, Discord, and Telegram alike" claim).

Fall back to the target channel when the reacted ref carries no channelId, for
parity with the other two adapters. Adds a regression test.
2026-06-24 14:25:28 +02:00
Alem Tuzlak c52985889e Merge branch 'main' into feat/bot-surface-parity 2026-06-24 12:38:39 +02:00
Ran Shemtov 6a6f551dfc feat(showcase/google-adk): surface ag-ui-adk 0.7.0 A2UI middleware + ADK-only recovery demo (#5662)
## What

Surfaces the OSS-158 A2UI work — now published as **`ag-ui-adk 0.7.0`**
— in the `google-adk` showcase, and adds an explicit **ADK-only
`a2ui-recovery` demo**.

Before this PR, the google-adk A2UI demos ran on a **hand-rolled
`google.genai` planner** (pinned `ag-ui-adk==0.6.3`, predating all the
A2UI work), so the middleware's recovery loop / healing / hard-fail were
never exercised. This re-wires them onto the published middleware
(`get_a2ui_tool`) and makes the recovery behavior visible.

## Key decisions

- **Backend-owned wiring (`injectA2UITool: false`)** for
`declarative-gen-ui` + `beautiful-chat`, replacing the hand-rolled
planner with the middleware's `get_a2ui_tool()` (forced `render_a2ui`
sub-agent + toolkit validate→retry recovery +
`render_as_llm_instructions`/`parse_and_fix` healing +
`a2ui_recovery_exhausted` hard-fail). This matches the **AWS Strands /
ag2 external-framework convention**, not langgraph-python's
runtime-driven `injectA2UITool: true`.
- The explicit `false` is **load-bearing**: the planner now lives in the
ADK middleware, so letting the runtime also inject would double-bind the
tool slot. It's also required post-**#5611** (a provider catalog
otherwise defaults `injectA2UITool` to `true`).
- **`a2ui-recovery` is ADK-only by design.** The validate→retry loop +
hard-fail envelope live in the ADK middleware; the runtime path
langgraph-python uses (`@ag-ui/a2ui-middleware`) is single-pass
parse-and-salvage with **no** recovery loop, so there's no LP parity
reference for this demo (exempt from LP e2e-parity). It reuses the
declarative-gen-ui catalog — no new components.

## Changes (commits)

1. `chore`: bump `ag-ui-adk==0.7.0`
2. `feat`: re-wire `declarative-gen-ui` + `beautiful-chat` onto the
backend-owned middleware path
3. `chore`: remove the dead hand-rolled planner + orphaned
`SalesPipelineAgent` from `main.py` (retire `test_generate_a2ui.py`; fix
the stale manifest highlight)
4. `feat`: add the ADK-only `a2ui-recovery` demo (heal + exhaust pills;
agent/route/page/manifest/qa/aimock-fixture +
`feature-registry`/`constraints` entries)
5. `test`: add `a2ui-recovery.spec.ts` (mirrors the AG-UI dojo recovery
spec)

Depends on `ag-ui-adk 0.7.0` (published to PyPI via ag-ui#2017).

## Verification (in-sandbox, all green)

- **Backend**: import-smoke (41 agents construct); OSS-158 assurance
gate through the exact wiring — sub-agent emit, recovery (invalid→valid,
2 attempts), hard-fail (`a2ui_recovery_exhausted`, capped at 3); a live
trial against real Gemini emitted a real `a2ui_operations` envelope.
- **Python suite**: 62 passed.
- **Static validators**: `generate-registry` ✅, `validate-constraints`
(google-adk OK) ✅, `validate-parity` 20/20 pass ✅, `aimock-fixtures` 818
passed ✅, `validate-fixture-tool-surface` no drift ✅.

## E2E coverage & parity

`a2ui-recovery` is **ADK-only** — the validate→retry recovery loop +
hard-fail envelope live in the `ag_ui_adk` middleware, and
langgraph-python's runtime A2UI path (`@ag-ui/a2ui-middleware`,
single-pass parse-and-salvage) has no equivalent. **There is therefore
no langgraph-python reference for this demo, and the d6 `e2e-parity`
harness skips it by design** (parity-exempt, like `voice` / the `byoc-*`
variants). Its automated coverage is the per-integration Playwright spec
`tests/e2e/a2ui-recovery.spec.ts`, run via `pnpm test:e2e` against the
local stack (aimock + `pnpm dev` with `GOOGLE_GEMINI_BASE_URL` →
aimock).

## ⚠️ Known-failing e2e test, kept on purpose (showcase-aimock
deficiency)

Running `pnpm test:e2e tests/e2e/a2ui-recovery.spec.ts` against the
local stack gives **2 pass / 1 fail**: page-load ✅, **exhaust/hard-fail
✅**, **heal ❌**. The heal failure is **left in deliberately** as a live
demonstration for the Showcase team — it does **not** red CI (these
per-integration specs aren't run for google-adk in CI; we only see it
via a manual run).

**Diagnosis (from the agent logs):** the showcase aimock **cannot
disambiguate the two pills' _inner_ `render_a2ui` sub-agent calls.** The
backend-owned middleware (`get_a2ui_tool`) issues the inner render with
a *generic* render prompt + shared suggestion context, so the "last user
turn" aimock keys on is **not** the pill prompt. Both pills therefore
match the **same** inner fixture (the EXHAUST one, by first-match order)
→ the heal pill **exhausts** (`a2ui_recovery_exhausted`, "Couldn't
generate the UI", 0 `declarative-metric`) instead of healing. The agent
log shows the heal pill running the full 3-attempt loop on the exhaust
payload (`unresolved_child 'never-defined'`).

This is an **aimock harness limitation, not a middleware/demo bug** —
the middleware heals free-form args correctly in the OSS-158 toolkit
gate (in-sandbox) and against real Gemini; the exhaust path proves the
recovery loop + hard-fail envelope + aimock context routing all work
end-to-end in the browser.

Two follow-ups (Linear):
- **OSS-374 — Showcase-aimock inner-subagent disambiguation** — the
harness needs a way to key an inner sub-agent call per pill (e.g. on
`surfaceId`/`intent`, or a per-pill agent/context). Likely **not**
ADK-specific: any backend-owned A2UI integration (Strands/ag2) that
wants per-pill inner renders hits the same wall.
- **OSS-375 — Recovery-demo langgraph-python parity** — `a2ui-recovery`
is ADK-only (LP's runtime A2UI path has no recovery loop); tracked so
the parity exemption is explicit and revisited as other frameworks gain
an equivalent.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-24 11:51:51 +02:00
Ran Shemtov aacc9eaa61 Merge branch 'main' into mark/oss-158-adk-a2ui-showcase 2026-06-24 11:51:41 +02:00
Ran Shemtov e231417ef0 chore(showcase): upgrade @copilotkit packages to 1.61.1 (#5664)
## What

Upgrade every `@copilotkit/*` dependency across the showcase from
`1.60.2` (plus stray `latest` override pins) to an exact `1.61.1` pin,
and regenerate the standalone npm lockfiles to match.

## Changes

- **22 `package.json`**: 20 integrations +
`langgraph-typescript/src/agent` + `shell`. All `@copilotkit/*` deps
pinned to exact `1.61.1` (`core`, `react-core`, `react-ui`, `runtime`,
`sdk-js`, `shared`, `voice`, `a2ui-renderer`, plus the
`web-inspector>core` overrides). The two `latest` override pins are now
exact `1.61.1` to honor the repo's exact-pin discipline.
- **22 `package-lock.json`**: regenerated with `npm install
--package-lock-only --legacy-peer-deps` (matches the `npm ci
--legacy-peer-deps` the Dockerfiles use). Transitive
`@copilotkit/runtime-client-gql` -> `1.61.1` and
`@copilotkit/license-verifier` -> `~0.5.0` came along.
- **`showcase/scripts/showcase-canonical-pins.json`**:
`canonicalCopilotKitVersion` `1.60.2` -> `1.61.1` (the pin SSOT consumed
by `validate-pins.ts`).

## Out of scope (intentionally unchanged)

- `@copilotkit/aimock` stays on its own version line (`1.26.1`).
- Python `copilotkit` SDK was already `0.1.94` in every
`requirements.txt`; no pyproject refs. No-op.
- Root `pnpm-lock.yaml` untouched — the integrations and shells are not
pnpm-workspace members (they ship standalone npm lockfiles).

## Validation

- `validate-pins`: ratchet unchanged (`FAIL=38`, identical hash) — zero
`@copilotkit` drift; the 38 are pre-existing non-copilotkit framework
range-pins already in `fail-baseline.json`.
- `validate-parity`: 20/20 pass.
- `validate-fixture-tool-surface`: no drift.
- `showcase/scripts` vitest: 2102 passed, 5 skipped.
2026-06-24 11:31:03 +02:00
Ran Shem Tov 9b77e8eeed chore(showcase): upgrade @copilotkit packages to 1.61.1
Bump every @copilotkit/* dependency across the showcase integrations and
the shell from 1.60.2 (and stray "latest" override pins) to an exact
1.61.1 pin, and move the canonical pin source of truth to match.
Regenerate each standalone npm package-lock.json with the same
--legacy-peer-deps flag the Dockerfiles use for "npm ci".

- showcase/integrations/*/package.json + package-lock.json
- showcase/integrations/langgraph-typescript/src/agent/*
- showcase/shell/package.json + package-lock.json
- showcase/scripts/showcase-canonical-pins.json: canonical 1.60.2 to 1.61.1

aimock stays on its own version line (1.26.1). The Python copilotkit SDK
was already 0.1.94 across every requirements.txt, so no change there.

validate-pins ratchet is unchanged (FAIL=38, identical hash);
validate-parity, validate-fixture-tool-surface, and the showcase/scripts
vitest suite (2102 tests) all pass.
2026-06-24 10:39:59 +02:00
Mark Fogle 364322987b docs(showcase/google-adk): wire OSS-374/OSS-375 into a2ui-recovery known-failing notes
Replace the "Tracked in Linear" placeholders with the filed issue IDs:
OSS-374 (showcase-aimock inner-subagent disambiguation) and OSS-375
(recovery-demo langgraph-python parity).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 07:51:46 +00:00
Mark Fogle 28d7467fbe docs(showcase/google-adk): document a2ui-recovery heal e2e as a known-failing aimock-deficiency demo
The `heal` e2e test fails against the live showcase aimock because the harness
cannot disambiguate the two pills' inner render_a2ui sub-agent calls (the
backend-owned middleware issues the inner call with a generic render prompt +
shared suggestion context, so the "last user turn" aimock keys on is not the
pill prompt). Both pills match the same inner fixture (the EXHAUST one, by
first-match order), so the heal pill exhausts instead of healing.

This is an aimock harness limitation, not a middleware/demo bug: the middleware
heals free-form args correctly in the OSS-158 toolkit gate and against real
Gemini. Keep the test running (not skipped) as a deliberate demonstration for
the Showcase team; it does not red CI (these specs aren't run for google-adk in
CI). Update the spec header, the heal-test note, the fixture _note, the qa doc,
and the recovery_agent docstring to reflect this accurately (and drop the stale
sequenceIndex description). Tracked in Linear: aimock inner-subagent
disambiguation + recovery-demo LP parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 07:43:27 +00:00
Mark Fogle 1cad3d2ee7 fix(showcase/google-adk): make a2ui-recovery heal deterministic via parse_and_fix (single pass)
The heal fixture relied on per-attempt aimock response switching (sequenceIndex
0=invalid -> 1=valid), but sequenceIndex doesn't advance across the recovery
loop's intra-turn render_a2ui calls in the per-integration Playwright context
(no X-Test-Id), so aimock served invalid on every attempt and the demo never
healed (0 metrics).

Switch the heal pill to a single deterministic response: the inner render_a2ui
returns FREE-FORM args (components/data as JSON strings) that the middleware
heals via parse_and_fix into a valid surface in one pass — no per-attempt
switching. Verified in-sandbox: free-form args -> a2ui_operations with 2 Metric
components. The exhaust pill is unchanged (structurally invalid every attempt ->
a2ui_recovery_exhausted hard-fail, already verified e2e).

Updates the agent docstring, qa checklist, and spec test name/comment to reflect
heal = parse_and_fix healing (not invalid->valid retry). aimock-fixtures schema
test: 818 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 07:00:06 +00:00
Mark Fogle 8d12b29bc0 test(showcase/google-adk): fix a2ui-recovery e2e user-bubble selector for react-core 1.60
The clickPill helper (copied from declarative-gen-ui.spec.ts) matched the user
message with the stale '[data-message-role="user"]' selector, which no longer
exists in @copilotkit/react-core/v2 >= 1.60 — the user bubble is now
'[data-testid="copilot-user-message"]' (CopilotChatUserMessage). The message
dispatched fine; the helper just never found the bubble, timing out before the
heal/exhaust assertions ran. The sibling google-adk specs carry the same stale
selector (they don't run in CI for this integration, so it drifted unnoticed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 06:29:20 +00:00
Tyler Slaton 53bd214818 feat: AG-UI standard interrupt support in useInterrupt (#5480)
## Summary

Adds first-class support for the **AG-UI standard interrupt protocol**
to `useInterrupt`, while keeping the legacy `on_interrupt` custom-event
path working (additive, no breaking changes).

The AG-UI spec
([concepts/interrupts](https://docs.ag-ui.com/concepts/interrupts))
pauses a run via `RUN_FINISHED` with `outcome: { type: "interrupt",
interrupts: Interrupt[] }` and resumes via a `resume: ResumeEntry[]`
array. CopilotKit previously only spoke the non-standard `on_interrupt`
+ `forwardedProps.command.resume` dialect; the standard resume path was
literally unreachable.

## What changed

- **core** — `CopilotKitCore.runAgent` forwards a standard `resume:
ResumeEntry[]` to `agent.runAgent` (no-op when omitted), making the spec
resume path reachable.
- **react-core `useInterrupt`** — dual-path detection (standard
`RUN_FINISHED outcome:interrupt` + `pendingInterrupts`, alongside legacy
`on_interrupt`); surfaces `interrupt` (primary) + `interrupts` (full
open set); `resolve(payload?, interruptId?)` / `cancel(interruptId?)`
that accumulate per-interrupt responses and auto-submit a spec `resume`
array once all open interrupts are addressed; expiry handling via
`isInterruptExpired`; re-exports `Interrupt` / `ResumeEntry` /
`ResumeStatus`.
- **vue** — full parity, incl. `CopilotChatMessageView.vue` slot
bindings.
- **react-native** — re-exports the standard interrupt types.
- **docs** — 4 shell-docs pages (`human-in-the-loop/useInterrupt`,
`headless`, and both reference pages).

`responseSchema` is surfaced on `interrupt` but not validated
client-side (the agent stays authoritative, per spec).

## Notable fix

vue legacy `resolve` now defers clearing the interrupt card to
`onRunStartedEvent` — matching react-core **and** vue's own standard
path. Previously it cleared synchronously, reintroducing a UX regression
react-core had a dedicated test/comment guarding against.

## Verification

- Tests green: react-core **1285**, vue **19**, core resume **2**
- `nx run-many -t build` green across core / react-core / react-native /
vue
- oxlint/oxfmt clean across all changed files
- Independent integration review **APPROVED**; multi-agent code review
converged (only doc + a vue-parity issue, all fixed; a
multi-interrupt-without-id safety warning was added)

## Deferred (separate follow-up PR — pre-existing, out-of-subject)

Surfaced incidentally during review, untouched by this PR:
- `run-handler.ts` parallel tool-call results spliced in reverse order
(`:623`/`:763`)
- wildcard tool handler not passed the abort `signal`
- `runTool` follow-up runs even after abort (missing the
`processAgentResult` abort guard)
- `isArgumentError` computed but never read (dead field)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-23 21:19:33 -07:00
Mark Fogle 133305d2eb test(showcase): bump catalog cell counts for the a2ui-recovery feature
Adding a2ui-recovery to the shared feature-registry cross-joins it across all
20 integrations (wired for google-adk, unshipped elsewhere), so the catalog
generator's hardcoded counts move: cross-join 920->940, metadata.total_cells
900->920, LGP 46->47 (unshipped 7->8). docs_only stays 20; the relative
sum-checks are unchanged. Fixes the Validate Showcase failure on #5662.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 04:10:10 +00:00
Tyler Slaton 2291fc545f fix(react-core): resolve interrupt typecheck failures 2026-06-23 21:08:37 -07:00
Tyler Slaton f330e9b795 fix(runtime): fail loud on malformed approval request 2026-06-23 20:56:33 -07:00
Mark 01b8b47fd2 Merge branch 'main' into mark/oss-158-adk-a2ui-showcase 2026-06-23 20:55:28 -07:00
Tyler Slaton a2f936ec61 style: format config files 2026-06-23 20:52:56 -07:00
Tyler Slaton a13c3ee663 chore: merge main into PR 5480 2026-06-23 20:50:16 -07:00
github-actions[bot] 580b0b683e style: auto-fix formatting 2026-06-24 03:42:22 +00:00
Tyler Slaton e3dfff8e18 fix(showcase): make strands-typescript demos work (+ strands-python parity) (#5632)
## Summary

Bug hunt on the **strands-typescript** showcase integration, comparing
every flagged demo against the reference **strands (Python)**
integration on real OpenAI (gpt-4o). Every showcase-side fix was applied
to **both** integrations for 1:1 parity. Each flagged issue was
classified as showcase-side (fixed here), upstream `@ag-ui/aws-strands`
adapter (chipped, not fixed here), or shared-with-Python.

## Fixed here (showcase) — verified working on real LLM, both
integrations

- **readonly-state-agent-context** — agent ignored `useAgentContext`
("Who am I?" hallucinated). Root cause: neither adapter surfaces
`RunAgentInput.context` to the model. Fix: lift `context` into the
prompt in `buildStatePrompt` / `build_state_prompt`. (Same context
channel also feeds open-gen-ui — see below.)
- **open-gen-ui** + **open-gen-ui-advanced** — model answered in plain
text instead of calling `generateSandboxedUi`. The
`openGenerativeUI.designSkill` is delivered via the same dropped
`context` channel; with context now lifted into the prompt, an added
imperative makes the model call the tool. Advanced cell also clarifies
sandbox functions are iframe→host bridges, not LLM tools.
- **hitl-in-chat** — "Schedule a 1:1 with Alice" sometimes produced no
time-picker (the shared backend `schedule_meeting` tool won over the
frontend `book_call` HITL). Fix: sharpen `book_call` to own scheduling
intents.
- **tool-rendering catch-all** ("Roll a d20" / "Chain tools") — no dice
tool existed. Fix: add a `roll_dice` tool (shared python tools + TS).

## Also

- **Register strands-typescript as a dashboard baseline partner**
(`BASELINE_PARTNERS`) so it gets its own coverage column like
langgraph-typescript. D5 (e2e-deep) is **green** for strands-typescript.

## Deferred / out of scope (by decision)

- **a2ui-fixed-schema** and **declarative-gen-ui** — both depend on A2UI
(`generate_a2ui`) which strands-typescript intentionally does not ship
yet. Pre-existing; left intact to handle later. (My exploratory route
opt-in was reverted.)
- **multimodal** attachments fail with a git-LFS pointer error — a
shared deploy-environment issue (same on Python), not a demo bug; base
chat works.

## Upstream (ag-ui `@ag-ui/aws-strands` TS adapter) — chipped, not fixed
here

These are genuine TS-vs-Python adapter divergences; Python's adapter
handles both. Tracked as task chips to land upstream first, then
incorporate:

- **U2 — empty tool-result content** (`agent.ts:178`): the adapter sends
`{text:""}` for empty render-tool results → OpenAI 400
(`STRANDS_ERROR`). Breaks beautiful-chat + chart demos (renders, then
the run dies and poisons the thread). Python coerces to a non-empty
placeholder.
- **U1 — INCOMPLETE_STREAM on parallel tool calls** (`agent.ts`
~1730-1756 / ~1864-1921): emits `RUN_FINISHED` without draining
`TOOL_CALL_END` for parallel calls; rejected by the AG-UI client verify
layer. Trips the catch-all "Chain tools" pill.

## Verification

- All fixes confirmed via real-LLM browser testing on both
strands-typescript (:3119) and strands-python (:3112) production
containers.
- `oxlint` 0 errors, `oxfmt` clean. D5 strands-typescript green. Both
containers rebuilt + booted clean.
- Note: shell-dashboard `vitest` could not run in the worktree
(`@vitejs/plugin-react` transitive dev-dep absent); the only
test-affecting change is the `BASELINE_PARTNERS` count 26→27, verified
by inspection — CI runs the suite authoritatively.

## Not yet green (separate follow-up)

D6 (e2e-full) for strands-typescript is 25/35; the remaining reds are
pre-existing (newly-merged) demo/fixture gaps and one harness-level
`__name` page.evaluate error — unrelated to this bug hunt.
2026-06-23 20:37:38 -07:00
Mark Fogle c1d03df20b test(showcase/google-adk): add a2ui-recovery e2e spec (mirrors AG-UI dojo recovery spec)
Playwright spec for the ADK-only a2ui-recovery demo, mirroring the AG-UI dojo
reference (apps/dojo/e2e/tests/adkMiddlewareTests/a2uiRecovery.spec.ts):
assert the STABLE end-states and skip the transient 'Retrying...' label
(threshold-gated/timing-dependent).

- heal pill: recovered surface paints (>=2 declarative-metric tiles), no
  hard-failure UI, no render-error banners
- exhaust pill: 'Couldn't generate the UI' shows (A2UIRecoveryStates on the
  a2ui_recovery_exhausted envelope), no faulty surface ever paints, chat stays usable

Selectors verified against @copilotkit/react-core/v2 A2UIRecoveryStates (failure
text) and the showcase declarative catalog testids. Clears the validate-parity
'no e2e spec' warning. Runs in the browser-e2e stack (frontend + aimock + agent_server).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 03:21:24 +00:00
Tyler Slaton f4a1e7fc36 fix(showcase): restore CI for strands PR 2026-06-23 19:46:43 -07:00
Ran Shem Tov f0ce7a7175 feat(showcase): wire dynamic A2UI declarative-gen-ui for strands + strands-typescript
Implements the Declarative Generative UI (A2UI dynamic schema) demo for both
the strands (Python) and strands-typescript integrations, bringing them to 1:1
parity with the canonical langgraph-python demo.

Backend:
- Add a dedicated dynamic-A2UI agent (a2ui_dynamic.py / buildA2uiDynamicAgent)
  that wires no generate_a2ui tool. The runtime route sets
  injectA2UITool: true + defaultCatalogId "declarative-gen-ui-catalog"; the
  Strands adapter auto-injects generate_a2ui and drives a secondary render_a2ui
  planner. StrandsAgentConfig.a2ui supplies the catalog id and a
  composition_guide (sales dataset + composition rules) so the planner is
  self-contained.
- Mount the agent on the /declarative-gen-ui sub-path and point the route's
  HttpAgent at it, mirroring the a2ui-fixed-schema pattern.
- Bump ag_ui_strands 0.1.9 -> 0.2.1 (Python): the A2UI auto-injection config
  (default_catalog_id, guidelines, render planner) landed in 0.2.0. Pulls
  ag-ui-a2ui-toolkit transitively. strands-typescript already on 0.2.2.

Frontend:
- Port the langgraph-python declarative-gen-ui frontend (suggestions,
  sales-context, definitions incl DataTable, renderers, chat) so the demo
  matches the shared D5/D6 probe's sales-analyst pills and catalog components.

Fixtures:
- Author gen-ui-declarative D6 fixtures for both slugs from the langgraph-python
  template (3 legs per pill: outer generate_a2ui, inner render_a2ui, narration).

Verified on real OpenAI (gpt-4o): both integrations paint a real A2UI surface
(4 Metric tiles + Revenue-by-Region PieChart + Monthly Revenue BarChart +
narration), matching the probe's expected testids and counts.

Note: the gen-ui-declarative D6 cell currently cannot go green because the
shared harness hits "ReferenceError: __name is not defined" in the probe's
page.evaluate (turn 1 preFill), before any agent call. That harness-tooling bug
is owned separately; the fixtures and wiring here are correct and the real-LLM
paint confirms the expected surface.
2026-06-23 19:46:43 -07:00
Ran Shem Tov 620cf00974 fix(showcase): green strands + strands-typescript D6 cells via id-invariant fixtures + real multimodal assets
Strands mints UUID tool_call_ids for tool results (confirmed via the aimock
journal), so the langgraph-python toolCallId-keyed follow-up fixtures never
matched and the agent re-emitted the tool, looping (text-unstable) across
frontend-tools, gen-ui-agent, gen-ui-open, gen-ui-open-advanced,
gen-ui-headless-complete, reasoning-chain and the weather pills. Re-key the
affected multi-leg fixtures to the id- and thread-history-invariant
sequenceIndex pattern (as built-in-agent does), for both integrations.

Also:
- Add write_document poem/email/quantum fixtures for shared-state-streaming
  (was a stale single fixture that 404d).
- Strip content from reasoning-chain tool legs (content+toolCalls in one
  fixture is undefined behavior); reasoning rides toolCalls alone.
- Narrow the over-broad d4 summarize catch-all to "Summarize the sales
  pipeline" so it stops shadowing gen-ui-agent competitor pill; matches
  langgraph-python.
- Commit the real multimodal sample.png/sample.pdf (were git-LFS pointers
  the harness could not resolve).
2026-06-23 19:46:43 -07:00
Ran Shem Tov 74208d8299 feat(showcase): add write_document tool, drop get_weather stopStreamingAfterResult (strands, strands-typescript)
Add a write_document tool + document state hook to both Strands agents
so the shared-state-streaming demo can stream a document into
state.document (mirrors langgraph-python StateStreamingMiddleware). Strands
updates state from the full tool args, which the D6 probe tolerates.

Remove the get_weather stopStreamingAfterResult / stop_streaming_after_result
guard from both agents so weather concludes with a natural follow-up turn,
matching the langgraph-python gold standard; the accompanying multi-turn
id-invariant weather fixtures prevent the aimock replay loop without it.
2026-06-23 19:46:43 -07:00
Ran Shem Tov 405d78d8e6 fix(showcase): shim __name + crypto.randomUUID in D6 harness page contexts
The D6 fleet worker drives each integration over its insecure Docker
origin (http://<slug>:10000), where crypto.randomUUID is undefined so
hand-rolled headless chats threw and never mounted (sse-missing). tsx/
esbuild also wraps named inner functions in __name(...) calls that leak
into page.evaluate and throw __name is not defined.

Add installBrowserContextShims (init-scripts.ts): a __name no-op helper
and a crypto.randomUUID secure-context polyfill, registered via
addInitScript at document_start of every D6 page; wired into the
d6-all-pills newPage goto path.
2026-06-23 19:46:43 -07:00
Ran Shem Tov f404791ecd chore(showcase): bump @ag-ui/aws-strands 0.2.0 -> 0.2.2 (strands-typescript)
0.2.2 ships the adapter fixes for the two upstream defects this hunt surfaced:
empty tool-result content (render-tool demos -> OpenAI 400) and RUN_FINISHED
emitted before parallel tool calls drain (INCOMPLETE_STREAM). With the bump,
beautiful-chat, the chart demos, and the catch-all 'Chain tools' pill complete
cleanly. Verified on real gpt-4o.
2026-06-23 19:46:43 -07:00
Ran Shem Tov a5dea4164c feat(showcase): render a2ui-fixed-schema via dedicated agent + A2UIMiddleware
Both integrations: a dedicated `a2ui_fixed_schema` backend agent exposes a
`display_flight` tool that returns the A2UI `a2ui_operations` envelope
(createSurface -> updateComponents -> updateDataModel) built from a fixed,
pre-authored flight layout targeting the page's `copilotkit://flight-fixed-catalog`
catalog. Mirrors the upstream ag-ui dojo a2ui_fixed_schema demo (ag-ui#2021)
adapted to the showcase's existing catalog/renderers.

- TS returns the envelope as an object (lands in a json content block); Python
  returns it as a JSON string (text block) — per each SDK's tool-return shape.
- Enable the runtime A2UIMiddleware on the a2ui-fixed-schema route
  (injectA2UITool: false, defaultCatalogId pinned) so it detects the envelope
  and paints; the agent emits the envelope itself, so no generate_a2ui injection.
- Mount the agent on the /a2ui-fixed-schema sub-path; point the route there.

Verified on real gpt-4o: the flight card paints on both integrations.
2026-06-23 19:46:43 -07:00
Ran Shem Tov e223fe0fb9 feat(showcase): register strands-typescript as a dashboard baseline partner
Add strands-typescript to BASELINE_PARTNERS so it gets its own coverage
column alongside its Python sibling (mirroring how langgraph-typescript sits
beside langgraph-python). Bump the partner-count assertion 26 -> 27.
2026-06-23 19:46:43 -07:00
Ran Shem Tov 0978b4f5ad fix(showcase): make strands + strands-typescript context, open-gen-ui, hitl, and dice demos work
Apply the same fixes to both the strands (Python) and strands-typescript
integrations for 1:1 parity:

- Lift RunAgentInput.context into the prompt (buildStatePrompt /
  build_state_prompt) so useAgentContext values (readonly-state-agent-context)
  and the openGenerativeUI design-skill / sandbox-function context actually
  reach the model. The adapter does not surface context on its own; this
  mirrors langgraph's lift-context-into-prompt pattern.
- open-gen-ui: prepend an imperative to the visualization design skill (and add
  a design skill to the advanced cell) so the model calls generateSandboxedUi
  instead of answering in plain text, and clarify that sandbox functions are
  iframe->host bridges, not LLM tools.
- hitl-in-chat: sharpen the book_call description so it wins scheduling intents
  over the shared backend schedule_meeting tool (which renders no picker).
- Add a roll_dice tool (shared python tools + TS tools) so the tool-rendering
  catch-all 'Roll a d20' and 'Chain tools' pills work.
2026-06-23 19:46:43 -07:00
Jordan Ritter b7d9e1f0e1 chore(showcase): remove redundant harness-legacy service from SSOT and fixtures (#5658)
## Why

The pool-fleet migration is **complete**. The control-plane harness plus
the prod workers (deployed 2026-06-19, `HARNESS_ROLE=worker`, pool count
2) are live in both envs and cover every probe dimension the interim
`harness-legacy` fleet-migration bridge was holding live.
`harness-legacy` is now dead config.

This PR is the **code-side cleanup only**. The live Railway
`harness-legacy` service is torn down separately (see follow-up below).

## What changed

- **SSOT** (`showcase/scripts/railway-envs.ts`): removed the entire
`harness-legacy` entry and the now-dead `key === "harness-legacy"`
special-case branch in `computePromoteClosure`. Updated stale doc
comments that referenced legacy.
- **Stale `harness-workers` comment fixed**: it claimed the worker is
"STAGING-ONLY". The prod worker is in fact live on Railway (deployed
2026-06-19, `HARNESS_ROLE=worker`, pool count 2). Comment now states
workers run in BOTH envs, while flagging that this SSOT entry still
models only the staging instance — see ambiguity note below.
- **Generated artifact**: regenerated `railway-envs.generated.json` (41
-> 40 services).
- **Golden snapshot** (`__tests__/fixtures/railway-envs.golden.json` +
its test header): dropped the `harness-legacy` block.
- **Fixtures / expectation sets**: removed `harness-legacy` from the
`GATE_IGNORED` sets in `__tests__/verify-railway-image-refs.test.ts`,
the `envsFor` assertion + the `computePromoteClosure` exclusion test in
`railway-envs.test.ts`, the promote-notify fixtures (`success.json`,
`partial.json`, `total-failure.json`), and the `redeploy-env.ts` doc
comments. Updated service-count assertions (41 -> 40) in
`railway-envs.test.ts`, `__tests__/verify-railway-image-refs.test.ts`,
and `__tests__/emit-railway-envs-json.test.ts`.
- **Ruby spec**
(`bin/spec/test_promote_single_service_fleet_invariants.rb`): renamed
the synthetic prod-only fixture name from `harness-legacy` to
`deprecated-prod-only-svc` (a fabricated example name with no SSOT
coupling; renamed to avoid implying the SSOT still carries legacy).

## Red-green proof

This is a behavior change to the SSOT. A new assertion (`railway-envs
SSOT > does NOT contain harness-legacy`) was added and observed FAILING
with the entry still present, then PASSING after removal.

**RED** (assertion added, SSOT entry still present):
```
 FAIL  railway-envs.test.ts > railway-envs SSOT > does NOT contain harness-legacy (fleet-migration bridge retired)
AssertionError: expected [ Array(41) ] to not include 'harness-legacy'
 ❯ railway-envs.test.ts:137:23
    136|     const names = listServiceNames();
    137|     expect(names).not.toContain("harness-legacy");
       |                       ^

 Test Files  1 failed (1)
      Tests  1 failed | 103 skipped (104)
```

**GREEN** (SSOT entry removed + fixtures updated):
```
 Test Files  4 passed (4)
      Tests  171 passed (171)
```
(`railway-envs.test.ts`, `__tests__/verify-railway-image-refs.test.ts`,
`__tests__/railway-envs.golden.test.ts`,
`__tests__/emit-railway-envs-json.test.ts`)

Ruby spec:
```
5 runs, 42 assertions, 0 failures, 0 errors, 0 skips
```

## Pre-push gates

- Formatter (`oxfmt --check`): green
- Lint (`oxlint`): 0 errors (2 pre-existing warnings in
`redeploy-env.ts`, unrelated)
- Typecheck (`tsc -p scripts/tsconfig.json`): green for changed files (1
pre-existing unrelated error in `generate-search-index.ts`, present on
the clean base)
- Tests: full `scripts` suite 2086 passed / 12 skipped. (Two test files
hit a flaky `/tmp/...-generated-data.lock` EEXIST parallel-mkdir race;
they pass when run serially and do not touch `harness-legacy`.)

## Follow-up infra step (NOT in this PR)

- [ ] Delete the live Railway `harness-legacy` service (id
`11279eba-97eb-417e-82a5-7cb4254eb147`) from **both** staging and prod
environments. Manual infra step handled separately by the orchestrator.

## Ambiguity note (prod-worker SSOT entry)

The `harness-workers` SSOT entry currently declares **only** a `staging`
env — there is no `prod` env entry, even though a prod worker is live on
Railway. Per task scope I did **not** invent the prod `serviceInstance`
config (it isn't verifiable from the repo), so I only corrected the
comment to stop asserting "staging-only" and left the entry shape
unchanged. Backfilling a real `prod` env entry is a separate follow-up.

---

## Update: README count sync + code review

Added commit `5859ae4` (`docs(showcase): sync promote-notify README
counts...`): the harness-legacy removal dropped each promote-notify
fixture by one service, so `test-fixtures/promote-notify/README.md` was
updated 29/26/29 → **28/25/28** (counts derived from the fixtures via
`jq`, not hand-set).

**Code review:** 11-agent CR round + 11-agent confirmation round, both
converged to **0 mandatory (bucket a) findings**; Procedure 3 bucket-(c)
promotion audit returned **PROMOTE_TO_A = 0**. The harness-legacy
removal is clean — no dangling SSOT references, generated JSON 41→40
exact, no behavior change.

### Follow-up backlog (NOT this PR — surfaced by CR)

_Distinct-subject (candidate spin-off PRs):_
- **prototype-key hardening** — `verify-railway-image-refs.ts:268`
`findUntrackedServices` uses bare `SERVICES[name]` instead of
`Object.hasOwn`; a Railway service named `constructor`/`toString` is
silently treated as tracked → Railway↔SSOT drift false-negative (real
bug, 2 reviewers).
- **harness-workers prod-backfill** — SSOT declares only a staging env
though a prod worker is live; promote-notify fixtures encode
harness-workers as a promote outcome vs SSOT-skipped;
`serviceId===prodInstanceId` smell.
- **promote-notify fixture fleet completeness** — fixtures model a
28-service fleet, omit the 12 `starter-*` services (live fleet = 40).

_Pre-existing subject-neutral nits:_ `emit-railway-envs-json.ts`
`--check` uncurated-crash edge; `serviceEnvPairs()` unconsumed export;
orphaned `promote-notify/validate.sh`; stale "starter-* staging-only"
rationale in the Ruby fleet spec; truncation-suffix/`failed_count`
README contract with no fixture coverage; unguarded `sibling` nil in the
Ruby spec.
2026-06-23 18:05:51 -07:00
Jordan Ritter 543a700c54 fix(showcase): record pydantic-ai generate_a2ui fixture
Add the missing "Show me my sales dashboard for this quarter." pill to
the pydantic-ai gen-ui-declarative D6 fixture set: an outer turn
(generate_a2ui, no args) plus the matching _design_a2ui_surface inner
turn carrying the dashboard component payload (KPI metrics row + revenue
pie + monthly-revenue bar), mirrored from the langgraph-python canonical
and the ms-agent-dotnet equivalent.

Closes the staging pydantic-ai D-chat 503 (no_fixture_match): the backend
hits aimock with tools=[generate_a2ui] for this userMessage under
x-aimock-context: pydantic-ai, but only ms-agent-dotnet had the fixture.

Deterministic canonical mirror (no real-LLM recording); contains only the
request-match shape and the A2UI response — no credentials.
2026-06-23 17:57:52 -07:00
Jordan Ritter ec646bbf4f Merge remote-tracking branch 'origin/main' into chore/remove-harness-legacy-ssot
# Conflicts:
#	showcase/scripts/railway-envs.generated.json
#	showcase/scripts/railway-envs.ts
2026-06-23 17:56:18 -07:00
Mark Fogle 06e6de87c7 feat(showcase/google-adk): add ADK-only a2ui-recovery demo (OSS-158 recovery made visible)
A new demo that surfaces the ag_ui_adk >= 0.7.0 validate->retry recovery loop
+ recovery-exhausted hard-fail envelope, rendered as the building/retrying/
failed lifecycle by @ag-ui/a2ui-middleware >= 0.0.10. Two pills, driven by
aimock fixtures that key the inner render_a2ui call by recovery attempt
(sequenceIndex):
- HEAL: attempt 0 structurally-invalid (unresolved child) -> heals to valid
- EXHAUST: invalid on every attempt -> a2ui_recovery_exhausted fallback

Backend-owned (get_a2ui_tool, recovery.maxAttempts=3, injectA2UITool=false),
reusing the declarative-gen-ui catalog + sales context. ADK-only by design:
the recovery loop lives in the ADK middleware; langgraph-python's runtime A2UI
path (@ag-ui/a2ui-middleware single-pass) has no equivalent, so there is no LP
parity reference (exempt from LP e2e-parity). Ported from ag-ui
examples/.../a2ui_recovery.py.

Wires: recovery_agent.py + registry; copilotkit-a2ui-recovery route;
demos/a2ui-recovery page/chat/suggestions; manifest demo+feature; qa spec;
aimock fixture; feature-registry + constraints (constrained-explicit) entries.

Verified in-sandbox: import-smoke (41 agents, recovery cfg), generate-registry +
validate-constraints + validate-parity (20/20 pass) + aimock-fixtures (818) +
python suite (62) all green. Browser e2e (lifecycle render + sequenceIndex
routing) + e2e spec are the documented outside-sandbox steps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 00:49:18 +00:00
Jordan Ritter eaa3a0a3fd fix(showcase): honest promote-notify message + durable healthcheckPath tracking (#5657)
## Summary

Two related fixes to the showcase promote pipeline:

**Promote-notify Slack message — say what actually happened.**
Previously a partial promote rendered a message that read as if
*everything* failed, with no indication of which services promoted vs.
failed. Now the message:
- announces `🚂 Promoting showcase → prod (N): <names>` with a legible
count
- on a partial/failed outcome leads with `Promoted:` / `Failed:` (one
header each, then bullets) so you can see exactly which services
succeeded and which didn't
- reports real wall-clock elapsed (integer-coerced; degrades to omitting
the phrase if metadata is unparseable)
- drops the constant `verify-prod:` legend line that preceded the
meaningful data

**Durable healthcheckPath tracking — stop the silent prod drift.** The
promote pin path and the provisioning path could leave a service's
Railway `healthcheckPath` diverged from intent (this is what left aimock
answering on the wrong path in prod). Now:
- `healthcheckPath` is tracked per-service/per-env in the SSOT
(`railway-envs`)
- the promote pin re-asserts it (omit-when-absent — never sends `null`)
- `deploy-to-railway` provisioning routes through `isTrackedService` /
`resolveProvisionHealthcheck`, so a tracked-null service correctly
*omits* the healthcheck while an untracked one keeps the `/api/health`
default

## Test plan
- [x] `test_promote_healthcheck_reassert.rb` — pin re-asserts SSOT
healthcheck, omits when absent, never null (3 runs / 11 assertions)
- [x] `deploy-to-railway.healthcheck.test.ts` + `railway-envs.test.ts` +
`emit-railway-envs-json.test.ts` (121 tests)
- [x] snapshot-ivar lint green (2 runs / 32 assertions)
- [x] notify renderer dry-run mirrors success / partial / total-failure
shapes
2026-06-23 17:44:44 -07:00
Mark Fogle 0e7b803c3e chore(showcase/google-adk): remove dead hand-rolled A2UI planner + SalesPipelineAgent
The hand-rolled google.genai generate_a2ui planner (and the orphaned
SalesPipelineAgent that consumed it) in main.py are superseded by the
ag_ui_adk 0.7.0 middleware (get_a2ui_tool), now wired backend-owned in
declarative_gen_ui_agent.py / beautiful_chat_agent.py. main.py is reduced to
the shared tool wrappers + before_model/before_agent callbacks still covered
by tests; dead A2UI imports pruned.

- delete tests/python/test_generate_a2ui.py (tested the removed planner)
- manifest declarative-gen-ui: drop stale src/agents/main.py highlight + fix description

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 00:32:32 +00:00
Jordan Ritter 5859ae4057 docs(showcase): sync promote-notify README counts after harness-legacy removal
PR #5658 dropped harness-legacy from the fixtures but the README still
showed pre-removal counts, contradicting the edited fixtures.
2026-06-23 17:19:29 -07:00
Jordan Ritter 0809c25dac chore(showcase): remove redundant harness-legacy service from SSOT and fixtures
The pool-fleet migration is complete: the control-plane harness plus the
prod workers (deployed 2026-06-19, HARNESS_ROLE=worker, pool count 2) now
cover every probe dimension the interim `harness-legacy` fleet-migration
bridge was holding live, so `harness-legacy` is dead config.

This is the code-side cleanup only:
- Remove the `harness-legacy` entry from the railway-envs SSOT and the
  now-dead `key === "harness-legacy"` special-case in computePromoteClosure.
- Regenerate railway-envs.generated.json (41 -> 40 services).
- Drop harness-legacy from the golden snapshot, the gateIgnore expectation
  sets, the promote-notify fixtures, and the redeploy-env doc comments;
  update the service-count assertions (41 -> 40).
- Fix the stale "STAGING-ONLY" harness-workers comment: prod workers are
  live on Railway, though this SSOT entry still models the staging
  instance only (no prod env backfilled here yet).

The live Railway `harness-legacy` service is torn down separately as a
follow-up infra step.
2026-06-23 17:05:26 -07:00
Jordan Ritter 59e18693eb feat(showcase): honest promote-notify message + durable healthcheckPath SSOT tracking
Promote-notify Slack message: name the promoted AND failed services (one
Failed: header + bullets), legible "(N): <names>" count, real wall-clock
elapsed (integer-coerced), and drop the constant verify-prod legend line.

Durable healthcheckPath: track it per-service/env in the SSOT (railway-envs),
re-assert it on the promote pin path (omit-when-absent, never null), and route
deploy-to-railway provisioning through isTrackedService/resolveProvisionHealthcheck
so a tracked-null service omits the healthcheck while an untracked one keeps the
/api/health default — fixing the silent prod-healthcheck drift that refused aimock.

Tests: ruby pin-reassert spec + deploy-to-railway healthcheck spec + emit/golden/accessor.
2026-06-23 16:56:16 -07:00
Mark Fogle 55f4f43afd feat(showcase/google-adk): surface 0.7.0 A2UI middleware (backend-owned) in declarative-gen-ui + beautiful-chat
Replace the hand-rolled google.genai A2UI planners (in main.py and
beautiful_chat_agent.py) with the published ag-ui-adk >= 0.7.0 middleware
sub-agent via get_a2ui_tool(), surfacing OSS-158 (forced render_a2ui
sub-agent + toolkit validate->retry recovery loop + recovery-exhausted
hard-fail envelope + render_as_llm_instructions / parse_and_fix healing).

Wiring is BACKEND-OWNED (injectA2UITool: false), matching the AWS Strands /
ag2 external-framework convention rather than langgraph-python's
runtime-driven injectA2UITool: true. Backend-owned is required: the planner
now lives in the ADK middleware, so letting the runtime also inject would
double-bind the tool slot. The explicit  is load-bearing post
CopilotKit#5611 (a provider catalog otherwise defaults injectA2UITool to true).

- declarative_gen_ui_agent / beautiful_chat_agent: tools include
  get_a2ui_tool({model, default_catalog_id}); beautiful keeps its other tools.
- shared_chat: add get_a2ui_model() to resolve a concrete Gemini BaseLlm for
  the sub-agent (mirrors get_model's aimock-proxy wiring).
- routes: injectA2UITool stays false (declarative + beautiful-chat).
- registry/agent_server: no spec-level a2ui config needed (tool is agent-owned).

Verified vs published 0.7.0 + real Gemini: backend-wired generate_a2ui emits
a2ui_operations; OSS-158 gate (subagent + recovery invalid->valid + hard-fail
a2ui_recovery_exhausted) all retained through this exact wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 23:19:45 +00:00
Mark Fogle ebd87a23f3 chore(showcase): bump google-adk to ag-ui-adk 0.7.0 (OSS-158 A2UI middleware)
Pins the published middleware that ships the A2UI auto-inject + toolkit
recovery + render_as_llm_instructions/parse_and_fix path. Foundational
step; the A2UI agent re-wire onto that path follows under verification
once 0.7.0 is on PyPI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 23:19:45 +00:00
Sam Julien e8c2cd5005 docs(shell-docs): show threads in authored framework nav 2026-06-23 15:42:01 -07:00
Sam Julien 7e54dbe746 docs(shell-docs): place threads with chat UI guides 2026-06-23 15:42:01 -07:00