mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
python-sdk/v0.1.95
2572 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 | ||
|
|
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
|
||
|
|
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>
|
||
|
|
4b25cf34b8 |
test(react-native): stop the entry guard racing its own module load
The #4893 entry-surface guard timed out nondeterministically at full test parallelism ("Test timed out in 5000ms" on `await import("../headless")`), which four independent agents each worked around with --testTimeout or --maxWorkers=2. A flaky hard gate is a gate people learn to ignore. Measured, not guessed. The import is a one-time module-graph load whose VARIANCE — not its mean — broke the default budget: ~0.7-1.1s for this file alone and ~0.9-1.8s inside the full 22-file suite (n=8 each), but 4568ms on the run straight after a cold `nx build`, i.e. 91% of the 5000ms budget spent on an otherwise idle machine. The cost is resolve/transform plus cold-page- cache I/O over the ~283 KB of workspace dist that vitest.config.mjs inlines via `server.deps.inline: [/@copilotkit/]` (core ~218 KB, react-core v2/headless ~55 KB, v2/context, shared); bare-Node `import()` of the equivalent prebuilt dist is 461ms, so evaluation is not the expensive part. Four separate tests each awaited that same import, so all four raced one cost against one budget — and when the first lost the race the other three inherited its in-flight import and timed out with it, which is why the observed signature was three simultaneous failures rather than one. They now share a single explicitly-budgeted `beforeAll`, so the cost lives in exactly one place and each test reports ~0ms. The hook is nested rather than top-level on purpose: its failure domain must cover only the tests that need the module, or an import failure would take down the fs-only graph tests too — the same blast-radius problem as blind spot #4, just relocated into a hook. No assertion is weakened: comment stripping, import()/require() extraction, the exact resolved-graph pin and the revived existence test are untouched, and the guard still fails on a real violation (injecting a lazy `import("@copilotkit/react-core/v2")` into src/streaming-fetch.ts trips 3 assertions; reverted). Verification: 5 consecutive full-suite runs at DEFAULT parallelism, no --maxWorkers or --testTimeout override, 271/271 passing in 3.97-5.87s wall each; plus 3 concurrent full suites (30 workers on 10 cores) all green, and one pass at load average 235. `check-types` clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e7f3d7644d |
fix(react-core): make the #4893 purity gate scan the graph it claimed to scan
`scripts/assert-headless-purity.mjs` is a hard-fail CI gate, and it did not do what its header said. It read four built entry files and asked `code.includes(dep)`. That is weaker than the claim in both directions, and every item below was reproduced against a real build before this rewrite: 1. It never followed an edge out of those four files. Re-exporting one hook from the fat `@copilotkit/react-core/v2` entry — which links shiki, mermaid, cytoscape, katex and streamdown — left `dist/v2/headless.mjs` importing that entry by name, and the gate printed "clean" for all four files, exit 0. Same for a heavy dep reached through `@copilotkit/core`, which is external to this build: the entry says only `from "@copilotkit/core"` and there is nothing to grep. A split-out relative chunk escaped identically. 2. The header claimed the check "follows into node_modules". It followed nothing — not node_modules, not a relative sibling chunk. 3. `code.includes(dep)` is unanchored, so it matched comments and strings. Not hypothetical in either direction: the built artifact is comment-PRESERVING (233 lines of block comments survive in dist/v2/headless.mjs), and the five banned tokens sit in `src/v2/headless.ts`'s own banner. They are absent from dist only because that module is a re-export shell whose banner attaches to no retained code — moving the same sentence into a module that ships code hard-failed CI on all five tokens while linking none of them. The gate now drives esbuild with `metafile: true` over each built entry and matches on the RESOLVED graph, so it follows relative chunk edges and into node_modules for real, resolves `exports` maps, subpaths and pnpm symlinks, and cannot be fooled or tripped by a comment. Matching is anchored at the package name (`@shikijs/langs` and `cytoscape-fcose` count; `shikimori` does not) and also covers a forbidden dep left external, which resolves to no graph input at all. Unresolvable edges FAIL LOUDLY instead of reading as clean, as does a graph that does not contain its own entry. One edge shape survives a bundler: `import(name)` with a non-literal argument, which esbuild leaves alone without even warning. For that the gate reads text — the only place it does — over the graph's first-party files, using the `stripComments` helper ported from the sibling RN guard so a documented counter-example cannot trip it. Adds `scripts/__tests__/assert-headless-purity.test.mjs` (17 tests, wired into `test:scripts` next to measure-copilotchat's), because a hard-fail gate with no coverage of its own failure mode is how this shipped. Proven after the fix: both false negatives now exit 1, a clean build exits 0, and a banned token that appears only in a comment exits 0. esbuild is already this package's devDependency and already runs in the same CI job, so the gate needs no workflow change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
60d3ef1071 |
fix(react-native): externalize react-dom in the headless size measurement
The esbuild `external` list omitted `react-dom`, unlike react-core's measure-copilotchat.mjs, so a stray web-oriented edge could be absorbed into the figure the PR's bundle claim rests on. Checked empirically before changing anything: `react-dom` is NOT reachable from @copilotkit/react-native/headless today. A metafile run shows 0 of the 653 input modules are react-dom, and no module references it even pre-resolution. The reported figure is therefore UNCHANGED — 94941 B gzip (92.7 kB) before and after, byte for byte. The headline "92.8 kB -> 92.7 kB, flat" claim is unaffected and stays comparable with previously reported numbers. The guard is still worth having. Simulating a stray edge measures the inflation it prevents: +56.3 kB gzip via react-dom/client, +57.3 kB via react-dom/server (not the ~130 kB estimated in review — that is closer to the raw magnitude; react-dom-client.production.js is 536 kB raw). The subtler case is a bare `react-dom` edge at +1.4 kB, small enough to read as noise while still being a real regression. No subpath entries: esbuild prefix-matches package paths, so `react-dom` already covers react-dom/client and react-dom/server (verified on the pinned 0.27.3; esbuild CHANGELOG 0.5.14 and 0.14.13). Listing them would imply they were required. Hoisted the list to an exported HEADLESS_EXTERNAL with per-entry rationale, mirroring the sibling's DEFAULT_EXTERNAL, and made `external` an overridable option so the new test can A/B it rather than assert on a literal. Both new tests fail if react-dom is removed from the list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4315adb1e7 |
test(react-native): make the tool-result assertions read the result
Two tests claimed to prove a tool RESULT reaches its renderer and neither
read it. "reports complete and passes the result through" rendered through a
registrar printing only status and args, so replacing the correlated tool
message's content with a constant left it green; its in-place-mutation twin
had the same hole. The integration test's only result assertion built its
tool message as `{ content: "ok" }` behind an `as never`, so it carried no
toolCallId — the id production correlates a result to a call by.
Both now render status, args AND result together, and a new negative case
gives a tool call a result belonging to a DIFFERENT call. That last one is
the only detector for a lookup that ignores the map key: every fixture in
the file matched on id, so an unkeyed "hand out any tool result we have"
lookup passed the whole suite unchanged.
Fixtures move to src/__mocks__/tool-fixtures.ts. toolMessage() takes
toolCallId as a required positional argument, so no fixture can omit the
correlation, and assistantToolCall() returns a typed AssistantMessage —
which retires the `as never`, an `as unknown as Message`, and three `any`s
in the touched files. A properly-typed ToolMessage typechecks at that call
site unchanged; the cast was convenience, not a type-system limit.
Test-only: CopilotChat.tsx is untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
d144757d8a |
test(react-native): guard the entry surface's export kinds against type-only stripping
`export type { X }` strips X's runtime binding. Five runtime values shipped from
`src/headless.ts` inside `export type` blocks — the `ToolCallStatus`,
`UseAgentUpdate`, `CopilotKitCoreErrorCode` and
`CopilotKitCoreRuntimeConnectionStatus` enums, and the `AbstractAgent` class —
while the reference docs told consumers to import and branch on them. Nothing in
the repo could see it: the package built, typechecked, linted and passed its
suite, because nothing here consumed its own entry the way a consumer does.
react-core's `headless-type-exports.test-d.ts` cannot cover this. Export kind is
a property of the re-exporting module, and that guard reads react-core's entry —
react-core's own `UseAgentUpdate` was already a correct value export while RN's
was wrong. The guard has to live on the RN side and read RN's own entries.
Adds `src/__tests__/headless-value-exports.test.ts`, in three layers:
- §1 asserts each of the five is a present runtime binding of the expected
`typeof`, with its enum members nameable, on BOTH `@copilotkit/react-native`
and `@copilotkit/react-native/headless`. A stripped export is an absent module
binding, so a runtime test is the direct instrument and cannot be faked by a
cast or an expect-error.
- §2 needs no symbol list: it parses both entry sources, and for every symbol
re-exported type-only it imports the module that symbol came from and fails if
that module has a runtime binding for it. A future contributor who adds a new
enum re-export inside an `export type { … }` block is caught without anyone
updating §1, and the failure names the symbol, the source module and the fix.
A floor on the parsed specifier count keeps a rotted parser from passing
vacuously.
- §3 type-checks the consumer-visible symptom (enum-member comparison on a
render-prop `status`, `extends AbstractAgent`, `instanceof`). The file lives
under `src/`, so `check-types` compiles it and a regression also fails there
with TS1362 naming the symbol. Its bodies are lazy on purpose: a module-scope
`extends` would crash collection and hide §1/§2's guided messages.
Proven by transiently restoring the defect three ways — the `AbstractAgent`
class, `ToolCallStatus` moved into an `export type` block, and `UseAgentUpdate`
via the inline `type ` prefix. Each produced 4 failing tests plus TS1362;
`src/headless.ts` is byte-identical to before.
RN suite 23 files / 276 tests (baseline 22 / 261, so +15 and no change
elsewhere); `nx run @copilotkit/react-native:check-types` clean; oxfmt and
oxlint clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
5a6bf1dc2b |
test(react-core): make the headless type guard actually detect type drift
`headless-type-exports.test-d.ts` asserted nothing. Its only check was a
value-position annotation (`const inProgress: RendererProps = { … }`), which is
an assignability check, so degrading `RendererProps` to `any` produced zero
`tsc` errors. And `status` was pinned through a force-cast
(`"inProgress" as RendererProps["status"] & "inProgress"`), which collapses to
whatever the left side already is and suppresses the comparison outright.
Verified against the live divergence the guard exists to catch: changing
`ReactToolCallRenderer["render"]`'s `status` from the `ToolCallStatus` enum
members to bare string literals produced six errors in unrelated files and
ZERO in the guard file. Those six are incidental to this package — React
Native, the consumer this contract protects, has no such incidental users, so
on that side the drift would have been entirely silent.
Rewritten on `expectTypeOf` (already the type-assertion idiom here, see
`hooks/__tests__/use-agent-types.test.tsx`), with every positive assertion as
`toEqualTypeOf` — exact type identity, no assignability, no `as` casts. The
expected props union is spelled out independently of the type under test so the
comparison is a real detector rather than a tautology. Now pinned: the exact
props union, an explicit `not.toBeAny()` tripwire, the arm keys (`args`, not
`parameters`), and `status` as the enum in both directions.
Also pins the known divergence between react-core's two same-named public
types — the canonical renderer props (`args`, `ToolCallStatus`) and public
`RenderToolProps` (`parameters`, string literals) — as a change-detector, so
converging them becomes a deliberate, visible edit instead of silent drift.
Proven by re-applying each degradation and confirming `tsc` fails: `any` (4
errors), the enum → literal drift (3), `status` → `string` (2), `args` →
`parameters` (2), and export removal (TS2305). All proof mutations reverted.
Coverage note: this guard canNOT catch the RN `export type`-on-a-value bug.
That failure is invisible to `tsc` by construction, and it lives in react-native's
entry, which no react-core assertion can reach. It needs a runtime guard in
that package — react-core's equivalent is the sibling runtime test
`headless-exports.test.ts`.
The file is read by `tsc` only (tsconfig includes `src/**/*`); vitest's
`include` globs do not match a `.test-d.ts` basename and the package sets no
`test.typecheck`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
63a1c94fbb |
fix(react-native): make the headless size measurement fail loudly, not print 0.0 kB
measure-headless.mjs prints the number the PR's bundle claim rests on, and it had three ways to report a broken run as a good one. All three reproduced: 1. No zero-output guard (the react-core sibling has one). A run whose bundle collapses to nothing measures ~20-35 B of gzip envelope, prints "0.0 kB" and exits 0 — reported into the CI job summary as a spectacular win. Note a zero-ONLY guard would not have caught the reproduction (35 B, not 0), so this adds a plausibility FLOOR of 8 kB alongside the zero check: ~11x below the real 92.7 kB, so legitimate size work can never trip it. 2. `logLevel: "silent"` discarded `result.warnings` and there was no try/catch, so esbuild resolution problems escaped as an unhandled rejection printing esbuild's internal frames and `errors: [Getter/Setter]` instead of the messages. Silent is kept (as in the sibling) so stdout stays the single figure line CI quotes; warnings are now formatted to stderr and errors are re-thrown with esbuild's own formatted diagnostics. 3. An unbuilt dist died on a raw "Could not resolve" stack. A preflight on dist/headless.mjs now names `npx nx run @copilotkit/react-native:build`, and the catch adds the same hint when the entry specifier is what failed. The measurement itself is untouched — same esbuild options, same synthetic entry, same six symbols, same external list — and still reports 92.7 kB, so comparability across PRs is preserved. A moved figure would have meant the measurement changed rather than its guards. Also adds the test hook RN lacked, mirroring react-core exactly: scripts/__tests__/measure-headless.test.mjs under `node --test`, wired as `test:scripts` and chained into `test`. Coverage targets the failure modes, not the happy path. Both packages' vitest `include` globs are scoped to `src/**`, so the .mjs test cannot collide with the jsdom setup — the reason the sibling runs under node --test in the first place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0ea71fc684 |
test(react-native): make the #4893 entry guard see what it claimed to see
The headless-entry import-graph guard was weaker than the PR claimed. Four
blind spots, each verified to let a real violation pass (or to flag a
non-violation), each now covered by a test:
1. Only `import … from "x"` was matched, so a lazy optional-peer
`require("@copilotkit/react-core/v2")` or `await import(…)` — which Metro
follows and bundles identically — defeated the guard entirely. Static,
bare side-effect, dynamic `import()` and `require()`/`require.resolve()`
are all extracted now, and a loader whose argument is not a string
literal is reported as unanalyzable rather than silently skipped.
2. Matching ran on raw text, so doc comments counted as imports. Not
hypothetical: the guard was harvesting EIGHT specifiers
(`@copilotkit/react-native`, `…/headless`, `…/polyfills` and its five
subpaths) that no source file imports — half the reported bare-specifier
set — purely from JSDoc examples. In the other direction, writing a
"don't do this: import from @copilotkit/react-core/v2" counter-example
in a doc comment failed the build. Comments are stripped first now, via
a single left-to-right pass that matches string/template literals with
the same alternation so a `//` inside a string stays a string.
3. `resolveLocal` returned null for an edge it could not resolve and the
caller dropped it, so an unresolvable specifier read as "clean" while
hiding the whole subgraph behind it. Proven: a real
`export … from "@copilotkit/react-core/v2"` reached through an ESM-style
`"./probe-heavy.js"` edge passed the old guard. Emitted-extension
specifiers now resolve, and anything still unresolvable FAILS LOUDLY
instead of vanishing. The resolved file set and bare-specifier set are
also asserted EXACTLY, so a new edge has to be looked at deliberately
rather than only being caught if someone thought to deny-list it.
4. The graph was walked in the `describe` body, so a missing entry file
threw at collection time and every test in the file — including the one
asserting the entry exists — never ran (`Tests no tests`). The walk is
lazy and memoized per entry now, and the existence assertion reports.
Every fix was proven by mutation in both directions: the violation passes
the old guard, fails the new one, and clean source still passes. Also drops
`localFiles`, which no test ever read.
Scope note: the ~5s `await import("../headless")` timeout flake in this
file is deliberately untouched — it is owned separately. Runs used
`--testTimeout=60000`.
RN suite 267 passed / 22 files (was 261; +6 new tests);
`nx run @copilotkit/react-native:check-types` clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
025b8d5979 |
test(react-native): make the useRenderTool suite detect its own forwardings
`useRenderTool` is a thin forwarder onto react-core's `useFrontendTool`, and its suite mocked exactly that hook. The double only modelled `name` and `render`, so deleting the `deps`, `handler` AND `agentId` forwarding from the hook each left the suite fully green — it could not detect a regression in any of the three things the hook exists to forward. Drop the four `vi.mock` blocks and drive a real `CopilotKitCoreReact` through the shared `TestCopilotKit` harness, the way the sibling `render-tool-call.integration.test.tsx` already does, then assert on core's own observable behaviour instead of a mock's call arguments: - handler — `core.runTool()`, i.e. core's real `executeToolHandler` path, so the handler is proven to RUN and its return value proven to become the tool result - agentId — the tool resolves for its agent and must NOT resolve as a global tool, and the renderer entry carries the agentId that keys it - deps — a render closure over a serialisable dep re-registers and the PAINTED text changes, observed through react-core's real `useRenderToolCall` Each mutation now fails exactly one test. Also pins the documented sharp edge that `useFrontendTool` compares deps with `JSON.stringify`, so a function dep collapses to a constant and can never re-register — a test asserting otherwise would assert a behaviour the code cannot deliver, and pinning it makes a change of comparator fail loudly. Test-only: `useRenderTool.ts` is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
63e7fa7fda |
fix(react-native): make the chat list's extraData contract true and explicit
CopilotChat's `extraData` memo carried a comment claiming it held "the exact
inputs renderItem reads", but it listed only { isRunning, renderToolCall,
toolMessages } while renderItem also read `listItems` — it answered "am I the
last row?" by index-reading the array's tail, and depended on `listItems` in
its own useCallback deps. The comment was false and the stated
row-memoisation contract was incomplete.
The defect is documentation and fragility, NOT observable behaviour. Verified
against the real react-native 0.85.2 sources in the pnpm store:
- FlatList is a PureComponent (Libraries/Lists/FlatList.js:307), and `data`
is one of the props it shallow-compares. `data={listItems}` is the same
reference, so any rebuild of `listItems` re-renders FlatList on its own.
- In the default non-strictMode path FlatList's render() uses `this._renderer`
rather than `this._memoizedRenderer` (FlatList.js:682), allocating a fresh
`renderProp` on every render, which is handed to every cell.
- VirtualizedList._pushCells passes that `renderItem` plus `item` to each
CellRenderer, which is itself a PureComponent
(VirtualizedListCellRenderer.js:63). `extraData` is NOT a cell prop.
- The `listItems` memo allocates fresh item objects on every rebuild, so each
cell's `item` prop also differs. Cells therefore invalidate through
`data`/`item` even under the narrowest path (strictMode with a memoizeOne
hit on renderItem and extraData).
So no stale last-row / stranded-loading-indicator state is reachable, and no
covering test is added: the behaviour is unchanged, and the package's test
FlatList is a mock that re-invokes renderItem for every row on every parent
render, so it cannot express cell memoisation in the first place.
Instead, make the contract honest. The tail id becomes a named `lastItemId`
memo; renderItem reads that scalar and deps on it rather than closing over
`listItems` and indexing it; `extraData` now lists exactly the four values
renderItem closes over, and the comment states why `listItems` is absent
(it is the `data` prop, which already invalidates cells). As a side benefit
renderItem's identity is now stable across `listItems` rebuilds that do not
move the tail.
Call-Site Enumeration (Procedure 2 step 8):
- `extraData` — grep over packages/react-native/src shows exactly two sites,
the memo itself and the `extraData={extraData}` prop on the list. No test
and no other module reads its keys. A caller-supplied `FlatListComponent`
(the documented BottomSheetFlatList case) receives it, but RN treats
extraData as an opaque re-render marker and never inspects its shape, so
adding `lastItemId` is not observable to any consumer.
- `renderItem` / `lastItemId` — local to CopilotChat; neither is exported.
- `isLoading` on AssistantMessage — value-identical by construction, since
`lastItemId` IS `listItems[listItems.length - 1]?.id`.
- No change to CopilotChatProps or to any entry-point export.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
7baed27370 |
fix(react-native): export the headless entry's runtime values as values
`src/headless.ts` re-exported five runtime values inside `export type { … }`
blocks. A type-only re-export strips the runtime binding, so the symbol is
unimportable and — for the enums — the field it types cannot be compared
against at all, because an enum-typed field rejects a bare string literal.
`RenderToolProps["status"]` is `ToolCallStatus`, so a consumer of this PR's own
render-prop contract had no working way to branch on `status`.
Determined empirically, not by reading: a throwaway probe imported all 32
re-exported symbols as values under the package's real tsconfig. The 27 that
are genuine types reported TS2693 ("only refers to a type"); five did not, and
those five are the ones moved. Declaration sites confirm each:
ToolCallStatus packages/core/src/types.ts:14 export enum
CopilotKitCoreErrorCode packages/core/src/core/core.ts:99 export enum
CopilotKitCoreRuntimeConnectionStatus packages/core/src/core/core.ts:320 export enum
UseAgentUpdate packages/react-core/src/v2/hooks/use-agent.tsx:13
export enum
AbstractAgent @ag-ui/client declare abstract class
Nothing else changed kind: Suggestion, FrontendTool, Message, ToolCall,
ToolMessage, AgentCapabilities, ResumeStatus, Interrupt, ResumeEntry, the
Interrupt*/RenderTool*/Thread*/CopilotChat* prop and config types,
ReactFrontendTool, ReactHumanInTheLoop, ReactToolCallRenderer and
CopilotKitContextValue are all genuine types and stay `export type`.
`src/index.ts` does `export * from "./headless"`, which republishes values and
types alike, so both published entry points are fixed. Verified in the built
output: all five appear without a `type` prefix in dist/headless.d.mts and
dist/index.d.mts, and as runtime bindings in headless.mjs, index.mjs and
index.cjs.
Negative control: with the pre-fix headless.ts the same probe produced ten
TS1362 errors ("cannot be used as a value because it was exported using
'export type'") across both entries; with the fix, zero.
This makes the already-merged docs on this branch true. The RN reference pages
write `import { ToolCallStatus } from "@copilotkit/react-native"` and
`status === ToolCallStatus.Executing` (useRenderTool.mdx:170, useFrontendTool.mdx:129,
useHumanInTheLoop.mdx:86) and `import { useAgent, UseAgentUpdate }` with
`updates: [UseAgentUpdate.OnMessagesChanged]` (useAgent.mdx:234). None of those
imports resolved before this commit.
AbstractAgent is a deliberate inclusion, not scope creep: it is a runtime class
and the AG-UI extension point consumers subclass, and @ag-ui/client is a
dependency of this package rather than a peer, so a consumer cannot reliably
import it from there directly. It carries no bundle cost — the headless entry
already imports @ag-ui/client transitively through
@copilotkit/react-core/v2/headless, and esbuild tree-shakes an unused
re-export, so scripts/measure-headless.mjs still reports 92.7 kB gzip.
Forced test change, in scope only because the fix causes it: the value
re-export makes headless.ts the first runtime importer of @copilotkit/core in
this package's graph, so `import "../index"` now evaluates real core, which
named-imports RUNTIME_MODE_SSE and friends from @copilotkit/shared.
headless-integration.test.tsx replaced that module wholesale with a two-key
factory, so the import threw. Fixed by spreading importOriginal() instead of
replacing — the form vitest's own error message prescribes — leaving
createLicenseContextValue the only stubbed member. No assertion, case or
coverage changed.
Call-Site Enumeration (Procedure 2 step 8) — `grep -rn` per symbol across
packages/ plus every importer of @copilotkit/react-native in the repo. Every
site holds, because type -> value is a widening: an `import type` of a value
export is still legal.
ToolCallStatus
packages/react-native/src/headless.ts:94 — the changed export. Holds.
No other site in packages/ or examples/ names it. Nothing imported it
before, which is the bug.
CopilotKitCoreRuntimeConnectionStatus
packages/react-native/src/headless.ts:95 — the changed export. Holds.
No other site.
CopilotKitCoreErrorCode
packages/react-native/src/headless.ts:96 — the changed export. Holds.
CopilotKitProvider.tsx:12,37 / CopilotChat.tsx:13,93 / CopilotPopup.tsx:26,223
— all `import type … from "@copilotkit/core"`, used only in a `code:` field
position. They import from core directly, not through this entry, and a
type position is unaffected by the re-export kind. Hold.
UseAgentUpdate
packages/react-native/src/headless.ts:65 — the changed export. Holds.
packages/react-core/src/v2/headless.ts:41 — already a value export there,
with a comment giving this exact reason; this commit makes RN agree with it
rather than diverge. Holds.
AbstractAgent
packages/react-native/src/headless.ts:107 — the changed export. Holds.
packages/react-native/src/__mocks__/test-copilotkit.tsx:22,41,42 — already
imports the class as a VALUE from @ag-ui/client and subclasses it, i.e. it
had to bypass this entry to do what the entry now permits. Unchanged and
still passing. Holds.
packages/react-core/src/v2/**, packages/channels-telegram/** — all import
from @ag-ui/client directly; none route through @copilotkit/react-native.
Hold.
Importers of @copilotkit/react-native outside the package
examples/v2/react-native/demo/{App.tsx,src/ChatScreen.tsx,index.js} — import
CopilotKitProvider, useAgent, useCopilotKit, useFrontendTool and the
polyfills entry. None of the five symbols appears anywhere in the demo, so
nothing to break; the demo is now able to import them. Holds.
Surface guards
src/__tests__/headless-entry-surface.test.ts — its `not.toHaveProperty`
denylist covers the chat/attachment exports and the two removed registry
symbols; none of the five is listed, and its bare-specifier bans
(@gorhom/bottom-sheet, expo-*, shiki/mermaid/katex/a2ui-renderer, non-headless
react-core entries) are unaffected by adding @copilotkit/core and
@ag-ui/client edges. Passes unchanged.
Verification: `pnpm nx run @copilotkit/react-native:check-types` succeeds
(tsc --noEmit, 0 errors). `npx vitest run --reporter=dot` — 22 files, 253
tests, all passing. `npx oxfmt --check` clean; `npx oxlint` 0 errors and 2
warnings, both pre-existing in the touched test file (no-shadow on a mocked
`React`, no-this-in-sfc).
Note on a pre-existing flake: headless-entry-surface.test.ts hits the 5000ms
default testTimeout on `await import("../headless")` when the machine is loaded.
Measured 6 serial runs each way on the same box — pre-fix headless.ts failed
4 of 6, post-fix 3 of 6, identical timeout signature — so it predates this
change and is load-induced, not caused by it. Every run above used
`--testTimeout=60000`; raising that default (or making those assertions
static) is worth a follow-up, and is not this commit's to make.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
10d8f43829 | chore: release monorepo v1.67.1 |