Microsoft Teams (plugin)

“Abandon all hope, ye who enter here.”

Cập nhật: 2026-01-21

Trạng thái: Hỗ trợ tin nhắn văn bản + file đính kèm trong DM; gửi file trong channel/group cần sharePointSiteId + quyền Graph (xem Gửi file trong group chat). Poll được gửi qua Adaptive Cards.

Cần cài plugin

Microsoft Teams được cung cấp dưới dạng plugin và không đi kèm với bản cài đặt core.

Breaking change (2026.1.15): MS Teams đã được tách ra khỏi core. Nếu các bạn dùng nó, phải cài plugin.

Lý do: giữ cho bản cài core nhẹ hơn và cho phép các dependency của MS Teams cập nhật độc lập.

Cài đặt qua CLI (npm registry):

openclaw plugins install @openclaw/msteams

Local checkout (khi chạy từ git repo):

openclaw plugins install ./extensions/msteams

Nếu các bạn chọn Teams trong quá trình configure/onboarding và phát hiện git checkout, OpenClaw sẽ tự động đề xuất đường dẫn cài đặt local.

Chi tiết: Plugins

Cài đặt nhanh (cho người mới)

  1. Cài đặt plugin Microsoft Teams.
  2. Tạo một Azure Bot (App ID + client secret + tenant ID).
  3. Cấu hình OpenClaw với các credentials đó.
  4. Expose /api/messages (mặc định port 3978) qua public URL hoặc tunnel.
  5. Cài đặt Teams app package và khởi động gateway.

Config tối thiểu:

