Commit Graph

15598 Commits

Author SHA1 Message Date
Alem Tuzlak b44a043304 Merge origin/main into feat/typegen
Keep the AgentId and Register typegen types. Drop the @copilotkit/cli package. That CLI belongs in the Intelligence repo.
2026-08-31 12:42:57 +02:00
Ran Shemtov 119cc4eb66 feat(showcase): MAF Python A2UI error-recovery demo on agent-framework 1.2.0 (#6738)
## What

Brings the just-released A2UI support and the latest Microsoft Agent
Framework
(Python) into the showcase, and moves the MAF A2UI demos onto the native
subagent / auto-inject technique so they match the langgraph-python
reference
instead of the pre-1.2.0 hand-rolled path.

- **Bump MAF to 1.2.0.** `agent-framework-ag-ui[a2ui]==1.2.0`,
  `agent-framework-openai==1.14.0`, `agent-framework-core==1.15.0`,
`ag-ui-a2ui-toolkit==0.0.4`. 1.2.0 is A2UI's first release; the `[a2ui]`
extra
  pulls the toolkit.

- **New `a2ui-recovery` demo** (the one A2UI demo the integration lacked
vs
langgraph-python). Backend-owned via the adapter's native `enable_a2ui`
(`injectA2UITool: false`), which runs the shared toolkit's
validate/retry
recovery loop in-process. Heal pill recovers a malformed first render;
exhaust
pill hits the attempt cap and surfaces the `a2ui_recovery_exhausted`
fallback.
Adds the agent, route, page, chat, suggestions, a D6 fixture, and an e2e
spec.

- **Migrate `declarative-gen-ui` to native A2UI auto-injection.**
Removed the
  hand-rolled `generate_a2ui` (a raw secondary OpenAI call to
`_design_a2ui_surface`); the agent now binds no A2UI tool and the route
sets
`injectA2UITool: true`, so the adapter's `plan_a2ui_injection`
auto-injects the
native `generate_a2ui` sub-agent. Reworked the D6 fixture to the native
  `render_a2ui` shape.

- **Migrate `beautiful-chat` to native A2UI auto-injection.** Same
change for the
flagship composite: removed its hand-rolled `generate_a2ui`, flipped the
route
to `injectA2UITool: true`. The adapter now auto-injects the native
sub-agent
  alongside the agent's own tools (todos, query, flights).

- **`a2ui-fixed-schema` unchanged** — it is the fixed-schema pattern
(client
  authored schema, agent streams data via a backend `display` tool,
  `injectA2UITool: false`), which langgraph-python does identically. Not
  hand-rolled generation.

- **Docs.** Enriched the MAF A2UI docs page into real "how to A2UI"
content
(dynamic / fixed / recovery) and connected the A2UI demos via
`docs-links`.

## Why native (not the dojo's example agents)

The showcase mirrors langgraph-python's frontend-catalog + auto-inject
pattern,
not the AG-UI dojo's `a2ui_config` example agents. Before 1.2.0 the MAF
adapter
had no native A2UI, so the showcase hand-rolled `generate_a2ui`. 1.2.0
ships the
native path, so these demos now use it and match langgraph 1:1.

## Validation

- `validate-pins` clean (FAIL count + hash unchanged), `validate-parity`
PASS,
  `generate-registry` clean.
- **D6 (full frontend, aimock replay), all green:**
- `a2ui-recovery` — heal paints the recovered surface; exhaust shows the
    hard-failure UI.
- `declarative-gen-ui` — all 4 dashboard pills paint on the auto-inject
path
    (confirms the runtime forwards `injectA2UITool: true` and
    `plan_a2ui_injection` fires).
  - `beautiful-chat` — regression across all 5 features (pie/bar chart,
schedule-meeting, search-flights, toggle-theme): wrapping the multi-tool
    agent in the A2UI planner loop does not break its non-A2UI tools.
- **AG-UI protocol layer** (published 1.2.0 wheel + aimock): recovery
  (heal/exhaust), declarative, and beautiful-chat all stream real
`a2ui_operations` / `a2ui_recovery_exhausted`, RUN_FINISHED, no
RUN_ERROR.

- **Remove the last hand-rolled A2UI (default agent).** The
general-purpose
default agent (`agent.py`, catch-all `/` endpoint) also carried a
hand-rolled
`generate_a2ui`; removed it (the default agent no longer offers A2UI,
matching
langgraph's default agent). Stripped the stale `_design_a2ui_surface`
fixture
residue and refreshed the e2e-spec comments that described the old
mechanism.

After this PR the MAF-python integration has **zero hand-rolled A2UI
anywhere
except the fixed-schema demo** (which is the intended fixed-schema
pattern,
identical to langgraph). The shared `tools/generate_a2ui.py` module is
intentionally left intact — it is symlinked by other integrations (ag2,
agno, …)
that have not migrated; MAF-python simply no longer imports it.

## Pre-existing, out of scope

Full D6 for ms-agent-python is 37/40. The 3 red cells — `multimodal`,
`voice`,
`hitl-approve-deny` — are **not** touched by this PR: their agents are
byte-identical to main and never used `generate_a2ui`. `multimodal` is
the known
shared-CopilotKit frontend bug (`runStartCount=0`, the run never
starts);
`voice`/`hitl-approve-deny` complete the run but their text does not
settle inside
the probe's tight budget. All A2UI and default-agent cells pass.
2026-08-31 09:37:00 +02:00
Ben Taylor 77c8ac1768 docs: add governed action approval UI pattern (#6277)
## Summary
- Adds a Human-in-the-Loop guide for governed side-effect actions
- Shows a vendor-neutral action envelope with summary, tool, reference,
verdict, and arguments
- Covers allow, deny, and require_approval handling with useInterrupt
and useHumanInTheLoop examples

## Validation
- Parsed human-in-the-loop meta.json and verified governed-actions is
present
- Checked the new MDX frontmatter and required approval terms/hooks
- Confirmed the new guide contains no OSuite/osuite branding
2026-08-30 23:54:05 -05:00
Ben Taylor 1a4e2c9a51 fix(examples): store Teams app icons in Git LFS (#6456)
## Summary

- normalize `examples/teams/appPackage/color.png` and `outline.png` into
Git LFS pointers
- preserve the original PNG contents and dimensions
- leave the `examples/integrations/a2a-a2ui/agent/images/` symlinks
untouched

## Root cause

The two PNGs match the repository's Git LFS attributes but were
committed as raw Git blobs. Git therefore continually cleans the working
files into LFS pointers and reports the worktree as dirty, which can
also block rebases.

## Validation

- verified both committed blobs are valid Git LFS pointers
- verified both LFS objects can be fetched from a fresh checkout of the
fork
- verified SHA-256 hashes match the original PNG contents:
- `color.png`:
`ee46987787ab5dfff4792e2df112a8d5422046296ddb1cb83fa1a85cf546d19f`
- `outline.png`:
`e653c3c3e4a700a8a46ee349463bba2169c368be803b5f48c2ae5c7ac4a7e452`
- verified image dimensions remain 192×192 and 32×32
- `MICROSOFT_APP_ID=00000000-0000-0000-0000-000000000000 NX_DAEMON=false
pnpm nx run teams-example:package --skip-nx-cache`
- independently reran previously flaky Nx test targets successfully:
  - `@copilotkit/web-inspector:test` (373 tests)
  - `@copilotkit/channels-slack:test` (389 tests)
  - `@copilotkit/vue:test` (1074 tests)
- checked against the latest upstream `main`; the affected paths are
unchanged and merge cleanly

Fixes #6420
2026-08-30 21:35:18 -05:00
Ben Taylor d4bcaaffb0 docs: fix typo "Retreive" -> "Retrieve" in LangGraph tutorial (#6358)
Fixes a typo in the AI travel app LangGraph tutorial: the step heading
"Retreive API keys" should read "Retrieve API keys".
2026-08-30 21:32:04 -05:00
Ben Taylor 69441ccfa4 fix(react-core): render every v1 tool call (#6682)
## What does this PR do?

Fixes the v1 compatibility render path so an assistant message can
render every tool call instead of only `toolCalls[0]`.

The returned lazy renderer now:

- matches each tool call with its corresponding tool result;
- renders all registered tool-call renderers in one fragment;
- removes `null` render results and returns `null` when no tool has a
renderer.

Keeping the fragment behind the existing lazy-renderer callback
preserves the exported `useLazyToolRenderer` return signature. Filtering
before returning also avoids attaching empty generative UI, so
caller-provided subcomponents are not suppressed when no renderer is
registered.

Regression tests cover multiple tool calls, per-call result matching,
the all-unhandled case, and a mixed handled/unhandled message.

## Related PRs and Issues

- Fixes #2946

## Verification

- `pnpm nx run @copilotkit/react-core:test` (133 files, 1,530 Vitest
tests plus 47 script tests)
- `pnpm nx run @copilotkit/react-core:check-types`
- `pnpm exec oxfmt --check
packages/react-core/src/v1-deprecated/hooks/use-lazy-tool-renderer.tsx
packages/react-core/src/v1-deprecated/hooks/__tests__/use-lazy-tool-renderer.test.tsx`

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] Documentation is unchanged because this restores existing v1
behavior without changing the public API
- [x] "Allow edits by maintainers" is checked
2026-08-30 16:59:05 -05:00
Ben Taylor b0077511bd docs(built-in-agent): mention Novita in OpenAI-compatible providers list (#6502)
## What does this PR do?

Adds Novita to the list of OpenAI-compatible providers documented for
the built-in agent's model selection, alongside OpenRouter, Ollama,
Together, and Groq. Novita exposes an OpenAI-compatible `/openai/v1`
endpoint, so it works through the existing `createOpenAI({ baseURL })`
pattern already documented for those providers.

A follow-up commit adds a callout clarifying that Novita only implements
the Chat Completions endpoint, not Responses — so readers should call
`provider.chat(model)` rather than the bare `provider(model)` form shown
in the adjacent OpenRouter example, which routes through Responses and
would error on Novita's endpoint.

## Related PRs and Issues

- None

## Verification

- `npm run typecheck` — pass
- `npm run test` — 55/55 files, 374/374 tests pass
- `npm run build` — Next.js production build succeeded, 222/222 static
pages generated
- Live verification: called Novita's OpenAI-compatible endpoint via
`createOpenAI({ baseURL }).chat(model)` with model
`deepseek/deepseek-v4-pro-0813` — HTTP 200, finish reason `stop`, usage
populated

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [ ] "Allow edits by maintainers" is checked
2026-08-30 15:06:48 -05:00
Ben Taylor 478a2aac95 fix(examples): MCP Apps widgets send ui/notifications/size-changed so the host resizes the iframe (#6700)
## What

The MCP Apps example widgets report their intrinsic size with
`ui/notifications/size-change`, but both the host and the ext-apps spec
use
`ui/notifications/size-changed`:

- Host: `MCPAppsActivityRenderer` only handles `case
"ui/notifications/size-changed"` and
  reads `{ width, height }` from it to size the iframe.
- Spec: `@modelcontextprotocol/ext-apps` defines
`McpUiSizeChangedNotification` with
  `method: "ui/notifications/size-changed"` (`App.sendSizeChanged`).

Because the names differ by one letter, the host never receives the size
and the widget
iframe stays at its initial height instead of growing to fit its
content.

## Fix

Rename the notification to the spec name in the affected widgets. The
payload is unchanged
(`{ width, height }`), which is exactly what the host reads, so this is
a one-line change per
widget on the sender side only.

No host change: in JSON-RPC a notification (no `id`) must not be
answered, so the host
silently ignoring the old name is correct behavior; the bug is purely
that the widgets sent
the wrong method name.

## Affected widgets

- `examples/showcases/mcp-apps/mcp-server/apps`: flights, hotels,
kanban, trading
- `examples/showcases/generative-ui-playground/mcp-server/apps`:
calculator, flights, hotels,
  kanban, todo, trading

## Testing

Reproduced against a local run of `examples/showcases/mcp-apps` (Next
frontend + MCP server):
before, the widget iframe rendered at its initial height; after the
rename the host receives
`size-changed` and the iframe resizes to the widget content.

## A note from the contributors

From the team at MCP Apps Builders - part of our ongoing series to round
out MCP Apps host
support in CopilotKit. Opening as a draft for review.
2026-08-30 15:04:00 -05:00
Ben Taylor 9909ca44d6 fix(react-core): make agentMetadata.nodeName match the node where the interrupt originates (#6488)
## Problem

`useAgentNodeName` must update React consumers when AG-UI node events
arrive, and `useLangGraphInterrupt.enabled()` must receive the node
where an interrupt actually occurred.

Current `main` includes the basic ref-to-state reactivity fix from
[ffd1580](https://github.com/CopilotKit/CopilotKit/commit/ffd15801d6d),
but that commit explicitly leaves #1426 open: a later `RUN_FINISHED` can
still replace the interrupting node with `"end"`, and v1 consumers can
still be hidden behind `useCoAgent`'s memoized return value.

## What remains in this PR

Rebased onto current `main` (`e9387e0`) after the v1 source migration,
this PR contains only the remaining behavior:

- Preserve the last active node when `RUN_FINISHED` reports `outcome:
"interrupt"`.
- Preserve it for the legacy `on_interrupt` custom-event flow as well.
- Continue transitioning successful and failed runs to `"end"`; reset
new runs and agent switches to `"start"`.
- Add `nodeName` to the `useCoAgent` return-value memo dependencies so
v1 consumers receive the reactive update.
- Share `INTERRUPT_EVENT_NAME` between the hook and interrupt
implementation.

The public hook signatures and AG-UI protocol remain unchanged.

## Preview workflow

- Disabled pkg-pr-new's generated all-package StackBlitz template;
package preview install URLs remain available.

## Changes

- `packages/react-core/src/v1-deprecated/hooks/use-agent-nodename.ts`
- `packages/react-core/src/v1-deprecated/hooks/use-coagent.ts`
-
`packages/react-core/src/v1-deprecated/hooks/__tests__/use-agent-nodename.test.tsx`
- `packages/react-core/src/v2/types/interrupt.ts`
- `packages/react-core/src/v2/hooks/use-interrupt.tsx`
- `.github/workflows/publish-commit.yml`

## Verification

- Full React Core Vitest suite: **131 files, 1520 tests passed**.
- Preview workflow: Nx formatting and YAML parsing passed.
- `nx run @copilotkit/react-core:check-types --skipNxCache`: passed,
including all 33 dependency tasks.
- `git diff --check origin/main...HEAD`: passed.
- The composite React Core test target then reaches the existing
Windows-only script baseline: 8 path-normalization failures plus 2
symlink-permission failures. These are outside this PR's files; the
complete Vitest suite passes before that script stage.

## Scope

This intentionally does not change the AG-UI event protocol, runtime
event ordering, HITL workflow, v1/v2 compatibility layer, or the
separate node tracking in `use-coagent-state-render-bridge.tsx`.

Fixes #1426
2026-08-30 10:05:27 -05:00
Ben Taylor 8870481474 fix(release): preserve breaking change footers (#6745)
## What does this PR do?

Preserves Conventional Commit bodies while collecting release changes so
breaking-change migration guidance can reach both raw and AI-generated
release notes.

The change:

- parses `git log` with explicit field and record separators, including
multiline bodies without splitting commits;
- extracts both `BREAKING CHANGE:` and `BREAKING-CHANGE:` footers and
keeps their continuation lines;
- recognizes only the Conventional Commit `!:` marker instead of
arbitrary exclamation marks;
- shares the raw release-note renderer between the release preparation
script and focused tests;
- adds a real temporary-Git-history regression test plus unit coverage
for footer-only, `!:`-only, trailer, multiline, and empty-body cases.

The implementation is intentionally limited to `scripts/release/`.

Validation completed:

- `pnpm exec vitest run scripts/release` — 14 files, 161 tests passed
- `pnpm run build`
- full test suite, with all initially environment-sensitive projects
rerun successfully
- `pnpm run check:packages`
- `pnpm run lint` — no errors
- `pnpm run check-format`
- `pnpm run release:prepare:dry`
- `bash scripts/release/verify-release-scope-dropdowns.sh`
- targeted TypeScript and oxlint checks for all six changed files

## Related PRs and Issues

- Fixes https://github.com/CopilotKit/CopilotKit/issues/6479
- Clean, release-only follow-up to
https://github.com/CopilotKit/CopilotKit/pull/6632

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation (not applicable; internal release tooling with
regression coverage)
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
2026-08-30 09:59:26 -05:00
guomengjia618-dot 4b53e86716 fix(ci): stop publishing the unusable all-package sandbox 2026-08-30 12:14:59 +08:00
Ben Taylor 9fcaed7690 fix(sdk-python): strip orphan OpenAI Responses function_call content blocks (#6781)
## What does this PR do?

`after_model` strips intercepted frontend `tool_calls` off the last
AIMessage, but under `responses/v1` output the equivalent
`function_call` **content blocks** stay behind in `message.content`. If
the run is cancelled mid-turn, that partial turn persists without a
`ToolMessage`. On replay, `langchain-openai` serializes the orphaned
block into the Responses API `input` with no matching
`function_call_output`, and OpenAI rejects the request:

```
400 - "No tool output found for function call call_..."
```

Because the poisoned message replays with the thread history, the thread
then fails **permanently** — every subsequent turn 400s, not just the
one that raced the cancellation.

The existing sanitizer (`_fix_messages_for_bedrock`) only strips
unanswered Anthropic `tool_use` blocks. This PR extends the same three
strip sites to OpenAI Responses `function_call` blocks, keyed on
`call_id` (`block["id"]` on those is the item id, not the call id):

1. blocks whose `call_id` is missing from `msg.tool_calls`
2. all of them when `tool_calls` is empty (e.g. after_model intercepted
everything)
3. the ones belonging to unanswered (non-adjacent) `tool_calls`

This is a re-land of the fix from #4473 (closed unmerged, tracked as
OSS-71); the defect is still present on current `main`.

## Side-effect analysis — orphan-handoff contract

The sanitizer runs request-scoped inside
`wrap_model_call`/`awrap_model_call`, in the same pass that already
strips Anthropic `tool_use` blocks on `main`; it writes nothing to the
checkpoint and does not change `after_agent`'s restore behavior:

- the intercepted frontend call is still restored with its synthetic
`forwarded_to_frontend` result before the sanitizer runs, so an
**answered** `function_call` block survives the wrap
(`test_next_model_call_keeps_answered_function_call_blocks_on_restore`);
- only blocks with no matching answered tool call are stripped — the
poisoned-replay shape is covered end-to-end through
`wrap_model_call`/`awrap_model_call`
(`test_replayed_cancelled_turn_strips_orphan_function_call_blocks`, sync
+ async).

## Related PRs and Issues

Fixes #6676
Re-lands #4473

## Checklist

- [x] Tests pass: `pytest tests/test_copilotkit_lg_middleware.py` → 91
passed (4 regression tests: the two unit shapes plus the two
request-scoped wrap paths above, sync + async)
- [x] Full local suite: `pytest tests/` → 232 passed, 11 skipped (the 4
`test_intercepted_tool_call_events.py` failures reproduce on pristine
`main` in the same local environment — dependency drift, not from this
branch)
- [x] `ruff format --check` clean on the changed files; no new `ruff
check` findings
- [x] `__tests__` / devSK: N/A (Python SDK)
2026-08-29 21:20:46 -05:00
Ben Taylor 2fd9cf72c1 docs(shell-docs): fix broken Action Handlers link in a2ui fixed-schema page (#6764)
The "Advanced — Action Handlers" link pointed to
`./advanced#action-handlers`, but there is no `advanced.mdx` page in
`docs/generative-ui/a2ui/`. The "Action handlers (reference)" section
lives in this same file, so the link now points to it directly with
`#action-handlers-reference`.
2026-08-29 21:15:33 -05:00
Ben Taylor 57fb0e7180 docs(example): remove dead links to non-existent docs files in agentcore README (#6763)
The `examples/integrations/agentcore` README references
`docs/LOCAL_DEVELOPMENT.md` and `docs/LOCAL_DOCKER_TESTING.md`, but the
example has no `docs/` directory and neither file exists anywhere in the
example. The local-development workflow is already fully documented
inline in the "Local Development" section, so these references are dead
links.

Verified: `docs/` is absent from `examples/integrations/agentcore/`, and
a repo-wide search finds no `LOCAL_DEVELOPMENT.md` /
`LOCAL_DOCKER_TESTING.md`.
2026-08-29 21:15:02 -05:00
Ben Taylor a68fd0b253 fix(showcase/harness): re-auth on 403 from an expired PocketBase token (#6466)
## Root cause

The harness's PocketBase client
(`showcase/harness/src/storage/pb-client.ts`) re-authenticated its
superuser token **only on HTTP 401**. But when the superuser/admin auth
token's ~14-day TTL expires, PocketBase does **not** return 401 — it
treats the request as an unauthenticated *guest* and returns:

```
HTTP 403 {"code":403,"message":"Only admins can perform this action.","data":{}}
```

on every write. Because 403 was never treated as an auth-expiry signal,
the expired token was never refreshed, so **all `status` writes failed
permanently** until the process restarted. `classifyWriterError` maps
403 → `pb_permission` (a terminal reason), so the failure looked like a
permission problem rather than an expired session. This is what blanked
the dashboard for ~46h.

## The fix

In `request()`, treat a 403 as the same stale-session signal as a 401 —
**but only when the request actually carried an `Authorization` header**
(`sentAuth`). A 403 on a request that sent no token is a genuine
guest-forbidden result that re-auth cannot fix, so it is left to
surface.

- The retry stays bounded by `MAX_AUTH_RETRIES` (1). A 403 that
**persists after a fresh, successful re-auth** is a real permission
error and falls through to the caller (still classified `pb_permission`)
— never an infinite re-auth loop.
- No change to the 401 path, the retry envelope, or any other status
class.

```
(res.status === 401 || (res.status === 403 && sentAuth)) &&
authRetries < MAX_AUTH_RETRIES && attempts < maxAttempts
```

## Local red-green proof (real PocketBase, real client — not a fake)

Stood up a live **PocketBase v0.22.21** (the pinned version) locally,
created an admin + a superuser-gated `status` collection, and set
`adminAuthToken.duration = 5` (5s — the server's minimum). A temporary
driver drove the **real `createPbClient`** against it: write #1 caches a
token, sleep 6.5s so the cached token **genuinely expires**, then write
#2.

First confirmed the raw failure surface — an expired admin token on a
write:

```
EXPIRED-token write status + body:
{"code":403,"message":"Only admins can perform this action.","data":{}}
HTTP 403
```

### RED (unmodified code)

```
[driver] write#1 OK id=setjh0ca1s09s14 — token now cached
[driver] sleeping 6.5s for the cached admin token to expire...
CVDIAG component=pb-client:create:status ... status=error error=status=403 {"code":403,"message":"Only admins can perform this action.","data":{}}
[driver] RED: write#2 FAILED after expiry: Error: pb create failed: 403 {"code":403,"message":"Only admins can perform this action.","data":{}}
EXIT=1
```

The expired token 403s, **no re-auth occurs**, the write stays failed.

### GREEN (with this fix)

```
[driver] write#1 OK id=tkl59dt5d3xt11g — token now cached
[driver] sleeping 6.5s for the cached admin token to expire...
[driver] GREEN: write#2 SUCCEEDED after expiry id=uns9y2dgysynpwz
EXIT=0
```

Same repro, same expired token: the 403 now triggers re-auth, the write
is retried once and **succeeds**.

## Regression tests

Added three tests to `pb-client.test.ts`:

1. `re-auths on 403 (expired superuser token treated as guest) then
retries the write` — 403-with-token → re-auth → retry succeeds (2 auths,
2 writes).
2. `caps 403 re-auth at 1 — a 403 that persists after a fresh auth
surfaces (no infinite loop)` — bounded; the persistent 403 surfaces (2
auths, 2 writes, then throws).
3. `does NOT re-auth on 403 when no credentials were sent (genuine
guest-forbidden)` — no token → no re-auth, no retry (0 auths, 1 write).

**Mutation check:** reverting the fix (403 branch removed) makes tests 1
and 2 fail while test 3 still passes — the tests are structurally able
to detect the fix.

## Code-review hardening (Tier-3 cr-loop)

A full-breadth review of the re-auth branch surfaced two additional
load-bearing issues in the exact code this PR modifies; both fixed here
with their own red-green + individual mutation checks:

- **Drain the response body on the re-auth path.** The 401/403 re-auth
branch did `continue` without draining the prior failed response —
unlike the 429/5xx branches, which call `drainBody()` — leaking a
half-consumed socket on every token refresh (F2.3 socket-reuse
discipline). `drainBody` was hoisted above the branch and invoked before
the retry.
- RED: `failed401.bodyUsed` = `false` (undrained). GREEN: body drained
after the fix.
- **Bound the re-auth gate by `attempts < maxAttempts`.** The re-auth
gate checked only `authRetries`, not `attempts` (the 429/5xx gates check
both), so a token expiring on the final attempt could fire a 4th
`fetchImpl`, exceeding the documented `maxAttempts = 3` envelope. Added
the guard for consistency.
- RED: `expected 4 to be 3` (4th fetch fired). GREEN: `writeCount ===
3`.

Full `pb-client.test.ts` suite: **35 passed**. CI green.

## Follow-ups (out of scope for this PR — pre-existing, tracked
separately)

The review confirmed the fix is sound and found no defect in it, but
flagged pre-existing issues in the same file that predate this change
and belong in their own PRs:

- **Observability regression (HF13-B1):** `create()`'s CVDIAG "every
record write failure is greppable" log is unreachable for
retry-exhausted 429/5xx writes, because `request()` now throws
`PbHttpError` before `create()`'s `!res.ok` block runs. (403 writes are
unaffected — they reach the log.)
- **Auth re-auth stampede:** `ensureAuth()` has no single-flight guard,
so at token expiry every concurrent writer re-auths independently.
Fixing this (coalesce concurrent re-auths behind one shared in-flight
promise) benefits both the 401 and 403 paths.
- **401 `sentAuth` symmetry (trivial):** the 401 re-auth path lacks the
`sentAuth` guard the new 403 path has, wasting one bounded attempt when
no credentials are configured.
- **`deleteByFilter` off-by-one:** the iteration cap throws on a
fully-successful delete of exactly a multiple-of-200 ≥ 20000 rows.
- **Inert `RETRY_AFTER_MAX_MS` cap + its mutation-blind test.**
2026-08-29 16:08:16 -05:00
Ben Taylor 415aa9af74 docs: fix typo in coagent troubleshooting (#6787)
Fix duplicate the/typo n -> in.
2026-08-29 16:05:43 -05:00
Ben Taylor 7f3d4e62df docs: fix GitHub branding and hyphenation in troubleshooting (#6786)
Fix GitHub branding (Github -> GitHub) and hyphenation open-source.
2026-08-29 16:05:37 -05:00
Ben Taylor 6067e87316 docs: fix heading spacing in CONTRIBUTING (#6785)
Fix missing space after numbered headings.
2026-08-29 16:05:31 -05:00
Ben Taylor 1433cd86f6 docs: fix hyphenation in CONTRIBUTING (#6784)
Fix hyphenation: open source -> open-source when used as adjective.
2026-08-29 16:05:23 -05:00
Ben Taylor 40beb51634 fix(channels): echo the originating interrupt back as command.interruptEvent (#6353)
## Problem

`thread.resume()` sends `forwardedProps.command = { resume }` and
nothing else.

LangGraph needs nothing more — it correlates a resume by `thread_id` —
so this gap has been invisible. Bridges that correlate by ids carried
**inside the interrupt** cannot work at all. `@ag-ui/mastra` gates its
resume on `command.interruptEvent` and needs `{toolCallId, runId}` out
of it:

```js
let o = input.forwardedProps?.command;
if (!o?.interruptEvent && Array.isArray(input.resume)) { /* standard path */ }
if (o?.resume === false && o?.interruptEvent) { /* cancelled */ }
if (o?.resume != null && o?.interruptEvent) { /* resume */ }
```

With no `interruptEvent`, **every branch is skipped**. The resume is not
an error — it is a silent no-op: the run proceeds as an ordinary new
turn, no `RUN_ERROR`, no tool result, and the suspended tool never
completes. That is the entire Mastra HITL story through Channels today:
the picker appears, the click lands, nothing resumes.

## Change

`Thread` retains the captured interrupt value when the interrupt fires,
and `resume` echoes it back.

- **Opaque.** Channels neither parses nor reshapes the value — it hands
back exactly what the agent sent, so no framework specifics leak in.
This mirrors what `useInterrupt` in `react-core` already does for legacy
interrupts.
- **Durable, not in-memory.** The click that resumes can arrive in a
later run, possibly after a restart — which is the entire reason this
path exists rather than `awaitChoice`.
- **One-use.** Read back with `kv.consume` (atomic take-and-delete),
matching the one-use continuation the resume already claims, so a
replayed click cannot resurrect a spent resume.
- **Retention tied to `actionRetentionMs`**, so the value and the button
that consumes it expire together. Configure a longer action retention
and the value can lapse first; the resume then omits the field, which is
the pre-existing behaviour rather than a new failure.
- **Best-effort store touches.** An agent that needs no correlation data
must not lose its interrupt to a store hiccup.

## Why this is additive

The field is **omitted** when no interrupt value was captured, so the
serialized command is byte-identical for agents that carry no
correlation data, and a normal (non-interrupt) run still writes nothing
at all. LangGraph ignores the extra field.

`interrupt-event-echo.test.tsx` covers all four properties — echo,
durability, one-use, omission. The echo test fails without the fix
(verified by reverting it).

## Verification

- `channels-core`: 268/268 tests, typecheck clean, `oxfmt` clean,
`oxlint` 0 errors.
- Verified end to end against a real Mastra agent driven through Slack:
two full cycles (approve, then decline), each producing a matching
`SUSPENDING` → `RESUMED` pair agent-side. Before the fix the same rig
produced suspends with zero resumes.
- The LangGraph interrupt path was re-checked against the same harness
and is unchanged.

## Reviewer note

One existing assertion in `hitl-continuation.test.tsx` moves from
counting `kv.set` / `kv.consume` calls to matching them by **key**. The
invariant it protects is "an interrupt turn writes exactly one action
snapshot, and one resume consumes it once" — both still asserted, now
stated directly rather than via a total that conflates action state with
unrelated bookkeeping. Flagging it because it is the one pre-existing
test this change edits.

## Out of scope

Channels ignores the standard AG-UI interrupt path (`RUN_FINISHED` with
`outcome.type === "interrupt"`, resumed via a top-level `resume` array).
Mastra emits it today alongside the legacy event, and it carries
interrupt ids and expiry the legacy event lacks. Worth a follow-up.
2026-08-29 14:29:29 -05:00
Ben Taylor 10587a8cd6 chore(channels): drop references to canceled OSS tickets in code comments (#6297)
## What

Three comments in the channels packages point at Linear tickets that
were **canceled on 2026-07-01** (superseded by OSS-401's consolidated
realtime foundation). They read as live tracking pointers when nothing
actually tracks the work.

| File | Reference | Ticket status |
|---|---|---|
| `packages/channels-core/src/codec.ts` | `TODO(OSS-363)` | Canceled |
| `packages/channels-intelligence/src/runtime.ts` | `TODO(OSS-377)` |
Canceled |
| `packages/channels-slack/src/ingress-normalize.ts` | `(OSS-362)`
provenance note | Canceled |

## How

The engineering intent is **preserved** in each case and re-marked as
`TODO (untracked)` with a note that the original ticket was canceled —
so a reader re-files rather than chasing a dead ticket. The `OSS-362`
mention was a bare provenance parenthetical carrying no intent, so it is
simply dropped.

`codec.ts` additionally records that the TODO's stated precondition is
**already met**: the pure Slack ingress mapping (mention stripping,
stable event-id derivation, real-user filtering, field extraction) now
lives in `channels-slack/src/ingress-normalize.ts`. The remaining work
is hoisting `normalizeIngress` onto the generic `PlatformCodec` — worth
knowing for whoever picks it up.

## Why this came up

While grounding a Channels x AG-UI gap analysis, several `OSS-###`
comments turned out to reference closed tickets — three canceled, two
already done. The done ones (OSS-450 Teams managed, OSS-491 delivery
terminal-signal) had already been cleaned out of `main` by subsequent
work; these three canceled ones are what remain.

## Scope / notes

- **Comment-only. No behavior change.** `oxfmt --check` and `oxlint`
both clean.
- Out of scope, flagged for a possible follow-up: `OSS-406`
(`channels-intelligence/src/index.ts`) and `OSS-599`
(`runtime/.../channel-manager.ts`, 5 references) are also canceled, but
sit outside the channels packages surveyed here. Other referenced
tickets (`OSS-96/162/476/566/568/621/623/641/670/691`) were not
status-checked.
2026-08-29 14:28:25 -05:00
Ben Taylor 55d321dd2f chore: add CodeRabbit configuration (#6077)
Tunes the already-installed CodeRabbit reviewer for this repo. Today it
runs on defaults; this makes it repo-aware and cuts noise.

## What this changes
- **`profile: chill`** + **`auto_review.ignore_title_keywords`** (skip
the `fix(showcase)`/release automation PRs) + **`path_filters`**
(exclude lockfiles, dist, snapshots, fixtures, aimock wiring, the
vendored openclaw demo) — keeps review budget on real contributor code.
- **Enables `gitleaks` / `semgrep` / `actionlint`** — secret + SAST +
workflow linting, complementing the existing `zizmor` / fork-PR-alert.
- **`path_instructions`** that encode repo conventions so they're
flagged inline on every PR:
- additions under the retired `packages/v1|v2/` or `docs/content/` trees
- the `showcase/shell-docs/src/content/ag-ui/` **downstream-mirror**
rule (changes belong upstream in ag-ui-protocol/ag-ui)
  - this repo does **not** use changesets (`.changeset/` debris)
- LangGraph routing: `goto END` on a frontend/HITL tool call is the
intended handoff, not a dropped tool
- verify reference-parity claims by **direction against the actual
reference**, not the PR description

## Notes
- Advisory only — no behavior is auto-merge/auto-close.
- CodeRabbit validates its own config, so it should confirm the schema
on this PR.
- An equivalent config for `ag-ui-protocol/ag-ui` is coming as a
separate PR (different retired paths; ag-ui docs are canonical, not a
mirror).

Part of the OSS backlog-triage automation plan (Phase 1).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-29 10:22:24 -05:00
Mark 17ed176736 chore: RuleTester coverage for no-single-arg-zod-record lint rule (#5032)
## Summary

Follow-up to #5025 addressing @marthakelly's [review
suggestion](https://github.com/CopilotKit/CopilotKit/pull/5025#discussion_r3307285578):
add oxlint `RuleTester` coverage for the
`copilotkit/no-single-arg-zod-record` rule. Since the underlying Zod 4
incompatibility is **type-level** (no runtime test can catch a
regression), this lint rule is the real safety net, so it's worth
testing directly.

## Cases (via `oxlint/plugins-dev` `RuleTester`)
**valid**
- two-arg `z.record(z.string(), z.unknown())` — no false positive
- two-arg with `.optional()` chain
- single-arg `.record()` on a non-`z` object (`cache.record(entry)`) —
confirms the rule is scoped to the `z` alias and won't over-fire

**invalid**
- single-arg `z.record(z.unknown())` → fires + autofix output
`z.record(z.string(), z.unknown())`
- chained `z.record(z.unknown()).optional()` → fixes the inner call
- `z.record(...spread)` → reports **without** a fix (`output: null`)

## Node version gate (important)
oxlint's `RuleTester` requires **Node ≥ 22** (it throws at parse time on
older runtimes). The CI unit matrix includes a **Node 20** job, so the
cases are gated to skip below Node 22 — verified locally: **6/6 pass on
Node 22**, **skips cleanly on Node 20**. The lint rule itself is still
exercised on every Node version through the `oxlint` job; only these
RuleTester unit tests are gated.

Also extends the `react-ui` vitest `include` to pick up co-located
`oxlint-rules/**/*.test.mjs`.

## Test plan
- [x] `vitest run` on the new file under Node 22 → 6 passed
- [x] same under Node 20 → 1 skipped (gate works; Node 20 CI job stays
green)
- [x] `oxlint` clean on the new test + config

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-29 07:54:58 -07:00
Rainer Hahnekamp dc6617c103 fix: ng version of the demo (#6659)
**exact `22.0.0`**, matching the library:

- `examples/v2/angular/demo/package.json`
- `examples/v2/angular/storybook/package.json`
2026-08-29 16:46:07 +02:00
Ben Taylor 564b35929f fix(telemetry): install registry outside parent workspace (#6621)
## Problem

Telemetry fragment workflows install the nested oss-path-to-production
checkout through CopilotKit's parent pnpm workspace. The checkout's own
dependencies, including Node type declarations, are therefore not
installed and reconciliation fails during TypeScript compilation.

## Why

pnpm discovers the parent workspace from p2p, reports the 70 CopilotKit
workspace projects, and ignores the external registry's lockfile.

## Fix

Run install and reconciliation with the ignore-workspace option in both
telemetry workflows, forcing pnpm to use the checked-out registry's
package metadata and lockfile. Verified with a clean
oss-path-to-production checkout.
2026-08-29 09:36:57 -05:00
1553126902 7b7e5cc510 test(sdk-python): cover orphan function_call stripping through the wrap path
Add two request-scoped regressions for #6676 beyond the unit tests:

- test_replayed_cancelled_turn_strips_orphan_function_call_blocks: the
  poisoned-checkpoint shape (run cancelled mid-turn, function_call blocks
  persisted with no ToolMessages and no intercept state) driven through
  wrap_model_call/awrap_model_call — the model must receive a history
  with no orphaned blocks and no unanswered tool_calls, which is what
  keeps langchain-openai from re-emitting them as Responses input items
  without a matching function_call_output.
- test_next_model_call_keeps_answered_function_call_blocks_on_restore:
  the legitimate resume path — restored frontend call with its synthetic
  result plus the answered backend call keep their function_call blocks,
  proving the sanitizer does not over-strip the orphan-handoff contract.
2026-08-29 22:22:48 +08:00
Tyler Slaton a9614495b8 fix(telemetry): install registry outside parent workspace 2026-08-29 08:58:08 -05:00
一片一片 8b5c6abfd6 Merge branch 'main' into docs/fix-agentcore-readme-dead-links 2026-08-29 18:43:49 +08:00
一片一片 3555b4a48e Merge branch 'main' into docs/fix-a2ui-action-handlers-link 2026-08-29 18:43:17 +08:00
Murat Sari bb80fff40a fix(angular): align demo and Storybook versions
The Angular demo and Storybook use Angular 22.1, while @copilotkit/angular is developed against Angular 22.0. Both consumers map @copilotkit/angular directly to its source files.

That causes TypeScript to load Angular types from two dependency contexts. Angular signal types contain unique-symbol brands, so signals originating from Angular 22.0 are incompatible with otherwise equivalent signals from Angular 22.1.

Pin the demo and Storybook to Angular 22.0 so the source-linked library and its consumers share the same Angular type identity. Update pnpm-lock.yaml to keep frozen installs reproducible.

The existing unit workflow intentionally selects only packages/**, while the packed-package test does not exercise these source-linked monorepo consumers.
2026-08-29 11:45:32 +02:00
deepshekhardas 6ff101e7c0 docs: fix typo in coagent troubleshooting 2026-08-29 11:29:05 +05:30
deepshekhardas 93248cf95f docs: fix GitHub branding and hyphenation in troubleshooting 2026-08-29 11:28:28 +05:30
deepshekhardas 220ac3236a docs: fix heading spacing in CONTRIBUTING 2026-08-29 11:27:54 +05:30
deepshekhardas d77ef77c02 docs: fix hyphenation in CONTRIBUTING 2026-08-29 11:27:02 +05:30
Ben Taylor b995233a4d test(shared): add unit tests for conditions engine (#6735)
Add comprehensive unit tests for the xecuteConditions rule engine in
packages/shared/src/utils/conditions.ts, which previously had no test
coverage. Covers all 12 comparison/existence rules, logical AND/OR/NOT
nesting, implicit AND across multiple conditions, and dot-path
resolution (including missing-path and whole-value fallback). Uses the
existing vitest setup in the shared package.
2026-08-28 23:23:22 -05:00
Ben Taylor 0056b6ac5e docs(examples/travel): fix broken search.py link in hook README (#6734)
The lib/hooks/README.md in the travel example links to
../../agent/travel/search.py, but the file is at
xamples/v1/travel/agent/src/search.py. Fix the relative path to
../../agent/src/search.py.
2026-08-28 22:31:35 -05:00
Ben Taylor 1d27fa4b2b docs: add missing question mark in What is CopilotKit heading (#6753)
docs: add missing question mark for heading.
2026-08-28 22:30:11 -05:00
Ben Taylor db6d885021 docs(shell-docs): fix broken Fixed Schema (Streaming) link (#6733)
The A2UI overview pages for both the DeepAgents and LangGraph
integrations link to ./fixed-schema-streaming, but that file does not
exist (only dvanced, dynamic-schema, ixed-schema, index, styling
exist). Point both links to the existing ./fixed-schema page.
2026-08-28 22:12:45 -05:00
gdut4140 1ef61717b8 docs(shell-docs): point Action Handlers link at the LangGraph advanced page 2026-08-29 11:06:54 +08:00
gdut4140 6f986a9a99 docs(example): remove dead LOCAL_DEVELOPMENT.md references in agentcore docker + validate script 2026-08-29 11:06:48 +08:00
hata33 963f753f53 fix(sdk-python): strip orphan OpenAI Responses function_call content blocks
after_model strips intercepted frontend tool_calls from the AIMessage but
leaves the equivalent responses/v1 function_call content blocks in
message.content. When a run is cancelled mid-turn the partial turn persists
without a ToolMessage, and on replay langchain-openai serializes the
orphaned block into the Responses API input with no matching
function_call_output, so OpenAI rejects every subsequent turn with
400 "No tool output found for function call call_...".

Extend the checkpoint message sanitizer to treat function_call blocks
like the existing Anthropic tool_use handling: strip blocks whose call_id
is missing from msg.tool_calls, strip all of them when tool_calls is
empty, and strip the ones belonging to unanswered tool_calls.

Fixes #6676
2026-08-29 10:58:40 +08:00
Mike Ryan 64181a34b9 feat(docs): add Intelligence onboarding prompts (#6779)
## Summary

- replace the homepage Channels activation block with an Intelligence
onboarding prompt focused on Learning
- place the same onboarding prompt across framework quickstarts, with
feature-specific copy for Learning and Rich Threads
- generate the CLI run ID only when the prompt is copied and record
successful copies in PostHog
- use existing local Lucide icons with no additional package or external
font dependency

## Experiment

The CTA uses one canonical agentic onboarding prompt everywhere while
changing the product promise by surface. Learning placements explain
Rich Threads, Learning, and support for new or existing agents. Threads
placements focus on persistent conversations.

Successful copies emit `docs.intelligence_onboarding_prompt_copied` with
`feature`, `from_path`, `run_id`, and `surface`. The same `run_id` is
embedded in the copied CLI command.

## Validation

- `pnpm exec oxfmt --check` on changed TypeScript files
- `npm run lint` in `showcase/shell-docs` (no errors; existing warnings
remain)
- `npm run typecheck` in `showcase/shell-docs`
- `vitest run src/lib/__tests__/inspector-docs.test.ts --maxWorkers=1`
(10/10 passed)
- memory-capped `npm run build` in `showcase/shell-docs`
- manual desktop, mobile, light-mode, and dark-mode checks on `/`,
`/threads`, and a framework quickstart

The full Shell Docs test command currently also reports failures
unrelated to this change, including unhydrated Git LFS image fixtures
and generated-doc baselines on current `main`. The affected Inspector
docs test passes independently.
2026-08-28 15:21:19 -07:00
Ben Taylor c8ffcd5353 docs(vue): document threads and the threads drawer for Vue (#6775)
Vue ships CopilotThreadsDrawer and useThreads but no page documents
them, so an integration guide that needs to name a Vue threads page has
nothing to cite. Adds the guide, modelled on the Angular one.

Needed by CopilotKit/Intelligence OSS-1033, whose conversion leg cites
this URL.

## Notes

Corrected against the source while writing this
(`packages/vue/src/v2/components/chat/CopilotThreadsDrawer.vue`,
`packages/vue/src/v2/hooks/use-threads.ts`):
- The "drawer + chat as bare siblings" example in the draft would not
actually sync selection to the chat — `CopilotThreadsDrawer` only calls
`config.value?.setActiveThreadId(...)` when a
`CopilotChatConfigurationProvider` ancestor exists; a sibling
`CopilotChat` provides its own config internally, which a sibling drawer
can't see. Wrapped both in a shared `CopilotChatConfigurationProvider`,
matching how the Angular guide documents the same pattern.
- `Thread.name` is `string | null`, not `title`; fixed the headless
example to use `name` with a fallback for `null`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-28 17:19:06 -05:00
Sam Julien 59a9279cdf fix(docs): balance Intelligence CTA columns 2026-08-28 14:24:23 -07:00
Sam Julien b10f6e7299 feat(docs): add Intelligence onboarding prompts 2026-08-28 14:19:10 -07:00
Ben Taylor 3409035d5a docs(vue): teach useComponent in the generative-UI guide (closes OSS-1037) (#6776)
## Correcting the ticket first

OSS-1037 was filed on a wrong diagnosis, mine. It said the Vue
generative-UI guide was unreachable because of a routing defect. It is
not:

```
docs.staging.copilotkit.ai/vue/guides/generative-ui.md   200
docs.copilotkit.ai/vue/guides/generative-ui.md           404
```

The page is merged, built, and live on staging. `showcase_promote.yml`
is `workflow_dispatch` only — *"Humans trigger. No automatic prod
promotes."* — so prod is simply behind. The same is true of
`pydantic-ai/agent-app-context.md` (added 08-28, also 404 on prod, 200
on staging), while pages from 08-24 are live. Nothing about Vue is
broken, and there is nothing here to "publish".

So this PR does the one thing that *was* actually wrong with the guide.

## What was wrong

`@copilotkit/vue/v2` exports its own `useComponent` — a Vue-native
composable, not the React one — and the guide never named it. Not in the
path table, not in the body, not in the closing reference list.

Its path table sent "your components" to `useRenderTool`, or
`useFrontendTool` with a `render`. Both work, and both ask the reader
for more than the display-only case needs: the agent shows a component
and nothing else runs. It is also the case the onboarding graph now
tells a Vue run to reach for, so a developer arriving at this guide
afterwards would not find the hook they had just been told to use.

## What changed

- A path-table row for the case: **A component the agent shows** →
`useComponent`. The existing row is re-scoped to what it is actually for
— the agent already owns the tool and you draw its call.
- A **Let the agent display a component** section, placed before the
server-side section because it is the shorter path. Vue SFC component
plus registration, with the schema arriving as props.
- A callout answering the question a reader will have — `useComponent`
or `useRenderTool`? The give-away is where the tool lives: if removing
the component would remove the tool from the agent's list, it is a
`useComponent`.
- The grounding warning. The model fills these props from what it knows,
so a card rendered over records the application does not hold looks
identical in a browser, a screenshot and a video to a correct one.
Points at `useAgentContext`.
- `useComponent` added to **Next steps**.

Every URL the new section cites was probed and returns 200:
`reference/vue/hooks/useComponent`,
`reference/vue/hooks/useAgentContext`,
`reference/vue/hooks/useRenderTool`.

## Testing

New `vue-generative-ui-docs.test.ts`, written first and confirmed red
(`expected '---\ntitle: Generative UI in Vue…' to contain
'useComponent'`). It asserts against the raw source, the loaded doc, and
the rendered llm-text, following `deepagents-interrupt-docs.test.ts`,
and separately asserts that a **path-table row** names the composable —
naming it only in prose would leave the table still recommending the
longer route for the simpler job.

`showcase/shell-docs`: 68/70 files, 487 tests pass. The 2 failures are
pre-existing on `main` and read files this branch does not touch — a
shared inspector snippet that says "Playground", and mastra
tool-rendering content.

Closes OSS-1037

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-28 15:39:13 -05:00
Ben Taylor 7f651dc62f docs(showcase): state the frontend-tool requirement per framework (refs OSS-1036) (#6777)
## Why

OSS-1034 makes `/<framework>/generative-ui/tool-based.md` the terminal
page every onboarding run fetches, on all 19 framework routes. That
page's `## How it works in code` section is a bundled
`frontend-tools-setup` concept, and only **5 of 19** frameworks shipped
one.

Where the concept is unbundled, `FrameworkSetup` returns `null` and the
section renders nothing. Absence encoded two different facts and nothing
separated them:

- this framework needs no agent-side wiring, or
- it needs some and nobody wrote it down.

For the 5 that had a snippet, the requirement is substantial and
load-bearing — ADK's `AGUIToolset()` in the `tools=` list, LangGraph's
`CopilotKitMiddleware` / `CopilotKitStateAnnotation`, Claude SDK's
Messages-API tool conversion. So "nobody wrote it down" was a live
possibility for the other 14, not a theoretical one.

## Coverage: 6 → 10 of 19

Four frameworks whose own `gen-ui-tool-based` demo agent settles the
question. I used the demo agents as the evidence rather than the docs,
because the demos are the thing that actually runs.

**Nothing to wire** — `pydantic-ai`, `llamaindex`, `ms-agent-python`.
Their demo agents declare no tools at all and say why in as many words:

> *"CopilotKit's runtime injects those tool definitions into the agent
request at runtime, so the agent does not need to declare them locally —
PydanticAI's AG-UI bridge surfaces frontend-registered tools to the
model on each run."*
> — `showcase/integrations/pydantic-ai/src/agents/gen_ui_tool_based.py`

Their snippets say that, and then say the half that is easy to miss:
**the tool arriving is not the same as the model calling it.** Every one
of those demo agents carries a `SYSTEM_PROMPT` naming the tool. Omit it
and the agent answers in prose while the component never renders — which
looks like a broken integration and is not one.

**Real wiring** — `crewai-crews`. The opposite case, and the one the
silence was hiding. A Flow owns its own model call, so the forwarded
tools do not reach the model unless the Flow passes them: read
`state.copilotkit.actions`, hand them over as `tools=`, wrap in
`copilotkit_stream` so the call reaches the browser as it streams, and
drive `tool_choice`. Its own demo comments on why:

> *"Force the chart on the user's turn. Once the browser has returned
the render result the follow-up is plain narration, so leaving this on
'auto' is what ends the run."*

I did **not** write snippets for the nine I could not establish from
source. Guessing "nothing is required" into the docs is worse than the
silence it replaces.

## A compile failure is no longer silent

```ts
// before — both states returned null
if (source === null) return null;          // nobody bundled it: deliberate
} catch (err) { console.error(...); return null; }  // bundled and broken: a defect
```

A snippet that fails to compile shipped looking exactly like a framework
with no requirement, and the only trace was a `console.error` nobody
reads in production. Absence stays quiet; a broken snippet now throws.
This is the shape `llm-text.ts` already refuses for
`channels-agent-setup`.

## The remaining nine are named, not silent

`frontend-tools-setup-coverage.test.ts` holds
`REQUIREMENT_NOT_ESTABLISHED` — `ag2`, `agno`, `built-in-agent`,
`deepagents`, `mastra`, `ms-agent-dotnet`, `ms-agent-harness-dotnet`,
`strands`, `strands-typescript`. Two tests bind it in both directions: a
framework serving the page with no snippet and no listing fails, and a
listed framework that has since been documented fails until its name is
removed. So a new framework cannot join the gap quietly, and closing one
is a deletion.

That test earned its keep immediately — it caught that I had written
`aws-strands` (the docs folder) where the registry slug is `strands`.

A third test asserts the two snippet shapes stay distinguishable, so a
future edit cannot quietly turn the CrewAI wiring into a "nothing
required" note.

## Testing

- `setup-concept.test.ts` — 2 new tests, the compile-failure one
confirmed red before the fix.
- `frontend-tools-setup-coverage.test.ts` — 3 new tests.
- `showcase/shell-docs`: 487 tests pass. The 2 failures are pre-existing
on `main` and read files this branch does not touch.
- Formatting: my files pass `oxfmt`. The two `claude-sdk-*` snippets it
also flags are pre-existing and untouched here.

## Not done

The nine frameworks above. Each needs its AG-UI adapter and demo agent
read, then a snippet — including where the honest content is "nothing is
required". Left on OSS-1036.

Refs OSS-1036

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-28 15:35:01 -05:00
Ben Taylor 10403aa7eb fix(angular): resolve human-in-the-loop results without the bus envelope (#6586)
Fixes the Angular half of #6571.

## The bug

`HumanInTheLoop.onResult` pipes the rxjs bus through `filter` +
`take(1)` but never maps, so `lastValueFrom` resolves with the **whole
bus event** rather than the result the human supplied:

```ts
// packages/angular/src/lib/human-in-the-loop.ts (before)
onResult(toolCallId: string, toolName: string): Promise<unknown> {
  return lastValueFrom(
    this.results.pipe(
      filter((result) => result.toolCallId === toolCallId && result.toolName === toolName),
      take(1),
    ),
  );
}
```

`toolCallId` and `toolName` are routing keys for this bus only. Leaking
them turns the tool result into an envelope, and the chain carries it
all the way to the agent:

1. `copilotkit.ts:534` — the bound HITL tool handler returns
`onResult(...)` directly as the tool's result.
2. `packages/core/src/core/run-handler.ts` — a non-string handler return
is `JSON.stringify`'d into the tool message `content`.

So a LangGraph agent receives:

```json
{"toolCallId": "call-1", "toolName": "request_page_oncall", "result": {"approved": true}}
```

where React sends the bare `{"approved": true}` —
`use-human-in-the-loop.tsx:24` resolves the handler promise with
`result` itself. This is a cross-framework inconsistency as much as a
bug: the same agent cannot serve both adapters.

## Why it matters

An agent gating a side effect on a field of the human's answer reads
`approved` off the envelope, finds nothing, and the gate silently never
fires — while the model still reports the action succeeded. That is the
failure #6571 describes: the gated node never runs, so the side effect's
audit trail stays clean, and a clean audit trail is exactly what a
correctly working gate looks like from inside the application.

## The fix

Map to the bare result:

```ts
take(1),
// Resolve with the bare result. toolCallId/toolName are routing keys for
// this bus only — leaking them would make the tool result an envelope
// that no consumer expects.
map((entry) => entry.result),
```

## Tests

The two existing tests asserted the envelope as correct behaviour, which
is why this survived:

```ts
await expect(promise).resolves.toEqual({
  toolCallId: "call-1", toolName: "approval", result: { status: "ok" },
});
```

They now pin the bare result, and a third test fails if the routing keys
leak back in. Verified that all three fail with the fix reverted and
pass with it. `nx run @copilotkit/angular:test` → 304 passed;
`check-types` and `build` clean.

## Scope

`onResult` is called only from `#bindHumanInTheLoopTool`
(`copilotkit.ts:534`); the sole other spec that touches it mocks the
method wholesale, so nothing else depends on the old shape. Any app that
had worked around the leak by reading `.result` itself would need to
drop that workaround — worth a line in release notes.

## Not addressed here

#6571's primary claim — that `ToolMessage.name` is unset — is a
**separate defect** and is untouched by this PR. For the record, since
the issue attributes both symptoms to `@ag-ui/langgraph`:

- The `{toolName, result}` envelope does **not** come from
`@ag-ui/langgraph`. It originates in this file. Grepping that package
for it turns up nothing, which risks the whole report being discounted.
- The missing `name` **is** in `@ag-ui/langgraph` —
`aguiMessagesToLangChain()`, `case "tool"` omits it. It is **not fixed
in 0.0.43**; that release only added `status`, so bumping the pin from
`0.0.42` will not help. The root cause is that `ToolMessageSchema` in
`@ag-ui/core` is the only message role without a `name` field. The
adapter can still recover it by matching `toolCallId` against the
preceding assistant message's `toolCalls[]` in the same loop —
`sdk-python/copilotkit/copilotkit_lg_middleware.py:809` and
`langgraph.py:129` already do exactly that lookup.

Two related spots worth separate fixes:
`packages/runtime/src/service-adapters/langchain/utils.ts:45` builds a
`ToolMessage` and discards `message.actionName`, which is already in
scope; and `examples/showcases/scene-creator/agent/agent.py:741` reads
`msg.name`, which is `None` for frontend and HITL results — a live
instance of the bug sitting in an example.
2026-08-28 15:33:41 -05:00
Benjamin Taylor fb2162f286 docs(showcase): state the frontend-tool requirement per framework
`generative-ui/tool-based` is now the terminal page every onboarding run fetches
(OSS-1034), and its "How it works in code" section is a bundled
`frontend-tools-setup` concept. Only 5 of 19 frameworks shipped one, so for the
rest the section rendered nothing and absence encoded two different facts: this
framework needs no agent-side wiring, or it needs some and nobody wrote it down.

Four frameworks whose own gen-ui-tool-based demo agent settles the question get
a snippet. pydantic-ai, llamaindex and ms-agent-python declare no tools at all --
the AG-UI request forwards them and their demo agents say so in as many words --
so their snippet states that, and then states the half that is easy to miss: a
model with no instruction about the tool answers in prose and the component never
renders. CrewAI is the opposite case. A Flow owns its own model call, so it has
to read `state.copilotkit.actions` and pass them itself, wrap the call in
`copilotkit_stream`, and drive `tool_choice`.

A compile failure in a bundled snippet no longer returns null. It shared that
return with "nobody bundled this", so a rendering defect shipped looking exactly
like a deliberate omission, traceable only through a console.error nobody reads
in production. Absence stays quiet; a broken snippet throws.

The nine frameworks still undetermined are named in a list a test reads, so a new
framework cannot join the gap silently and closing one means deleting a name.

Refs OSS-1036

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 15:25:12 -05:00
Benjamin Taylor c2b6078666 docs(vue): teach useComponent in the generative-UI guide
`@copilotkit/vue/v2` exports its own `useComponent`, and the guide never named
it. Its path table sent the display-only case -- the agent shows a component and
nothing else runs -- to `useRenderTool` or `useFrontendTool` with a `render`,
both of which ask the reader for more than that case needs, and neither of which
is what the onboarding graph now tells a Vue run to reach for.

The guide gains a row for the case, a section that teaches the composable, and
the distinction that decides between the two: `useComponent` declares the tool
from the frontend, `useRenderTool` draws a tool the agent already owns. It also
carries the grounding warning, because a card rendered over records the
application does not hold looks the same in a browser as a correct one.

Refs OSS-1037

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 15:23:07 -05:00