Commit Graph

1815 Commits

Author SHA1 Message Date
Mike Ryan d906171c26 fix: honor thread endpoint capabilities 2026-06-23 11:33:10 -07:00
Mike Ryan db09796809 fix: gate thread endpoints by runtime capability 2026-06-23 11:32:30 -07:00
Alem Tuzlak 43fdba74aa feat: AG-UI standard interrupt support in useInterrupt + BuiltInAgent
Adds the AG-UI standard interrupt flow (RUN_FINISHED outcome:interrupt + resume array) alongside the legacy on_interrupt path.

- core: forward the standard resume array through runAgent.
- react-core / vue / react-native: useInterrupt handles standard interrupts with resolve()/cancel(), surfaces the primary + full interrupt set, and persists each resolved tool-backed interrupt as a tool-result message so multi-turn conversations stay well-formed (no dangling tool call -> no tool-call loop).
- runtime BuiltInAgent: native interrupts for the aisdk + tanstack factory paths via each SDK's needsApproval primitive (tool-approval-request / CUSTOM approval-requested -> outcome:interrupt); classic interrupt-tool emission + ctx.interrupt() factory primitive; idempotent resume injection mapped to each SDK's native tool-result; getCapabilities advertises humanInTheLoop.interrupts.
- docs: document standard interrupt support.