{
  channels: {
    msteams: {
      enabled: true,
      appId: "<APP_ID>",
      appPassword: "<APP_PASSWORD>",
      tenantId: "<TENANT_ID>",
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}

Lưu ý: group chat bị chặn mặc định (channels.msteams.groupPolicy: "allowlist"). Để cho phép trả lời trong group, set channels.msteams.groupAllowFrom (hoặc dùng groupPolicy: "open" để cho phép bất kỳ thành viên nào, có mention-gated).

Mục tiêu

  • Chat với OpenClaw qua Teams DM, group chat, hoặc channel.
  • Giữ routing xác định: reply luôn quay về channel mà nó đến.
  • Mặc định là hành vi channel an toàn (cần mention trừ khi cấu hình khác).

Ghi config

Mặc định, Microsoft Teams được phép ghi các cập nhật config được kích hoạt bởi /config set|unset (cần commands.config: true).

Tắt bằng:

{
  channels: { msteams: { configWrites: false } },
}

Kiểm soát truy cập (DM + group)

Truy cập DM

  • Mặc định: channels.msteams.dmPolicy = "pairing". Người gửi không xác định sẽ bị bỏ qua cho đến khi được phê duyệt.
  • channels.msteams.allowFrom chấp nhận AAD object ID, UPN, hoặc display name. Wizard sẽ resolve name thành ID qua Microsoft Graph khi có credentials.

Truy cập Group

  • Mặc định: channels.msteams.groupPolicy = "allowlist" (bị chặn trừ khi các bạn thêm groupAllowFrom). Dùng channels.defaults.groupPolicy để override mặc định khi chưa set.
  • channels.msteams.groupAllowFrom kiểm soát người gửi nào có thể trigger trong group chat/channel (fallback về channels.msteams.allowFrom).
  • Set groupPolicy: "open" để cho phép bất kỳ thành viên nào (vẫn mention-gated mặc định).
  • Để không cho phép channel nào, set channels.msteams.groupPolicy: "disabled".

Ví dụ:

{
  channels: {
    msteams: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["[email protected]"],
    },
  },
}

Teams + channel allowlist

  • Giới hạn group/channel reply bằng cách liệt kê team và channel trong channels.msteams.teams.
  • Key có thể là team ID hoặc tên; channel key có thể là conversation ID hoặc tên.
  • Khi groupPolicy="allowlist" và có teams allowlist, chỉ các team/channel được liệt kê mới được chấp nhận (mention-gated).
  • Configure wizard chấp nhận entry Team/Channel và lưu trữ cho các bạn.
  • Khi khởi động, OpenClaw resolve team/channel và user allowlist name thành ID (khi có quyền Graph) và log mapping; các entry chưa resolve được giữ nguyên như đã nhập.

Ví dụ:

{
  channels: {
    msteams: {
      groupPolicy: "allowlist",
      teams: {
        "My Team": {
          channels: {
            General: { requireMention: true },
          },
        },
      },
    },
  },
}

Cách hoạt động

  1. Cài đặt plugin Microsoft Teams.
  2. Tạo một Azure Bot (App ID + secret + tenant ID).
  3. Build một Teams app package tham chiếu đến bot và bao gồm các quyền RSC bên dưới.
  4. Upload/cài đặt Teams app vào một team (hoặc personal scope cho DM).
  5. Cấu hình msteams trong ~/.openclaw/openclaw.json (hoặc env vars) và khởi động gateway.
  6. Gateway lắng nghe Bot Framework webhook traffic trên /api/messages mặc định.

Cài đặt Azure Bot (Yêu cầu trước)

Trước khi cấu hình OpenClaw, các bạn cần tạo một Azure Bot resource.

Bước 1: Tạo Azure Bot

  1. Vào Create Azure Bot

  2. Điền vào tab Basics:

    TrườngGiá trị
    Bot handleTên bot của bạn, ví dụ openclaw-msteams (phải unique)
    SubscriptionChọn Azure subscription của bạn
    Resource groupTạo mới hoặc dùng có sẵn
    Pricing tierFree cho dev/testing
    Type of AppSingle Tenant (khuyên dùng - xem lưu ý bên dưới)
    Creation typeCreate new Microsoft App ID

Lưu ý deprecation: Việc tạo multi-tenant bot mới đã bị deprecated sau 2025-07-31. Dùng Single Tenant cho bot mới nhé.

  1. Click Review + createCreate (đợi khoảng 1-2 phút)

Bước 2: Lấy Credentials

  1. Vào Azure Bot resource của bạn → Configuration
  2. Copy Microsoft App ID → đây là appId của bạn
  3. Click Manage Password → vào App Registration
  4. Trong Certificates & secretsNew client secret → copy Value → đây là appPassword của bạn
  5. Vào Overview → copy Directory (tenant) ID → đây là tenantId của bạn

Bước 3: Cấu hình Messaging Endpoint

  1. Trong Azure Bot → Configuration
  2. Set Messaging endpoint thành webhook URL của bạn:
    • Production: https://your-domain.com/api/messages
    • Local dev: Dùng tunnel (xem Local Development bên dưới)

Bước 4: Bật Teams Channel

  1. Trong Azure Bot → Channels
  2. Click Microsoft Teams → Configure → Save
  3. Chấp nhận Terms of Service

Local Development (Tunneling)

Teams không thể kết nối tới localhost. Dùng tunnel cho local development:

Option A: ngrok

ngrok http 3978
# Copy URL https, ví dụ: https://abc123.ngrok.io
# Set messaging endpoint thành: https://abc123.ngrok.io/api/messages

Option B: Tailscale Funnel

tailscale funnel 3978
# Dùng Tailscale funnel URL của bạn làm messaging endpoint

Teams Developer Portal (Cách khác)

Thay vì tạo manifest ZIP thủ công, các bạn có thể dùng Teams Developer Portal:

  1. Click + New app
  2. Điền thông tin cơ bản (name, description, developer info)
  3. Vào App featuresBot
  4. Chọn Enter a bot ID manually và paste Azure Bot App ID của bạn
  5. Check scope: Personal, Team, Group Chat
  6. Click DistributeDownload app package
  7. Trong Teams: AppsManage your appsUpload a custom app → chọn file ZIP

Cách này thường dễ hơn là chỉnh sửa JSON manifest thủ công.

Test Bot

Option A: Azure Web Chat (verify webhook trước)

  1. Trong Azure Portal → Azure Bot resource của bạn → Test in Web Chat
  2. Gửi một tin nhắn - bạn sẽ thấy response
  3. Điều này xác nhận webhook endpoint hoạt động trước khi setup Teams

Option B: Teams (sau khi cài app)

  1. Cài đặt Teams app (sideload hoặc org catalog)
  2. Tìm bot trong Teams và gửi DM
  3. Check gateway log để xem incoming activity

Cài đặt (chỉ text cơ bản)

  1. Cài đặt plugin Microsoft Teams

    • Từ npm: openclaw plugins install @openclaw/msteams
    • Từ local checkout: openclaw plugins install ./extensions/msteams
  2. Đăng ký Bot

    • Tạo Azure Bot (xem bên trên) và ghi lại:
      • App ID
      • Client secret (App password)
      • Tenant ID (single-tenant)
  3. Teams app manifest

    • Bao gồm entry bot với botId = <App ID>.
    • Scope: personal, team, groupChat.
    • supportsFiles: true (cần cho xử lý file trong personal scope).
    • Thêm quyền RSC (bên dưới).
    • Tạo icon: outline.png (32x32) và color.png (192x192).
    • Zip cả ba file lại: manifest.json, outline.png, color.png.
  4. Cấu hình OpenClaw

    {
      "msteams": {
        "enabled": true,
        "appId": "<APP_ID>",
        "appPassword": "<APP_PASSWORD>",
        "tenantId": "<TENANT_ID>",
        "webhook": { "port": 3978, "path": "/api/messages" }
      }
    }

    Các bạn cũng có thể dùng biến môi trường thay vì config key:

    • MSTEAMS_APP_ID
    • MSTEAMS_APP_PASSWORD
    • MSTEAMS_TENANT_ID
  5. Bot endpoint

    • Set Azure Bot Messaging Endpoint thành:
      • https://<host>:3978/api/messages (hoặc path/port bạn chọn).
  6. Chạy gateway

    • Teams channel tự động khởi động khi plugin được cài và config msteams tồn tại với credentials.

History context

  • channels.msteams.historyLimit kiểm soát số lượng tin nhắn channel/group gần đây được đưa vào prompt.
  • Fallback về messages.groupChat.historyLimit. Set 0 để tắt (mặc định 50).
  • DM history có thể giới hạn bằng channels.msteams.dmHistoryLimit (user turns). Override theo user: channels.msteams.dms["<user_id>"].historyLimit.

Quyền RSC hiện tại của Teams (Manifest)

Đây là các quyền resourceSpecific hiện có trong Teams app manifest của chúng ta. Chúng chỉ áp dụng bên trong team/chat nơi app được cài đặt.

Cho channel (team scope):

  • ChannelMessage.Read.Group (Application) - nhận tất cả tin nhắn channel mà không cần @mention
  • ChannelMessage.Send.Group (Application)
  • Member.Read.Group (Application)
  • Owner.Read.Group (Application)
  • ChannelSettings.Read.Group (Application)
  • TeamMember.Read.Group (Application)
  • TeamSettings.Read.Group (Application)

Cho group chat:

  • ChatMessage.Read.Chat (Application) - nhận tất cả tin nhắn group chat mà không cần @mention

Ví dụ Teams Manifest (đã ẩn thông tin)

Ví dụ tối thiểu, hợp lệ với các trường bắt buộc. Thay thế ID và URL.

{
  "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json",
  "manifestVersion": "1.23",
  "version": "1.0.0",
  "id": "00000000-0000-0000-0000-000000000000",
  "name": { "short": "OpenClaw" },
  "developer": {
    "name": "Your Org",
    "websiteUrl": "https://example.com",
    "privacyUrl": "https://example.com/privacy",
    "termsOfUseUrl": "https://example.com/terms"
  },
  "description": { "short": "OpenClaw in Teams", "full": "OpenClaw in Teams" },
  "icons": { "outline": "outline.png", "color": "color.png" },
  "accentColor": "#5B6DEF",
  "bots": [
    {
      "botId": "11111111-1111-1111-1111-111111111111",
      "scopes": ["personal", "team", "groupChat"],
      "isNotificationOnly": false,
      "supportsCalling": false,
      "supportsVideo": false,
      "supportsFiles": true
    }
  ],
  "webApplicationInfo": {
    "id": "11111111-1111-1111-1111-111111111111"
  },
  "authorization": {
    "permissions": {
      "resourceSpecific": [
        { "name": "ChannelMessage.Read.Group", "type": "Application" },
        { "name": "ChannelMessage.Send.Group", "type": "Application" },
        { "name": "Member.Read.Group", "type": "Application" },
        { "name": "Owner.Read.Group", "type": "Application" },
        { "name": "ChannelSettings.Read.Group", "type": "Application" },
        { "name": "TeamMember.Read.Group", "type": "Application" },
        { "name": "TeamSettings.Read.Group", "type": "Application" },
        { "name": "ChatMessage.Read.Chat", "type": "Application" }
      ]
    }
  }
}

Lưu ý về Manifest (các trường bắt buộc)

  • bots[].botId phải khớp với Azure Bot App ID.
  • webApplicationInfo.id phải khớp với Azure Bot App ID.
  • bots[].scopes phải bao gồm các surface bạn định dùng (personal, team, groupChat).
  • bots[].supportsFiles: true cần cho xử lý file trong personal scope.
  • authorization.permissions.resourceSpecific phải bao gồm channel read/send nếu bạn muốn channel traffic.

Cập nhật app đã tồn tại

Để cập nhật Teams app đã cài (ví dụ: thêm quyền RSC):

  1. Cập nhật manifest.json với các setting mới
  2. Tăng trường version (ví dụ: 1.0.01.1.0)
  3. Re-zip manifest với icon (manifest.json, outline.png, color.png)
  4. Upload zip mới:
    • Option A (Teams Admin Center): Teams Admin Center → Teams apps → Manage apps → tìm app của bạn → Upload new version
    • Option B (Sideload): Trong Teams → Apps → Manage your apps → Upload a custom app
  5. Cho team channel: Cài lại app trong mỗi team để quyền mới có hiệu lực
  6. Thoát hoàn toàn và khởi động lại Teams (không chỉ đóng cửa sổ) để xóa cached app metadata

Khả năng: Chỉ RSC vs Graph

Với chỉ Teams RSC (app đã cài, không có quyền Graph API)

Hoạt động:

  • Đọc nội dung text tin nhắn channel.
  • Gửi nội dung text tin nhắn channel.
  • Nhận file đính kèm personal (DM).

KHÔNG hoạt động:

  • Hình ảnh hoặc nội dung file trong channel/group (payload chỉ bao gồm HTML stub).
  • Download attachment được lưu trong SharePoint/OneDrive.
  • Đọc message history (ngoài live webhook event).

Với Teams RSC + quyền Microsoft Graph Application

Thêm:

  • Download hosted content (hình ảnh paste vào tin nhắn).
  • Download file attachment được lưu trong SharePoint/OneDrive.
  • Đọc channel/chat message history qua Graph.

RSC vs Graph API

Khả năngQuyền RSCGraph API
Tin nhắn real-timeCó (qua webhook)Không (chỉ polling)
Tin nhắn lịch sửKhôngCó (có thể query history)
Độ phức tạp setupChỉ app manifestCần admin consent + token flow
Hoạt động offlineKhông (phải đang chạy)Có (query bất cứ lúc nào)

Kết luận: RSC dùng cho lắng nghe real-time; Graph API dùng cho truy cập lịch sử. Để bắt kịp tin nhắn bị miss khi offline, các bạn cần Graph API với ChannelMessage.Read.All (cần admin consent).

Graph-enabled media + history (cần cho channel)

Nếu các bạn cần hình ảnh/file trong channel hoặc muốn fetch message history, phải bật quyền Microsoft Graph và grant admin consent.

  1. Trong Entra ID (Azure AD) App Registration, thêm Microsoft Graph Application permissions:
    • ChannelMessage.Read.All (channel attachment + history)
    • Chat.Read.All hoặc ChatMessage.Read.All (group chat)
  2. Grant admin consent cho tenant.
  3. Tăng manifest version của Teams app, re-upload, và cài lại app trong Teams.
  4. Thoát hoàn toàn và khởi động lại Teams để xóa cached app metadata.

Hạn chế đã biết

Webhook timeout

Teams gửi tin nhắn qua HTTP webhook. Nếu xử lý quá lâu (ví dụ: LLM response chậm), các bạn có thể thấy:

  • Gateway timeout
  • Teams retry tin nhắn (gây duplicate)
  • Reply bị drop

OpenClaw xử lý điều này bằng cách return nhanh và gửi reply proactively, nhưng response rất chậm vẫn có thể gây vấn đề.

Formatting

Teams markdown hạn chế hơn Slack hoặc Discord:

  • Formatting cơ bản hoạt động: bold, italic, code, link
  • Markdown phức tạp (table, nested list) có thể không render đúng
  • Adaptive Card được hỗ trợ cho poll và gửi card tùy ý (xem bên dưới)

Cấu hình

Các setting chính (xem /gateway/configuration cho các pattern channel chung):

  • channels.msteams.enabled: bật/tắt channel.
  • channels.msteams.appId, channels.msteams.appPassword, channels.msteams.tenantId: bot credentials.
  • channels.msteams.webhook.port (mặc định 3978)
  • channels.msteams.webhook.path (mặc định /api/messages)
  • channels.msteams.dmPolicy: pairing | allowlist | open | disabled (mặc định: pairing)
  • channels.msteams.allowFrom: allowlist cho DM (AAD object ID, UPN, hoặc display name). Wizard resolve name thành ID trong setup khi có Graph access.
  • channels.msteams.textChunkLimit: kích thước chunk text outbound.
  • channels.msteams.chunkMode: length (mặc định) hoặc newline để split theo dòng trống (paragraph boundary) trước khi length chunking.
  • channels.msteams.mediaAllowHosts: allowlist cho inbound attachment host (mặc định là Microsoft/Teams domain).
  • channels.msteams.requireMention: cần @mention trong channel/group (mặc định true).
  • channels.msteams.replyStyle: thread | top-level (xem Reply Style).
  • channels.msteams.teams.<teamId>.replyStyle: override theo team.
  • channels.msteams.teams.<teamId>.requireMention: override theo team.
  • channels.msteams.teams.<teamId>.tools: default per-team tool policy override (allow/deny/alsoAllow) dùng khi thiếu channel override.
  • channels.msteams.teams.<teamId>.toolsBySender: default per-team per-sender tool policy override (hỗ trợ wildcard "*").
  • channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle: override theo channel.
  • channels.msteams.teams.<teamId>.channels.<conversationId>.requireMention: override theo channel.
  • channels.msteams.teams.<teamId>.channels.<conversationId>.tools: per-channel tool policy override (allow/deny/alsoAllow).
  • channels.msteams.teams.<teamId>.channels.<conversationId>.toolsBySender: per-channel per-sender tool policy override (hỗ trợ wildcard "*").
  • channels.msteams.sharePointSiteId: SharePoint site ID cho upload file trong group chat/channel (xem Gửi file trong group chat).

Routing & Session

  • Session key theo format agent chuẩn (xem /concepts/session):
    • Tin nhắn trực tiếp chia sẻ main session (agent:<agentId>:<mainKey>).
    • Tin nhắn channel/group dùng conversation id:
      • agent:<agentId>:msteams:channel:<conversationId>
      • agent:<agentId>:msteams:group:<conversationId>

Reply Style: Thread vs Post

Teams gần đây giới thiệu hai kiểu UI channel trên cùng một data model:

StyleMô tảreplyStyle khuyên dùng
Posts (classic)Tin nhắn hiển thị dạng card với reply thread bên dướithread (mặc định)
Threads (giống Slack)Tin nhắn chảy tuyến tính, giống Slack hơntop-level

Vấn đề: Teams API không expose kiểu UI nào mà channel đang dùng. Nếu các bạn dùng sai replyStyle:

  • thread trong channel kiểu Threads → reply hiển thị nested khó hiểu
  • top-level trong channel kiểu Posts → reply hiển thị như post riêng thay vì trong thread

Giải pháp: Cấu hình replyStyle theo từng channel dựa trên cách channel được setup:

{
  "msteams": {
    "replyStyle": "thread",
    "teams": {
      "19:[email protected]": {
        "channels": {
          "19:[email protected]": {
            "replyStyle": "top-level"
          }
        }
      }
    }
  }
}

Attachment & Hình ảnh

Hạn chế hiện tại:

  • DM: Hình ảnh và file attachment hoạt động qua Teams bot file API.
  • Channel/group: Attachment nằm trong M365 storage (SharePoint/OneDrive). Webhook payload chỉ bao gồm HTML stub, không phải file byte thực tế. Cần quyền Graph API để download channel attachment.

Không có quyền Graph, tin nhắn channel có hình ảnh sẽ được nhận dưới dạng text-only (nội dung hình ảnh không thể truy cập bởi bot). Mặc định, OpenClaw chỉ download media từ Microsoft/Teams hostname. Override bằng channels.msteams.mediaAllowHosts (dùng ["*"] để cho phép bất kỳ host nào).

Gửi file trong group chat

Bot có thể gửi file trong DM bằng FileConsentCard flow (có sẵn). Tuy nhiên, gửi file trong group chat/channel cần setup thêm:

ContextCách gửi fileSetup cần thiết
DMFileConsentCard → user chấp nhận → bot uploadHoạt động ngay
Group chat/channelUpload lên SharePoint → chia sẻ linkCần sharePointSiteId + quyền Graph
Hình ảnh (bất kỳ context)Base64-encoded inlineHoạt động ngay

Tại sao group chat cần SharePoint

Bot không có OneDrive drive cá nhân (Graph API endpoint /me/drive không hoạt động với application identity). Để gửi file trong group chat/channel, bot upload lên SharePoint site và tạo sharing link.

Setup

  1. Thêm quyền Graph API trong Entra ID (Azure AD) → App Registration:

    • Sites.ReadWrite.All (Application) - upload file lên SharePoint
    • Chat.Read.All (Application) - tùy chọn, cho phép sharing link theo user
  2. Grant admin consent cho tenant.

  3. Lấy SharePoint site ID của bạn:

    # Qua Graph Explorer hoặc curl với token hợp lệ:
    curl -H "Authorization: Bearer $TOKEN" \
      "https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}"
    
    # Ví dụ: cho site tại "contoso.sharepoint.com/sites/BotFiles"
    curl -H "Authorization: Bearer $TOKEN" \
      "https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles"
    
    # Response bao gồm: "id": "contoso.sharepoint.com,guid1,guid2"
  4. Cấu hình OpenClaw:

    {
      channels: {
        msteams: {
          // ... config khác ...
          sharePointSiteId: "contoso.sharepoint.com,guid1,guid2",
        },
      },
    }

Hành vi chia sẻ

QuyềnHành vi chia sẻ
Chỉ Sites.ReadWrite.AllSharing link toàn tổ chức (bất kỳ ai trong org đều truy cập được)
Sites.ReadWrite.All + Chat.Read.AllSharing link theo user (chỉ thành viên chat truy cập được)

Sharing theo user an toàn hơn vì chỉ người tham gia chat mới truy cập được file. Nếu thiếu quyền Chat.Read.All, bot sẽ fallback về sharing toàn tổ chức.

Hành vi fallback

Tình huốngKết quả
Group chat + file + đã config sharePointSiteIdUpload lên SharePoint, gửi sharing link
Group chat + file + không có sharePointSiteIdThử upload OneDrive (có thể fail), chỉ gửi text
Personal chat + fileFileConsentCard flow (hoạt động không cần SharePoint)
Bất kỳ context + hình ảnhBase64-encoded inline (hoạt động không cần SharePoint)

Vị trí lưu file

File được upload sẽ lưu trong thư mục /OpenClawShared/ trong default document library của SharePoint site đã cấu hình.

Poll (Adaptive Cards)

OpenClaw gửi Teams poll dưới dạng Adaptive Card (không có native Teams poll API).

  • CLI: openclaw message poll --channel msteams --target conversation:<id> ...
  • Vote được ghi lại bởi gateway trong ~/.openclaw/msteams-polls.json.
  • Gateway phải online để ghi vote.
  • Poll chưa tự động post kết quả tổng hợp (kiểm tra store file nếu cần).

Adaptive Card (tùy ý)

Gửi bất kỳ Adaptive Card JSON nào tới Teams user hoặc conversation bằng tool message hoặc CLI.

Tham số card chấp nhận object Adaptive Card JSON. Khi có card, text tin nhắn là tùy chọn.

Agent tool:

{
  "action": "send",
  "channel": "msteams",
  "target": "user:<id>",
  "card": {
    "type": "AdaptiveCard",
    "version": "1.5",
    "body": [{ "type": "TextBlock", "text": "Hello!" }]
  }
}

CLI:

openclaw message send --channel msteams \
  --target "conversation:19:[email protected]" \
  --card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello!"}]}'

Xem tài liệu Adaptive Cards cho schema và ví dụ. Để biết chi tiết format target, xem Target formats bên dưới.

Target formats

MSTeams target dùng prefix để phân biệt giữa user và conversation:

Loại targetFormatVí dụ
User (theo ID)user:<aad-object-id>user:40a1a0ed-4ff2-4164-a219-55518990c197
User (theo tên)user:<display-name>user:John Smith (cần Graph API)
Group/channelconversation:<conversation-id>conversation:19:[email protected]
Group/channel (raw)<conversation-id>19:[email protected] (nếu chứa @thread)

Ví dụ CLI:

# Gửi tới user theo ID
openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"

# Gửi tới user theo display name (trigger Graph API lookup)
openclaw message send --channel msteams --target "user:John Smith" --message "Hello"

# Gửi tới group chat hoặc channel
openclaw message send --channel msteams --target "conversation:19:[email protected]" --message "Hello"

# Gửi Adaptive Card tới conversation
openclaw message send --channel msteams --target "conversation:19:[email protected]" \
  --card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}'

