The Vue examples cannot consume the drawer until @copilotkit/web-components +
a react-core release ship AND the examples support managed Intelligence — same
constraint as the Angular vertical, which kept its demo route as a separate
follow-up PR. This PR is SDK-only (packages/vue).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ports two React-parity gaps (audit-flagged as user-facing) into the Vue
`useThreads` composable, mirroring react-core `use-threads.tsx`.
Fix A — register the thread store with core's single-slot registry:
- Adds a `watch([resolvedEnabled, resolvedAgentId])` that calls
`copilotkit.value.registerThreadStore(agentId, store)` and, via the
watch's `onCleanup`, `unregisterThreadStore(agentId)` on
disable/agentId-change/unmount. Gated on `resolvedEnabled` so a disabled
(unlicensed) store never evicts a co-mounted live store for the same agent.
Fix B — gate list + mutations on `copilotkit.threadEndpoints`:
- Derives `threadListEndpointSupported`/`threadMutationsSupported` via
`!== false` (legacy runtimes advertise `undefined` => supported).
- Context-dispatch watcher skips dispatching (setContext(null)) when the
list endpoint is unsupported, so no `/threads` fetch fires.
- Folds `threadEndpointsError` ("Thread endpoints are not available on this
CopilotKit runtime") into `error` (NOT `listError`, which stays
storeError-only) and factors `!threadEndpointsUnavailable` into
`preConnectLoading` so the UI doesn't spin against an endpoint-less runtime.
- `guardMutation` wraps rename/archive/unarchive/delete to reject with
"Thread mutations are not available on this CopilotKit runtime" when
`threadEndpoints.mutations === false`, before touching the network.
Call sites:
- The shipped `CopilotThreadsDrawer` consumes `error` for its user-facing
error banner (now surfaces the endpoints-unavailable message instead of
spinning) and `listError` for genuine list-load failures (unchanged
contract: storeError only, no config/runtime-setup leakage). Its `enabled`
prop (unlicensed gate) already suppressed fetches; it now also correctly
suppresses core-registry registration so a co-mounted live chat store for
the same agent is not evicted. Mutation buttons (rename/archive/delete)
reject locally on runtimes that don't serve mutations.
Item 3 (startNewThread configErrorDismissed) intentionally left out of scope.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Release monorepo v1.62.1
**Scope:** `monorepo` | **Bump:** `patch`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `monorepo` packages to `1.62.1`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `monorepo` packages to npm at version `1.62.1`
- Creates git tag `monorepo/v1.62.1`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
Wave-4 introduced setModalOpenWithDrawerExclusion as an always-defined
function on configurationValue.setModalOpen. Under a bare
<CopilotChatConfigurationProvider> (no isModalDefaultOpen, no parent
providing modal state), this broke the pre-existing contract that
setModalOpen is undefined when the provider owns no backing modal
state — the function was defined but a no-op, since it delegated to
parentConfigValue?.setModalOpen, which was undefined.
CopilotChatToggleButton.vue depends on that undefined-ness: it checks
`config.value?.setModalOpen` and falls back to a local `fallbackOpen`
ref when absent. With the regression, clicks routed into the no-op
setter instead of the fallback, permanently stuck closed.
Fix: replace the standalone function with a `publicSetModalOpen`
computed that returns undefined when resolvedSetModalOpen (the real,
possibly-parent-inherited setter) is undefined, and otherwise wraps it
with the mobile drawer-exclusion behavior. This preserves the
undefined contract without touching resolvedIsModalOpen,
resolvedSetModalOpen, or the drawer-registration code.
Call-site enumeration for setModalOpen consumers (all read via
optional chaining, so all remain safe):
- CopilotChatToggleButton.vue: `config.value?.setModalOpen` — restored
fallback-open behavior when the provider is bare, confirmed via new
presence-contract tests plus the existing 5-test
CopilotChatToggleButton.test.ts suite (all pass).
- CopilotModalHeader.vue: `config.value?.setModalOpen?.(false)` — only
used inside modal-backed compositions (CopilotPopup/Sidebar), which
always pass isModalDefaultOpen, so setModalOpen is always defined
there; unaffected.
- CopilotSidebarViewInternal.vue / CopilotPopupViewInternal.vue: same
pattern, same modal-backed guarantee; unaffected.
React's CopilotChatConfigurationProvider.tsx intentionally keeps
setModalOpen always-defined and always backed by internal state — a
different resolution than Vue's. This fix takes the minimal Vue-local
path (preserve the undefined contract CopilotChatToggleButton relies
on) rather than reworking resolvedIsModalOpen's backing, to avoid
changing bare-provider modal-open semantics.
Tests: added a "modal-setter presence contract" describe block
verifying (1) a bare provider exposes setModalOpen as undefined
(red-green verified: fails on pre-fix code, passes after), and (2) a
provider with isModalDefaultOpen exposes a working setModalOpen that
toggles isModalOpen. All 21 tests in the provider suite pass,
including the existing mobile drawer<->modal mutual-exclusion tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
- Include `@copilotkit/web-components` in the shared `monorepo` release
scope.
- Remove the standalone `web-components` release scope from workflow
dispatch options and type definitions.
- Keep notification package counts aligned with the 16-package monorepo
scope.
## Why
`@copilotkit/web-components` should move with the rest of the shared
monorepo packages after its initial manual publish, not be released as a
separate scope.
## How
- Added `@copilotkit/web-components` to `release.config.json` under
`monorepo`.
- Removed standalone scope dropdown/options and npm URL handling.
- Verified dropdown parity, monorepo dry run, standalone-scope
rejection, and notification tests.
## What does this PR do?
This PR fixes build failures on Windows by replacing Unix-only shell
commands (`rm -rf`, `cp`, `mkdir -p`) in `package.json` scripts with
cross-platform Node.js `fs` built-in commands.
This follows the project's existing codebase pattern for cross-platform
operations, as seen in `packages/react-ui/package.json` (line 45).
### 🛠️ Changes:
- **`packages/runtime`**: Replaced `rm -rf` in `generate-graphql-schema`
with `fs.rmSync`.
- **`packages/vue`**: Replaced `cp` in `build:types` and `rm -rf` in
`clean` with `fs.cpSync` and `fs.rmSync`.
- **`packages/angular`**: Replaced `mkdir -p` and `cp` in `build:css`
with `fs.mkdirSync` and `fs.cpSync`.
- **`examples/v1/next-openai`, `next-pages-router`, `state-machine`**:
Replaced `rm -rf` clean commands with a single Node.js loop that deletes
`.turbo`, `node_modules`, `dist`, and `.next`.
All modified packages now build successfully on Windows.
## Related PRs and Issues
- Closes#5601
## Checklist
- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [ ] 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)
CopilotThreadsDrawer.vue declared local handlers `onNewThread`/`onLicensed`
with the same names as the `onNewThread`/`onLicensed` input-callback props,
tripping vue/no-dupe-keys. Renamed the handlers to `handleNewThread`/
`handleLicensed` (props left untouched) and updated their template bindings
(`@new-thread`, `@licensed`) and the one internal call site in `onDelete`
that re-implemented the new-thread flow inline.
Rewrote the per-row `:slot="`row:${t.id}`"` binding as
`v-bind="{ slot: `row:${t.id}` }"` to satisfy vue/no-deprecated-slot-attribute
without changing behavior — it still compiles to a real DOM `slot` attribute
used to project light-DOM children into the custom element's named shadow
slots (not a Vue component slot).
Added a `typeof window.matchMedia !== "function"` guard to
isMobileViewport(), matching CopilotModalHeader.vue and the React reference,
so environments where `window` exists but `matchMedia` doesn't (some test
runners, embedded webviews) don't throw when the provider opens the
drawer/modal.
CopilotModalHeader.vue:40 `_className` unused-var lint error predates this
branch (introduced in 28d07ccaa, already on main) and is left as out of
scope.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
focusChatInput() queried `[data-testid="copilot-chat-textarea"]`, a testid
that exists nowhere in the Vue package (copied from React) — the `?.` made
the miss silent, so focus never returned to the composer after selecting a
thread. Corrected to `copilot-chat-input-textarea` (CopilotChatInput.vue)
and added React-parity scoping: `findChatInput` walks up from the drawer's
element via `closest('[data-testid="copilot-chat-view"]')` to scope the
input lookup to the enclosing chat, falling back to a document-global query
when no such ancestor exists. Sole call site: `onThreadSelected`.
Added a `localDrawerOpen` ref fallback for when there is no surrounding
`CopilotChatConfigurationProvider` (`config.value === null`): previously
`el.open` was pinned to `false` forever and `onOpenChange` was a no-op in
that case, since both routed through `config.value?.`. A `drawerOpen`
computed / `setDrawerOpen` function now pick the provider's state when
present and the local ref otherwise; both the property-push watcher and
`onOpenChange` route through them. Mirrors React's bare-drawer-starts-closed
fallback.
Documented (no behavior change) that the `row` scoped slot, unlike React's
`renderRow`, has no per-row escape hatch back to the element default — once
provided it projects for every thread.
Added tests: focus-return after thread-selected (red against the old
testid), delete-of-active-thread resets to a new thread (plus the
negative non-active case), and the provider-less drawerOpen fallback
reflecting open-change events.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Call sites: `preConnectLoading` only feeds `isLoading` (returned to consumers,
e.g. CopilotThreadsDrawer's licensed-gate loading state); `hasDispatchedContext`
is read only by `preConnectLoading` and written only in the context watcher.
Without gating on `resolvedEnabled`, useThreads({ enabled: false }) left
isLoading stuck true forever (hasDispatchedContext never set). Without
resetting the flag on the disabled/no-runtimeUrl branch, toggling
enabled false->true (or runtimeUrl removed+re-added) failed to re-arm the
pre-connect loading synthesis, regressing the empty-list flash it exists
to suppress. Brings the Vue hook back in parity with the React reference's
reset behavior for these two branches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add examples/v2/vue/demo/pages/threads.vue demonstrating the drawer
alongside chat. The demo has no root CopilotKitProvider (app.vue only
renders <NuxtPage/>), so the page mounts its own provider chain,
matching the runtime-url + show-dev-console convention used by every
sibling demo page. CopilotChatConfigurationProvider is left
uncontrolled (no threadId) so the drawer drives the active thread.
No natural nav list exists in index.vue or elsewhere in the demo, so
no nav link was added.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a mobile-only launcher button at the leading edge of
CopilotModalHeader that toggles the drawer. It renders only when a
CopilotThreadsDrawer has registered with the chat configuration AND
the viewport is mobile (<=767px, matched via matchMedia and mirrored
from the React CopilotModalHeader implementation). Also exports
IconPanelLeftOpen from the shared icons barrel for the button's icon.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Release monorepo v1.62.0
**Scope:** `monorepo` | **Bump:** `minor`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `monorepo` packages to `1.62.0`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `monorepo` packages to npm at version `1.62.0`
- Creates git tag `monorepo/v1.62.0`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
## What
Revert the v2 tool-call resolver so an **unhandled** tool call (no
per-tool or wildcard renderer registered) renders **nothing** again,
instead of auto-painting the built-in `DefaultToolCallRenderer` card.
Showing the card is opt-in, exactly as before:
| Call | Result |
|---|---|
| `useDefaultRenderTool()` | built-in default tool-call card |
| `useDefaultRenderTool({ render })` | the caller's custom UI |
| neither | nothing renders |
## Why
The auto-card default was introduced in commit `ba60df5d33`
(2026-05-07), buried as the **last bullet** of a commit titled
`feat(showcase/langgraph-python): add per-tool testids`. It shipped
silently in **v1.57.2** with **no CHANGELOG entry** and is still live
through 1.61.2.
Effect: in v2, **every** agent tool call that surfaces in chat without a
registered renderer paints a card exposing the tool name, raw arguments
JSON, and raw result JSON — in production, for all customers. This is
how internal A2UI plumbing tools (`generate_a2ui`, `render_a2ui`,
`log_a2ui_event`) leaked into deployed showcase demos, and it equally
leaks any customer's own un-rendered tools.
Pre-1.57.2 behavior was `return null` (invisible). This restores that
opt-in contract.
## Changes
- `use-render-tool-call.tsx`: resolver returns `null` when no
per-tool/wildcard renderer matches.
- Removed the now-dead `defaultToolCallRenderAdapter` + `__testOnly_`
export + unused imports.
- Rewrote `use-render-tool-call.test.tsx` to drive the real
`useDefaultRenderTool` → real `useRenderToolCall` through a notifying
mock core, asserting all 3 states (card / custom / nothing).
## Showcase impact
None required. The opt-in `tool-rendering-*-catchall` demos already call
`useDefaultRenderTool()`, so their cards (and e2e specs) are unchanged.
No a2ui/declarative spec asserts the card — those demos simply stop
leaking it in prod.
## Verification
- `nx test react-core`: 1368 pass (107 files), incl. the new 3-scenario
suite.
- `nx check-types react-core`: clean.
- `nx build react-core`: clean.
- oxfmt `--check`: clean.
## What
Renames the inspector's user-facing feature name from **Memories** to
**Learning**:
- The menu tab label (`Memories` → `Learning`).
- The enabled-state panel heading (`Memory store` → `Learning`).
## What's intentionally left unchanged
- The internal `"memories"` menu key, the `Brain` icon, and the REST
wiring.
- Entry-level copy that refers to individual records (the `Search
memories…` box, the `No memories yet` empty state) — the *feature* is
"Learning" but the *entries* are still memories.
## Notes
- Not touched here: the locked-teaser copy (`Long-term memory` / "isn't
enabled on this deployment") — flagged for a separate decision on
whether the teaser should also say "Learning".
- Updated the tab-presence test accordingly; `web-inspector` tests pass
(71).
Targets `mme/memory-core`.
Rename the inspector's user-facing feature name from Memories to Learning:
the menu tab label and the enabled-state panel heading. The internal
"memories" menu key, the Brain icon, the REST wiring, and entry-level copy
(search box, empty states) are unchanged. Update the tab-presence test.
Fixes#5773, a regression of #3872. The inspector event timeline stays
empty during runs — no `RUN_STARTED`, `TEXT_MESSAGE_*`, `TOOL_CALL_*`,
etc. — while connection status, agents, context and tools still
populate.
The cause is the same one #3872 fixed and that has since been dropped:
`useAgent` runs a per-thread *clone* of the registry agent, and clones
are never added to the registry. The inspector only subscribes to AG-UI
events via `onAgentsChanged`, so it never sees the clone that actually
runs, and every `recordAgentEvent` sits on the wrong instance. The
`onAgentRunStarted` bridge #3872 added is no longer declared on
`CopilotKitCoreSubscriber`.
Re-landing it against the current run path: `RunHandler` fires
`onAgentRunStarted` with the real run instance at the start of
`runAgent` (top-level only, so recursive tool follow-ups don't
re-subscribe) and `connectAgent`, and the inspector subscribes to that
instance. `subscribeToAgent` already keys by `agentId` and replaces, so
the clone cleanly supersedes the registry subscription and there's no
leak. Per-thread cloning is untouched.
Added a core regression test driving a run on a clone that isn't in the
registry and asserting the event fires once with that instance, plus
that recursive follow-up runs don't re-notify. Core and web-inspector
both typecheck; core suite green.