Commit Graph

12629 Commits

Author SHA1 Message Date
GeneralJerel cbc3bac399 docs(shell-docs): correct OpenBox recipe accuracy per review 2026-06-24 09:58:57 -07:00
Jordan Ritter b507054f51 fix(showcase): map AG-UI multimodal content to native pydantic-ai types (fix assert_never) (#5675)
## Summary
The pydantic-ai showcase integration crashed with `assert_never` in
pydantic-ai's `_map_user_prompt` whenever AG-UI multimodal
`InputContent` (images / documents / binary, data: and url: sources)
reached the model — AG-UI content types were never normalized to the
native pydantic-ai types (`str` / `ImageUrl` / `BinaryContent` /
`DocumentUrl`) the mapper requires.

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

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

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

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

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

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

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

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

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

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

## How it was reviewed

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

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

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

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

## Test plan

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

Please review the replay/primary-signal approach. Not auto-merging.
2026-06-24 09:57:13 -07:00
Jordan Ritter ab76e3167b fix(showcase): harden promote-notify alerting + debt cleanup (#5674)
## Summary

Hardening + pre-existing-debt cleanup of the showcase promote-notify
Slack renderer (follow-up to #5657, scoped to deferred review items).

**Fail loud on a dropped page-the-humans alert.** The #oss-alerts
failure cross-post used to pipe the Slack API response to `/dev/null`,
so a `200`/`{"ok":false,"error":"channel_not_found"}` silently dropped
the alert that fires when a promote *failed* — on a green job nobody
would notice. Both posts now capture the response via a shared
`slack_alert_posted_ok` predicate; the #oss-alerts page fails the
renderer job loud on a dropped delivery, while the informational thread
reply stays warn-only. The predicate is mirrored byte-identically across
the live workflow and the dry-run helper.

**Debt cleanup.** Removed a dead `failed_count` var, corrected a
misleading `gha_url` comment, and added `^[0-9a-f]{6}$` validation on
the decoded blob `run_id` in the render step so a malformed run_id can't
reach Slack or the run name.

## Test plan
- [x] `promote-notify.bats` 11/11 — predicate edge cases (non-JSON,
malformed, missing/`null` ok), anti-drift parity guard (predicate
identical in both files), and call-site tests locking the #oss-alerts
fail-loud vs thread warn-only exit semantics
- [x] red→green proven: re-adding `|| true` to the #oss-alerts call-site
flips the call-site test red
- [x] shellcheck clean on the dry-run helper
2026-06-24 09:56:54 -07:00
GeneralJerel 3fc2f07f1d docs(shell-docs): write OpenBox governance recipe content 2026-06-24 09:44:05 -07:00
Markus Ecker 2cad274c93 fix(runtime): preserve JSON-schema string constraints in tool conversion
convertJsonSchemaToZodSchema dropped every string constraint except enum,
so a tool param like { type: "string", format: "uuid" } reached the model
as a bare optional string. Models then fill such optionals with "" instead
of omitting them, and the loosened model-side validation accepts "" — only
for a stricter downstream (e.g. an MCP server) to reject it.

Carry format (uuid/email/url/date-time), pattern, and minLength/maxLength
through to the generated Zod schema so the model sees the field's real
shape and the model-side validation matches the server's.
2026-06-24 18:29:00 +02:00
GeneralJerel 112d3d8907 docs(shell-docs): add OpenBox governance cookbook recipe 2026-06-24 09:17:45 -07:00
Jordan Ritter 27b5e79a00 test(showcase): cover pydantic-ai multimodal content mapping + degrade paths 2026-06-24 09:16:07 -07:00
Ran Shem Tov c5554f317f fix(showcase/google-adk): full D6 green — restore post-tool loop, fix interrupt + headless
Brings google-adk to 39/39 D6 (reproduced across two independent full-matrix
runs, zero regressions). Four changes:

- entrypoint.sh: remove ADK_DISABLE_PROGRESSIVE_SSE_STREAMING=1. That flag's
  non-progressive aggregation path ended ADK's agentic loop after the first
  tool round (no post-tool LLM re-invoke), which broke every demo needing a
  second turn: the subagents chain (research -> writing -> critique),
  tool-rendering-reasoning-chain (AAPL -> MSFT), shared-state-read-write's
  confirmation, and the custom-catchall narration. The partial-event abort it
  guarded against is already handled in-callback by stop_on_terminal_text.
- manifest.yaml: un-skip-list tool-rendering-reasoning-chain (now passes with
  the loop restored).
- headless_complete_agent.py: add AGUIToolset() so the frontend highlight_note
  tool is injected and routed to the browser. Removing the flag unmasked this
  pre-existing gap — turn 3 dispatched highlight_note server-side and the
  backend registry rejected it. langgraph-python auto-injects frontend tools;
  ADK needs AGUIToolset() in the agent's tools list.
- aimock/d6/google-adk/gen-ui-interrupt.json: order each pill's narration leg
  (toolCallId) before its emit leg and drop the thread-global hasToolResult
  gate, so the alice pill no longer 503s after the sales pill leaves a tool
  result in the thread.
2026-06-24 18:12:17 +02:00
Jordan Ritter d56dcbbc9d fix(showcase): map AG-UI multimodal content to native pydantic-ai types at the model-call boundary
Flatten AG-UI attachment content into native pydantic-ai content types in
the OUTGOING request only, via a WrapperModel-scoped flatten rather than a
history_processor (so the flatten never persists into ctx.state.message_history
and leak into UI state). Normalize mime types, gate on supported content types,
and degrade unsupported types at the single emission choke point. Fixes the
_map_user_prompt assert_never crash on raw AG-UI multimodal content.
2026-06-24 09:03:19 -07:00
Alem Tuzlak 85518c60bf fix(showcase/built-in-agent): bump tanstack ai-openai to 0.15.6 to fix strict-mode 400 (OSS-132)
@tanstack/ai-openai@0.15.6 (via @tanstack/openai-base@0.9.2) fixes the upstream
isStrictModeCompatible bug: it now detects typeless / z.any() properties and
sends strict:false, so OpenAI accepts them instead of 400ing with
"schema must have a type key".

With the upstream fix the showcase needs no schema change: revert the
z.string() workaround so the state tools use z.any() again (state-tools.ts is
now identical to main). Net change vs main is just the version bump:
@tanstack/ai 0.18.0 -> 0.35.0, @tanstack/ai-openai 0.9.1 -> 0.15.6.

Verified on the real OpenAI Responses API with all three z.any() state tools
attached (AGUISendStateSnapshot / AGUISendStateDelta / set_steps): adapter
emits strict:false, request ACCEPTED, model calls the tool.
2026-06-24 18:02:42 +02:00
Ran Shemtov e744e363ed Merge branch 'main' into claude/strands-d6-green 2026-06-24 17:56:54 +02:00
Jordan Ritter 82523b7297 fix(showcase): harden promote-notify alerting + debt cleanup
Fail loud on a dropped #oss-alerts page: the failure-alert cross-post no longer
swallows a 200/ok:false Slack response, so a dropped page-the-humans alert reds
the renderer job instead of vanishing on a green run. The thread reply stays
warn-only. Both posts capture the response via a shared slack_alert_posted_ok
predicate, mirrored byte-identically across the live workflow and the dry-run
helper.

Debt cleanup: drop a dead failed_count var, correct a misleading gha_url comment,
and validate the decoded blob run_id against ^[0-9a-f]{6}$ in the render step so
a malformed run_id can't reach Slack or the run name.

Tests: predicate edge cases (non-JSON, malformed, missing/null ok), an anti-drift
parity guard asserting the predicate is identical in both files, and call-site
tests locking the #oss-alerts fail-loud vs thread warn-only exit semantics.
2026-06-24 08:55:53 -07:00
Alem Tuzlak 9e10283a37 chore(showcase/built-in-agent): bump tanstack ai to 0.35.0 / ai-openai 0.15.5, drop ai-client (OSS-132)
@tanstack/ai-openai@0.15.5 dropped @tanstack/ai-client from its
peerDependencies (now just zod ^4 and @tanstack/ai ^0.35.0), and the
showcase never imported ai-client anyway. Bump to the latest releases and
remove the ai-client dep entirely.

Tool-schema serialization is unchanged: the z.string() state-tool params
still emit a strict-valid schema (root type:object, snapshot type:string,
strict:true), verified by capturing the outbound /responses tool payload on
0.35.0 / 0.15.5.
2026-06-24 17:22:08 +02:00
Ran Shem Tov cb687d7fb0 chore(showcase): bump strands adapters for A2UI-dynamic completion fix
- @ag-ui/aws-strands 0.2.2 -> 0.2.3 (strands-typescript)
- ag_ui_strands 0.2.1 -> 0.2.2 (strands)

These releases carry the A2UI-dynamic (declarative-gen-ui) run-completion fix:
the auto-injected generate_a2ui now completes after the A2UI surface paints,
so the run emits RUN_FINISHED instead of hanging 'Running'. Should green the
gen-ui-declarative D6 cell on both integrations (-> 35/35) and resolve the
real-LLM staging hang. Pending local D6 re-verify.
2026-06-24 17:16:47 +02:00
Jerel Velarde 670bb17591 Merge branch 'main' into showcase/oracle-agent-memory 2026-06-24 23:03:09 +08:00
Tyler Slaton 947fe2142a fix(core): preserve agent-level headers instead of overwriting them (#5635) (#5637)
Fixes #5635.

## What

Headers set directly on an `HttpAgent` registered via
`agents__unsafe_dev_only` were silently replaced by the provider
headers. Per-agent auth headers (like an `Authorization` for a
self-hosted backend) got dropped, causing 401s.

## Why

`AgentRegistry.applyHeadersToAgent` did `agent.headers = {
...core.headers }`, a full overwrite. The run handler and the react-core
`useAgent` hook did the same. So an agent built with its own headers
lost them on registration, on every `setHeaders`, and before each
request.

## Fix

Merge instead of replace. The registry captures each agent's own headers
once (in a WeakMap, before the first apply) and rebuilds `{
...ownHeaders, ...coreHeaders }`. Core wins on key conflicts, which
keeps the existing "provider headers are authoritative" and logout/clear
behavior. All header application now routes through one method,
`CopilotKitCore.applyHeadersToAgent`, so runs never clobber per-agent
headers.

Vue and Angular benefit too: they dispatch runs through `core.runAgent`
/ `connectAgent`, so the merge is re-applied before every request.

## Tests

- core: 3 new cases in `core-headers.test.ts` (preserve, merge,
retain-across-setHeaders); existing overwrite and clear tests still
pass.
- react-core: new `use-agent-provider-headers.e2e.test.tsx` with a real
provider and an HttpAgent that has its own headers.

Verified locally: format, lint, full core + react-core suites, and both
builds.
2026-06-24 07:45:37 -07:00
Ben Taylor fbf154cb0d feat(core,react-core): add unarchiveThread to v2 thread store + useThreads (#5624)
## Summary

Adds `unarchiveThread(id)` to the v2 thread store (`@copilotkit/core`)
and the `useThreads` hook (`@copilotkit/react-core/v2`), restoring an
archived thread via the existing generic `PATCH /threads/:id { archived:
false }` update path — no new runtime endpoint. Mirrors `archiveThread`
across the store and hook.

This is the durable, architecture-independent piece extracted from the
threads-drawer effort. The drawer UI itself is being restarted as a
framework-agnostic **CopilotDrawer** (Lit web component + React/Angular
wrappers) under a separate spec; this hook method stands on its own and
is needed regardless.

## Testing

TDD. New core store test (`PATCH … { archived: false }`) and
`useThreads` hook test; full suites green (core, react-core).
2026-06-24 08:56:08 -05:00
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
Alem Tuzlak f6fe0870dd fix(showcase/built-in-agent): make state tools strict-mode valid; bump tanstack ai (OSS-132)
The built-in-agent showcase 400s on every prompt against the real OpenAI
Responses API. The state tools (AGUISendStateSnapshot / AGUISendStateDelta /
set_steps) declared their arbitrary payloads as z.any(), which serializes to
a typeless JSON Schema property. @tanstack/openai-base's
isStrictModeCompatible only screens for oneOf/allOf/not/$ref/$defs, so it
misses the missing type, sends the tool with strict:true, and OpenAI
rejects it:

  400 Invalid schema for function 'AGUISendStateSnapshot':
  In context=('properties','snapshot'), schema must have a 'type' key.

This is the real-OpenAI form of OSS-132 (the earlier zod3/zod4 drift that
produced a typeless root is already fixed by the zod 4 migration). It was
masked in production because the deployed showcase runs against aimock,
which replays fixtures without validating the request schema.

Fix: declare each arbitrary payload as a JSON-encoded string (z.string() ->
{ type: string }, strict-valid) and parse it in the server handler.
parseJson also tolerates an already-parsed object/array so recorded aimock
fixtures keep working. The TanStack->AG-UI converter reads the parsed
structure off the tool result, so it needs no change. Verified accepted and
correctly round-tripped by gpt-4o on the real Responses API with all three
tools attached.

Also bump @tanstack/ai 0.18.0 -> 0.34.0 and @tanstack/ai-openai 0.9.1 ->
0.15.4, and add the newly-required @tanstack/ai-client 0.18.2 peer. Adapter
schema output is byte-identical across the bump.
2026-06-24 15:34:26 +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
Markus Ecker b0f2a6fbf3 feat(core): memory store registry on CopilotKitCore
Adds MemoryStoreRegistry (mirroring ThreadStoreRegistry) plus
register/unregister/get/getMemoryStores on CopilotKitCore and
onMemoryStoreRegistered/onMemoryStoreUnregistered subscriber hooks, so a
binding can register a per-agent memory store for sharing/lookup and react to
its lifecycle. Memory stores auto-unregister on agent removal, symmetric with
thread stores. Full core suite green (477 tests).
2026-06-24 13:10:11 +02:00
Markus Ecker be13e02d0c fix(core): memory REST path is runtime-relative /memories (not /api/memories)
runtimeUrl is the CopilotKit runtime mount (e.g. /api/copilotkit); the thread
store uses the relative suffix /threads, which the runtime maps to app-api's
/api/threads. Memory mirrors that with /memories -> /api/memories. /api/memories
would have wrongly produced /api/copilotkit/api/memories.
2026-06-24 13:04:40 +02:00
Markus Ecker 6d96bcdfff feat(core): export the memory store from the package barrel
Surfaces the ɵMemory* types, ɵcreateMemoryStore, selectors, and event groups
from @copilotkit/core, mirroring how ./threads is re-exported.
2026-06-24 12:50:32 +02:00
Markus Ecker dbcf64ee04 feat(core): memory store mutations (server-authoritative add/update/remove)
addMemory POSTs, updateMemory PATCHes (supersede — the id changes), removeMemory
DELETEs against /api/memories. The REST response is applied to local state on
resolve (server-authoritative) and the realtime event reconciles idempotently;
mutation promises resolve to the stored memory (add/update) or void (remove).
Adds isMutating state + mutationFinished tracking via a trackMutation helper
mirroring the thread store.
2026-06-24 12:48:54 +02:00
Markus Ecker 6aef784151 feat(core): memory store REST snapshot + context + selectors
setContext bumps a session and triggers GET /api/memories; the snapshot keeps
only user-scoped memories (v1 surfaces user scope only, matching the realtime
stream). Adds listRequested/listFailed + isLoading/error state, refresh(), and
ɵselectMemories/IsLoading/Error selectors, mirroring the thread store's
bootstrap+fetch effects. Note: REST path is /api/memories — reconcile with how
runtimeUrl is configured if it differs from the thread store's /threads base.
2026-06-24 12:43:34 +02:00
Alem Tuzlak c52985889e Merge branch 'main' into feat/bot-surface-parity 2026-06-24 12:38:39 +02:00
Markus Ecker 3426d8c702 feat(core): wire memory realtime — createMemoryStore consumes memory_metadata
Adds the memory_metadata event type + mapper (created/updated -> upsert,
invalidated -> remove, projecting to the public Memory shape) and a
createMemoryStore that reduces those deltas into observable state. The store
rides an injected user_meta event source (the thread store's
ɵobserveUserMetaEvent in production) — one socket, no lifecycle code on the
memory side. select() re-emits on every change, so React/Angular bindings
re-render when a memory arrives. REST snapshot + mutations land next.
2026-06-24 12:34:56 +02:00
Markus Ecker 4492cb1fdc feat(core): expose ɵobserveUserMetaEvent on the thread store
Lets sibling per-user stores (e.g. the upcoming memory store) ride the
single user_meta socket/channel the thread store already owns, instead of
opening a second socket. The socket effect publishes the live channel session
into a BehaviorSubject; observeUserMetaEvent memoizes a shared, per-event
stream over it (one channel.on(event) fanned out to all subscribers, cleared
on teardown). Phoenix stays fully encapsulated — callers get plain payloads.
Scoped to the user_meta channel only. Additive; socket/reconnect/teardown
flow unchanged.
2026-06-24 12:29:31 +02:00
Markus Ecker 8482743484 feat(core): add memory store reducer (snapshot + realtime upsert/invalidate)
First slice of the framework-agnostic MemoryStore, mirroring the thread
store's micro-redux patterns: session-guarded MemoryState, listSucceeded
snapshot population, and memoryUpserted/memoryInvalidated domain reducers
(prepend-new / replace-in-place / remove). Public kind vocabulary
(topical/episodic/operational). Reducer covered by unit tests.
2026-06-24 12:16:23 +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