mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
fix/thread-name-first-message-fallback
3049 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c94032f3fa |
feat(react-ui): report feedback state to onThumbsUp/onThumbsDown
The thumbs callbacks only received the message, so a consumer could not tell whether a click applied feedback or retracted it. Custom AssistantMessage implementations that track their own toggle state had no way to pass that value through, and the built-in feedback state was write-only: clicking an active button re-applied the same value. Add an optional second argument reporting the state the click transitions to. The built-in AssistantMessage derives it from the message's current feedback, which also makes the built-in buttons toggle; a custom AssistantMessage may pass its own value. The argument is optional, so existing one-argument handlers are unaffected. The toggle logic is extracted to ./feedback so it can be unit tested — this package's vitest project runs in a node environment and has no component-rendering harness. Closes #2615 |
||
|
|
1ea18ca864 |
fix(react-ui): pad the chat header on mobile viewports
The `.copilotKitHeader` rule declared `padding-right` only inside `@media (min-width: 640px)`, so below that breakpoint the header's `space-between` children ran flush to the viewport edge and the close button had no gutter. Move the horizontal padding into the unconditional rule so it applies at every viewport. The value matches the 24px the sm breakpoint already used, so wide layouts are unchanged. Fixes #2493 |
||
|
|
6e3ed6e319 |
fix(core): copy the tool array handed to initialize
The merged tool view is memoized, so holding the caller's array meant an in-place mutation of it would go unnoticed. `setTools` already copied; `initialize` now matches. Also documents why the memo exists: `tools` is public and returned a stable array instance until a registry write, which consumers may rely on as an effect dependency — rebuilding on read would hand out a new array each time. |
||
|
|
b17c8556be |
feat(react-core): let a catch-all action wait for a response
`useCopilotAction({ name: "*" })` could only ever render. `getActionConfig`
short-circuited on the wildcard name before it looked for a wait-render, so
a catch-all declaring `renderAndWaitForResponse` was silently downgraded to
a render-only action: the render was called without `respond`, and in
practice threw `render is not a function` because the render-only path reads
`action.render`. Handling N human-in-the-loop tools therefore required N
separate hooks.
A catch-all now routes to the human-in-the-loop path when it declares a
wait-render, so the tool call stays pending until `respond` is called. Core
already had the execution half (`getWildcardTool`/`executeWildcardTool`),
so this is a routing fix rather than new machinery.
Two supporting changes make the render 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 for annotating the props.
Fixes #1746
|
||
|
|
31aa1e2162 |
fix(core): keep hook-registered tools across provider re-syncs
Frontend tools reached the registry two ways that shared one array:
provider-owned tools via `initialize`/`setTools`, and imperatively-added
ones via `addTool` (`useFrontendTool`, `useHumanInTheLoop`, direct
`core.addTool()` calls). Because `setTools` replaced the array wholesale,
every provider re-sync after mount silently wiped the hook tools.
A runtime with `openGenerativeUI` enabled made this reproducible on every
mount: the `/info` response flips the flag asynchronously, the provider
re-derives its tool list to add `generateSandboxedUi`, and the re-sync
dropped everything the hooks had registered — so the agent received only
`generateSandboxedUi` and the app's own tools were never advertised.
Split the two owners into their own buckets, merged on read with hook
entries taking precedence, mirroring what `CopilotKitCoreReact` already
does for render tool calls. `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.
Also stop advertising a wildcard ("*") tool 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.
Fixes #4952
|
||
|
|
d45a478a83 |
fix(channels-slack): rename decimal_allowed to Slack's is_decimal_allowed
`SlackNativeProps` declared `decimal_allowed`. Slack's `number_input` field is `is_decimal_allowed`. Slack accepts a message whole or not at all, so the unrecognised key refused the entire `chat.postMessage` call with `invalid_blocks: invalid field at /blocks/N/element` and ended the Channels delivery that carried it — no exception reached the caller and nothing arrived in the channel. The typed path was therefore the broken one: a developer who bypassed our types and hand-wrote `is_decimal_allowed` got it right, while everyone using `Slack.Element.NumberInput` silently lost the message. Verified live against Slack, one delivery per case: - `decimal_allowed` alone — refused, `invalid_blocks: invalid field at /blocks/0/element` - `is_decimal_allowed` alone — delivered - both names present — refused with the same error, which isolates the unknown key as the cause: Slack's required field is satisfied and the payload is otherwise identical to the one that delivers Not treated as a breaking change and no alias is kept. The old name never worked, so no caller can depend on its behaviour, and keeping it would preserve the trap. Removing it converts a silent message loss into a compile error that names the right field. Also adds a test comparing every field name `native.ts` declares against Slack's own Block Kit declarations in `@slack/types` (already a dependency), parsed with the TypeScript parser rather than matched as text. Names `@slack/types` does not cover — `container`'s `blocks`, `rich_text_list`'s `offset`, `card`'s `slack_icon` and `subtext`, and the whole `data_visualization` vocabulary — are listed individually with the reason, so the check states its coverage instead of implying completeness. Floors on the vocabulary size and on the number of names actually compared keep a collapsed comparison from passing as success. Sweeping the remaining names turned up no other mismatch. |
||
|
|
703944880d | feat(angular): expose agent capabilities | ||
|
|
5720cd7fdc | feat(angular): support fetch credentials | ||
|
|
e86e7be328 |
fix(packages): declare the MIT SPDX license on five published packages
@copilotkit/core, /agentcore-runner, /sqlite-runner, /voice and /web-inspector all publish to npm without a "license" field, so registry metadata and automated license scanners report them as Unknown. The repo is MIT (see LICENSE) and every other published @copilotkit/* package already declares it; these five were simply missed. Reported downstream in #2860, where a corporate procurement scan blocked on Unknown-license dependencies. Note that the @ag-ui/* packages named in that report are NOT affected -- they have carried "license": "MIT" since at least 0.0.35, including every version the reporter's scanner flagged. Placed before "repository" to match the sibling packages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
12e89e60fa | Merge branch 'main' into fix/state-manager-run-id | ||
|
|
1f9b60b231 | chore: release monorepo v1.68.1 | ||
|
|
f8cb4d2447 | chore: release channels v0.9.0 | ||
|
|
e6864b6bdd | chore: release monorepo v1.68.0 | ||
|
|
f248a7eb30 |
feat(channels-slack): render table cells as rich_text when they carry markup
Portable <Cell> content was always emitted as a Slack `raw_text` cell, which is literal: markdown links, Slack link syntax and bare URLs all rendered as plain characters, so there was no way to get a clickable link or bold text into a table cell through the portable vocabulary. Body cells whose content contains a link, bold, italic, strikethrough or inline code are now emitted as a `rich_text` cell. Plain content still produces the byte-identical `raw_text` payload, and header cells always stay `raw_text` (Slack renders them bold already, and `rich_text` is not allowed in a `data_table` header cell). The conversion reuses `markdownToMrkdwn` — the package's single source of truth for the portable dialect — and tokenizes its `mrkdwn` output into rich-text runs, so the package keeps one markdown parser. The 2000-char cell budget now applies to the visible text of a rich cell. |
||
|
|
eb3f430ae1 | feat(runtime): mark Learning config experimental | ||
|
|
99da13de53 | fix(runtime): preserve Learning compatibility contracts | ||
|
|
a9f283ab55 | feat(runtime): assign threads to Learning Containers | ||
|
|
e7e8f7dc37 |
fix(runtime): only treat a request stream as consumed once it is drained
`isStreamConsumed` checked `req.complete` and the private `_readableState.ended`/`endEmitted` alongside `req.readableEnded`. The first two are set by the Node HTTP parser once all network bytes reach the socket, which happens before the route handler reads anything. Any framework that awaits between socket read and dispatch — notably the Next.js pages router — therefore reported an unread body as already consumed. With `bodyParser: false` there is no `req.body` to rebuild the request from either, so `copilotRuntimeNodeHttpEndpoint` logged "Request stream consumed with no available body" and forwarded an empty payload upstream, and the request failed with `400 Invalid JSON payload`. Rely only on `readableEnded`, which flips true after the `end` event fires from genuinely draining the stream. The `parsedBody !== undefined` check at the call site still covers the body-parser case. Diagnosed by @AlexNti in #3489, which patched the since-retired `packages/v1/runtime` path; re-applied here on `packages/runtime` with the tests ported and a live `http.IncomingMessage` regression test added. |
||
|
|
4e9eee3094 |
feat(runtime): add MiniMax built-in models (#6464)
Reason: Add the current MiniMax text models to BuiltInAgent model resolution. - Register MiniMax-M3 and MiniMax-M2.7 as built-in model identifiers. - Resolve MiniMax model strings through the global endpoint with API key and regional base URL configuration. - Document both model specifiers and cover global and China endpoint selection. Checks: - `node_modules/.bin/nx run @copilotkit/runtime:test -- src/agent/__tests__/resolve-model-baseurl.test.ts` - `node_modules/.bin/nx run @copilotkit/runtime:check-types` - `pnpm validate:model-names` - `node_modules/.bin/nx format:check --files=packages/runtime/src/agent/index.ts,packages/runtime/src/agent/__tests__/resolve-model-baseurl.test.ts` - `git diff --check` |
||
|
|
7187a0aa19 |
fix(channels-slack): three defects that silently broke Slack Block Kit (#6462)
Closes OSS-819. Part of OSS-794, which stays open for the OpenTag demonstration (OSS-820). *Reopened from #6454 — the branch was renamed so Linear links the right sub-issue, and GitHub closed the original rather than retargeting it. Same three commits, unchanged.* Three defects in the Slack Block Kit catalog, each verified against a real workspace. **99 lines changed across three files.** The reason these sat undetected matters more than their size: **a payload Slack refuses produces no error anywhere.** No log line, no exception, no failing test — the message simply never arrives, which is indistinguishable from a bot that had nothing to say. The renderer compounds it by design, dropping unknown nodes silently so one bad node cannot fail a whole message. ## 1. `container` was refused on every send Its children serialized into `blocks`; Slack reads `child_blocks`. ## 2. Every menu, checkbox, radio group, overflow and confirm dialog was refused The codec stamped `type` onto every catalog entry, including composition objects whose schema has none — Slack's option object is `{text, value}`, and the same holds for `confirm`, `option_group`, `conversation_filter`, `dispatch_action_config`, `slack_file`, `trigger` and `workflow`. An unknown field makes Slack reject the entire message, so the whole interactive surface was unusable through `Slack.Object.*`. Measured against a live workspace: **1 of 26 block elements delivered before this fix, 23 after.** Note the existing `native-catalog.test.ts` asserted the very assumption that was wrong — that every entry serializes its discriminator. It was green while the product was broken. It now asserts the corrected rule. ## 3. An image could not use a file already in the workspace The required-field check demanded `image_url` unconditionally; Slack accepts `image_url` *or* `slack_file`. An image needs alt text plus either source now, and passing neither is still an error. ## Two catalog corrections `file` leaves the authorable manifest. Slack: *"You can't add this block to app surfaces directly, but it will show up when retrieving messages that contain remote files."* The same sentence appears verbatim in `@slack/types`' own doc comment. It is an inbound shape; offering it as a component meant offering something that can never succeed. `alert` stays out with its citation — *"Alert blocks are currently only supported in modals."* Verified rather than assumed: Slack's own example payload posted verbatim into a message is refused, while a plain section in the same delivery seconds later arrives. ## How these were found A fixture per catalog entry — 19 authorable blocks, 26 elements, 15 composition objects — with the expected payload **transcribed from `docs.slack.dev`, not captured from our serializer**, delivered through a managed Channel into a real workspace. **55 of 60 deliver.** That corpus is a working instrument, not a deliverable, so it is deliberately not part of this PR — ~1700 lines of fixtures to maintain against a 99-line change is a bad trade for reviewers. It lives with the team and gets re-run when the catalog moves. One methodological note, because it changed what we count as proof: the first live run passed entries that demonstrated nothing. A rich-text block with one unstyled run renders exactly like a plain section; a carousel with one card renders like a card. Both were accepted and worthless as evidence — caught by a human looking at the output, not by the harness. Fixtures had to *exercise* each entry, and that is what surfaced defect 2. ## Found in the same pass, tracked separately - **OSS-817** — the managed path dropped every picker's value (9 of 26 elements). Fixed and confirmed live. - **OSS-818** — handler ids collide across structurally identical messages. ## Verification `test`, `check-types` and `build` green across `channels-slack`, `channels`, `channels-intelligence` and `runtime`, both with and without the fixture corpus present. Every block, element and object was delivered into a live Slack workspace and reviewed by eye. |
||
|
|
44d54c65d6 |
fix(react-core): repair useCopilotReadable effect deps, convert args, and dependencies (#6409)
Fixes #6383. Fixes #6243.
Both issues land in the same 35 lines of `useCopilotReadable`, so they
are fixed together. This PR also covers a third defect neither issue
reports.
All of it traces to a single commit:
|
||
|
|
47ad5e34a3 |
refactor(react-native)!: converge tool-call rendering onto CopilotKit's shared registry (#6438)
## What does this PR do?
`@copilotkit/react-native` maintained a **private tool-call render
registry** (`hooks/RenderToolContext.tsx`) alongside the canonical one
that `CopilotKitCoreReact` already provides — and which every React
Native app already ships, unused. This PR deletes the fork and points
React Native at the shared registry.
That fork caused three bugs:
| Bug | Symptom | Cause |
|---|---|---|
| **Tool renders never streamed** | A component registered with
`useRenderTool` / `useComponent` painted nothing until the tool call
completed | `CopilotChat` used `JSON.parse` on the argument buffer.
While a model writes a tool call that buffer is *invalid JSON by design*
— AG-UI delivers `TOOL_CALL_ARGS` deltas that are concatenated
client-side — so the parse threw on every delta, warned, and fell back
to `{}` |
| **`useComponent` rendered nowhere** | Silently, with no error | It
writes to core's registry; React Native's chat read React Native's
private `Map` |
| **Chat history degraded** | Navigating away from the registering
screen turned earlier tool calls into a `Called: <name>` placeholder |
The private `Map` deleted renderers on unmount; core deliberately keeps
them |
`@copilotkit/react-core` has used `partialJSONParse` on this path since
v2 shipped. React Native diverged because `useRenderToolCall` was
excluded from its re-exports on the stated grounds that it "depends on
DOM elements via `DefaultToolCallRenderer`" — a claim that was never
true of the hook itself. It was only ever reachable through the fat
`/v2` entry, whose weight is the real hazard (#4893). #5883 moved it
into `/v2/headless` on 2026-07-23; the exclusion comment was rewritten
the next day without revisiting the reason.
### What changed
- **One registry.** `useRenderTool` registers through `useFrontendTool`
into `CopilotKitCoreReact.renderToolCalls`. `CopilotChat` and any custom
surface consume react-core's `useRenderToolCall`.
- **Types are derived, not declared.** `RenderToolProps` is now
`React.ComponentProps<ReactToolCallRenderer<T>["render"]>`, so React
Native cannot drift from `ReactToolCallRenderer` — the contract every
registered renderer is actually invoked against. Change that contract
and `check-types` names every React Native renderer the change breaks.
React Native narrows only the *return* type to `ReactElement | null`,
which `FlatList`'s `renderItem` genuinely requires.
_Scope of that guarantee (corrected during review):_ it does **not**
extend to the type react-core publicly exports under the same name.
Web's `RenderToolProps<S>`
(`react-core/src/v2/hooks/use-render-tool.tsx`) is a separate
hand-declared union, generic over a schema, carrying arguments under
`parameters` (not `args`) and declaring `status` as string literals
rather than `ToolCallStatus` members. Both divergences are live today
and nothing type-checks them shut — the one place the shapes meet,
react-core's own bridge, compiles because a string-enum member is
assignable to its own literal type but not the reverse. Aligning web's
alias is a breaking web API change, filed separately.
- **`RenderToolContext.tsx` deleted** (−150 lines), along with 15 tests
that described the removed subsystem. One of them — `unregisters the
render function on unmount` — asserted the chat-history bug as a
requirement.
- **Two structural CI guards for #4893**, in opposite directions: a test
failing if any React Native source imports the fat `/v2` entry, and a
script failing if react-core's `/v2/headless` or `/v2/context` chunks
ever link shiki/mermaid/cytoscape/katex/streamdown. Both were verified
able to fail by deliberately introducing the regression. These are
*structural* assertions, not size budgets — `dev-docs/bundle-size.md`
freezes `limit` fields until OSS-122.
- **`react-native` added to the bundle-size glob**, which it had never
been in, plus a `size:headless` measurement.
React Native also gains capabilities it lacked: render props inferred
from your schema, `name`/`toolCallId` on render props, and `result` on
completed calls.
**Corrected during review — two capabilities this originally claimed are
not delivered:**
- **Wildcard (`"*"`) renderers do not work on React Native.** Because
`useRenderTool` routes through `useFrontendTool` (which calls
`addTool`), `name: "*"` registers a frontend tool literally named `*` —
advertised to the model, and colliding with core's separate
wildcard-executable-tool path. react-core's `useRenderTool` is
renderer-only and special-cases the wildcard; React Native's is not. The
guide now advises against it.
- **`followUp` (and `available`) are not forwarded**, and the handler's
`context` argument is dropped, so `stopAgent()`'s abort signal is
unreachable from an RN handler.
Both are tracked in § Known limitations for the follow-up that converges
React Native onto react-core's hooks — deleting RN's `useRenderTool` in
favour of re-exporting `useFrontendTool` (tool + renderer) and
react-core's `useRenderTool` (renderer-only, wildcard-capable). That is
an API change with its own migration note, so it is not in this PR.
### ⚠️ Breaking (in a minor)
`useRenderToolRegistry` and `RenderToolProvider` are **removed**. Both
are documented on the docs site, so this is a real break — see the
`BREAKING CHANGE:` footer on `db67ccf`, which is what the release notes
derive from, plus the rewritten reference pages.
```diff
- const registry = useRenderToolRegistry();
- const renderer = registry.get(toolCall.function.name);
- return renderer ? renderer({ args, status }) : null;
+ const renderToolCall = useRenderToolCall();
+ return renderToolCall({ toolCall });
```
Also note two semantic changes: `args` is `Partial<T>` **only** while
`status` is `"inProgress"`, and a render function is now captured at
registration — if it closes over changing values you must declare them
in `deps` (React Native previously refreshed the closure on every
render).
**Known limitation:** agent-scoped renderer resolution does not take
effect on React Native. `CopilotChatConfigurationProvider` is not in
RN's provider tree, so `agentId` always resolves to the default.
Renderers still resolve by name; two agents registering the same tool
name resolve arbitrarily. Filed separately.
### A data point worth recording
Adding `useRenderToolCall` to the measured headless entry moved the
bundle **92.8 kB → 92.7 kB**. Flat. The hook React Native spent months
not using was already inside the chunk every RN app resolves whole —
Metro doesn't tree-shake, so the fork never saved a byte. It cost them.
### Testing
- `@copilotkit/react-native`: **253 passing / 22 files** ·
`@copilotkit/react-core`: **1480 passing / 123 files** · `check-types`
and `build` green for both.
- Each of the three bugs has a deterministic test driving a real
`CopilotKitCoreReact` — no mocking of the code under test.
- Both #4893 guards carry mutation evidence: introduce the regression,
watch them fail, revert, watch them pass.
### Follow-up
`useRenderTool`'s JSDoc is split across two blocks, which orphans the
primary description from IDE hover (the `@param deps` warning still
surfaces). One-line fix, deliberately left out of the final fix wave.
## Related PRs and Issues
- **Supersedes #6346** (@davidmckayv) — its diagnoses were correct and
its test assertions are ported here, re-driven through the real registry
rather than a mocked local one. Credited via `Co-Authored-By` on
`4104bd1`.
- Addresses the React Native half of **#4893**.
- Builds on **#5883**, which created the lean `/v2/headless` entry this
PR consumes.
## 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
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
3a80c2696e |
feat(vue): mirror React's useAgent thread scoping, remove thread cloning (#6234)
## Problem
Vue's `useAgent` implemented per-thread agent **cloning** — a mechanism
React never had. Passing a `threadId` silently handed you a copy of the
agent:
```ts
useAgent({ agentId: "assistant", threadId: "thread-1" }) // → a clone, keyed (agent, threadId)
```
The clones lived in a module-level `WeakMap` (`globalThreadCloneMap`),
so:
- Nothing tied a clone's lifetime to the scope that created it — they
were never released.
- Components had to *look up* which copy was live.
`CopilotChatMessageView` called `getThreadClone(registryAgent,
config.threadId) ?? registryAgent` just to find the agent actually being
rendered.
- `getThreadClone` / `globalThreadCloneMap` were exported from the
module purely so components could do that lookup.
Meanwhile React grew an explicit contract for the same use case in
#6141: a private *proxied* agent, registered under a local `agentId` and
routed to a `runtimeAgentId`.
## Change
Deletes cloning entirely and ports React #6141's contract to Vue.
`cloneForThread`, `getOrCreateThreadClone`, `getThreadClone` and
`globalThreadCloneMap` are gone — zero references remain, including in
prose.
`UseAgentProps` becomes a base plus a two-branch union, with the same
all-or-nothing rule React now enforces:
```ts
useAgent() // shared registry agent
useAgent({ agentId }) // shared registry agent
useAgent({ agentId, runtimeAgentId, threadId }) // private proxied agent
```
Every partial set — `{ agentId, threadId }`, `{ agentId, runtimeAgentId
}`, `{ runtimeAgentId, threadId }` — is a compile error, backed by the
same three runtime guards with the same messages for callers TypeScript
doesn't reach.
### Parity with #6141
| | React (#6141) | Vue (this PR) |
|---|---|---|
| scoped branch | `agentId` / `threadId` / `runtimeAgentId`, all
required `string` | same, as `MaybeRefOrGetter<string>` |
| unscoped branch | `agentId?: string`, `threadId?: undefined`,
`runtimeAgentId?: undefined` | identical |
| runtime guards | 3 | same 3, same messages |
| thread resolution | prop → chat config, gated on `hasExplicitThreadId`
| identical |
| proxy registration | balanced effect on core + both ids | same deps |
## Two Vue-specific details
Both are load-bearing and were found by tests failing, not by
inspection:
**The pin watcher's first source is `() => agent.value`, not `agent`.**
Vue sets `forceTrigger` when any array watch source is a shallow ref, so
passing the ref directly re-ran the pin on *every* `triggerRef(agent)` —
i.e. every streamed message — re-pinning the inherited thread over one
`CopilotChat` had deliberately set for the chat it renders. Two existing
suites cover this (`uses the explicit agentId and threadId over
inherited configuration`). React has no equivalent hazard because effect
deps compare by identity.
**`CopilotChat` assigns `agent.threadId` inside its `/connect`
watcher**, not a separate one. `CopilotKitCore.connectAgent` reads that
field *synchronously* (`run-handler.ts`) to decide whether a restore is
fresh, so a later assignment lets `/connect` address the previous thread
— skipping the messages/state reset and re-stamping its restore key with
the stale id. Same placement as React's `CopilotChat`.
`CopilotChatMessageView` now resolves the registry agent directly
instead of consulting the clone map, and reads `copilotkit.agents` so it
recomputes when the registry changes.
## What callers see
**One agent per `agentId`** — the model React has always had. Thread
isolation is now explicit instead of implicit: ask for it and you get a
real, separately-registered agent rather than a copy that appears out of
nowhere.
```ts
// before — silently produced a copy of the "assistant" agent
useAgent({ agentId: "assistant", threadId: "thread-1" })
// now — an explicit private agent of your own, routed to "assistant"
useAgent({ agentId: "chat-1", runtimeAgentId: "assistant", threadId: "thread-1" })
```
Nothing in this repo needed updating: `CopilotChat`, `use-capabilities`,
`use-interrupt` and all six example apps already used `{ agentId }`.
`<CopilotChat agentId threadId>` is unchanged for consumers.
## Tests
`use-agent-thread-isolation.test.ts` (433 lines) covered clone semantics
that no longer exist; it's replaced by
`use-agent-thread-pinning.test.ts`, which pins the new invariants — one
instance per `agentId` never a copy, config-thread pinning gated on
explicitness, and all three all-or-nothing guards.
Four component suites used `getThreadClone` purely as a lookup to find
the agent under test and now read from the registry.
`MockMCPProxyAgent` recorded `addMessage` **only inside its `clone()`
override**, so those assertions were passing only because cloning
existed. The recording moves onto the class. `clone()` itself is left
intact everywhere — `CopilotKitCore`'s `SuggestionEngine` still clones
agents (`packages/core/src/core/suggestion-engine.ts`), so removing
those overrides would have planted a latent trap.
## Deliberately not included
Found while reviewing this area, real, but out of scope — each wants its
own change:
- `useAgent`'s header watcher **replaces** `agent.headers` instead of
calling `copilotkit.applyHeadersToAgent()`, dropping per-agent
construction-time headers. Regresses #5635 in Vue; React does this
correctly.
- `credentials` never reach a provisional agent.
- No `onAgentsChanged` subscription anywhere in `packages/vue`, so `()
=> copilotkit.value.agents` as a watch source never re-evaluates on
registry change.
- `/connect` is skipped for a plain `HttpAgent` — the `hasCustomConnect`
prototype comparison matches every real agent. Vue-only, no React
equivalent.
- `CopilotThreadsDrawer.ssr.test.ts` is a latent flake (5s timeout on a
dynamic import; passes in isolation).
|
||
|
|
4b17ea7d35 |
fix(scripts): tokenize before hunting loader calls in the purity gate
The #4893 hard-fail gate's loader-call detector gave WRONG VERDICTS IN BOTH DIRECTIONS. It layered two regexes — a comment/string/template alternation that blanked only the comment branch, and `\b(?:import|require(?:\.resolve)?)\s*\(` over the result — then classified an argument as static from the FIRST CHARACTER after the paren. All nine shapes below were reproduced against the real gate before the rewrite: false FAIL throw new Error("use require(path) instead") false FAIL `import(${x})` inside a template false FAIL o.import(y) / mod.require(x) (member calls, not loaders) false PASS /https:\/\//; …import(n) (the regex's `//` blanked the rest of the line, hiding a real dynamic call) false PASS import(`stream${n}`) (merely STARTS with a quote) false PASS import("zo" + n) (same) false PASS import(`${base}/v2/index.mjs`) (same — the fat entry) false PASS __require(name) (no \b inside `__require`) Replaced with `scanSource`, a single-pass tokenizer that classifies every character as code / comment / string / template / regex and returns a length-preserving masked view plus a literal-span list. The one surviving regex now only ever sees code, so import-shaped TEXT cannot reach it at all; an argument counts as static only when it is one COMPLETE literal with no concatenation or interpolation; `__require` is matched; and a member call is rejected both by lookbehind and by a whitespace-skipping back-scan (so `m\n .import(x)` is not a loader either). Proven in both directions: nine innocent/violation pairs run through the real `assertEntryPurity`, each innocent form CLEAN and each matching real violation FAIL. Re-proved end-to-end by prepending `import "streamdown"` to the real dist/v2/headless.mjs — exit 1 naming all five families — then restoring it byte-identically. On the untouched dist the scan sees 66 loader calls in the `.cjs` graph and classifies all 66 static, so it passes because it LOOKED. Also adds the first `.cjs` fixtures: every existing fixture was `.mjs`, leaving the script's `format: "cjs"` branch and the `require()` shape asserted by nothing. Tests 24 → 47. `stripComments` is renamed `maskNonCode`, since it now blanks literals and regexes too; it had no caller outside this script and its test. The RN guard keeps its own copy, untouched. dev-docs/bundle-size.md: the four holes a sibling agent documented as known limitations this round are closed and removed from that list; what genuinely remains (regex-vs-division heuristic, no JSX/TS, indirect loaders) replaces them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4c17a8fe8c |
fix(react-native): key the messages fingerprint on object content
`messagesFingerprint`'s content key collapsed every object to 0, so an
in-place content replacement that kept the same message id was invisible to
every memo derived from it. Its comment claimed to mirror react-core's
`messagesMemoKey`, which stopped being true when react-core #6325
(
|
||
|
|
1b39c12e36 |
fix(scripts): stop the headless CLI gates skipping themselves on odd paths
Both scripts decided "am I the entrypoint?" by comparing `import.meta.url` to a `file://`-concatenated `process.argv[1]`. `import.meta.url` is percent-encoded and symlink-resolved; raw argv[1] is neither. So the comparison was false for any checkout path containing a space, for any invocation through a symlink (macOS /tmp is one), and on Windows — and a false guard skipped the whole CLI block. Reproduced before fixing: the #4893 purity gate and the bundle-size measurement both exited 0 having printed nothing and asserted nothing, which is worse than a gate with holes because it manufactures confidence. The guard was added by this PR so the modules could export internals to their new negative tests; making the gates testable introduced a way for them not to run. Both now compare real filesystem paths through an exported `isEntrypoint`: `fileURLToPath` defeats the encoding and Windows forms, `fs.realpathSync` on both sides defeats symlinks, and a `path.resolve` fallback keeps a nonexistent argv[1] from throwing. Each `node --test` suite gains five entry-guard tests, including an end-to-end spawn of the real script through a symlinked package-root alias whose name contains a space — the only case that catches the call site regressing back to a string comparison (verified: it fails against the old expression). The unit cases assert the naive comparison really would have failed, so none of them can pass vacuously. Both negative gates were re-proven to still bite: a doctored dist entry pulling streamdown fails the purity gate, and a stubbed dist entry trips the measurement's plausibility floor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c7d176f264 |
test(react-native): make the #4893 entry guard fail on violations, not on growth
The headless import-graph guard pinned the resolved graph EXACTLY — the
11-module list and the 8 bare specifiers, both `toEqual`. That catch-all was
deliberate (a heavy dependency nobody enumerated still had to be looked at),
but it also went red on innocent growth: adding any first-party `src/` module
to the headless graph failed it, on someone else's unrelated PR. A guard that
fails on innocent changes gets deleted by the third person who hits it, and
then it guards nothing.
Express the catch-all over PACKAGES instead of MODULES: the graph may only
reach packages a headless consumer is guaranteed to be able to resolve — this
package's `dependencies` plus its NON-optional `peerDependencies`, read from
package.json rather than hand-copied. That is precisely the promise the
headless entry sells ("bundles with nothing stubbed in metro.config.js"), so
it still fails on any new third-party edge, on every optional peer, on a
devDependency, and on a Node builtin — while a new first-party module or
another import of an already-sanctioned package is free.
The two other things the pin bought are kept explicitly:
- Comment stripping. The eight phantom specifiers JSDoc examples used to
harvest were all self-references, and this package's own name is not in the
guaranteed set, so a `stripComments` regression still fails here.
- Non-vacuity. Every remaining graph assertion is a deny-list, and a deny-list
over a truncated graph passes for the wrong reason, so a subset floor
asserts the walk still reaches the provider, the polyfills and the
react-core headless edge.
Not changed: comment stripping itself, the import()/require()/require.resolve
extraction, non-literal loader flagging, emitted-extension resolution, the
loud failure on unresolvable edges, the entry-presence tests, the #4893
fat-entry ban (still the assertion that catches `@copilotkit/react-core/v2`)
or the heavy-dependency ban. The runtime-export `beforeAll` is untouched.
Proven both directions: a new first-party module passes the loosened guard and
fails the old pin; `@copilotkit/react-core/v2`, `shiki`, an unenumerated
devDependency edge and a truncated walk each fail.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
00caf5fa7b |
docs(react-native): fix the chat memo comment's identity rationale
`messagesFingerprint`'s header JSDoc and the matching inline comment near the
`listItems` memo justified keying on message CONTENT with a claim that is false:
that `agent.messages` is mutated in place throughout, and that "the AG-UI apply
pipeline reuses one array for a whole run".
It does not. `@ag-ui/client`'s `AbstractAgent.processApplyEvents` REASSIGNS
`this.messages = applied.messages` for every applied event, so a streaming run
hands down a new array — and new message, `toolCall` and `function` objects — per
delta. Verified against a real AG-UI run by the PR reviewer, and confirmed here in
@ag-ui/client 0.0.57's `AbstractAgent`. The old grep behind the claim ("assigning
`.messages` in packages/core/src hits test files only") is accurate but proves
nothing: `@ag-ui/client` is a dependency, outside that tree.
The fix itself stands. Identity is unreliable in BOTH directions, which is the
actual rationale: it changes on the apply path, and it does NOT change on the
paths these memos exist to serve — core splices tool results in place
(`agent.messages.splice(insertAt, 0, toolMessage)`,
packages/core/src/core/run-handler.ts:931, :1080), `AbstractAgent.addMessage` is a
`this.messages.push(...)`, and `useAgent` re-renders with a bare `forceUpdate()`
(packages/react-core/src/v2/hooks/use-agent.tsx:382-396). A signal that both
misses changes and fires without them cannot be a dependency, so the derivations
must key on content.
Comments only: three sites reworded (the JSDoc, the "cannot be used" pointer at
the `messagesKey` call, and the inline note on the `listItems` memo). `git diff`
touches no behaviour, type or dependency array — every changed line is a comment.
The `contentKey` length-vs-value paragraph is left alone; another change owns it.
Note: the same false claim is in commit 77ed31c437's body, which cannot be
rewritten, and in a GitHub review comment.
Not run in this worktree: it has no node_modules, and the change is comment-only,
so it cannot affect types, lint or tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
a3b814a041 |
fix(core): refresh Intelligence delegate headers before every join (#6469)
## Summary
`ProxiedCopilotRuntimeAgent` builds its `IntelligenceAgent` delegate
**once** and caches it for the proxy's lifetime, copying `headers` into
the delegate's constructor config. Nothing ever refreshed that copy, so
**a header that changed after the delegate was created never reached
`/connect` or `/run`** — for the life of the agent.
For a multi-tenant app carrying the active tenant in a header, the join
was attempted under the *previous* tenant's identity with the *new*
tenant's thread id, and the platform correctly answered
`THREAD_NOT_FOUND`. Only a full page reload cleared it, because that
rebuilds the delegate. A rotated or refreshed `Authorization` bearer has
the same exposure.
Reported by Sameday against 1.67.1 with a deterministic staging repro:
```
19:45:23.554 | /copilotkit/runtime/threads | hdr=<tenant B> | 200
19:45:23.886 | /copilotkit/runtime/threads/subscribe | hdr=<tenant B> | 200
19:45:23.893 | /copilotkit/runtime/agent/<id>/connect | hdr=<tenant A> | body.companyId=<tenant B> | 404
```
`/threads` carries **B** while `/connect` carries **A**, ~340ms apart in
the same switch. Not a race — a stale copy with no refresh path.
## Root cause
`setHeaders` / `applyHeadersToAgent` could not fix this: they write an
agent's `.headers`, and `IntelligenceAgent` exposed only `private
config`. `syncDelegate` *looks* like the refresh path, but its
`hasHeaders` probe is `"headers" in agent` — false for the delegate,
since `headers` is declared on `HttpAgent`, not on `AbstractAgent`. So
`config.headers` was the sole header source for Intelligence REST calls,
with no refresh path at all.
## The fix
Expose `headers` as a public accessor pair backed by `config`, and read
it in `requestJoinCredentials$`.
**The accessor is the entire fix**: it makes `hasHeaders` true, so
`syncDelegate` — which already runs on every `resolveDelegate()`, and is
preceded by `applyHeadersToAgent` in `RunHandler.connectAgent` — starts
actually refreshing the delegate before each join. No new plumbing.
Two things worth flagging for reviewers:
1. **The originally-suggested fix ("make `requestJoinCredentials$` read
live headers") does not work on its own** — and is actively harmful.
There was no live header source on the class to read: without the
accessor, `this.headers` is `undefined` and **every header is dropped**
(verified: only `Content-Type` survives). The read here goes through the
accessor for a single source of truth, not because that read carries the
fix.
2. **The setter replaces the config object rather than mutating it**,
because `clone()` shares the config reference. The join path alone would
mask an in-place write (`syncDelegate` rewrites headers just before
every join), but the credential re-acquisition inside a running pipeline
(`intelligence-agent.ts:563`) does not re-sync — so a clone's tenant
could ride out on the original's socket-error refresh. That's the same
cross-tenant leak this accessor exists to prevent.
`credentials` had the identical defect via `config.credentials`
(`hasCredentials` was false too) and gets the same treatment.
## Testing
**Unit tests (5 new, each written first and watched fail).** The pre-fix
failure is the staging symptom reproduced:
```
FAIL > sends a header changed after the delegate was created
AssertionError: expected { …(2) } to match object { 'X-Tenant': 'tenant-b' }
- "X-Tenant": "tenant-b",
+ "X-Tenant": "tenant-a",
```
Coverage: a header changed post-construction reaches `/connect`; the
same on the `/run` path (which was independently verified broken
pre-fix, sending tenant A where B was expected); credentials likewise; a
clone's header update must not reach the original
(`IntelligenceAgent.clone()` invariant — this one fails under in-place
config mutation); and a per-thread clone and its original each send
their own tenant.
**Verified beyond the unit tests.** Because the mocked-harness result
alone doesn't prove the production wiring, I drove the real chain —
`CopilotKitCore.setHeaders` → registry → proxy → delegate → outbound
POST — in a plain Node process with no vitest and no `vi.mock`, stubbing
only `fetch` at the network boundary. Same script against the unfixed
file, then the fix:
```
BEFORE (origin/main) AFTER (this PR)
"headers" in delegate: false "headers" in delegate: true
delegate.headers: undefined delegate.headers: { X-Tenant: tenant-b }
proxy.headers after setHeaders(B): proxy.headers after setHeaders(B):
{ X-Tenant: tenant-b } { X-Tenant: tenant-b }
0: POST /connect X-Tenant=tenant-a 0: POST /connect X-Tenant=tenant-a
1: POST /connect X-Tenant=tenant-a <-- 1: POST /connect X-Tenant=tenant-b credentials=include
FAIL (stale headers) PASS (live headers reach /connect)
```
The "before" column reproduces the report's tell exactly:
`proxy.headers` correct at tenant B while `/connect` still sends tenant
A, through the very API the report found ineffective.
**Gates** (run in a worktree with a freshly built `@copilotkit/shared`,
since a stale dist otherwise produces 20 unrelated
`core-inspector-metadata` failures and 4 `tsc` errors):
| Gate | Result |
| --- | --- |
| `@copilotkit/core` vitest | **654 passed / 654**, 59/59 files |
| `tsc --noEmit` | clean |
| `oxlint` | 0 errors (2 warnings, both pre-existing test helpers) |
| `oxfmt` | no reformatting needed |
**Not covered:** `fetch` is stubbed, so this does not exercise a live
Intelligence gateway or a browser tenant switch — it proves the outbound
header is correct, not the platform's response to it.
## Note for whoever merges
#6450 and #6468 also touch `intelligence-agent.ts` (thread-restore work)
but neither goes near the header path, so conflicts should be textual at
worst.
## Follow-up left out of scope
Two separate pre-existing defects surfaced while verifying this one.
Neither is touched here.
**1. `credentials` passed to a `ProxiedCopilotRuntimeAgent` constructor
are dropped at registration.** `applyCredentialsToAgent` overwrites
`agent.credentials` from core unconditionally, with no per-agent
baseline — unlike `applyHeadersToAgent`, which merges over the
`agentOwnHeaders` baseline captured for exactly this reason (#5635).
Probed in a real process: an agent constructed with `credentials:
"include"` in a core with none configured reports `undefined`
immediately after registration, and every join goes out without
credentials. Identical before and after this PR, so it is not a
regression from this change — but the headers/credentials asymmetry
looks unintended, given #5433 was specifically about preserving proxied
runtime credentials.
**2. `buildRuntimeUrl` reads `config.agentId`
(`intelligence-agent.ts:770`), (`intelligence-agent.ts:770`), so
`syncDelegate`'s `delegate.agentId = routedAgentId()` is cosmetic for
the REST URL. Same root-cause class as this bug, but latent rather than
live (routing is fixed per proxy instance).
Happy to file both separately.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
70545f072a |
test(core): correct an overclaiming comment, tighten the credentials assertion
The per-thread-clone test's comment claimed it guards the copy-on-write setter. It does not: syncDelegate rewrites headers before every join, so it passes even with an in-place write (verified). Say what it actually pins — each proxy's joins carry its own tenant — and point at the clone-invariant test that does guard the setter. Also assert the pre-change join carried no credentials, so the credentials test shows a transition rather than a single end state. |
||
|
|
dde84a5804 | fix(angular): prevent duplicate OpenGenerativeUI sandboxes | ||
|
|
c06709aff7 | Merge branch 'main' into fix/state-manager-run-id | ||
|
|
01c7283210 | fix(core): prevent duplicate interrupt tool results (#6201) | ||
|
|
7d1cdc15df |
test(core): pin the run path against stale Intelligence headers
The report names both /connect and /run. The run path reaches the delegate through #runViaDelegate, which shares resolveDelegate with the connect path, so the accessor fixes both — but that was inferred from the shared call site rather than pinned. Verified failing against the pre-fix file (sent tenant-a where tenant-b was expected). |
||
|
|
a3562c20a6 |
fix(core): refresh Intelligence delegate headers before every join
`ProxiedCopilotRuntimeAgent` builds its `IntelligenceAgent` delegate once and caches it for the proxy's lifetime, copying `headers` into the delegate's constructor config. Nothing ever refreshed that copy, so a header that changed later never reached `/connect` or `/run` — for the life of the agent. `setHeaders`/`applyHeadersToAgent` could not fix it: they write an agent's `.headers`, and `IntelligenceAgent` exposed only `private config`. `syncDelegate` looked like the refresh path but its `hasHeaders` probe is `"headers" in agent`, which was false for the delegate. Multi-tenant apps that carry the active tenant in a header saw the join attempted under the previous tenant's identity with the new tenant's thread id, answered THREAD_NOT_FOUND. A rotated `Authorization` bearer has the same exposure. Only a full reload cleared it. Expose `headers` as a public accessor pair backed by `config`. The accessor is the entire fix: it makes `hasHeaders` true, so `syncDelegate` — which already runs on every `resolveDelegate()` — starts actually refreshing the delegate before each join. Note that changing `requestJoinCredentials$` to read live headers, as the report suggested, does nothing on its own: there was no live source on the class to read, and without the accessor `this.headers` is `undefined`, which drops every header. It reads through the accessor here for a single source of truth, not because that read carries the fix. The setter replaces the config object rather than mutating it, because `clone()` shares the config reference. The join path alone would mask an in-place write (syncDelegate rewrites headers just before every join), but the credential re-acquisition inside a running pipeline does not re-sync, so a clone's tenant could ride out on the original's socket-error refresh. `credentials` had the identical defect via `config.credentials` (`hasCredentials` was false too) and gets the same treatment. Verified beyond the unit tests by driving the real chain (`CopilotKitCore.setHeaders` -> registry -> proxy -> delegate -> outbound POST) in a plain Node process with only `fetch` stubbed: before, `"headers" in delegate` was false and the join after a tenant switch still sent tenant A; after, it sends tenant B. Reported by Sameday against 1.67.1 with a deterministic staging repro. |
||
|
|
528dea6483 |
fix(react-core): ship a single v2 context instance (#6440)
## Problem
`@copilotkit/react-core` ships **two independent copies** of the v2
context module, so `useLicenseContext` imported from
`@copilotkit/react-core/v2/context` returns the default forever —
`status: null` even when `/info` reports `licenseStatus: "valid"`.
Reported downstream as a chat-history sidebar that never loads, because
`useThreads` is gated on license status.
`src/v2/context.ts` is compiled by two separate tsdown builds:
| Build | Output | Contains |
|---|---|---|
| `entry: ["src/index.tsx", "src/v2/index.ts"]` | `dist/` shared chunk |
inlined copy **A** |
| `entry: {context: "src/v2/context.ts"}` | `dist/v2/context.*` |
standalone copy **B** |
There is no import edge between them, so `createContext()` runs twice.
`CopilotKitProvider` lives in the shared chunk and publishes to **A**;
`@copilotkit/react-core/v2/context` exports **B**, which nothing ever
provides.
Verified against the published 1.66.4 artifact:
```
$ grep -n "createContext" dist/v2/context.mjs
104:const CopilotKitContext = createContext(null);
124:const LicenseContext = createContext({
$ grep -n "createContext" dist/copilotkit-nRjRp2_5.mjs # inside //#region src/v2/context.ts
1522:const CopilotKitContext = createContext(null);
1544:const LicenseContext = createContext({
$ grep -E '^import .*from "[^"]*context[^"]*"' dist/copilotkit-nRjRp2_5.mjs
# (empty — no import edge)
```
`CopilotKitContext` is duplicated identically, so `useCopilotKit`
imported from that subpath throws `"useCopilotKit must be used within
CopilotKitProvider"`. The subpath was effectively unusable for web
consumers; license was just the *silent* failure mode.
**Compounding defect:** `src/v2/providers/index.ts` enumerates its
exports by name and omits `useLicenseContext` (even though
`CopilotKitProvider.tsx:19` re-exports it). So the live copy had **no
public import path at all**, leaving consumers with no correct
alternative.
Not a 1.66.x regression — broken since
|
||
|
|
20da9fae41 |
feat(runtime): report managed Channel drops and recoveries as telemetry (refs OSS-825)
A managed Channel that loses its gateway link was invisible outside the host process: the only trace was the injected `log` seam, which for a self-hosted or Railway-hosted runtime reaches nobody who can act on it. One 2026-08-12 outage ran 2.7 days on `kite-community` before a customer reported it, and reconstructing it needed pod forensics. Emit `channel_session_dropped` (carrying the cause we already compute for the log line) and `channel_session_recovered` (carrying how long the outage lasted). A session may report `online` without a preceding drop, which is not a recovery and is not reported as one. Events deliberately omit the Channel name — it is a customer-chosen identifier — and carry no message content. Capture is fire-and-forget with failures swallowed, the same contract fireInstanceCreatedTelemetry uses: telemetry must never break a live session. Also replay the drop cause on each "still down" reminder. In prod those lines read `still down after 233134s; Phoenix is retrying` with no cause at all, so an operator had to find the first line, 15 minutes earlier, to learn it was an HTTP 502. |
||
|
|
e8d5fa71d6 |
fix(runtime): bump uuid off deprecated v10.0.0 (#6118)
## Summary
`packages/runtime` declares its own `"uuid": "^10.0.0"` dependency, but
nothing in the package's source actually imports it directly — id
generation in `@copilotkit/runtime` goes through `randomUUID()`
re-exported from `@copilotkit/shared`, which already depends on
`uuid@^11.1.0`. The unused v10 pin just adds an npm deprecation warning
for every consumer installing `@copilotkit/runtime`:
```
npm warn deprecated uuid@10.0.0: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
```
This bumps it to `^11.1.0` to match `@copilotkit/shared` and clears the
warning.
## Test plan
- [x] `pnpm --filter "@copilotkit/runtime^..." run build` — all
workspace dependencies build cleanly
- [x] `pnpm --filter @copilotkit/runtime run check-types` — no type
errors
- [x] `pnpm --filter @copilotkit/runtime run test` — 126 test files /
1746 tests passing
- [x] Confirmed no file in `packages/runtime/src` imports `uuid`
directly (grepped for both `from "uuid"` / `from 'uuid'` and
`require("uuid")` — zero matches)
- [x] Confirmed `pnpm-lock.yaml` now resolves `uuid@11.1.0` for this
dependency, which is not on npm's deprecated-versions list
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
d6ee68706f |
feat(runtime): add agentId to AgentRunnerConnectRequest (#6120)
Closes #5911 Adds an optional `agentId` field to `AgentRunnerConnectRequest` so custom agent runners can hydrate messages when the cache is empty. Also passes the already-available `agentId` in `handleSseConnect` through to `runner.connect()`. ## Changes - `packages/runtime/src/v2/runtime/runner/agent-runner.ts`: Added `agentId?: string` to `AgentRunnerConnectRequest` interface - `packages/runtime/src/v2/runtime/handlers/sse/connect.ts`: Pass `agentId` to `runner.connect()` ## Verification - Runtime package builds successfully (`pnpm --filter @copilotkit/runtime build`) - The change is purely additive — the field is optional and does not affect existing runners |
||
|
|
4383a86198 |
fix(runtime): populate request headers in runtime error context (#6287)
## Summary `CopilotRuntime` exposes an `onError` callback, but the v1-to-v2 delegation drops it before server request processing. Runtime failures therefore can't provide the incoming request headers that applications use to correlate errors with a user or tenant. ## Root cause The legacy constructor retains the callback type, while delegated runtime options omit it. Common, SSE, and Intelligence handlers consume failures inside their own boundaries, before a generic endpoint hook can reconstruct the legacy event. ## Changes - Add one internal runtime reporter that snapshots incoming Fetch request headers. - Attach the configured legacy callback to the delegated runtime instance. - Route common, SSE, and Intelligence agent-run failures, including standard `RUN_ERROR` events, through that reporter exactly once. - Redact sensitive request headers (authorization, proxy-authorization, cookie, set-cookie, x-api-key, api-key, and the CopilotKit public-key header) before they reach the `onError` event. - Preserve responses, stream close behavior, telemetry, cleanup, logging, endpoint hooks, and callback isolation. - Add production-path regressions. ## Compatibility The existing `CopilotErrorEvent` type and optional `context.request.headers` field remain unchanged. Header names and values come from the failing request's Fetch `Headers` object, with sensitive credentials stripped before the event is emitted. The callback receives a fresh record, so mutation cannot alter the request or a later event. ## Out of scope React provider propagation, Chat, CopilotMessages, the deprecated runtime-client hook, redaction policy, HTTP response exposure, v2 endpoint-hook semantics, and non-agent runtime routes remain outside this slice. ## Related PRs and Issues Addresses #2716. Scope follows https://github.com/CopilotKit/CopilotKit/issues/2716#issuecomment-5086936254. Runtime error-routing precedent: https://github.com/CopilotKit/CopilotKit/pull/2143. ## Test plan - [x] Runtime error regression and reporter tests, 12 + 4 tests passed. Covers public routing, sanitized header propagation with credential redaction, callback rejection containment, snapshots, mutation isolation, and malformed requests. - [x] SSE and Intelligence telemetry tests, 7 + 14 tests passed. Covers agent-run failures, `RUN_ERROR`, setup boundaries, exact-once reporting, telemetry, cleanup, and response preservation. - [x] Runtime preservation suites, 57 + 129 + 56 tests passed. Existing request, endpoint-hook, and runtime-library behavior remains intact. - [x] Typecheck, formatting, and lint passed on changed runtime files; lint reported four pre-existing warnings. - [ ] CI green (`static / quality`, `test / unit` on Node 20/22/24). |
||
|
|
30986613d8 | feat(runtime): add MiniMax built-in models | ||
|
|
8c670653ce |
fix: align Angular 20 support and resolve packed smoke paths (#6452)
## What broke The regression was introduced by commit |
||
|
|
04c4198a14 |
docs: fix Copilot Runtime reference links (#5296)
## What does this PR do? Fixes stale Copilot Runtime documentation links that still point to `/concepts/copilot-runtime` and now route users to the existing `/backend/copilot-runtime` page. This updates both the source JSDoc and the generated reference MDX so the current docs content and future regenerated reference docs stay aligned. ## Related PRs and Issues - Closes #2082 ## Testing - `rg -n "concepts/copilot-runtime" packages showcase/shell-docs/src/content` returns no matches - `rg -n "backend/copilot-runtime" packages/runtime/src/lib/runtime/copilot-runtime.ts packages/react-core/src/components/copilot-provider/copilotkit-props.tsx showcase/shell-docs/src/content/reference/v1/classes/CopilotRuntime.mdx showcase/shell-docs/src/content/reference/v1/components/CopilotKit.mdx` - `git diff --check` ## 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) |
||
|
|
cbf79ef52a | fix: align Angular 20 support and resolve packed smoke paths | ||
|
|
9303fabcdd |
fix(channels-slack): let an image use a file already in the workspace
Slack's image block takes either an external `image_url` or a `slack_file` pointing at a file that already exists in the workspace. The required-field check demanded `image_url` unconditionally, so the `slack_file` form could not be built at all — an image sourced from Slack itself was unreachable through the catalog. An image now needs alt text plus *either* source. Passing neither is still an error: the check moved, it did not disappear, and the test covers both halves. |
||
|
|
078116ee8d |
fix(channels-slack): stop tagging untyped composition objects with a type
Slack's option object is `{text, value}` — it has no `type` field, and neither do
confirm, option_group, conversation_filter, dispatch_action_config, slack_file,
trigger or workflow. The codec tagged every catalog entry with its manifest type
regardless, so each of those carried an unknown field and Slack refused the
entire message containing it.
That took out every select, multi-select, checkbox, radio group, overflow menu
and confirmation dialog authored through `Slack.Object.*` — the whole interactive
surface. Measured against a real workspace: 1 of 26 block elements was delivered
before this change, 23 after.
It went unnoticed because a refused payload produces no error. There is no log
line and no exception; the message simply never arrives, which looks exactly like
a bot that had nothing to say.
The existing catalog test asserted the very assumption that was wrong — that
every entry serializes its discriminator — so it was green while the product was
broken. It now asserts the corrected rule and guards against a silent relapse.
|
||
|
|
81e7fefb18 |
fix(channels-slack): correct the container slot and drop the unpostable file block
Two catalog errors, both surfaced by delivering every documented block into a real Slack workspace rather than reading the reference again. container serialized its children into `blocks`; Slack reads `child_blocks`, so every container an app sent was refused — silently, because a refused payload produces no error anywhere, just a message that never arrives. file leaves the manifest. Slack states you cannot add it to app surfaces directly; it only appears when *reading* messages that contain remote files, and the same sentence appears verbatim in `@slack/types`' own doc comment. Keeping it in an authorable catalog offered a component that could never succeed. Sending a file remains thread.postFile(). alert stays out for the same class of reason (modals only), and both exclusions now carry their citation so "missing" and "deliberately not authorable" stay distinguishable. |
||
|
|
6540848745 | chore: refresh agent artifacts for 1.67.1 | ||
|
|
1853a24d00 | fix(skills): align public guidance with current APIs | ||
|
|
b4cfcf6f98 |
fix(react-core): stop the purity gate crashing opaquely on the declared Node floor
`assert-headless-purity.mjs` resolved its dist directory with
`import.meta.dirname`, which landed in Node 20.11 and is `undefined` below it.
The root package.json declares `engines: { "node": ">=18" }`, so a contributor
or runner on Node 18 hit this hard-fail CI gate as:
TypeError [ERR_INVALID_ARG_TYPE]: The "paths[0]" argument must be of type
string. Received undefined
at Object.resolve (node:path:1115:7)
at .../scripts/assert-headless-purity.mjs:71:19
— a stack trace into node internals, at module load, that names neither the
gate nor the real problem. Reproduced against a real Node 18.20.8.
Switch to `path.dirname(fileURLToPath(import.meta.url))`, which both sibling
scripts in this CI job already use (react-core's measure-copilotchat.mjs and
react-native's measure-headless.mjs), so all three read the same and none of
them carries a hidden runtime floor its own package does not declare.
Verified under real Node 18.20.8: the script now walks all four entries (650 /
646 / 649 / 645 modules) and exits 0, and still exits 1 with the full
`links the heavy render stack` report when a forbidden dep is injected into a
dist entry. The metafile-driven graph walk, the loud failure on unresolvable
edges and all 17 negative tests are untouched (`test:scripts`: 19 pass).
Skill-staleness check (reskinnable-demo CLAUDE.md rule): not applicable — this
touches packages/react-core, nothing under .claude/skills/reskin/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|