Ví dụ Agent tool:

{
  "action": "send",
  "channel": "msteams",
  "target": "user:John Smith",
  "message": "Hello!"
}
{
  "action": "send",
  "channel": "msteams",
  "target": "conversation:19:[email protected]",
  "card": {
    "type": "AdaptiveCard",
    "version": "1.5",
    "body": [{ "type": "TextBlock", "text": "Hello" }]
  }
}

Lưu ý: Không có prefix user:, tên sẽ mặc định resolve thành group/team. Luôn dùng user: khi target người theo display name.

Proactive messaging

  • Tin nhắn proactive chỉ có thể sau khi user đã tương tác, vì chúng ta lưu conversation reference tại thời điểm đó.
  • Xem /gateway/configuration cho dmPolicy và allowlist gating.

Team và Channel ID (Lỗi thường gặp)

Tham số groupId trong Teams URL KHÔNG PHẢI là team ID dùng cho cấu hình. Trích xuất ID từ URL path thay vì:

Team URL:

https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
                                    └────────────────────────────┘
                                    Team ID (URL-decode cái này)

Channel URL:

https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
                                      └─────────────────────────┘
                                      Channel ID (URL-decode cái này)

Cho config:

  • Team ID = path segment sau /team/ (URL-decoded, ví dụ: 19:[email protected])
  • Channel ID = path segment sau /channel/ (URL-decoded)
  • Bỏ qua tham số query groupId

