Angular signal wrapper over the core thread store: reactive runtime context
(re-dispatches when wsUrl/threadEndpoints arrive), enabled-gated single-slot
registration, bound result methods, and synthesized error/loading parity with
react-core.
Add <CopilotDrawer> (interops with the shadow-DOM element, two-pronged license
gate, scoped chat-input focus return, registration-gated header launcher) and
extend CopilotChatConfigurationProvider with drawerOpen + mobile mutual
exclusion + a non-explicit active-thread setter so a bare drawer connects to the
picked thread and resets on New with no host wiring. useThreads gains an
{enabled} gate and a list-only error channel.
Shadow-DOM Lit element that renders the threads drawer: self-contained styles
with build-time token sync from react-core's theme, slot projection for custom
rows, license/upsell gating, filtering, and a mobile modal. View-state lives on
the element; domain state stays with the host. Multi-format build (ESM/CJS
externalize lit as a peer dep; UMD inlines lit so the CDN <script> path is
self-contained).
Add the thread store (optimistic add/rename/archive/delete with delete
rollback-on-reject, session-guarded results, startNewThread/refetchThreads,
getServerState/getServerSnapshot, per-store memoized selectors, and non-fatal
realtime diagnostics for channel-join and metadata-credential failures) and
register the "threads" feature so the SDK can gate thread management.
## What
Replaces the homemade animated `⏳` "thinking"
placeholder (a posted message whose dots cycled via `setInterval`) with
Slack's **native** `assistant.threads.setStatus` "is thinking…"
indicator — and generalizes it from assistant-pane-only to **every
thread-anchored reply**: channel @-mentions, channel threads, DMs, and
the pane.
## Why
The hourglass was an extra post-then-delete message that looked nothing
like the native loading state other Slack agents show. Slack has since
relaxed `assistant.threads.setStatus` to accept the ordinary
**`chat:write`** scope (not just `assistant:write`), specifically so
channel-based apps can show AI loading states in channels and DMs
([docs](https://docs.slack.dev/reference/methods/assistant.threads.setStatus/)).
The adapter already used `setStatus` for the pane — this just extends it
everywhere and deletes the placeholder.
## How
- **`event-renderer.ts`** — delete the hourglass machinery
(`startThinking`/`claimThinking`/`clearThinking`/`setInterval`);
generalize `setPaneStatus`/`clearPaneStatus` →
`setStatus`/`clearStatus`. New `status?: { threadTs, isPane, config }`
arg: `statusMode` drives the thinking indicator; `isPane` only selects
tool-progress surface (pane composer status vs `task_update` timeline /
`🔧` rows — both unchanged).
- **`adapter.ts`** — `createRunRenderer` builds `status` for any target
with a thread anchor (`threadTs ?? statusTs`); `assistant: false` opts
out everywhere.
- **`types.ts` / `slack-listener.ts`** — flat DMs (no `thread_ts`) carry
the inbound message `ts` as the status anchor; replies still post flat.
- **README** — document the broadened status + the `chat:write`
relaxation.
## Testing
- `nx run-many -t test build` for `bot-slack` — **248 tests pass**,
build clean, oxlint 0 errors, oxfmt clean.
- New tests cover non-pane channel-thread status, the `🔧` tool
path staying on non-pane, and the DM `statusTs` anchor.
- Net **−149 lines**.
> Note: the DM path anchors `setStatus` to a non-assistant DM message ts
— the one piece only verifiable against the live Slack API. Channel
@-mentions already carry a real `thread_ts`.
ɵMemoryStore exposes addMemory(input: NewMemory) and
updateMemory(id, changes: MemoryChanges), but those input types were
internal — a thin framework binding (useMemories / injectMemories) had
no way to name them when wrapping the store. Export them alongside the
existing ɵMemory / ɵMemoryKind / ɵMemoryScope.
Replace the homemade animated ⏳ placeholder (a posted
message with setInterval-cycled dots) with Slack's native
assistant.threads.setStatus "is thinking..." indicator on every thread-anchored
reply -- channel @-mentions, channel threads, DMs, and the assistant pane -- not
just the pane. Slack now accepts setStatus under chat:write (not only
assistant:write), so it works for channel-based apps. Flat DMs carry the inbound
message ts as the status anchor. assistant:false opts out everywhere.
Memory handlers now forward client-actionable 4xx platform statuses
verbatim (404 not-found, 409 conflict, 422 unprocessable) so a useMemories
consumer can distinguish those from a server error, and map a platform 5xx
(or malformed status) to 502 rather than collapsing everything to 500 — the
runtime is healthy, its dependency failed. This also avoids a Response
RangeError on an out-of-range status.
parseMemoryBody and the platform client's createMemory/updateMemory now
treat scope as optional, deferring the default to the platform.
refresh() now returns a promise that rejects when the re-pull fails
(listFailed) or the store is stopped mid-flight, instead of resolving
silently as if it succeeded — symmetric with the mutation methods. The
error still lands in the error selector for declarative consumers.
toMutationBody no longer defaults scope to "user"; it forwards scope only
when the caller supplies it, so the platform owns the single default.
Adds POST /memories (create), PATCH /memories/:id (supersede), and
DELETE /memories/:id (retire) to the intelligence runtime, mirroring the GET
read route: CopilotKitIntelligence.{createMemory,updateMemory,removeMemory}
proxy the platform's /api/memories with the identifyUser-resolved user via the
x-cpki-user-id header. Completes the REST surface the client memory store's
addMemory/updateMemory/removeMemory call.
refresh() now resolves once the re-pulled snapshot settles (listSucceeded/
listFailed) for the current session, or immediately when no context is set —
so a useMemories-style binding can expose refresh(): Promise<void>. Mirrors
trackMutation's completion handling.
memories/list fell through to the POST-only default in validateHttpMethod,
so GET /memories returned 405. Add it to the GET-allowed group alongside
threads/list.
Adds a runtime-native memory read endpoint mirroring /threads: route
(memories/list) → handleListMemories → CopilotKitIntelligence.listMemories,
which proxies the platform's GET /api/memories with the project API key and
the user resolved via identifyUser (scoped through the x-cpki-user-id header,
never a client-supplied id). The client memory store's {runtimeUrl}/memories
fetch now resolves out of the box for any intelligence runtime — no per-app
BFF code. Read-only for now (list); mutations to follow.
## Release monorepo v1.61.2
**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.61.2`
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.61.2`
- Creates git tag `monorepo/v1.61.2`
- 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.
Bare useAgent() calls inside a CopilotChatConfigurationProvider were falling back to the default agent instead of the provider's resolved agentId. The hook now keeps explicit props first, then chat configuration, then the shared default, with Vue coverage for inheritance and explicit-prop precedence.
Constraint: Vue useAgent owns its own agent resolution path separate from React
Rejected: Threading agentId through every consumer | duplicates provider state and misses bare hook users
Confidence: high
Scope-risk: narrow
Tested: pnpm nx run @copilotkit/vue:test -- src/v2/hooks/__tests__/use-agent.test.ts
Tested: pnpm --dir packages/vue exec eslint src/v2/hooks/use-agent.ts src/v2/hooks/__tests__/use-agent.test.ts
Tested: pnpm nx run @copilotkit/vue:check-types
Tested: pnpm nx run @copilotkit/vue:test
Tested: lefthook pre-commit test-and-check-packages (ran @copilotkit/vue test, build, publint, attw)
Not-tested: pnpm nx run @copilotkit/vue:lint (fails on pre-existing unrelated Vue lint errors)
Related: CopilotKit/CopilotKit#5656
## Summary
- Add Slack `respondTo` routing options with mention-only channel thread
replies by default.
- Keep DMs and assistant pane conversational while allowing legacy
owned-thread continuation by config.
- Update the Slack example config, manifests, docs, and Discord README
trigger wording.
## Why
Slack bots should stay quiet in channel threads unless explicitly
mentioned or configured for continuation. This makes Kite-style bots
less noisy by default while preserving the old behavior for teams that
want it.
## How
- Resolve `respondTo` once in `SlackAdapter.start()` and pass the
resolved policy into the Slack listener.
- Gate `app_mention`, `message.im`, and non-DM thread replies against
that policy.
- Add listener coverage for defaults, disabled routes, in-channel
mention replies, and legacy thread continuation.
## Test Plan
- `pnpm exec nx run @copilotkit/bot-slack:test --
src/__tests__/slack-listener.test.ts`
- `pnpm exec nx run @copilotkit/bot-slack:test`
- `pnpm exec nx run @copilotkit/bot-slack:check-types`
- `pnpm exec nx run slack-example:test`
- `pnpm exec nx run slack-example:check-types`
- targeted `pnpm exec oxfmt --check ...`
- targeted `git diff --check ...`
Reverts 2cad274. The real fix for models filling optional tool params is to
use OpenAI's Chat Completions API (the Responses API fills every declared
optional); preserving format/pattern in convertJsonSchemaToZodSchema was
unnecessary (OpenAI ignores format) and broadened behavior for every tool.
convertJsonSchemaToZodSchema dropped every string constraint except enum,
so a tool param like { type: "string", format: "uuid" } reached the model
as a bare optional string. Models then fill such optionals with "" instead
of omitting them, and the loosened model-side validation accepts "" — only
for a stricter downstream (e.g. an MCP server) to reject it.
Carry format (uuid/email/url/date-time), pattern, and minLength/maxLength
through to the generated Zod schema so the model sees the field's real
shape and the model-side validation matches the server's.
Fixes#5635.
## What
Headers set directly on an `HttpAgent` registered via
`agents__unsafe_dev_only` were silently replaced by the provider
headers. Per-agent auth headers (like an `Authorization` for a
self-hosted backend) got dropped, causing 401s.
## Why
`AgentRegistry.applyHeadersToAgent` did `agent.headers = {
...core.headers }`, a full overwrite. The run handler and the react-core
`useAgent` hook did the same. So an agent built with its own headers
lost them on registration, on every `setHeaders`, and before each
request.
## Fix
Merge instead of replace. The registry captures each agent's own headers
once (in a WeakMap, before the first apply) and rebuilds `{
...ownHeaders, ...coreHeaders }`. Core wins on key conflicts, which
keeps the existing "provider headers are authoritative" and logout/clear
behavior. All header application now routes through one method,
`CopilotKitCore.applyHeadersToAgent`, so runs never clobber per-agent
headers.
Vue and Angular benefit too: they dispatch runs through `core.runAgent`
/ `connectAgent`, so the merge is re-applied before every request.
## Tests
- core: 3 new cases in `core-headers.test.ts` (preserve, merge,
retain-across-setHeaders); existing overwrite and clear tests still
pass.
- react-core: new `use-agent-provider-headers.e2e.test.tsx` with a real
provider and an HttpAgent that has its own headers.
Verified locally: format, lint, full core + react-core suites, and both
builds.
## Summary
Adds `unarchiveThread(id)` to the v2 thread store (`@copilotkit/core`)
and the `useThreads` hook (`@copilotkit/react-core/v2`), restoring an
archived thread via the existing generic `PATCH /threads/:id { archived:
false }` update path — no new runtime endpoint. Mirrors `archiveThread`
across the store and hook.
This is the durable, architecture-independent piece extracted from the
threads-drawer effort. The drawer UI itself is being restarted as a
framework-agnostic **CopilotDrawer** (Lit web component + React/Angular
wrappers) under a separate spec; this hook method stands on its own and
is needed regardless.
## Testing
TDD. New core store test (`PATCH … { archived: false }`) and
`useThreads` hook test; full suites green (core, react-core).
Restores an archived thread via the existing generic PATCH /threads/:id
update path with { archived: false } — the same mechanism example apps
already use for restore — so no new runtime route is required. Mirrors
archiveThread across the core thread store and the v2 useThreads hook.
addReaction/removeReaction resolved the channel from messageRef.channelId only,
unlike Slack and Telegram which fall back to the target channel. The shipped
example reacts with `{ id }` (no channelId) — the channel is meant to come from
the thread's reply target — so on Discord fetchSendable("") threw, the call
returned { ok: false }, and the 👀/✅/⚠️ acks silently never fired (contradicting
the example's "works on Slack, Discord, and Telegram alike" claim).
Fall back to the target channel when the reacted ref carries no channelId, for
parity with the other two adapters. Adds a regression test.
Adds MemoryStoreRegistry (mirroring ThreadStoreRegistry) plus
register/unregister/get/getMemoryStores on CopilotKitCore and
onMemoryStoreRegistered/onMemoryStoreUnregistered subscriber hooks, so a
binding can register a per-agent memory store for sharing/lookup and react to
its lifecycle. Memory stores auto-unregister on agent removal, symmetric with
thread stores. Full core suite green (477 tests).
runtimeUrl is the CopilotKit runtime mount (e.g. /api/copilotkit); the thread
store uses the relative suffix /threads, which the runtime maps to app-api's
/api/threads. Memory mirrors that with /memories -> /api/memories. /api/memories
would have wrongly produced /api/copilotkit/api/memories.
addMemory POSTs, updateMemory PATCHes (supersede — the id changes), removeMemory
DELETEs against /api/memories. The REST response is applied to local state on
resolve (server-authoritative) and the realtime event reconciles idempotently;
mutation promises resolve to the stored memory (add/update) or void (remove).
Adds isMutating state + mutationFinished tracking via a trackMutation helper
mirroring the thread store.
setContext bumps a session and triggers GET /api/memories; the snapshot keeps
only user-scoped memories (v1 surfaces user scope only, matching the realtime
stream). Adds listRequested/listFailed + isLoading/error state, refresh(), and
ɵselectMemories/IsLoading/Error selectors, mirroring the thread store's
bootstrap+fetch effects. Note: REST path is /api/memories — reconcile with how
runtimeUrl is configured if it differs from the thread store's /threads base.
Adds the memory_metadata event type + mapper (created/updated -> upsert,
invalidated -> remove, projecting to the public Memory shape) and a
createMemoryStore that reduces those deltas into observable state. The store
rides an injected user_meta event source (the thread store's
ɵobserveUserMetaEvent in production) — one socket, no lifecycle code on the
memory side. select() re-emits on every change, so React/Angular bindings
re-render when a memory arrives. REST snapshot + mutations land next.
Lets sibling per-user stores (e.g. the upcoming memory store) ride the
single user_meta socket/channel the thread store already owns, instead of
opening a second socket. The socket effect publishes the live channel session
into a BehaviorSubject; observeUserMetaEvent memoizes a shared, per-event
stream over it (one channel.on(event) fanned out to all subscribers, cleared
on teardown). Phoenix stays fully encapsulated — callers get plain payloads.
Scoped to the user_meta channel only. Additive; socket/reconnect/teardown
flow unchanged.
First slice of the framework-agnostic MemoryStore, mirroring the thread
store's micro-redux patterns: session-guarded MemoryState, listSucceeded
snapshot population, and memoryUpserted/memoryInvalidated domain reducers
(prepend-new / replace-in-place / remove). Public kind vocabulary
(topical/episodic/operational). Reducer covered by unit tests.