## 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
## 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
The add-menu ("+") button's tooltip hardcoded the string "Add attachments",
so `labels.chatInputToolbarAddButtonLabel` only retitled the menu item and
the tooltip stayed English. That blocked full localization of CopilotChat
without replacing the whole add-button slot.
Every other tooltip in the v2 chat surface is already label-driven, and the
Angular implementation already derives this tooltip from the same label, so
this was an oversight rather than a deliberate split.
The "/" shortcut glyph stays hardcoded — it is a key name, not prose.
Fixes#6750
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five comments justified routing thread requests through the instrumented fetch
by saying it lets opening a view restore the status after an outage. It does
not: every binding withholds its thread requests until the status is already
connected, so while it is red nothing is sent. The justification is DETECTION
only, which is what the CopilotChat site already said correctly.
Also:
- Documents both meanings of the Error state and the invariant behind them —
the status reports the last actual contact with the runtime — on the
connection-status reference page, which described only the startup meaning.
- Renames RUNTIME_PROBE_TIMEOUT_MS to ɵRUNTIME_PROBE_TIMEOUT_MS. core/index.ts
re-exports agent-registry wholesale, so a constant whose own doc says
"exported for tests" was public API of @copilotkit/core.
- Guards the Inspector's read of ɵruntimeFetch the way it guards its four other
internal core accessors. A newer Inspector against an older pinned core was
handing the thread store `undefined`, which breaks the Threads view outright
rather than merely losing detection through it.
- Corrects the stop-request comment, which claimed to be the only runtime
destination off the seam; the suggestion route's stateless path, the memory
store and /inspector-metadata are too, just not by design.
- Corrects OSS-904-VERIFY.md, which said scenario 4 had real traffic to work
with and left it off the not-covered list.
Three gaps the reviewers named, all of them behaviour the safety argument
already depends on.
The submission gate through the error state had no automated test in any
binding, though it is the third of three decisions that hold each other up:
if the red state closed the gate, no successful request could be issued and
only a page reload would leave it. The new react-core test drives the real
provider, the real core and the real submit path against a runtime that
goes away mid-session and asserts the state is left through the user
interface. Verified against a mutation that reuses the destructive startup
failure path mid-session: the test goes red.
A mid-session status round trip in a mounted tree was flagged as reasoned
rather than measured. Measured now: the run-activity effect lists the
status in its dependencies, so it does tear down and re-establish, but a
user-initiated run in flight is neither detached nor reconnected, and the
run-activity subscription is back once the status returns. When this chat
owns its run-activity store the round trip does restart it, re-issuing the
thread list and subscribe requests — documented rather than changed: it is
paid on a transition caused by user activity, not while idle.
Both connection-health suites pinned "rest" while the product default is
"auto". Core now covers rest, single and auto; the Intelligence suite
covers a runtime negotiated over the single-endpoint transport.
Thread REST calls go to the runtime, so under the destination rule they
are runtime traffic — but every binding injected the global fetch, so
their outcomes never reached the connection status. The practical effect:
with a dead runtime the Threads view left the status green, and after the
runtime came back, opening the Threads view could not clear it. Only
sending a message could.
React (both the useThreads store and CopilotChat's standalone
run-activity store), Vue, Angular and the Inspector's own owned store now
take `copilotkit.ɵruntimeFetch` instead. It is a pass-through and is
memoized per core, so nothing changes in the healthy case and no extra
request is issued.
Each binding's thread suite asserts the injection at the seam rather than
inferring it, since a regression back to the global fetch is invisible
from the rendered result.
## What does this PR do?
Lets a developer open a saved Inspector thread in the live official
chat.
- New header action: **View in your app**
- Official React and Vue chat switch to that thread
- A pinned `threadId` does not block the switch
- **Stop viewing** or an app thread change restores the previous thread
- Example threads have no action
- Production builds hide the action
- Same agent only. No matching official chat shows an error in the
Inspector
Core owns a two-way EventClient bridge
(`@tanstack/devtools-event-client`). The root import is a no-op in
production.
Docs: Inspector guide, section **View a thread in your app**.
## Related PRs and Issues
-
https://linear.app/copilotkit/issue/OSS-871/new-features-add-a-new-view-thread-in-your-app-feature
## Checklist
- [x] I have read the Contribution Guide
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] Allow edits by maintainers is checked
Open Inspector Event Snippets on localhost. You can compile, save, and
replay AG-UI events in chat. Chat shows a bookmark icon next to a tool
call, an A2UI block, or generative UI. Click the icon to save that turn
as a snippet.
## What does this PR do?
This PR adds the Inspector Event Snippets pane.
You can:
- Compile a snippet from a recipe (tool-call, reasoning, text, activity,
raw)
- Save snippets in origin-scoped localStorage
(`cpk:inspector:event-snippets`)
- Import and export snippets from the pane header
- Replay a snippet into live chat through Inspector-only Core inject
Each Run remints `messageId`, `parentMessageId`, `toolCallId`, and
`runId`. The second Run of the same snippet is a new turn.
On localhost, chat shows a bookmark icon beside a tool call, A2UI block,
or generative UI. The icon is absolutely positioned. It hangs to the
right when there is room. Otherwise it hangs to the left. The card stays
full chat width.
The React demo adds `sayHello`, `getTime`, `addNumbers`, and a **Call 3
tools** suggestion.
## Related PRs and Issues
- Linear
[OSS-874](https://linear.app/copilotkit/issue/OSS-874/new-features-also-allow-users-to-emit-specific-events-from-the)
## 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
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
## Testing
### Commands run
1. Lefthook pre-commit ran `nx` targets `test`, `publint`, and `attw`
for 27 affected projects. All passed.
2. I did not run `pnpm test:pr` (full repo). Lefthook ran the affected
package matrix only.
### Manual test
1. Run `pnpm demo:react` from the repo root.
2. Open http://localhost:3000
3. Open Inspector and select Event Snippets.
4. In chat, click **Call 3 tools**. Confirm three tool cards at full
chat width, with the bookmark hanging outside the card.
5. Click a bookmark, then click Run twice. Chat shows a second turn with
new IDs.
### How this PR makes testing easy
- `packages/web-inspector/src/lib/__tests__/event-snippets.test.ts`
- `packages/core/src/__tests__/inspect-inject.test.ts` (covers two
injects)
- React demo: `examples/v2/react/demo/src/app/page.tsx`
## Linked issues
Linear
[OSS-874](https://linear.app/copilotkit/issue/OSS-874/new-features-also-allow-users-to-emit-specific-events-from-the)
## Risk / rollback
- If ID remint is wrong, a second Run can no-op or duplicate a turn.
- The save icon shows on localhost Inspector (or when `showDevConsole`
is `true`).
- Rollback: revert this PR.
## Public API change
**Before**
Angular has no Inspector service.
```ts
// no CopilotInspector export from @copilotkit/angular
```
**After**
```ts
import { CopilotInspector } from "@copilotkit/angular";
const inspector = inject(CopilotInspector);
inspector.openInspector({
messageId: "msg-1",
menu: "event-snippets",
});
```
React and Vue apps that already mount Inspector on localhost need no new
caller code. Chat wires the bookmark through Inspector context.
`@copilotkit/core` exports `ɵinjectInspectorEvents` for Inspector only.
App code must not call it. There is no public Core emit API.
- Raw recipe: the Events JSON input now calls requestUpdate, so Run and
Save stop being permanently disabled.
- Tool args recovery: one depth scan replaces the parse-every-prefix loop.
Truncated args from a streaming tool call now fail at once, not after
seconds of blocked main thread.
- Chat bookmark: hidden while the tool arguments are incomplete, so a
partial payload cannot be captured.
- saveEventSnippet: React, Vue, and Angular wrap the body, so a compile or
storage failure is reported instead of becoming an unhandled rejection.
- Vue and Angular now gate the in-chat affordances on a dev build plus
localhost, the same as React. showDevConsole: true on a staging URL no
longer puts a bookmark into a production chat.
useAgentNodeName stored the node name in a ref, so AG-UI STEP_STARTED
events never triggered a re-render and useLangGraphInterrupt's
agentMetadata.nodeName was stale by the time enabled() ran.
Replace the ref with a typed, pure state machine (useState + reducer):
- STEP_STARTED updates nodeName reactively
- RUN_FINISHED with outcome "interrupt" (standard) or a preceding
legacy on_interrupt custom event keeps the interrupting node instead
of advancing to "end"
- success/error still resolve to "end"; new runs and agent switches
reset to "start"
- share the on_interrupt event name via INTERRUPT_EVENT_NAME exported
from v2/types/interrupt so the protocol fact has a single definition
Fixes#1426