Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
96aac0a
feat(teams): Microsoft Teams connector (card-only)
vuyiv Jul 6, 2026
cdabdca
fix(review): teams connector code-review fixes
vuyiv Jul 6, 2026
58e33b2
feat(teams): async webhook ack + optional serviceURL host allowlist
vuyiv Jul 6, 2026
de22159
fix(teams): channel-issuer-only inbound + bounded dispatch concurrency
vuyiv Jul 6, 2026
b7f3e27
feat(teams): configurable card_loading_text (default suggested, empty…
vuyiv Jul 6, 2026
dbce1fb
feat(teams): parse inbound 1:1 attachments off the activity
vuyiv Jul 7, 2026
ce954fa
feat(teams): bounded attachment download on the connector
vuyiv Jul 7, 2026
588a55e
feat(teams): deliver 1:1 inbound files and images to the agent
vuyiv Jul 7, 2026
06803e9
feat(teams): notify the user when an inbound attachment can't be read
vuyiv Jul 7, 2026
14dcae8
docs(teams): document 1:1 inbound media, supportsFiles manifest, size…
vuyiv Jul 7, 2026
c19d7df
fix(review): notify on malformed attachments; gate dispatch on handle…
vuyiv Jul 7, 2026
f7db4cc
docs(teams): fix doc consistency for AI-label timing and allow_from d…
vuyiv Jul 8, 2026
186bacd
feat(teams): add inline media fields to outbound attachment schema
vuyiv Jul 8, 2026
d1eb8e8
feat(teams): build inline image message activity from ImageAttachment
vuyiv Jul 8, 2026
1040ee1
feat(teams): implement ImageSender for outbound inline images
vuyiv Jul 8, 2026
22df968
docs(teams): document outbound image send
vuyiv Jul 8, 2026
ffb8cb4
fix(review): rename ContentUrl->ContentURL; add SendImage error-propa…
vuyiv Jul 8, 2026
eb59c3c
docs(core): document outbound-media delivery model on ImageSender/Fil…
vuyiv Jul 8, 2026
2d9f8f8
docs(readme): add Teams column to platform feature snapshot
vuyiv Jul 8, 2026
73b85db
Revert "docs(core): document outbound-media delivery model on ImageSe…
vuyiv Jul 8, 2026
42e246b
feat(teams): track active streaming card per conversation
vuyiv Jul 9, 2026
8f76573
feat(teams): render Action.Submit buttons in Adaptive Cards
vuyiv Jul 9, 2026
3661eb4
feat(teams): implement InlineButtonSender folding prompts into the card
vuyiv Jul 9, 2026
36824ef
feat(teams): map permission card-action clicks to resolver keywords
vuyiv Jul 9, 2026
ff290a5
docs(teams): document interactive permission/question card buttons
vuyiv Jul 9, 2026
bf03c7b
fix(review): correct multiSelect docs; evict stale card on new stream
vuyiv Jul 9, 2026
388f076
feat(teams): map Bot Connector 413 to errActivityTooLarge sentinel
vuyiv Jul 9, 2026
8fd01b0
feat(teams): degrade to notice on connector 413, drop guessed image cap
vuyiv Jul 9, 2026
ee61beb
docs(teams): image size limit is Teams' 413, not a fixed cap
vuyiv Jul 9, 2026
9bebfbe
fix(review): inject wrapped 413 error in SendImage degradation test
vuyiv Jul 9, 2026
496ba74
refactor(teams): send interactive prompts as standalone cards, not fo…
vuyiv Jul 9, 2026
c1d580e
refactor(teams): remove dead active-card registry and promptUpdate
vuyiv Jul 9, 2026
0a6bfe7
docs(teams): prompts render as standalone interactive cards
vuyiv Jul 9, 2026
a3c6728
fix(teams): check resp.Body.Close() error to satisfy errcheck lint
vuyiv Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- **`agent_session_idle_timeout_mins`**: new per-project config option that closes an idle live agent process after a clean turn while preserving the cc-connect session and saved agent session ID. The next message starts a new agent process and resumes the same conversation. Set to `0` or leave unset to disable (#1338).
- **Reasonix agent**: new agent adapter for Reasonix multi-model coding agent, bridging via HTTP serve API (POST /submit, SSE /events, POST /approve). Supports default/yolo/plan permission modes, SSE auto-reconnect with backoff, and thinking accumulator. (#1281)
- **cloud_web platform**: 新增 self-hosted IM Gateway 作为 first-class platform 接入 (CWIP v1 协议,支持 websocket / long_poll / gateway 3 种 transport,完整 inbound/outbound + capability negotiation + graceful degradation)。 详见 docs/cloud-web.md + #1282。
- **Microsoft Teams platform**: new Bot Framework connector — inbound webhook with JWT + serviceURL validation, outbound Bot Connector REST with AAD auth, streaming Adaptive Card replies (working card → answer, native AI label), inbound 1:1 files/images, outbound inline image send (size-capped), interactive permission & AskUserQuestion buttons folded into the streaming card, mention-to-engage with per-thread follow, and `session_scope` (thread/channel/user). See docs/teams.md.

## Unreleased

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ go build -tags 'no_discord no_dingtalk no_qq no_qqbot no_line' ./cmd/cc-connect
Available tags: `no_acp`, `no_claudecode`, `no_codex`, `no_cursor`, `no_gemini`,
`no_iflow`, `no_opencode`, `no_qoder`, `no_feishu`, `no_telegram`,
`no_discord`, `no_slack`, `no_dingtalk`, `no_wecom`, `no_weixin`, `no_qq`, `no_qqbot`,
`no_line`, `no_weibo`, `no_matrix`, `no_webex`.
`no_line`, `no_weibo`, `no_matrix`, `no_webex`, `no_teams`.

## Pre-Commit Checklist

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ PLATFORMS := \
# ---------------------------------------------------------------------------

ALL_AGENTS := acp antigravity claudecode codex copilot cursor devin gemini iflow kimi opencode pi qoder reasonix tmux
ALL_PLATFORMS := feishu telegram discord slack dingtalk wecom weixin qq qqbot line weibo max matrix webex cloud_web
ALL_PLATFORMS := feishu telegram discord slack dingtalk wecom weixin qq qqbot line weibo max matrix webex teams cloud_web
ALL_EXTRAS := web

COMMA := ,
Expand Down
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,17 +207,18 @@ High-level view of what each **built-in platform** can do in cc-connect.

† **QQ (NapCat / OneBot)** — unofficial self-hosted bridge; behaviour depends on your NapCat / network setup.

| Capability | Feishu | WPS Xiezuo | DingTalk | Telegram | Slack | Discord | LINE | WeCom | Weibo | **Weixin**<br>*(personal)* | QQ† | QQ Bot | Matrix |
|------------|:------:|:----------:|:--------:|:--------:|:-----:|:-------:|:----:|:-----:|:-----:|:-------------------------:|:---:|:------:|:------:|
| Text & slash commands | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Markdown / cards | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | ❌ | ✅ | ✅ | ✅ | ⚠️ |
| Streaming / chunked replies | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Images & files | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| Voice / STT / TTS | ⚠️ | ❌ | ⚠️ | ✅ | ⚠️ | ⚠️ | ❌ | ⚠️ | ❌ | ✅ | ⚠️ | ⚠️ | ❌ |
| Private (DM) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Group / channel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| Capability | Feishu | WPS Xiezuo | DingTalk | Telegram | Slack | Discord | LINE | WeCom | Weibo | **Weixin**<br>*(personal)* | QQ† | QQ Bot | Matrix | Teams |
|------------|:------:|:----------:|:--------:|:--------:|:-----:|:-------:|:----:|:-----:|:-----:|:-------------------------:|:---:|:------:|:------:|:-----:|
| Text & slash commands | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Markdown / cards | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ✅ |
| Streaming / chunked replies | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Images & files | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ⚠️ |
| Voice / STT / TTS | ⚠️ | ❌ | ⚠️ | ✅ | ⚠️ | ⚠️ | ❌ | ⚠️ | ❌ | ✅ | ⚠️ | ⚠️ | ❌ | ❌ |
| Private (DM) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Group / channel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |

> **WeCom:** Webhook mode needs a **public URL**; long-connection / WS style setups often do not.
> **Teams:** needs a **public URL** (Bot Framework webhook). Images & files = ⚠️: inbound files/images in 1:1 chats and outbound **images** only — no outbound files, no channel/group inbound. See [docs/teams.md](docs/teams.md).
> **Voice row:** many platforms need `[speech]` / TTS providers enabled in `config.toml`; values are a best-effort summary.
> Per-platform setup: [Platform setup guides](#-platform-setup-guides) below.

Expand All @@ -228,7 +229,7 @@ High-level view of what each **built-in platform** can do in cc-connect.
**10+ AI Agents** — Claude Code, Codex, Cursor Agent, Kimi CLI, Qoder CLI, Gemini CLI, OpenCode, iFlow CLI, Pi, Devin, Copilot — plus any agent that supports the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/get-started/agents). Use whichever fits your workflow, or all of them at once.

### 📱 Platform Flexibility
**13 Chat Platforms** — Feishu, WPS Xiezuo, DingTalk, Slack, Telegram, Discord, WeChat Work, Weibo, LINE, QQ, QQ Bot (Official), Matrix, plus **Weixin (personal ilink)** for **personal WeChat**. Most platforms need **zero public IP**.
**14 Chat Platforms** — Feishu, WPS Xiezuo, DingTalk, Slack, Telegram, Discord, WeChat Work, Weibo, LINE, Microsoft Teams, QQ, QQ Bot (Official), Matrix, plus **Weixin (personal ilink)** for **personal WeChat**. Most platforms need **zero public IP**.

### 🔄 Multi-Agent Orchestration
**Multi-Bot Relay** — Bind multiple bots in a group chat and let them communicate with each other. Ask Claude, get insights from Gemini — all in one conversation.
Expand Down Expand Up @@ -373,6 +374,7 @@ cc-connect update --pre # Include pre-releases
| Platform | Discord | ✅ Gateway — no public IP needed |
| Platform | Weibo | ✅ WebSocket — no public IP needed |
| Platform | LINE | ✅ Webhook — public URL required |
| Platform | Microsoft Teams | ✅ Webhook (Bot Framework) — public URL required |
| Platform | WeChat Work | ✅ WebSocket / Webhook |
| Platform | Weixin (personal, ilink) | ✅— HTTP long polling — no public IP needed |
| Platform | QQ (NapCat/OneBot) | ✅ WebSocket |
Expand All @@ -395,6 +397,7 @@ cc-connect update --pre # Include pre-releases
| Weixin (personal) | [docs/weixin.md](docs/weixin.md) | HTTP long polling (ilink) | No |
| QQ / QQ Bot | [docs/qq.md](docs/qq.md) | WebSocket | No |
| Matrix | [docs/matrix.md](docs/matrix.md) | /sync (Long Polling) | No |
| Microsoft Teams | [docs/teams.md](docs/teams.md) | Webhook (Bot Framework) | Yes |


## 🎯 Key Features
Expand Down
5 changes: 5 additions & 0 deletions cmd/cc-connect/plugin_platform_teams.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !no_teams

package main

import _ "github.com/chenhg5/cc-connect/platform/teams"
31 changes: 31 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,37 @@ app_secret = "your-feishu-app-secret"
# token = "YOUR_WEBEX_BOT_ACCESS_TOKEN" # from developer.webex.com (Bot)
# allow_from = "you@cisco.com" # comma-separated email allowlist / 逗号分隔的邮箱白名单

# Microsoft Teams (uncomment to enable / 取消注释以启用)
# 1. Create an Azure Bot resource; note its App ID + client secret
# 创建 Azure Bot 资源,记录 App ID 与客户端密钥
# 2. Set the bot's "messaging endpoint" to https://<your-host><webhook_path>
# 将机器人的"消息终结点"设置为 https://<你的域名><webhook_path>
# Connection: inbound webhook (Bot Framework) — a public HTTPS URL IS required / 需要公网 HTTPS 地址
# Replies stream as an Adaptive Card. See docs/teams.md for the full setup. / 完整配置见 docs/teams.md。
# Single-tenant only (tenant_id required) — multi-tenant bot creation is deprecated by Azure.
# 仅支持单租户(必须填 tenant_id)——Azure 已弃用多租户机器人创建。
# Omitting allow_from (or "*") permits anyone in your tenant; set an AAD-object-ID allowlist
# to restrict to specific users. / 留空 allow_from(或填 "*")允许本租户任何人;填 AAD 用户 ID 可限定到特定用户。

# [[projects.platforms]]
# type = "teams"
#
# [projects.platforms.options]
# app_id = "00000000-0000-0000-0000-000000000000" # Azure Bot App (client) ID
# app_password = "YOUR_CLIENT_SECRET" # Azure Bot client secret
# tenant_id = "00000000-0000-0000-0000-000000000000" # REQUIRED — AAD tenant (directory) ID that owns the bot / 必填:拥有该机器人的 AAD 租户 ID
# webhook_port = "3978" # local bind port (default 3978)
# webhook_path = "/api/messages" # must match the Azure messaging endpoint path
# allow_from = "*" # "*"/empty = all users in the tenant; or comma-separated AAD object IDs / "*"/留空=租户内所有人,或逗号分隔的 AAD 用户 ID
# session_scope = "thread" # "thread" (default) | "channel" | "user"
# card_update_interval_ms = 1500 # streaming card edit throttle (ms; Teams limits edits ~1/s)
# card_loading_text = "💭 Thinking…" # label on the placeholder card while the agent works; empty = no label
# service_url_allowlist = "" # optional defense-in-depth: comma-separated Bot Connector hosts the
# # bot may reply to; empty = any JWT-validated host. e.g. public cloud:
# # "smba.trafficmanager.net" (see docs/teams.md for gov-cloud hosts)
# max_attachment_bytes = 20971520 # cap per inbound 1:1 file/image download (default 20 MiB); larger = skipped with a notice
# # 1:1 file receive also needs supportsFiles:true in the Teams app manifest (see docs/teams.md)

# MAX messenger (uncomment to enable / 取消注释以启用)
# 1. Create a bot via @MasterBot in MAX, get the access token
# 在 MAX 中通过 @MasterBot 创建机器人,获取 access token
Expand Down
178 changes: 178 additions & 0 deletions docs/teams.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Microsoft Teams

cc-connect connects to Microsoft Teams as a Bot Framework bot. Unlike the
outbound-connection platforms (Slack socket mode, Feishu websocket), Teams
delivers messages by **POSTing to a public HTTPS webhook**, so this platform
requires a publicly reachable URL.

## How it works

- Azure Bot Service relays Teams messages to the connector's webhook
(`/api/messages` by default) as Bot Framework activities.
- The connector validates each request's JWT, then forwards the message to the
cc-connect engine.
- Replies are sent back through the Bot Connector REST API and stream as an
**Adaptive Card**: a "working" card is posted immediately, then edited in place
as the answer grows. Every card — the working card included — carries the native
"AI generated" label, so it is present from the first render. The card
renders uniformly in channels, group chats, and 1:1 (unlike the native
`streamType` protocol, which is one-on-one only).

Engagement model: in a **channel or group chat**, an **@mention** of the bot
engages that reply thread; subsequent messages **in that thread** are then
followed without re-mentioning. A message that @mentions other people but not the
bot is treated as human-to-human side chatter and ignored. Engagement is
persisted under the cc-connect data dir (`<data_dir>/teams/<project>-engaged.json`),
so it survives a restart. In a **1:1 (personal) chat** the bot responds to every
message — Teams does not allow @mentioning a bot there.

## Prerequisites

- An **Azure Bot resource** (Azure Portal → "Azure Bot"), created as a
**single-tenant** app. Note its **Microsoft App ID**, its **tenant (directory)
ID**, and create a **client secret**. (Multi-tenant bots are not supported —
Azure deprecated their creation after 2025-07-31.)
- The bot's **Microsoft Teams** channel enabled.
- A public HTTPS endpoint that routes to the connector's webhook port.

## Setup

1. **Create the Azure Bot** (single-tenant) and record the App ID, tenant ID, and client secret.
2. **Set the messaging endpoint** (Azure Bot → Configuration →
"Messaging endpoint") to:

```
https://<your-host><webhook_path>
```

where `<webhook_path>` matches your config (default `/api/messages`).
3. **Enable the Teams channel** (Azure Bot → Channels → Microsoft Teams).
4. **Configure cc-connect** — add a Teams platform to your project:

```toml
[[projects.platforms]]
type = "teams"

[projects.platforms.options]
app_id = "00000000-0000-0000-0000-000000000000" # Azure Bot App (client) ID
app_password = "YOUR_CLIENT_SECRET" # Azure Bot client secret
tenant_id = "<your-tenant-id>" # required — AAD tenant that owns the bot (single-tenant)
webhook_port = "3978" # local bind port
webhook_path = "/api/messages" # must match the messaging endpoint path
allow_from = "*" # "*"/empty = all users in the tenant, or AAD object IDs
session_scope = "thread" # "thread" (default) | "channel" | "user"
card_update_interval_ms = 1500 # streaming card edit throttle (ms)
```

> **Access control:** the connector is **single-tenant** (`tenant_id` required), so only
> users in that one organization can reach the bot. `allow_from = "*"` (the default) then
> permits everyone in the tenant; set an AAD-object-ID allowlist to restrict to specific
> people when you want a tighter boundary.

5. **Expose the webhook.** Put a reverse proxy / ingress with TLS in front of
`webhook_port`, or use a tunnel (e.g. for local testing) so Azure Bot Service
can reach `https://<your-host><webhook_path>`.
6. **Install the bot in Teams** (sideload an app manifest pointing at your bot,
or add it from your org's catalog) and @mention it to start.

## Options

| Option | Required | Default | Description |
|--------|----------|---------|-------------|
| `app_id` | yes | — | Azure Bot App (client) ID; the inbound JWT audience |
| `app_password` | yes | — | Azure Bot client secret (outbound token) |
| `tenant_id` | **yes** | — | AAD tenant that owns the bot. Single-tenant only — Azure deprecated multi-tenant bot creation after 2025-07-31 |
| `webhook_port` | no | `3978` | Local port the webhook binds to |
| `webhook_path` | no | `/api/messages` | Path of the messaging endpoint |
| `allow_from` | no | `""` | Comma-separated AAD object IDs allowed to use the bot; `*` or empty = all users in the tenant |
| `session_scope` | no | `thread` | `thread` (one session per reply thread), `channel` (one shared session across the channel), `user` (one session per user within a thread) |
| `card_update_interval_ms` | no | `1500` | Streaming-card edit throttle in ms; Teams rate-limits edits to ~1/s |
| `card_loading_text` | no | `""` | Label on the placeholder card shown while the agent works (e.g. `💭 Thinking…`); empty renders a label-less card |
| `service_url_allowlist` | no | `""` | Comma-separated hosts the bot may send replies to. Empty = any JWT-validated host (default). Set it to pin the bot to your cloud's Bot Connector host(s) as defense-in-depth. See "serviceURL allowlist" below |
| `max_attachment_bytes` | no | `20971520` (20 MiB) | Cap per inbound 1:1 file/image download. A larger attachment is skipped with a notice rather than buffered. See "Receiving files and images" below |

## serviceURL allowlist

The bot sends its replies (carrying its Bot Connector bearer token) to the
`serviceUrl` from each inbound activity. That URL is already bound to a
JWT-validated request, so by default any authenticated `serviceUrl` is trusted —
the same model as the Bot Framework / M365 Agents SDK.

For defense-in-depth (or a compliance lockdown), set `service_url_allowlist` to
the Bot Connector host(s) your tenant's cloud uses; the bot then drops any
activity whose `serviceUrl` host is not listed. Matching is on **host** (so
regional paths like `/amer/`, `/emea/` are fine). Current Microsoft hosts for
reference (verify against Azure docs — this list can change):

| Cloud | Host |
|-------|------|
| Public | `smba.trafficmanager.net` |
| GCC | `smba.infra.gcc.teams.microsoft.com` |
| GCC High | `smba.infra.gov.teams.microsoft.us` |
| DoD | `smba.infra.dod.teams.microsoft.us` |

Leave it empty unless you have a reason to pin — a too-narrow list silently drops
legitimate traffic.

## Receiving files and images

In a **1:1 (personal) chat**, a file or image the user attaches to the bot is
downloaded by the connector and handed to the agent (a file becomes a saved file
the agent can read; an image is passed as an image attachment). Text and an
attachment sent together arrive on the same turn.

**Manifest prerequisite:** for the bot to receive files in 1:1, the Teams app
manifest must declare:

```json
"bots": [
{ "botId": "<your-app-id>", "supportsFiles": true, "scopes": ["personal"] }
]
```

Without `supportsFiles: true`, Teams does not deliver file attachments to the bot
— no connector setting substitutes for it.

**Size limit:** each download is capped by `max_attachment_bytes` (default
20 MiB). An oversized attachment, or one whose download fails, is skipped and the
user gets a brief notice; the turn still proceeds with any text and other
attachments.

## Sending images

When the agent produces an image (or a user runs `cc-connect send --image`), the
bot sends it back **inline** as a base64 data-URI attachment, threaded to the
originating message. It renders in 1:1 chats, group chats, and channels. If the
Bot Connector rejects the image as too large (HTTP 413), the bot degrades to a
brief text notice rather than failing the turn — so Teams' own size limit
governs, not a fixed cap (a generous safety guard only rejects pathologically
large images up front). Sending **files** (non-image) is not supported — Teams
requires a separate file consent / SharePoint flow.

**Not supported:**
- **Channel / group attachments.** Files posted in a channel or group chat live
in SharePoint and require Microsoft Graph + tenant admin consent; attachments
outside a 1:1 chat are ignored, not partially handled.
- **Outbound files** (the bot *sending* non-image files) are not implemented.
Outbound **images** are supported (see "Sending images").

## Connection type

Webhook (Bot Framework) — **a public HTTPS URL is required**. This is inherent
to the Bot Framework: Teams messages always route through Azure Bot Service,
which POSTs to your endpoint.

## Limitations (MVP)

- Replies stream as an Adaptive Card only. Plain text streaming, a `reply_format`
toggle, and the native `streamType` 1:1 animation are deferred to follow-ups.
- Permission prompts and AskUserQuestion prompts render as their **own
interactive Adaptive Card** (Allow / Deny / Allow-all; one button per option) —
a distinct message, not folded into the streaming answer card, so a prompt is
never missed. Replying with text (`allow`, `deny`, a number) still works.
multiSelect questions also render as buttons and resolve on the **first tap**
(no multi-pick) — reply with comma-separated numbers as text to pick several.
- Inbound files and images are supported in **1:1 chats only** (see "Receiving
files and images"); channel/group attachments and inbound audio are not.
- No cron/timer → Teams proactive messages (a conversation-reference store is
needed to send without an incoming activity).
Loading
Loading