The reaction handler now receives the conversation `thread` and an
update-capable `messageRef` for the reacted message — the same surface an
`onClick` gets via `ctx.thread`/`ctx.message.ref`. A reaction can now post new
UI (`thread.post`), swap the message in place (`thread.update(messageRef, …)`),
run the agent, or block on a human choice (`thread.awaitChoice`, HITL).
- bot-ui: `MessageReaction` gains `thread` and `messageRef`.
- platform-adapter: `IncomingReaction` gains an optional adapter-provided
`messageRef` (engine falls back to `{ id: messageId }`).
- create-bot: threads `thread` + `messageRef` into both the global
`ReactionEvent` and the per-message handler.
- Slack/Discord/Telegram reaction decoders emit a platform-specific,
update-capable `messageRef` (channel+ts / channelId+id / chatId+messageId).
Type the bot UI surface and broaden the cross-platform component vocabulary,
adding only capabilities that more than one adapter can express.
- bot-ui: type the `Thread` interface's `ui` params (`post`/`update`/
`awaitChoice`/`postEphemeral`) as `Renderable` instead of `unknown`, so JSX
is checked at the call site and the `{ raw }` escape hatch is explicit.
- `<Message onReaction>`: per-message reaction callback `(emoji, { added, user,
rawEmoji, messageId })`. Stripped from the IR before it reaches the adapter,
routed from reaction ingress by message id. Durable on the same terms as a
component `onClick` — a `{ component, props }` snapshot is persisted and the
component is re-rendered to re-derive the handler after a restart; inline
handlers route in-process only.
- `Button.url` link buttons — Slack, Discord, Teams, Telegram.
- `Field.label` — typed (Discord and Telegram already consumed it untyped);
newly rendered on Slack.
- `Select.multi` multi-select — Slack (`multi_static_select` in an input block
+ `selected_options` decode), Discord (min/max values + `component` bounds
decode), Teams (`isMultiSelect`); Telegram/WhatsApp degrade to single-select.
Slack-only capabilities (Button.confirm, Image.title, Input label/initialValue/
required, Select.initialValue) were intentionally left out.
Adds the @copilotkit/bot-teams package: a PlatformAdapter that bridges
the CopilotKit runtime to Microsoft Teams. Renders agent output as
Adaptive Cards (including native <Chart> components via bot-ui), streams
replies with a typing-indicator heartbeat, supports human-in-the-loop
confirmations, and handles inbound files via the Graph API with channel
file support.
Add capability-gated surface methods across the bot packages so one handler
degrades gracefully per platform:
- thread.react / thread.unreact — emoji reactions (Slack, Discord, Telegram)
- thread.postEphemeral — only-you messages with a required DM fallback
- openModal (interaction/command-scoped) — structured forms (Slack rich,
Discord text-only), omitted on surfaces with no live interaction trigger
Spans @copilotkit/bot-ui, bot, bot-slack, bot-discord, bot-telegram with
per-adapter capability flags. Discord: open modals from slash-command
interactions, and never clear registered commands on an empty publish.
Adds a durable persistence layer for @copilotkit/bot, replacing the
in-memory-only ActionStore with a pluggable StateStore.
- StateStore interface (kv/list/lock/dedup/queue) with a shared
conformance suite; MemoryStore default plus @copilotkit/bot-store-redis
and @copilotkit/bot-store-postgres backends.
- createBot({ store }): typed per-thread state via Standard Schema,
action snapshots persisted through the store, per-conversation turn
lock (onLockConflict drop|force), and inbound-event dedup keyed on a
stable eventId. ActionStore is kept as a deprecated alias.
- Cross-platform transcripts (bot.transcripts + identity resolver) with
age-bounded retention (prune on append + filter on read), and
runAgent({ transcript: true }) to auto-inject history and capture the
reply.
- createBot({ components }) re-registers components so durable actions
re-fire after a restart; restart-durability demo in examples/slack.
- Dedup is marked seen only after the turn lock is acquired, so a turn
dropped on lock-conflict does not burn its eventId (no lost retries).
- Release lockstep: bot-store-redis/postgres version with bot + bot-ui.
Activate Slack's assistant pane and native streaming as the default experience,
with zero config and safe degradation. The portable surface lands in
@copilotkit/bot (onThreadStarted lifecycle, capability-gated
thread.setSuggestedPrompts/setTitle, two SurfaceCapabilities flags + optional
adapter methods); @copilotkit/bot-slack adds the Bolt Assistant middleware
(assistant.ts), the chat.startStream transport with automatic legacy fallback
(native-stream.ts), a per-thread no-double-delivery listener guard, and
renderer pane-status mode. Everything degrades, never throws.
Pure JSX runtime (no React, no Slack) producing a BotNode IR tree.
Statically typed component props via a package-owned JSX namespace:
unknown attributes, bad values, and bad children are compile errors.
Components: Message, Header, Section, Markdown, Field, Context,
Actions, Button, Select, Input, Image, Divider; bind() escape hatch
for non-serializable handler captures.