Verified across core/react-core/runtime unit suites and a real-model multi-turn run on both aisdk and tanstack.
2026-06-23 20:14:17 +02:00
Austin Merrick e0dfe56940 fix(web-inspector): forward core headers on owned thread store requests (#5596)
## Problem

Fixes #5581.

When `enableInspector={true}` and `useThreads()` is **not** mounted, the
inspector creates its own thread store per agent
(`ensureOwnedThreadStore`). That store initialized its context with
empty headers:

```ts
store.setContext({
  runtimeUrl: core.runtimeUrl,
  headers: {}, // ← ignores headers configured on <CopilotKit>
  agentId,
});
```

So the inspector's `/threads` requests omitted the headers configured on
`<CopilotKit>` (e.g. `X-CSRF`, `Authorization`). In environments that
enforce CSRF/auth checks this returns **HTTP 403**; in lax local envs it
200s but still sends no headers. This is the inspector-side counterpart
to the `useThreads()` fix in #5300.

## Solution

1. Source the headers from `core.headers` when the owned store's context
is created.
2. Add an `onHeadersChanged` subscriber that re-applies headers to all
owned stores when the host updates them at runtime (e.g. a refreshed
auth/CSRF token via `core.setHeaders`). This mirrors `useThreads()`,
which re-dispatches the context whenever `core.headers` change, so the
owned stores' requests stay authorized.

Headers are spread (`{ ...core.headers }`) to match the existing pattern
in `use-threads.tsx` and produce a fresh mutable object. Stores
registered by `useThreads()` are untouched — only inspector-owned stores
are affected.

## Testing

Added two regression tests in
`packages/web-inspector/src/__tests__/web-inspector.spec.ts` (stub
`globalThis.fetch`, drive the owned store via the agents-changed path):

- the owned store's `/threads` request carries `core.headers`;
- an `onHeadersChanged` update re-applies the new headers on the next
request.

`pnpm --filter @copilotkit/web-inspector test` → 34 passed. Verified the
first test fails when the fix is reverted. Lint (oxlint) and formatting
(oxfmt) clean on the changed files.
2026-06-23 11:13:45 -07:00
Nathan 🔶 Tarbert 5c03c25a3f Merge branch 'main' into fix/issue-5533-agentid-runtime-sync 2026-06-23 13:58:03 -04:00
Austin Merrick 59f96620bb fix(core): preserve agent-level headers instead of overwriting them (#5635)
HttpAgent headers configured directly on an agent registered via
agents__unsafe_dev_only were silently replaced by core headers, dropping
per-agent auth headers and causing 401s against self-hosted backends.

Core headers are now merged ON TOP of each agent's construction-time
headers (captured once in a WeakMap before the first apply), with the
core-level value winning on a key conflict. Header application is
centralized in CopilotKitCore.applyHeadersToAgent so the run handler and
the react-core useAgent hook share one merge path and never clobber
per-agent headers.
2026-06-23 10:33:32 -07:00
Nathan 🔶 Tarbert 518c12e984 fix: reject pending human-in-the-loop promise on run abort (#5554) (#5633)
## Summary

Fixes #5554

When a backend agent calls a frontend tool via
`renderAndWaitForResponse` (the `useHumanInTheLoop` hook) and the run is
aborted (`stopAgent`/`abortRun`) while the form is still pending, the
handler promise was settled only by `respond()` — so on abort it either
hung forever or silently resolved to an empty string. The backend
received an empty `tool_call_result` (no error), which downstream agent
logic interpreted as "no input" — a silent state corruption.

## Root cause

`useHumanInTheLoop`
(`packages/react-core/src/v2/hooks/use-human-in-the-loop.tsx`) created
its handler promise capturing only `resolve`, and ignored the
`AbortSignal` that the core `RunHandler` already passes to tool handlers
(`packages/core/src/core/run-handler.ts`). An aborted run therefore
never settled the pending promise; an `undefined` result is stringified
to `""` (run-handler.ts) → silent empty tool result.

## Fix

Honor the existing `AbortSignal` in the HITL handler:
- If the signal is already aborted when the handler runs, reject
immediately.
- Otherwise attach a one-shot `abort` listener that rejects the pending
promise with an explicit `Error("Human-in-the-loop interaction
aborted")`.
- `respond()` detaches the listener before resolving, so a normal
response is unchanged and abort cannot fire after a normal resolve.

Core's existing catch path converts the rejection into an explicit error
tool result instead of a silent `""`. Scoped to the one hook; no
protocol change, no new timeout API, and **unmount is deliberately not
touched** (to avoid regressing reconnect/remount-resume).

## Tests added


`packages/react-core/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx`
— drives a HITL tool to the executing state, aborts the run without
calling `respond()`, and asserts an explicit non-empty error surfaces
(via `onToolExecutionEnd`) rather than a silent empty result. Fails
before the fix (handler hangs), passes after.

## Checklist

- [x] Failing test written and confirmed failing before the fix
- [x] Fix applied, test passes
- [x] Full `@copilotkit/react-core` suite passes (1291 passed;
reconnect/remount tests green)
- [x] Build succeeds (`nx build @copilotkit/react-core`)
- [x] Formatter passes
2026-06-23 13:20:19 -04:00
Alem Tuzlak 5ecdee36b8 feat(bot): pluggable StateStore persistence + cross-platform transcripts
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.
2026-06-23 18:33:38 +02:00
Nathan 🔶 Tarbert 6e14deeca9 Merge branch 'main' into fix/issue-5554-hitl-abort-signal 2026-06-23 10:48:43 -04:00
Nathan 🔶 Tarbert a4d0e577df Merge branch 'main' into fix/issue-5533-agentid-runtime-sync 2026-06-23 10:02:08 -04:00
Ran Shemtov 47fa480a55 Merge branch 'main' into fix/react-ui-markdown-xss-sanitize 2026-06-23 09:41:44 +02:00
Jordan Ritter 76c21b90e4 fix(react-ui): sanitize raw HTML in Markdown renderer to prevent XSS
The legacy Markdown renderer enabled rehype-raw with no HTML sanitizer,
so raw HTML embedded in assistant/model output reached the DOM (CWE-79).

Add rehype-sanitize as the terminal rehype pass so it runs after any
consumer-supplied rehypePlugins and cannot be bypassed. Add a regression
test covering the dangerous-HTML vectors (script/style/base/form/iframe,
event handlers, javascript: URLs) and the consumer-plugin injection path,
and assert legitimate Markdown/GFM features still render. Pin react-dom to
a caret range for the SSR-based test.
2026-06-22 23:54:58 -07:00
Jordan Ritter b9311f94b9 fix(core): preserve runtime agent instance across re-connection
`updateRuntimeConnection` unconditionally rebuilt the `remoteAgents` map
with a fresh `ProxiedCopilotRuntimeAgent` for every id on each connect,
discarding the already-registered live instance along with its
accumulated `messages`, `threadId`, and subscriptions. A re-connection
(an `/info` re-settle, or a header/config/transport change) therefore
swapped the live instance for an empty one. Downstream the `use-agent`
memo keys on the instance identity returned by `getAgent(id)`, so the
swap unmounted an already-rendered conversation — the source of the
showcase auth `dom-missing` flap.

Reuse the existing instance for ids still advertised by the runtime
(re-applying only registry-owned headers/credentials in place); mint a
new proxy only for genuinely-new ids; drop ids no longer present. The
disconnect/no-runtime and error paths still clear `remoteAgents`.
2026-06-22 23:16:35 -07:00
Tyler Slaton b77d2710aa feat(bot-whatsapp): WhatsApp Cloud API platform adapter + example + docs (#5449)
## What

Adds **`@copilotkit/bot-whatsapp`** — a WhatsApp Business **Cloud API**
`PlatformAdapter` for the platform-agnostic `@copilotkit/bot` engine —
plus a runnable **`examples/whatsapp`** app and docs. This brings
WhatsApp to the bots ecosystem alongside the existing Slack support,
reusing the engine, the `@copilotkit/bot-ui` IR, and the pluggable
`ActionStore` untouched.

## How it works

- **Ingress:** the adapter owns its own HTTP server — GET verification
handshake (`hub.challenge`) + POST intake validated by
`X-Hub-Signature-256` HMAC (timing-safe), acked `200` immediately then
processed async.
- **No streaming:** WhatsApp messages are immutable, so the run renderer
**buffers** text and sends once on `TEXT_MESSAGE_END`
(`supportsStreaming: false`; `update()` posts fresh, `delete()` no-ops).
- **Interactive mapping:** text/section → text; ≤3 buttons →
reply-button message; `Select` or 4–10 actions → list message; >10 →
numbered-text fallback. A control's `value` round-trips by encoding it
into the reply id (`ck:…::<json>`), since WhatsApp replies carry no
value field; oversized encodings fail loud rather than corrupt silently.
- **Memory:** WhatsApp exposes no readable history, so a pluggable
**`HistoryStore`** (default `InMemoryHistoryStore`) holds it and replays
it into `agent.messages` each turn (fresh threadId per turn, mirroring
`bot-slack`). Swap in a durable backend to persist across restarts.
- **Commands:** leading-keyword matching (`commandPrefix`, default `/`);
the command text is injected via the engine's `runAgent({ prompt })`
path (not persisted at ingress).
- **Inbound media** → AG-UI multimodal content parts; **HITL** via
interactive replies.

## Example

`examples/whatsapp` mirrors `examples/slack`: a CopilotKit
`BuiltInAgent` over MCP (Linear + Notion), with `issue_list`, an
interactive `show_incident`, and a `confirm_write` HITL gate.

## Tests & verification

- 62 unit tests across the package (render mapping, markdown→WhatsApp,
signature verification incl. wrong-but-equal-length, interaction
decode/round-trip, buffered renderer, webhook listener/server, stores,
media, adapter).
- `build` ✅, package `check-types` ✅, `publint`/`attw` (ESM-only) ✅,
example `check-types` ✅. Full `nx run-many -t test
--projects=packages/**` passes.
- Two rounds of code review (APPROVE) — fixed slash-command history
double-append and silent value-truncation; minors (HMAC over raw bytes,
conversationKey invariant, offset-correct Blob, unused-dep pruning,
added tests).

## Docs

Package `README.md` + `ARCHITECTURE.md`, example setup guide (Meta app +
webhook + tunnel), and a `shell-docs` WhatsApp guide page (registered in
`meta.json` + early-access gate).

## Notes / out of scope (v1)

- No template-send path for messaging outside WhatsApp's 24-hour
customer-service window (documented limitation).
- Pre-existing, unrelated `@copilotkit/core` `phoenix-observable.ts`
typecheck error exists on the branch base (missing `@types/phoenix`) —
not introduced here.
2026-06-22 20:34:54 -07:00
Nathan 🔶 Tarbert 7d644f22fa fix: reject pending human-in-the-loop promise on run abort (#5554) 2026-06-22 18:04:50 -04:00
Mark af81e8e899 Merge branch 'main' into main 2026-06-22 12:03:31 -07:00
Nathan 🔶 Tarbert b67109433a Merge branch 'main' into fix/issue-5533-agentid-runtime-sync 2026-06-22 10:57:13 -04:00
Ran Shem Tov 15005794a5 feat(a2ui): passing a catalog to the provider auto-enables A2UI and tool injection
A catalog on <CopilotKit a2ui={{ catalog }}> is now enough to use A2UI
end to end. Previously developers also had to set a2ui.injectA2UITool: true
on the runtime.

The provider forwards an a2uiCatalogAvailable signal per run whenever it has
a catalog (and renders surfaces locally). handle-run reads that signal and
hands it to configureAgentForRequest, which enables A2UIMiddleware and defaults
injectA2UITool to true. An explicit injectA2UITool value (including false) and
an explicit a2ui.enabled: false are always respected via ?? / short-circuit, so
this only fills the default and never overrides a deeper opt-out.
2026-06-22 14:04:36 +02:00
Alem Tuzlak b2c28a046c Merge branch 'main' into feat/bot-whatsapp 2026-06-22 11:12:54 +02:00
Ran Shemtov 2d8f276bbe Merge branch 'main' into fix/1980-a2ui-middleware-state-channel 2026-06-22 10:29:56 +02:00
Jeel Gor f2e69a80b7 fix(build): replace Unix-only commands in package.json scripts with Node.js equivalents 2026-06-21 21:33:51 +05:30
serhiizghama 412369a8a9 test(web-inspector): cover header forwarding on owned thread store
Assert the owned store's /threads request carries core.headers and that an
onHeadersChanged update re-applies the new headers.
2026-06-20 08:28:47 +07:00
serhiizghama 17703449b8 fix(web-inspector): forward core headers on owned thread store requests
The inspector's owned thread store (created when useThreads() isn't mounted)
initialized its context with empty headers, so its /threads requests omitted
the headers configured on <CopilotKit> (e.g. X-CSRF, auth). This produced
HTTP 403 in environments that enforce CSRF/auth checks.

Source the headers from core.headers at store creation, and re-apply them via
onHeadersChanged so the owned store stays authorized when headers are updated
at runtime, mirroring how useThreads() keeps its context in sync.
2026-06-20 08:28:47 +07:00
Nathan 🔶 Tarbert 910dd7b383 test: assert explicit agentId precedence over chat config (#5533) 2026-06-19 17:20:06 -04:00
Austin Merrick 4c71ea1138 fix(core): allow clearing headers via setHeaders with null/undefined
setHeaders typed headers as Record<string, string>, so there was no
type-safe way to clear a header (e.g. Authorization on logout) — passing
an empty string left the header present with a blank value.

Widen the signature to Record<string, string | null | undefined> and drop
any entry whose value is null/undefined. setHeaders remains a full overwrite,
so clearing one header while keeping the rest is the spread pattern:
setHeaders({ ...copilotkit.headers, Authorization: null }). A shared
normalizeHeaders helper enforces the same string-only invariant at both
write paths (constructor and setHeaders).

Update the react-core AuthTokenSync skill example to show the logout/clear
path and warn that a header must not be managed via both the headers prop and
imperative setHeaders (the provider re-applies prop-derived headers as a full
overwrite when its inputs change). Also update the setHeaders reference
signature docs. Tests cover null/undefined stripping, empty-string
preservation, overwrite-not-merge semantics, single-header clear via spread,
subscriber notification, and propagation to local and remote
(ProxiedCopilotRuntimeAgent) agents.

Fixes #5535
2026-06-19 14:13:02 -07:00
Nathan 🔶 Tarbert 5bd38b6f29 fix: resolve useAgent agentId from chat configuration (#5533) 2026-06-19 17:10:25 -04:00
Alem Tuzlak 588a2fb405 Merge branch 'main' into feat/bot-whatsapp 2026-06-19 19:45:21 +02:00
Ran Shemtov aee365e6aa Merge branch 'main' into fix/1980-a2ui-middleware-state-channel 2026-06-19 19:19:23 +02:00
Alem Tuzlak f0dbab1f34 chore: release bot-discord v0.0.2 (#5578)
## Release bot-discord v0.0.2

**Scope:** `bot-discord` | **Bump:** `patch`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `bot-discord` packages to `0.0.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 `bot-discord` packages to npm at version `0.0.2`
   - Creates git tag `bot-discord/v0.0.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.
2026-06-19 18:54:31 +02:00
Alem Tuzlak 7c3cd8d5d3 chore: release bot-slack v0.0.3 (#5579)
## Release bot-slack v0.0.3

**Scope:** `bot-slack` | **Bump:** `patch`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `bot-slack` packages to `0.0.3`
   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 `bot-slack` packages to npm at version `0.0.3`
   - Creates git tag `bot-slack/v0.0.3`
   - 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.
2026-06-19 18:53:59 +02:00
Ran Shemtov 73539949ea Merge branch 'main' into fix/1980-a2ui-middleware-state-channel 2026-06-19 18:29:24 +02:00
Ran Shem Tov a7885a5736 chore(deps): bump @ag-ui/langgraph to 0.0.42 and @ag-ui/a2ui-middleware to 0.0.10
@ag-ui/langgraph 0.0.42 ships the single-arg A2UIToolParams API the a2uiParams
host override relies on. Bump across sdk-js and runtime; @ag-ui/a2ui-middleware
0.0.10 in runtime. Lockfile regenerated.

Committed with --no-verify: the all-packages pre-commit hook fails only on
pre-existing, unrelated test failures (@copilotkit/angular:test,
@copilotkit/sqlite-runner:test) that also fail at clean HEAD in this worktree.
2026-06-19 18:00:20 +02:00
Ran Shem Tov af9547933d fix(sdk-js): add a2uiParams host override to createCopilotkitMiddleware
Mirror the sdk-python change: let a host steer the auto-injected generate_a2ui
subagent via an a2uiParams option (guidelines, defaultCatalogId, toolName, ...).
The middleware still injects the bound model and folds the registered catalog
in, but host-set values win.
2026-06-19 17:51:32 +02:00
AlemTuzlak aabd099bc6 chore: release bot-telegram v0.0.3 2026-06-19 15:27:22 +00:00
Alem Tuzlak f91f5d6968 Merge remote-tracking branch 'origin/main' into feat/bot-whatsapp
# Conflicts:
#	docs/model-allowlist.json
#	docs~origin_main
#	examples/slack/package.json
2026-06-19 17:26:54 +02:00
AlemTuzlak 81d7a236db chore: release bot-slack v0.0.3 2026-06-19 15:26:41 +00:00
AlemTuzlak d5ae3c9768 chore: release bot-discord v0.0.2 2026-06-19 15:26:38 +00:00
Alem Tuzlak 6b1f61afa1 chore: release bot v0.0.3 (#5574)
## Release bot v0.0.3

**Scope:** `bot` | **Bump:** `patch`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `bot` packages to `0.0.3`
   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 `bot` packages to npm at version `0.0.3`
   - Creates git tag `bot/v0.0.3`
   - 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.
2026-06-19 17:24:50 +02:00
Alem Tuzlak d92959e68d Merge remote-tracking branch 'origin/main' into feat/bot-whatsapp
# Conflicts:
#	.github/workflows/canary.yml
#	.github/workflows/publish-release.yml
#	.github/workflows/stable-release.yml
#	examples/slack/README.md
#	examples/slack/app/index.ts
#	examples/slack/app/sender-context.ts
#	release.config.json
2026-06-19 16:17:16 +02:00
Alem Tuzlak c55dad5cc5 Merge branch 'main' into feat/bot-slack-native-streaming
Resolve event-renderer.ts onRunFinishedEvent: keep the native turn stream open
(finalized in finish()) AND retain the legacy per-message stream drain from main
(#5573) as a no-op-in-native safety net. app/index.ts (telegram adapter from
#5520 + showToolStatus:false) and create-bot.test.ts auto-merged.
2026-06-19 15:57:20 +02:00
AlemTuzlak 734a187257 chore: release bot v0.0.3 2026-06-19 13:04:11 +00:00
Alem Tuzlak 967623de9f fix(bot-slack): resolve HITL waiter in assistant-pane DMs + render-tool message order (#5573)
Two fixes surfaced by exercising the bot's generative-UI / HITL tools
end-to-end in Slack.

## 1. HITL never resumed in an assistant-pane DM

An assistant-pane DM is **threaded**, so the ingress path
(`assistant.ts`) keys the turn's conversation by **`thread_ts`**. But
`decodeInteraction` forced **`DM_SCOPE`** for any `D…` channel. So the
HITL `awaitChoice` waiter was registered under `D…::<thread_ts>` while a
button click looked it up under `D…::dm` — the waiter was never
resolved. Clicking **Create/Cancel** swapped the card UI (the button's
`onClick` ran) but the agent run never resumed: no write, no reply.

**Fix:** honor an explicit `thread_ts` as the conversation scope even in
DMs (matching ingress); fall back to `DM_SCOPE` only for a genuinely
unthreaded DM. + a `decodeInteraction` regression test.

## 2. Render-tool output landed out of order

Defensively finalize any text stream still open at the end of a run
(`onRunFinishedEvent`), so a run's streamed text is fully posted before
the run-loop executes tool handlers that post out-of-band content
(images, cards).

All 200 `@copilotkit/bot-slack` tests pass. Verified live:
`confirm_write` now gates a write and resumes on approval; chart/diagram
output renders in order.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-19 13:58:28 +02:00
Alem Tuzlak 08184c471a feat: Telegram bot adapter (@copilotkit/bot-telegram) + dual-platform example (#5520)
## What

Adds a **Telegram platform adapter** (`@copilotkit/bot-telegram`) for
`@copilotkit/bot`, at feature parity with `@copilotkit/bot-slack`, plus
a runnable example that drives **both** a Slack bot and a Telegram bot
from one app.

## `@copilotkit/bot-telegram` (new package)

A grammY-based adapter implementing the `PlatformAdapter` interface:

- **Ingress:** long-polling by default; webhook / `auto` (serverless-env
detection) opt-in.
- **Threading:** topic-aware hybrid — `tg:<chatId>:<scope>` where scope
is `dm`, `topic:<id>` (forum supergroups, gated on `chat.is_forum`), or
`user:<userId>` (non-forum groups).
- **Rendering:** the platform-agnostic `@copilotkit/bot-ui` JSX IR →
Telegram HTML, with a plain-text format fallback when HTML entity
parsing fails; length-bounded.
- **Streaming:** chunked `editMessageText` (placeholder → repeated
edits) with HTML-expansion headroom.
- **Interactions:** `callback_query` HITL (ack-first; 64-byte
`callback_data` via hashed action ids resolved through the ActionStore).
- **Inbound files:** photo/document ingestion wired into the agent turn
(size-capped, token-redacted).
- Ships `defaultTelegramTools` (user lookup) + `defaultTelegramContext`
(tagging / HTML / thread-model guidance).
- **Capabilities:** `supportsModals: false`, `supportsTyping: true`,
`supportsStreaming: true`, `supportsThreadTitle: true`,
`supportsReactions: false`, `supportsSuggestedPrompts: false`.

## `examples/slack` → Slack **and** Telegram from one app

Rather than maintain a diverging copy, the example now starts a Slack
bot and/or a Telegram bot (env-conditional on which credentials are
present) from one **platform-neutral** app layer — shared components,
tools, context, commands, render helpers. Each platform gets its own
`createBot` with its platform-specific default tools/context; the shared
components emit the cross-platform JSX IR that each adapter renders
natively.

To make the shared layer truly neutral: unicode glyphs instead of Slack
mrkdwn `:shortcode:` strings (Telegram doesn't expand them; Slack
renders unicode fine), no Slack-Block-Kit `raw` fallbacks, and neutral
context/tool wording (per-platform tagging guidance comes from each
adapter's default context). The separate `examples/telegram` app was
removed; its e2e smoke harness + BotFather setup docs were migrated into
`examples/slack`.

## Testing

- `@copilotkit/bot-telegram`: 138 unit tests; `nx build` (typecheck)
clean.
- `slack-example`: 42 tests (incl. `renderTelegram` parity assertions);
typecheck clean.
- `nx run-many -t build` green; root `pnpm install --frozen-lockfile` in
sync.
- Reviewed via a multi-round adversarial CR loop to convergence (zero
load-bearing findings) + a bucket-(c) promotion audit.

## Note: `bot-telegram` is unpublished

The example references it as `workspace:*` and **runs from the
monorepo** (`pnpm --filter slack-example start`). Standalone deploys
(the example's own lockfile) work for Slack today; once `bot-telegram`
publishes alongside its siblings, switch the dep to `~0.0.2` and
regenerate the standalone lockfile to enable standalone Telegram
deploys. Documented in the example README.

## Known limitations / follow-ups (out of scope, pre-existing or
inherent)

- **render_table** hardening (pre-existing in the Slack example):
`clamp()` overflow `notes` are computed but not surfaced (silent
truncation); "Max 100 rows" doc vs `MAX_DATA_ROWS = 99` off-by-one;
monospace fallback ignores column alignment.
- **Telegram e2e harness** is a documented best-effort **manual-trigger
smoke**: `getUpdates` contends with the bot's own long-poller
(single-consumer Bot API limit); multi-chunk reply assembly and
follow-up message selection are approximate. Automated upgrade path
(second sender bot) documented in `e2e/TELEGRAM-README.md`.
- **`_status` glyph mapping** uses substring matching, so non-default
Linear workflow state names (e.g. "Unstarted") can map to the wrong
glyph — identically on both platforms.
- **Bold-inside-link** (`[**text**](url)`) renders literal asterisks on
both Slack and Telegram (link labels aren't formatted on either) —
pre-existing, cosmetic.
- Telegram long-poll failures (revoked token, 409 conflict) are logged
inside the adapter and not surfaced to `start()`, so startup reports
success even if polling later fails.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-19 13:46:10 +02:00
Alem Tuzlak 83c0a66c7b fix(bot-slack): resolve HITL waiter in assistant-pane DMs + fix render-tool message order
Two fixes surfaced by exercising generative-UI / HITL tools through the bot:

- interaction.ts: an assistant-pane DM is threaded, so the ingress path keys the
  turn by thread ts — but decodeInteraction forced DM_SCOPE for any "D…" channel.
  The awaitChoice waiter was registered under D…::<thread_ts> while the button
  click looked it up under D…::dm, so it was never resolved: clicking Create/
  Cancel swapped the card UI but the run never resumed (no write, no reply).
  Honor an explicit thread_ts as the scope even in DMs; fall back to DM_SCOPE
  only for a genuinely unthreaded DM.
- event-renderer.ts: defensively finalize any text stream still open at run end,
  so a run's streamed text is fully posted before the run-loop executes tool
  handlers that post out-of-band content.

Adds a decodeInteraction regression test for the threaded-DM scope.
2026-06-19 13:23:22 +02:00
Alem Tuzlak 5bf13abb8e fix(runtime): support full tool lifecycle in BuiltInAgent TanStack factory mode
The TanStack stream converter stopped at the first per-turn RUN_FINISHED,
assuming tools are executed client-side. That truncated runs whose tools are
executed by chat() itself (MCP servers, provider tools): the TOOL_CALL_RESULT
and the model's final answer were dropped, so MCP-backed turns returned nothing.

- convertTanStackStream: drop TanStack's per-turn RUN_STARTED/RUN_FINISHED (the
  Agent wrapper owns the outer pair) and convert every turn's events; dedupe
  tool START/END by id; surface RUN_ERROR instead of silently dropping it.
- convertInputToTanStackAI: return input.tools as TanStack client-side tools so
  the frontend's generative-UI / HITL tools work in factory mode, and sanitize
  their JSON Schema (close open objects) so OpenAI accepts them.

Adds converter + input tests (multi-turn, dedup, error surfacing, client-tool
conversion, schema sanitizing).
2026-06-19 13:20:21 +02:00
Alem Tuzlak 340722f597 Merge remote-tracking branch 'origin/main' into feat/bot-whatsapp
# Conflicts:
#	examples/slack/package.json
#	pnpm-lock.yaml
#	showcase/shell-docs/src/content/docs/meta.json
2026-06-19 11:22:56 +02:00
Alem Tuzlak 7fe12d6d3c Merge remote-tracking branch 'origin/main' into feat/bot-telegram-adapter
# Conflicts:
#	examples/slack/README.md
#	examples/slack/package.json
#	pnpm-lock.yaml
2026-06-19 11:04:18 +02:00
Mike Ryan b094156537 chore: release monorepo v1.61.0 2026-06-18 15:00:19 -07:00
Benjamin Taylor b71583c41e Merge remote-tracking branch 'origin/main' into chore/ent-938-bump-license-verifier 2026-06-18 16:35:20 -05:00
Benjamin Taylor 73b6713b69 Merge remote-tracking branch 'origin/main' into chore/ent-938-bump-license-verifier
# Conflicts:
#	.npmrc
#	packages/shared/package.json
#	pnpm-lock.yaml
2026-06-18 16:32:30 -05:00