## Release monorepo v1.70.0
**Scope:** `monorepo` | **Bump:** `minor`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `monorepo` packages to `1.70.0`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `monorepo` packages to npm at version `1.70.0`
- Creates git tag `monorepo/v1.70.0`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
One `@bot say hi` in a Slack channel produced about fifty identical
replies in eight seconds. The loop is in shared ingress code, so it
reaches any Channels app on Slack, not just the one that found it.
## What happens
`message_changed` does not mean somebody edited the text. Slack also
sends it when a reply lands in the message's thread, when a link
unfurls, and when attachments are added.
Every one of those carries a fresh `revisionId`, and that is exactly
what the engine's inbound dedup keys on — deliberately, so a real edit
is never swallowed as a duplicate. So each announcement arrived as a
fresh mention and started another turn: the bot answered, the answer
revised the parent message, the revision asked the same question again,
and the next answer revised it again.
Instrumenting one mention showed eleven turns reaching the app. One was
the original message. Ten were revisions of it.
## The fix
A revision is a turn only when its text actually differs.
`previous_message` is the direct evidence where Slack sends it. Where it
does not, `edited` stands in: Slack stamps that only when a person
edited the message, and the metadata-only revisions that cause the loop
have neither. Edited mentions still reach the app, deletes are
untouched, and the normalizer stays pure — so this covers the
Intelligence-side webhook ingress that shares it as well as the local
adapter.
## Verification
- `packages/channels-slack`: 414 tests pass, three of them new.
- Each new test fails without the guard (checked by reverting it).
- Found and confirmed against a live Slack workspace, running an OpenTag
Composio integration. After the fix: one mention, one agent run, one
reply.
The app that hit this carries a local workaround — it ignores any turn
that is not the first revision of a message — which it can drop once
this ships.
## What changed
- Add standalone `CPK_TELEMETRY_ID` support to Runtime v1 and v2.
- Keep telemetry opt-out, sampling, Segment, and legacy license fallback
behavior.
- Fetch structured Intelligence entitlements and map them to current
client status.
- Share concurrent entitlement lookups, retry short-lived failures, and
reject stale grants.
- Make managed React, Angular, and Vue thread UIs use Runtime
entitlement authority.
- Keep assistant feedback stable when unrelated Inspector settings
change.
- Update Runtime, telemetry, self-hosting, and Web Inspector docs.
## Why
Managed Intelligence projects use a project API key for product access
and a non-secret telemetry ID for attribution. Offline license tokens
remain a self-hosted entitlement concern.
Starter-template and AgentCore changes live in #6188.
## Companion PRs
- Starter templates: #6188
- CopilotKit/Intelligence#628
- CopilotKit/oss-path-to-production#226
## Review corrections
- Scope shared entitlement attempts to one API key and endpoint.
- Ignore stale attempts after credentials change.
- Bound retries after short denials and transport failures.
- Accept telemetry IDs only when they match the public identifier
contract.
- Read Inspector context in its button, so unrelated label changes do
not rerender assistant feedback.
## Validation
- React Core full suite: 1,537 Vitest tests and 47 script tests passed.
- Runtime, Core, Shared, Angular, Vue, and Web Inspector focused suites
passed.
- React Core typecheck and build passed after the final rebase.
- Direct builds and type checks passed for Angular, Core, Runtime,
Shared, Vue, and Web Inspector.
- Shell docs typecheck and production build passed.
- Changed Vue files passed ESLint.
- `git diff --check` passed.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added structured runtime entitlement support for managed and
self-hosted deployments.
- Feature access and usage limits now reflect active entitlements, with
legacy license compatibility.
- Added runtime entitlement diagnostics to the Inspector’s Threads view.
- Added runtime-scoped telemetry identities and configurable telemetry
ID support.
- **Bug Fixes**
- Licensing interfaces remain in a loading state during retryable
entitlement outages.
- Improved recovery after runtime connection, target, or transport
changes.
- Prevented stale entitlement data from granting access after refresh
failures.
- **Documentation**
- Documented entitlement statuses, telemetry identity precedence,
sampling, and Inspector telemetry behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Releases are driven by conventional commit subjects; `check-binaries`
fails any PR that adds one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Re-derived against current main from **#4259** (mxmzb), which diagnosed
this in April. That branch is 7,386 commits behind and conflicts, so
this ports the mechanism rather than rebasing it.
## The bug
`useFrontendTool` registered its tool inside a `useEffect`. React
flushes passive effects **child-first in tree order**, so a component
mounted *before* the registering component runs its own `useEffect`
against an empty tool list.
That is the cross-page-navigation failure: a page mounts `CopilotChat`
and its tool-registering components in one commit, `CopilotChat`'s
connect effect fires first, and the connect request goes out carrying no
frontend tools.
## The fix
Register in `useLayoutEffect`. Layout effects run during commit, ahead
of every passive effect regardless of tree order, closing the window.
This is not a new pattern here — **`useAgentContext` already registers
via `useLayoutEffect`** (`use-agent-context.tsx:2`). The context half of
this hook family was fixed; the tool half was not. This makes them
consistent.
## Testing
### The original test did not detect the bug
Worth recording. #4259 shipped `use-frontend-tool-timing.test.tsx`,
which mounts the tool registrar **before** the observing component. I
ported it verbatim and ran it against unmodified main:
```
✓ src/v2/hooks/__tests__/use-frontend-tool-timing.test.tsx (1 test) 10ms
Test Files 1 passed (1)
```
It passes without the fix. React runs the registrar's effect first in
that order, so the observer always sees the tool. The PR's own comment
concedes the point — *"the result depends on component ordering and may
be absent."*
The test here mounts the consumer **first**, which is the shape that
actually breaks, and says so in the file so nobody reorders it back.
### RED → GREEN on the real surface
**RED** (current main, `useEffect`):
```
× registers the tool before an earlier-mounted sibling's useEffect runs
AssertionError: expected [] to include 'timingTestTool'
Tests 1 failed (1)
```
The empty array is the bug: the consumer's effect saw no tools.
**GREEN** (`useLayoutEffect`):
```
✓ src/v2/hooks/__tests__/use-frontend-tool-timing.test.tsx (1 test) 12ms
Tests 1 passed (1)
```
### No regressions
Full `src/v2/hooks` suite, same environment, with and without the
change:
| | Test files | Tests |
|---|---|---|
| without fix | 8 failed / 29 passed | **37 failed** / 283 passed |
| with fix | 7 failed / 30 passed | **36 failed** / 284 passed |
The delta is exactly the new test. The remaining 36 failures are
pre-existing in my local worktree (stale cross-package `dist`
resolution), identical on both sides.
## Notes
- **SSR:** `useLayoutEffect` warns during server rendering. These hooks
run inside `CopilotKitProvider`'s client context, and the sibling
`useAgentContext` already uses a bare `useLayoutEffect`, so this follows
the established pattern rather than introducing an isomorphic wrapper.
- **Scope:** #4259 also carried a second, independent mechanism — an
`ensureToolMiddleware` fallback that injects tools/context into direct
`agent.runAgent()` calls bypassing `copilotkit.runAgent()`
(`run-handler.ts` +44, plus `core.ts`, `agent-registry.ts`, Angular's
`agent.ts`, `use-agent.tsx`). That addresses a **different** failure and
deserves its own PR, tests and review. It is deliberately **not**
included here and should not be considered resolved by this.
Credit to @mxmzb for the diagnosis.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Frontend tools are now registered earlier, ensuring availability
before the interface is displayed.
* Improved consistency when components access frontend tools during
initial effects.
* Renderer cleanup now occurs at the appropriate stage when components
are removed.
* **Tests**
* Added coverage verifying frontend tools are available to
earlier-mounted components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Two frontend-tool defects that share a shape: a tool that was registered
correctly still never reached the agent, or reached the render without
the ability to respond.
## `useFrontendTool` tools dropped when the runtime enables
`openGenerativeUI` (#4952)
Tools reached the core registry through two owners that shared one array
— the provider via `setTools()`, hooks via `addTool()` — and `setTools`
replaced the array wholesale. Any provider re-sync after mount therefore
wiped every hook-registered tool.
A runtime with `openGenerativeUI: true` made it reproduce on **every**
mount: `/info` flips the flag asynchronously, the provider re-derives
its tool list to add `generateSandboxedUi`, and the re-sync dropped the
app's own tools. The agent then received only `generateSandboxedUi`,
exactly as reported.
The fix splits the two owners into their own buckets, merged on read
with hook entries winning. This mirrors what `CopilotKitCoreReact`
already does for render tool calls (`react-core.ts`) — tools simply
never got the same treatment. Because the clobber lived in core rather
than in one provider, **Vue had the identical bug and is fixed by the
same change** — verified end to end, not by shape:
`CopilotKitProvider.vue:461` assigns `runtimeOpenGenerativeUIEnabled`
from the core, `:297` derives `openGenerativeUIActive`, `:328`/`:354`
add the built-in to `allTools`, and `:492` re-syncs it through
`setTools` behind the same `didMountRef` skip. Angular is unaffected: it
never calls `setTools`, passes `tools` only through the constructor
(`copilotkit.ts:152`), and registers just the *renderers* from
`config.tools` (`:224`) — no second registration.
`addTool` now shadows a provider tool of the same name instead of
refusing to register, and only warns when another imperative
registration already holds the name. It also no longer pushes onto the
array the provider passed in, and `initialize` copies that array like
`setTools` already did.
**One intentional behavior change worth a reviewer's eye:** `setTools`
now replaces provider-owned tools only, so `setTools([])` no longer
clears tools registered through `addTool`. That narrowing *is* the fix,
but anyone calling `setTools([])` as a "clear everything" would now need
`removeTool` per tool. Nothing in the repo does (core, react-core, vue,
angular suites all pass).
## Catch-all actions could not wait for a response (#1746)
`getActionConfig` short-circuited on `name === "*"` before checking for
a wait-render, so a catch-all declaring `renderAndWaitForResponse` was
silently downgraded to render-only — no `respond`, and in practice a
`render is not a function` throw, since the render-only path reads
`action.render`. Handling N human-in-the-loop tools required N hooks.
A catch-all with a wait-render now routes to the human-in-the-loop path.
Core already had the execution half
(`getWildcardTool`/`executeWildcardTool`), so this is routing, not new
machinery. Two supporting changes make it usable:
- The HITL render props now carry **the name of the tool actually being
called**. It equals the registration name for a normal action, but a
catch-all needs it to tell N tools apart, and `"*"` was being written
over it in both the v1 wrapper and the v2 hook.
- `CatchAllFrontendAction` accepts
`renderAndWaitForResponse`/`renderAndWait` alongside `render`, mutually
exclusive as on `FrontendAction`, with `CatchAllActionRenderPropsWait`
exported.
Also: a wildcard tool is no longer advertised to the agent. It is a
local catch-all handler for calls with no exact match, so offering the
model a tool literally named `*` was never meaningful. Latent before
this PR (nothing in React registered a wildcard *tool*); catch-all HITL
activates it.
## Synced with `main` (2026-08-31)
`main` moved react-core's v1 tree under `src/v1-deprecated/` while this
branch was open, so the merge had exactly two conflicts, both
relocations:
- `use-default-tool.ts`'s `DistributiveOmit` change re-applied on the
moved file, keeping main's deprecation banner.
- the catch-all HITL e2e test moved into
`src/v1-deprecated/hooks/__tests__/`, with its `../../v2/...` imports
re-rooted to `../../../v2/...` to match its sibling
`use-copilot-action.e2e.test.tsx`.
The defect is still live on current `main` —
`CopilotKitProvider.tsx:838` still calls `copilotkit.setTools(allTools)`
— and both regression tests still bite there. The suites, the
before/after checks, `tsc --noEmit`, `oxlint` and `oxfmt` below were all
re-run on the merged tree; the browser walkthrough under **Live
verification** is from the pre-merge branch and was not repeated.
## Testing
**New regression tests**
- `packages/core/src/core/__tests__/run-handler-tool-registry.test.ts` —
13 tests: `addTool` survives `setTools`, hook precedence, agent-scoped
vs global, `removeTool` across both buckets, remount re-registration,
capability toggles surviving a re-sync, provider ordering, caller-array
aliasing in both directions, wildcard never advertised.
-
`packages/react-core/src/v2/providers/__tests__/CopilotKitProvider.openGenerativeUIToolLoss.test.tsx`
— drives the **real** core over a stubbed `/info` that returns
`openGenerativeUIEnabled: true`, asserting the hook tool survives.
-
`packages/react-core/src/v1-deprecated/hooks/__tests__/use-copilot-action-catch-all-hitl.e2e.test.tsx`
— end-to-end through the real provider and core: catch-all gets the real
tool name and a live `respond`, the tool result lands on the original
`toolCallId`, the follow-up run fires, and `*` is absent from
`runInputs[0].tools`.
**Both new tests were confirmed to fail before the fix — re-confirmed
after syncing `main`,** by checking the touched sources out at
`origin/main` and rebuilding core's dist:
```
× keeps the hook tool once the runtime turns openGenerativeUI on
→ expected [ 'generateSandboxedUi' ] to include 'sayHello'
```
and before the routing fix, the catch-all test failed with the exact
defect from the issue:
```
× gives the catch-all render a live respond and the real tool name
→ TypeError: render is not a function
❯ render src/hooks/use-render-tool-call.ts:44:22
```
**Suites (all green)**
```
@copilotkit/core 67 files, 799 tests passed
@copilotkit/react-core 137 files, 1558 tests passed
@copilotkit/vue 101 files, 1092 tests passed
@copilotkit/angular 49 files, 317 tests passed (1 skipped)
```
`tsc --noEmit` clean for `core` and `react-core`; `oxlint` 0 errors on
the changed files (16 warnings, all pre-existing); `oxfmt` applied.
**Live verification** — `examples/v2/react/demo` in a browser against
built dists, with a temporary stub AG-UI agent (no LLM key) that echoes
the tool names it receives, a runtime configured `openGenerativeUI:
true`, no `openGenerativeUI` prop on the provider, one
`useCopilotAction` frontend tool and one `useCopilotAction({ name: "*",
renderAndWaitForResponse })`:
```
TOOLS_RECEIVED: ["generateSandboxedUi","sayHello"] <- #4952: hook tool survived; no "*" leaked
catch-all handling: book_call status: executing <- #1746: real tool name, live respond
[click "Pick Tuesday"]
catch-all handling: book_call status: complete
TOOL_RESULT_RECEIVED: "{"slot":"tuesday"}" <- follow-up run received the result
```
0 console errors. The stub route and page were scratch and are not in
this branch.
## Notes for reviewers
- Community PR #4967 also targets #4952 by merging in the provider
instead. I took the core-layer fix because the clobber is in core's
registry and every framework provider hits it — patching one provider
leaves Vue broken. Happy to reconcile.
- Pre-existing and deliberately **not** changed here:
`useCopilotAction({ name: "*" })` render props do not infer, because the
hook's parameter is a union TypeScript cannot contextually type. This
already affected plain `render` before this PR (verified), so the new
tests and docs annotate props explicitly. Fixing it needs a
`useCopilotAction` overload — worth a follow-up.
- Deliberate small duplications, flagged rather than abstracted:
`WILDCARD_TOOL_NAME` is a one-line const in both core and react-core's
v2 HITL hook (sharing it would mean a new public export from core), and
the link between "a catch-all render receives `name`" and "the v1 HITL
wrapper supplies it" is a cast rather than a type — making it typed
means adding `name` to the public `ActionRenderPropsWait`, which is
wider than this fix.
- #4759 is left with contributor PR #5308, and #6101 needs its own
design pass since it introduces new public API.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added catch-all human-in-the-loop actions that can handle unregistered
tools and wait for user responses.
- Catch-all action renderers now receive the actual invoked tool name
and arguments.
- Added support for wait-aware catch-all rendering types.
- **Bug Fixes**
- Preserved frontend tools when provider tool lists are refreshed.
- Prevented duplicate tools and ensured registered tools take
precedence.
- Hidden wildcard tools from agent-advertised tool lists while keeping
them available for handling requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
Three surfaces mint an onboarding run id and report it to PostHog:
| surface | event | property |
|---|---|---|
| managed-service Home | `managed_service.onboarding_prompt_copied` |
`onboarding_run_id` |
| web inspector | `oss.inspector.home_prompt_copied` |
`onboarding_run_id` |
| **docs CTA** | `docs.intelligence_onboarding_prompt_copied` |
**`run_id`** |
All six `cli.onboarding.*` events also use `onboarding_run_id`. The docs
CTA was the lone outlier.
**The cost was not cosmetic.** A join written against the canonical name
found nothing on `managed_service.onboarding_prompt_copied`, which is
how that surface came to be described in OSS-1060 as unmeasurable, with
70 stranded runs. It is not: it carries the id on 100% of copies and
joins fine. The unmeasurable surface was this one, by name only.
With both names read correctly, the comparison the ticket wanted is
available — and the finding survives, sharper:
| surface | run ids copied | became a run | classified | completed |
|---|---|---|---|---|
| managed | 157 | **41 (26%)** | 15 | 10 |
| docs | 31 | **2 (6%)** | 0 | 0 |
This is solution item 8 of OSS-1060.
## Change
One line: `run_id` → `onboarding_run_id`, so all three surfaces agree.
Adds a test for the copy handler, which had none — it asserts the
canonical property name, the absence of the old one, and that the
reported id is the one actually pasted into the prompt (otherwise the
join points at a run that never existed).
## Validation
- new test passes; RED first (`expected { … } to not have property
"run_id"`)
- `src/components` suite: 48 passed. 6 files fail to collect on missing
gitignored `src/data/*.json` — **verified identical on unmodified
`origin/main`** (47 passed, same 6 files), so this change adds one
passing test and breaks nothing.
## Querying across this change
The 31 docs copy events from 2026-08-28 to 08-31 carry `run_id`. A
window spanning this commit needs
`coalesce(properties.onboarding_run_id, properties.run_id)`. I kept the
canonical name rather than dual-emitting, since the surface is days old
and low-volume.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Corrected analytics tracking for copied onboarding prompts by
recording the onboarding run ID under the standardized property name.
* Preserved existing clipboard behavior, error handling, and copy-state
updates.
* **Tests**
* Added coverage confirming the tracked run ID matches the value copied
to the clipboard and uses the correct event details.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Three surfaces mint an onboarding run id and report it to PostHog. The
managed-service Home and the web inspector both call it
`onboarding_run_id`, as do all six `cli.onboarding.*` events. The docs
CTA was the lone outlier, reporting the same value as `run_id`.
The cost was not cosmetic. A join written against the canonical name
found nothing on `managed_service.onboarding_prompt_copied`, which is how
that surface came to be described as unmeasurable and its runs as
stranded -- when in fact it carries the id on 100% of copies and joins
fine. The unmeasurable surface was this one, by name only.
Rename the property so all three agree.
Note for anyone querying across the change: the 31 docs copy events from
2026-08-28 to 08-31 carry `run_id`, so a window spanning this commit
needs `coalesce(properties.onboarding_run_id, properties.run_id)`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bumps the published packages from `@ag-ui/*` **0.0.57 → 0.0.59** — 27
declarations across 18 packages in `packages/**`.
0.0.59 is the first AG-UI release carrying the subagent protocol surface
(`SUBAGENT_STARTED` / `SUBAGENT_FINISHED` / `SUBAGENT_ERROR`,
`subagentRunId`) together with the null-omission cleanup, so it is the
dependency the subagent work needs. Landing the bump on its own keeps
that review separate from the feature work in #5873, which currently
pins `pkg.pr.new` previews of the (now released) ag-ui PR.
### Scope
`packages/**` only — the shipped libraries, which were uniformly on
0.0.57. Examples and showcases are deliberately untouched: they sit on a
spread of pins from 0.0.40 to 0.0.58 and bumping them would bury this
diff. `@ag-ui/langgraph` (0.0.42) and the mcp middlewares are separate
packages and also left alone.
### One behavioural change worth reviewing
`channels-core` ships `sanitizeAgentEventStream` because `@ag-ui/client`
used to **reject** a `TOOL_CALL_START` carrying `parentMessageId: null`
— the shape `@ag-ui/langgraph` emits for the tool call that triggers an
interrupt. As of 0.0.59 the client **accepts** that null and treats it
as absent (it is one of a small set of receive tolerances kept for
producers that still emit it).
That invalidates the two tests asserting the run dies *without* the
sanitizer:
- `aborts that same run when the sanitizer is not applied` → now
`survives that same run without the sanitizer, since 0.0.59 tolerates
the null`. It asserts the tool call actually arrives, not merely that
nothing throws, so it cannot pass vacuously.
- `lets the run abort when sanitizing is disabled` → `no longer aborts
the turn when sanitizing is disabled`.
**The sanitizer itself is untouched** and its coercion tests are
unchanged — it still rewrites the byte on the wire. It is simply no
longer the thing keeping such a run alive. Whether to keep it as
belt-and-braces or retire it is a follow-up decision, not part of this
bump.
### Verification
- All `packages/**` build against 0.0.59.
- `check-types` clean for `core`, `shared`, `react-core`, `runtime`.
- Tests pass: core 786, shared 304, runtime 2084, react-core 1514,
channels-core 267.
- The full `test,publint,attw` pre-commit gate passes across the 26
affected projects.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved handling of tool calls with missing parent message
identifiers, preventing unnecessary turn failures—even when event
sanitization is disabled.
* Ensured Angular validation uses the matching AG UI client version,
improving compatibility during package verification.
* **Chores**
* Updated AG UI client, core, and encoder packages to version 0.0.59
across supported integrations and runtimes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What
- Adds `integrations/adk/agent-app-context.mdx`, the page ADK was
missing.
- Lists it under **App Control** in `adk/meta.json`, beside
`frontend-tools` and `shared-state`, matching Mastra.
- Scopes the built-in-agent page's "no backend configuration needed" to
the built-in agent, and points at the per-framework pages.
## Why
`ag_ui_adk` stores `RunAgentInput.context` in ADK session state under
`CONTEXT_STATE_KEY` (`"_ag_ui_context"`) and stops there. Its own
docstring says so:
> Context from RunAgentInput is always stored in session state under the
`_ag_ui_context` key (CONTEXT_STATE_KEY), making it accessible to both
tools (via `tool_context.state`) and instruction providers (via
`ctx.state`).
Nothing in the package puts it in front of the model —
`CONTEXT_STATE_KEY` is read in exactly one place, `a2ui_tool.py`, for
the A2UI catalog entry. ADK's `inject_session_state` substitutes only
explicit `{key}` placeholders, so an `LlmAgent` built with a plain
string `instruction` (the form every ADK quickstart shows) never sees
what `useAgentContext` sent.
The agent still answers. Probing one such agent with the page's full
queue in `context` and then with `context: []`:
| context sent | report the model passed to its own tool |
| -- | -- |
| full queue | `"The payment gateway is returning 500 errors for 40% of
checkout requests…"` |
| full queue, repeated | `"The production payment service is returning
500 errors for 80%…"` |
| **empty** | `"Database connection pool exhausted on us-east-1
production cluster…"` |
Three inventions, and the empty-context run is indistinguishable from
the two full ones. Nothing errors, nothing is missing from the UI, and
the answer is well formed — which is exactly why a page is the right fix
rather than a troubleshooting note.
Mastra and LangGraph both document their retrieval step. ADK had none,
and `langgraph/programmatic-control.mdx` already links to a sibling page
that did not exist for ADK.
The page covers both retrieval forms — an `InstructionProvider` reaching
`ctx.state` (with the note that `canonical_instruction` reports
`bypass_state_injection=True` for a provider, so JSON braces in the
rendered context survive), and `tool_context.state` when only one tool
needs the data — and closes with the empty-context control as the way to
check the wiring.
## Testing
- Both `.mdx` files compile under `@mdx-js/mdx`.
- `adk/meta.json` parses, and `agent-app-context` sits in the group
Mastra puts it in.
- Internal links use the repo's own convention (`/adk/…`,
`/langgraph/agent-app-context`, `/mastra/agent-app-context`), matching
links already in the tree.
- `<Callout type="warn" title="…">` matches existing usage.
- The docs site build was not run locally — the worktree has no install.
Leaving that to CI.
Filed as OSS-1045 internally, a sibling of OSS-1027 (Pydantic AI's
adapter drops the same field outright).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Added ADK integration guidance for sharing application context with
agents, including setup steps and access from instructions and tools.
* Updated documentation navigation to include the new ADK application
context page.
* Clarified how built-in and self-hosted agents receive and process
application context, with links to framework-specific guidance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sibling of #6796 (ADK). Same class of gap, one layer earlier.
## CrewAI does not behave either
OSS-1045 asked whether CrewAI behaves, since it was one of the two
frameworks with no `agent-app-context` page. It does not, and its
failure is worse than ADK's.
`ag_ui_crewai` 0.3.0 threads `RunAgentInput.context` into the run state,
and its own comment says why:
> Thread `input.context` into the run so agent code and tools can read
it from state.
But `CopilotKitState` declares `messages` and `copilotkit` — not
`context`. A Flow state is a Pydantic model, and Pydantic drops unknown
keys by default, so the key the endpoint just populated is discarded on
validation before any `@start()` method runs:
```
CopilotKitState fields : ['agent_threads', 'copilotkit', 'current_user_message', 'ended',
'events', 'id', 'last_intent', 'last_user_message',
'messages', 'session_ready']
extra policy : None
context survived? : False <<DROPPED>>
```
`self.state.context` does not exist in the shape every quickstart shows.
ADK at least parks the value somewhere an integrator can reach
(`ctx.state[CONTEXT_STATE_KEY]`).
## Proved with a control, not by reading one answer
The same run body sent three times to a CrewAI Flow agent, unmodified,
on `ag-ui-crewai==0.3.0`. User message `Triage INC-2002.` throughout,
with a three-incident operator queue in `context`:
| context sent | `incident_summary` | severity |
| -- | -- | -- |
| full queue | `"Triage INC-2002."` | low |
| full queue, repeated | `"Triage INC-2002."` | low |
| **empty** | `"Triage INC-2002."` | low |
The empty control is indistinguishable from both full-context runs.
Declaring the field and rendering it separates them — the agent then
names the incident the page actually holds ("A canary deploy of the
search indexer is writing malformed documents…", severity medium).
Worth noting for anyone reading a CrewAI run: this fixture *refused*
rather than fabricating, because its backstory forbids invention. That
is a property of the prompt, not of the adapter. An agent without that
line fabricates exactly as the ADK one did. The page says so.
## The page's snippets were run before shipping
Two corrections came out of that and are in the page because of it:
- The rendered context goes through `inputs` rather than being formatted
into the task `description`, so JSON braces are not read as more
`{placeholder}`s.
- The flow appends its answer to `self.state.messages`. Without that the
turn completes, the crew runs, and **nothing renders** — the first draft
of this page had that bug, and the probe caught it.
Final verification, against the page's own advice: with the colleagues
context the agent answers "You should email Aisha Okonkwo, who is the
Security Lead"; with `context: []` it answers "The page sent no
colleagues" rather than inventing one.
## Also filed
The retrieval arguably belongs upstream rather than in every
integrator's agent — for CrewAI it is not a design call but a defect,
since the package's own base class discards what the package's own
endpoint wrote. Filed on `ag-ui-protocol/ag-ui`; this page is the fix
that works against the shipped version today.
Closes OSS-1051.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Added guidance for sharing app-specific context with CrewAI Flows.
* Documented how to register app state, declare context in the flow
state, and include it in prompts.
* Added troubleshooting advice for silently missing context, including
testing with an empty context.
* Added the new guide to the CrewAI Flows documentation navigation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
CodeRabbit flagged two overstatements on the new page, both confirmed
against source:
- ag_ui_adk 0.7.0's _default_run_config also copies the context into
RunConfig.custom_metadata['ag_ui_context'] when google-adk >= 1.22.0,
so ctx.state is not the only place it exists. Keep ctx.state as the
cross-version form and name the alternative.
- inject_session_state's _replace_match returns the match verbatim
unless the brace contents pass _is_valid_state_name, so JSON braces
survive a string instruction. The real hazard is an identifier-shaped
block in a context value, which raises KeyError.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Why
Angular had no counterpart to `useComponent` in
`@copilotkit/react-core/v2` and `@copilotkit/vue/v2`. Letting an agent
display one of your components meant either `registerFrontendTool` with
a handler the component does not need, or `registerRenderToolCall`,
which draws a tool the agent already has and therefore requires that
tool to exist in the agent.
That gap is load-bearing for OSS-1034, which ends the default onboarding
proof at a component rendered through the frontend's own registration.
Every other supported frontend ships `useComponent`; Angular was the
only one that could not express the shape.
## What changed
**`FrontendToolConfig.handler` is now optional**, and
`CopilotKit.#bindClientTool` returns the config unwrapped when it is
absent. This is the actual missing capability. Core already has the
render-only path — a tool that declares no handler gets an empty tool
result and the turn completes (`packages/core/src/core/run-handler.ts`,
the `if (tool.handler)` guards around lines 854 and 1112). Angular bound
a wrapper unconditionally:
\`\`\`ts
handler: (args, context) =>
runInInjectionContext(injector, () => handler(args, context)),
\`\`\`
so a display-only tool called \`undefined\` on its first tool call. A
stub handler is the wrong fix in the other direction: whatever it
returns becomes the tool's result in the thread, which is a fabricated
result rather than an absent one.
**`registerComponent`** is that shape with the model-facing description
built for it — the same prefix react-core and vue build, so the tool
reads identically to the model whichever frontend registered the
component. It carries no `handler` field at all: a display-only
component that quietly ran application code would be a different
feature, and a caller who wants both wants `registerFrontendTool`.
The component stays an ordinary `ToolRenderer` with a required
`toolCall` signal input reading `toolCall().args`. No new component
contract, and it renders through the existing `pickToolCallHandler` →
`NgComponentOutlet` path unchanged.
Because the tool is declared by the frontend and forwarded over AG-UI, a
display-only component needs nothing added to the agent — on any
framework, Python included.
\`\`\`ts
registerComponent({
name: "show_incident",
description: "Show one incident from the incident table.",
parameters: z.object({ id: z.string(), severity: z.string() }),
component: IncidentCardComponent,
});
\`\`\`
## Docs
- New `reference/angular/functions/registerComponent.mdx`, wired into
the reference index callout and the `public-api.mdx` summary.
- A display-only section and a new path-table row in the Angular
generative-UI guide.
- `packages/angular/API.md` — the `public-api-documentation.spec.ts`
gate fails on any undocumented export, which is how the omission
surfaced immediately.
The reference page also carries the grounding warning: the model fills
these props from what it knows, so a component rendered over records the
application does not hold looks identical to a correct one in a browser
and in a screenshot.
## Testing
Five tests written before the implementation, confirmed red (`(0 ,
registerComponent) is not a function` ×4, plus the handler-less binding
test) with the 14 pre-existing registration tests still passing, then
green.
- `pnpm nx run @copilotkit/angular:test` — 49 files, 321 passed, 1
skipped.
- `pnpm nx build @copilotkit/angular` — passes, so widening `handler` to
optional broke no caller.
- `showcase/shell-docs` — 66/68 files, 480 tests pass. The 2 failures
are pre-existing on `main` and read files this branch does not touch (a
shared inspector snippet, and mastra tool-rendering content).
- `pnpm run check-format` — the 22 reported files are all pre-existing;
none is in this diff.
Refs OSS-1034
🤖 Generated with [Claude Code](https://claude.com/claude-code)
OSS-1045 shipped the ADK page and left CrewAI unchecked. It does not behave, and it fails
one layer earlier than ADK does.
`ag_ui_crewai` 0.3.0 threads `RunAgentInput.context` into the run state and says so in a
comment: "so agent code and tools can read it from state". But `CopilotKitState` declares
`messages` and `copilotkit` and not `context`, and a Flow state is a Pydantic model, so the
key the endpoint just populated is dropped on validation before any `@start()` method runs.
`self.state.context` does not exist in the shape every quickstart shows. ADK at least parks
the value somewhere an integrator can reach.
Proved with a control rather than by reading one answer. The same run body sent to a CrewAI
Flow agent three times, twice with a three-incident operator queue in `context` and once
with `context: []`, produced three identical answers: the empty control was indistinguishable
from both full-context runs. Declaring the field and rendering it separates them, and the
agent then names the incident the page actually holds.
The page's own snippets were run as an agent before shipping. Two things surfaced that way
and are in the page because of it: `inputs` carries the rendered context so JSON braces are
not read as more `{placeholder}`s, and the flow appends its answer to `self.state.messages`,
without which the turn finishes with nothing rendered at all.
Verified against the page's own advice: with the colleagues context the agent answers "You
should email Aisha Okonkwo, who is the Security Lead", and with an empty context it answers
"The page sent no colleagues" rather than inventing one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The new page linked out to both, but neither linked back, so a reader on
either page had no way to discover the display-only shape. React's
useFrontendTool/useRenderTool/useDefaultRenderTool and Vue's
useFrontendTool all point at useComponent; Angular was the only frontend
where the links ran one way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
useFrontendTool now registers the tool renderer in a layout effect, but
useHumanInTheLoop still removed that renderer from a passive effect cleanup.
React runs each phase's cleanups before that phase's effects, but runs the
entire layout phase ahead of the entire passive phase. With the two split
across phases, a keyed remount ordered the outgoing instance's removal after
the incoming instance's registration:
add (new, layout) -> remove (old, passive)
which deleted the renderer that had just been added and left the HITL tool
unrenderable. Moving the teardown to useLayoutEffect restores the correct
remove-then-add ordering.
Caught by the existing 'should maintain executing state across component
remount' test in use-human-in-the-loop.e2e.test.tsx.
## Problem
Inspector is a browser overlay, so a React Native developer cannot open
it. Nothing in the docs said so.
The docs already skipped the Open Inspector step on React Native, so
they never told a mobile developer to click a button that isn't there.
But they also never stated the absence, and Inspector is presented as
the debugging story on every other frontend.
An onboarding run on 2026-08-25 reached a fully working native app on an
Android emulator — round trip proven, three grounded turns typed into
the device — and fell back to `adb exec-out screencap` for visual proof,
with no way to tell whether the missing Inspector meant something was
wrong.
## Why the absence is permanent
- `packages/react-core/src/v2/components/CopilotKitInspector.tsx` mounts
`@copilotkit/web-inspector` through `@lit-labs/react` — a Lit custom
element needing `document` and `customElements`.
- `packages/react-native` does not depend on
`@copilotkit/web-inspector`, and
`packages/react-native/src/__tests__/headless-entry-surface.test.ts`
lists it in `FORBIDDEN_HEAVY`, so that test fails if the mobile bundle
ever reaches it.
This documents an existing, tested architectural decision. No Inspector
or package change.
## Change
- **`docs/inspector.mdx`** — new `## Where Inspector runs` section,
placed before the pane tour so a reader learns whether the page applies
to them before learning its navigation. It states the browser
requirement and maps the panes a mobile developer loses onto what
replaces them: `npx copilotkit verify --round-trip`, runtime Debug Mode
beside `adb logcat`, and the Intelligence thread view.
- **`docs/frontends/react-native.mdx`** — Inspector added to **Known
limitations**, the list a mobile developer actually checks. It already
named voice, the web-only rendering hooks, `threadId`, and the cloud
provider props.
- **`skills/inspector-docs/references/pane-map.md`** — the "Surfaces
that do not get the Open Inspector step" section now records where each
browserless surface states its absence.
Channels — Slack and Teams — are named in the same section for the same
reason.
## Tests
`inspector-docs.test.ts` already had a negative guard (`React Native and
Channels do not tell the reader to click the Inspector button`), which
forbade the wrong statement without requiring the right one. Two
positive tests now require it:
- `Inspector states that it needs a browser and React Native has none`
- `the React Native page lists the missing Inspector among its
limitations`
Both were written first and observed failing.
## Verification
- `showcase/shell-docs` `inspector-docs.test.ts`: **11/11 pass** (9
before, +2 new).
- Mutation check: replacing `There is no React Native build of
Inspector` and `no React Native surface` in the two pages fails both new
tests (2 failed | 9 passed), so they are not self-fulfilling.
- `oxfmt --check` and `oxlint` clean on the changed test.
- `scripts/sync-plugin-skills.ts --check`: `plugin skill mirror in sync`
(`inspector-docs` is a `RESERVED_LIFECYCLE_SLUGS` standalone skill, so
root `skills/` is its source, not a mirror).
- The full shell-docs suite shows 18 unrelated failures in
`channels-guide-search`, `registry`, `setup-content`, `setup-concept`,
and `llm-text`. **Confirmed pre-existing**: the same 18 fail identically
in a pristine `origin/main` worktree sharing the same `node_modules` and
the same generated `src/data`. None of them read either page touched
here.
## Companion
The CLI half is CopilotKit/Intelligence#1001 — the onboarding graph's
completion prompt carried the same unconditional "How to use the
CopilotKit Inspector" instruction. Both carry `refs OSS-977` rather than
`closes`, so the ticket stays open until both land.
The v1 react-core tree moved under src/v1-deprecated/, so the two conflicts
were relocations: use-default-tool.ts's DistributiveOmit change re-applied on
the moved file, and the catch-all HITL e2e test moved into
src/v1-deprecated/hooks/__tests__/ with its v2 imports re-rooted.
## Problem
The two "check your agent name" accordions on the LangGraph
troubleshooting page each carry the **other** accordion's hook name. A
reader who lands here from the "agent not found" error gets
contradictory guidance in both directions:
| Accordion title (before) | Prose said | Code sample uses |
| --- | --- | --- |
| `Check your agent name in useCoAgent` | `useCoAgent` | `useAgent({
agentId })` |
| `Check your agent name in useCoAgentStateRender` | `useAgent` |
`useCoAgentStateRender({ name })` |
Someone migrated the first accordion's code sample to the v2 `useAgent`
hook without updating its title or prose, and the second accordion's
prose picked up `useAgent` while its title and sample stayed on
`useCoAgentStateRender`.
This matters more than a typo: the two hooks take **different prop
names** (`agentId` vs `name`), so a reader following the mislabelled
prose reaches for the wrong prop on the wrong hook while debugging
exactly the failure this page exists to explain.
## Fix
Align each accordion's title and prose with the hook its code sample
actually uses, and name the specific prop rather than the vague "what
you use":
- **`useAgent`** accordion → prose now points at the `agentId` prop.
- **`useCoAgentStateRender`** accordion → prose now points at the `name`
prop.
Both hooks are correct as documented and neither is being migrated here
— `useAgent({ agentId })` is the current v2 signature
(`packages/react-core/src/v2/hooks/use-agent.tsx:128`), and
`useCoAgentStateRender` remains exported through the v1 compatibility
surface (`packages/react-core/src/v1-deprecated-compatibility.ts:301`)
with its own reference page at
`/reference/v1/hooks/useCoAgentStateRender`. This PR only makes each
accordion internally consistent.
The two prose lines also carried `Make sure the that the` and `what you
use n`, both fixed in passing since the same lines are being rewritten.
## Testing
**1. MDX still compiles.** Compiled the page with `@mdx-js/mdx@3.1.1`
before and after:
```
--- BEFORE (origin/main) ---
MDX COMPILE OK — 17287 bytes emitted
--- AFTER (this branch) ---
MDX COMPILE OK — 17396 bytes emitted
```
**2. Title / prose / code sample now agree.** Wrote a probe that parses
every `<Accordion>` on the page and compares the hook named in the
title, the hook named in the prose, and the hook actually called in the
code sample. Ran it against `origin/main` and against this branch:
```
--- BEFORE (origin/main) ---
accordions parsed: 11
MISMATCH title=useCoAgent prose=useCoAgent code=useAgent
MISMATCH title=useCoAgentStateRender prose=useAgent code=useCoAgentStateRender
--- AFTER (this branch) ---
accordions parsed: 11
AGREE title=useAgent prose=useAgent code=useAgent
AGREE title=useCoAgentStateRender prose=useCoAgentStateRender code=useCoAgentStateRender
```
The probe reports `MISMATCH` on the pre-fix content and `AGREE` after,
so it is measuring the actual defect rather than passing vacuously.
(Probe was scratch tooling and is not committed.)
**3. Signatures verified against source, not memory.**
```
packages/react-core/src/v2/hooks/use-agent.tsx:128
* - **Bind to an agent** — `useAgent()`, `useAgent({ agentId })`. The shared
packages/react-core/src/v1-deprecated-compatibility.ts:301
useCoAgentStateRender,
```
**4. Completeness.** No remaining instances of either typo anywhere in
the repo, and nothing references the renamed accordion title:
```
$ grep -rIn "Make sure the that" --include='*.mdx' --include='*.md' . # no matches
$ grep -rIn "you use n " --include='*.mdx' --include='*.md' . # no matches
$ grep -rIn "Check your agent name in useCoAgent" ...
common-coagent-issues.mdx:140: <Accordion title="Check your agent name in useCoAgentStateRender"> # the intended one
```
The sibling snippet `snippets/shared/troubleshooting/common-issues.mdx`
does **not** contain these accordions, so the fix is correctly confined
to one file.
## Note on overlap with #6787
Community PR #6787 edits these same two lines, fixing the `the that` /
`use n` typos but leaving the crossed hook names in place. This PR
supersedes both of its hunks. Whichever lands second will need a trivial
conflict resolution — happy to rebase behind #6787 if you'd rather merge
that one first.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Updated coagent troubleshooting guidance with clearer terminology,
including “open-source” and “GitHub.”
* Renamed the `useCoAgent` troubleshooting section to `useAgent` and
clarified its `agentId` reference.
* Corrected the `useCoAgentStateRender` guidance to reference its `name`
parameter accurately.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
The telemetry fragment workflows regenerate the canonical registry by
running, in the nested `p2p` checkout:
```
pnpm --ignore-workspace install --frozen-lockfile
pnpm --ignore-workspace run reconcile # -> ts-node telemetry-registry/reconcile/index.ts
```
If that install does not put the registry's own dependencies in place,
`reconcile` does not say so. It fails as a cascade of TypeScript errors
pointing at the registry's source:
```
error TS2307: Cannot find module 'node:fs' or its corresponding type declarations.
error TS2307: Cannot find module 'node:path' or its corresponding type declarations.
error TS2304: Cannot find name 'structuredClone'.
error TS2304: Cannot find name '__dirname'.
```
That reads as a bug in oss-path-to-production. It isn't — it's a failed
install. Three `telemetry-docs-fragment` runs failed exactly this way on
2026-08-28 (e.g. run `33216460960`) before #6621 fixed the underlying
cause.
## Change
#6621 fixed the *cause* (pnpm was resolving CopilotKit's parent
workspace instead of the registry's lockfile — visible in the log as
`Scope: all 70 workspace projects`). This adds the missing diagnostic so
a *future* recurrence, from any cause, names itself:
```bash
if [ ! -x node_modules/.bin/ts-node ]; then
echo "::error::Registry dependencies were not installed in p2p/node_modules — check the pnpm install step above."
exit 1
fi
```
Applied to both `telemetry-docs-fragment.yml` and
`telemetry-runtime-fragment.yml`. No behavior change on the success
path.
`ts-node` is the right thing to probe: it is the binary `reconcile`
invokes, and the registry declares it (`ts-node: ^10.9.2`).
## Testing
**1. YAML parses, guard is wired into the intended step**
```
telemetry-docs-fragment: OK, 10 steps, guard present in 1 step(s)
telemetry-runtime-fragment: OK, 10 steps, guard present in 1 step(s)
```
**2. Guard fires when ts-node is absent** (empty dir, no `node_modules`)
```
::error::Registry dependencies were not installed in p2p/node_modules — check the pnpm install step above.
exit=1
```
**3. Guard passes through when ts-node is present**
(`node_modules/.bin/ts-node`, executable)
```
guard passed — would run reconcile
exit=0
```
**4. Probe target confirmed against the real registry checkout**
```
reconcile script: ts-node telemetry-registry/reconcile/index.ts
ts-node declared: ^10.9.2
ts-node present in this checkout's node_modules: yes
```
**5. Formatting**
```
oxfmt --check ... -> All matched files use the correct format.
```
Not provable locally: the workflows themselves only run on their
schedule/dispatch against a real cross-repo token, so the first
end-to-end exercise is their next scheduled run.
## Credit
The guard is lifted from **onsclom's #6435**, which I closed as
otherwise superseded — its telemetry half by #6621 and its
Inspector-flake half by `669132d731`. This piece was the part neither of
those carries.
## What & why
The **"Frontend Tools"** demo (`setThemeColor`) silently fails in
`examples/integrations/adk`: asking "set the theme to green" makes the
agent reply *"I can only help with proverbs or the weather"* and nothing
recolors.
**Root cause:** the ADK agent's tools are static `[set_proverbs,
get_weather]`. `ag_ui_adk` only injects a run's forwarded client tools
(the frontend-registered `setThemeColor`) into the LLM's tool set when
the agent's `tools` include an **`AGUIToolset`** placeholder — it swaps
that for a `ClientProxyToolset` wired to `input.tools`. Without it,
`gemini-2.5-flash` never sees `setThemeColor` and declines. (No
CopilotKit ADK example currently includes `AGUIToolset`, and the docs
don't mention it.)
## Change
```python
from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint, AGUIToolset
...
tools=[set_proverbs, get_weather, AGUIToolset()],
```
Agent-side only; no prompt change needed.
## Testing
Verified **live** (real Gemini key) via the identical change in
`examples/integrations/adk-angular` (same `agent/main.py` + `ag_ui_adk`
adapter): "Set the theme to green" now recolors the panel; before the
fix the agent refused. Because the fix is agent-side and
framework-agnostic, and `setThemeColor` was already confirmed forwarded
in the run body, the React `adk` frontend + this agent behaves
identically.
## Related
- Companion to CopilotKit/CopilotKit#6097 (the new `adk-angular` example
carries the same fix).
- Discovered while validating #6097 by live-comparing Angular vs React
ADK behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## What does this PR do?
Fixes 112 broken doc links in the vendored `ag-ui` content tree.
That tree is vendored from `ag-ui-protocol/ag-ui`, where those docs are
served at the **site root** (`docs.ag-ui.com/concepts/events` → 200). On
`docs.copilotkit.ai` the same pages live one level down under `/ag-ui`,
so every root-relative link copied over verbatim 404s:
```
404 https://docs.copilotkit.ai/concepts/events
200 https://docs.copilotkit.ai/ag-ui/concepts/events
```
This is the same class of breakage as the `/concepts/copilot-runtime`
link reported in #2082 and fixed in #5296 — I found the rest while
verifying that PR.
**What changed:** each link that names a real vendored ag-ui page is
repointed at its actual URL — `/concepts/*`, `/sdk/*`, `/drafts/*`,
`/development/contributing`, `/quickstart/applications`, and the bare
`/integrations` (which in these pages means AG-UI's integrations page,
not CopilotKit's). The Kotlin SDK links additionally carried an upstream
`/docs/` prefix (`/docs/sdk/kotlin/core/types`), which is dropped.
**What deliberately did not change:** links that resolve to CopilotKit's
own pages — `/`, `/quickstart`, `/frontend-tools`,
`/integrations/<framework>` — all return 200 today and have no ag-ui
counterpart, so a blanket prefix sweep would have broken them. Each of
the 52 distinct link targets in the tree was checked individually rather
than rewritten by pattern.
Also adds a regression test, because there is no automated sync for this
tree: a future re-vendor from upstream would otherwise silently
reintroduce the same 404s.
## Why not fix this upstream or in the renderer?
- **Not upstream:** these links are *correct* in `ag-ui-protocol/ag-ui`
— that site serves at the root. The breakage is introduced purely by
vendoring under a subpath, so the fix belongs in this copy only.
- **Not in `resolveDocsHref`:** a render-time `/ag-ui` prefix rule would
rewrite the 13 links that legitimately point at CopilotKit pages,
turning working links into 404s. The mapping is not mechanical, so it is
pinned in content and enforced by test.
## Testing
- **New test fails before the change, passes after.** Written first, run
against unfixed content: 112 violations across 27 files. After the fix:
```
✓ src/lib/__tests__/ag-ui-content-links.test.ts (2 tests) 71ms
✓ root-relative links that name an ag-ui page carry the /ag-ui prefix
✓ no links use the upstream /docs/ path prefix
Test Files 1 passed (1)
```
- **Every one of the 38 unique new link targets was fetched against
production:** 35 return 200. The 3 exceptions are pre-existing page
errors, unrelated to link paths (see below).
- **Residue check** — the only root-absolute links left in the tree are
the intended CopilotKit-owned ones:
```
6 / 2 /quickstart 2 /integrations 2 /frontend-tools 11
/integrations/<framework>
```
and no `](/ag-ui/ag-ui` double prefixes or leftover `](/docs/sdk/`
remain.
- `oxfmt --write` + `oxlint` clean on the new test file. No changesets
(docs/test only).
- Pre-existing, unrelated: `src/lib/__tests__/docs-link-rewrite.test.ts`
cannot collect in a fresh worktree because `@/data/setup-content.json`
is generated at build time and untracked. Unaffected by this change.
## Separately: 6 of 8 AG-UI Rust SDK pages return 500 in production
Found while verifying link targets — **not** a link problem and **not**
fixed here; the URLs below are correct and the MDX files exist:
```
500 /ag-ui/sdk/rust/client/agent-trait 500 /ag-ui/sdk/rust/core/types
500 /ag-ui/sdk/rust/client/http-agent 500 /ag-ui/sdk/rust/core/events
500 /ag-ui/sdk/rust/client/subscriber 500 /ag-ui/sdk/rust/core/overview
200 /ag-ui/sdk/rust/client/overview 200 /ag-ui/sdk/rust/overview
```
Only the two `overview` pages render. I ruled out the obvious MDX
hazards (bare `<Generic>` brackets, braces outside code fences,
frontmatter shape, missing `meta.json`) — none correlate with the
failures, so the cause is elsewhere in the page pipeline. Happy to open
a separate issue for it.
## Related PRs and Issues
- Follows #5296 / #2082 (same breakage class, CopilotKit-authored side)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
|
[zizmorcore/zizmor-action](https://redirect.github.com/zizmorcore/zizmor-action)
| action | patch | `v0.6.2` → `v0.6.3` |
---
### Release Notes
<details>
<summary>zizmorcore/zizmor-action (zizmorcore/zizmor-action)</summary>
###
[`v0.6.3`](https://redirect.github.com/zizmorcore/zizmor-action/compare/v0.6.2...v0.6.3)
[Compare
Source](https://redirect.github.com/zizmorcore/zizmor-action/compare/v0.6.2...v0.6.3)
</details>
---
### Configuration
📅 **Schedule**: (in timezone America/Los_Angeles)
- Branch creation
- "before 9am every weekday"
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Enabled.
♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/CopilotKit/CopilotKit).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC40OS4wIiwidXBkYXRlZEluVmVyIjoiNDQuNDkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->