Private Channel

Bot có hỗ trợ hạn chế trong private channel:

Tính năngStandard ChannelPrivate Channel
Cài đặt botHạn chế
Tin nhắn real-time (webhook)Có thể không hoạt động
Quyền RSCCó thể hoạt động khác
@mentionNếu bot có thể truy cập
Graph API historyCó (với quyền)

Giải pháp nếu private channel không hoạt động:

  1. Dùng standard channel cho tương tác bot
  2. Dùng DM - user luôn có thể nhắn tin trực tiếp với bot
  3. Dùng Graph API cho truy cập lịch sử (cần ChannelMessage.Read.All)

Troubleshooting

Vấn đề thường gặp

  • Hình ảnh không hiển thị trong channel: Thiếu quyền Graph hoặc admin consent. Cài lại Teams app và thoát hoàn toàn/mở lại Teams.
  • Không có response trong channel: mention là bắt buộc mặc định; set channels.msteams.requireMention=false hoặc cấu hình theo team/channel.
  • Version mismatch (Teams vẫn hiển thị manifest cũ): gỡ + cài lại app và thoát hoàn toàn Teams để refresh.
  • 401 Unauthorized từ webhook: Dự kiến khi test thủ công không có Azure JWT - nghĩa là endpoint có thể truy cập nhưng auth fail. Dùng Azure Web Chat để test đúng cách.

Lỗi upload manifest

  • “Icon file cannot be empty”: Manifest tham chiếu đến icon file có 0 byte. Tạo PNG icon hợp lệ (32x32 cho outline.png, 192x192 cho color.png).
  • “webApplicationInfo.Id already in use”: App vẫn được cài trong team/chat khác. Tìm và gỡ cài đặt trước, hoặc đợi 5-10 phút để propagation.
  • “Something went wrong” khi upload: Upload qua https://admin.teams.microsoft.com thay vì, mở browser DevTools (F12) → tab Network, và check response body để xem lỗi thực tế.
  • Sideload fail: Thử “Upload an app to your org’s app catalog” thay vì “Upload a custom app” - cách này thường bypass sideload restriction.

Quyền RSC không hoạt động

  1. Xác minh webApplicationInfo.id khớp chính xác với App ID của bot
  2. Re-upload app và cài lại trong team/chat
  3. Kiểm tra xem org admin có chặn quyền RSC không
  4. Xác nhận bạn đang dùng đúng scope: ChannelMessage.Read.Group cho team, ChatMessage.Read.Chat cho group chat

Tài liệu tham khảo