Microsoft Teams

适用范围

在以下情况使用本页:

  • 配置或开发 Microsoft Teams 集成
  • 设置本地开发 webhook 隧道
  • 排查 Teams bot 权限或文件上传问题

状态:捆绑插件。使用 Azure Bot Framework Direct Line API 与 Microsoft Teams 通信。

概述

  • 通过 Azure Bot Framework 与 Teams 集成。
  • 支持 DM(个人聊天)和频道/群组消息。
  • 消息通过 Azure Bot webhook 路由到 Gateway。
  • 支持 RSC(资源特定许可)权限和 Graph API 历史。
  • 文件附件通过 Teams bot 文件 API 或 SharePoint 处理。
  • 支持通过 Adaptive Cards 发送轮询。
  • 支持 @提及和回复样式(回复、提及)。
  • 需要公开可访问的 webhook 端点(或 ngrok/tailscale 等隧道)。

快速开始

  1. Azure Portal 中创建 Azure Bot:
    • 获取 App ID客户端密钥(机密)。
    • 启用 带有流量的 Direct Line
    • 获取 Direct Line 端点:https://<bot-name>.botframework.com/api/messages
  2. 创建 Teams 应用包(manifest + 图标)。
  3. 在 Teams 中安装应用并授予 RSC 权限。
  4. 配置 Clawdbot:
    {
      channels: {
        msteams: {
          enabled: true,
          botAppId: "your-bot-app-id",
          botClientSecret: "your-bot-client-secret",
          tenantId: "your-tenant-id"
        }
      }
    }
  5. 启动 Gateway;它将注册 webhook 处理程序并开始接收消息。

访问控制

  • DM 策略pairingallowlistopendisabled
    • 默认:pairing(配对代码)。
    • 使用 channels.msteams.allowFrom 控制谁可以发送 DM。
  • 群组/频道openallowlistdisabled
    • 默认:allowlist
    • 使用 channels.msteams.groupAllowFrom 控制谁可以在群组中触发。
  • 提及要求:默认情况下,群组/频道中的消息需要 @提及。
    • 使用 channels.msteams.requireMention=false 禁用。
    • 通过 channels.msteams.groups 按团队/频道配置。

Azure Bot 设置步骤

步骤 1:在 Azure Portal 中创建 Bot

  1. 转到 Azure Portal创建资源Azure Bot
  2. 配置:
    • Bot handle:bot 的唯一 ID。
    • 定价层:标准 或更高。
    • 应用类型:多租户。
  3. 获取 App ID客户端密钥(机密)。
  4. 启用 Direct Line 通道:
    • 在 Azure Bot 中,转到 通道Direct Line
    • 添加具有密钥的站点。
    • 复制 Direct Line 端点:https://<bot-name>.botframework.com/api/messages

步骤 2:创建 Teams 应用包

Teams 应用需要一个 manifestmanifest.json)和两个图标:

  • color.png:192x192 像素。
  • outline.png:32x32 像素。

示例 manifest:

{
  "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
  "manifestVersion": "1.16",
  "version": "1.0.0",
  "id": "YOUR-BOT-APP-ID",
  "packageName": "com.clawdbot.teams",
  "developer": {
    "name": "Your Name",
    "websiteUrl": "https://example.com",
    "privacyUrl": "https://example.com/privacy",
    "termsOfUseUrl": "https://example.com/terms"
  },
  "icons": {
    "color": "color.png",
    "outline": "outline.png"
  },
  "name": {
    "short": "Clawdbot",
    "full": "Clawdbot AI Assistant"
  },
  "description": {
    "short": "AI assistant for Microsoft Teams",
    "full": "An AI assistant that integrates with Microsoft Teams."
  },
  "accentColor": "#FFFFFF",
  "bots": [
    {
      "botId": "YOUR-BOT-APP-ID",
      "needsChannelSelector": false,
      "isNotificationOnly": false,
      "scopes": ["personal", "team", "groupchat"]
    }
  ],
  "webApplicationInfo": {
    "id": "YOUR-BOT-APP-ID",
    "resource": "https://RSC_PERMISSION_ID"
  },
  "permissions": {
    "resourceSpecific": [
      {
        "name": "ChannelMessage.Read.Group",
        "type": "Application"
      }
    ]
  },
  "validDomains": ["*.botframework.com"]
}

打包应用:

  1. 创建一个包含 manifest.json 和图标的文件夹。
  2. 压缩文件夹(.zip)。

步骤 3:在 Teams 中安装应用

  1. 转到 Teams Developer Portal应用导入应用
  2. 上传 .zip 文件。
  3. 将应用添加到团队或聊天。
  4. 授予 RSC 权限(如果提示)。

最小仅文本设置(无 RSC/Graph)

如果只需要基本的 DM/群组文本消息(无历史、无文件):

  1. 跳过 RSC 权限:从 manifest 中删除 permissions.resourceSpecific
  2. 跳过 Graph API:无需配置 Graph 权限。
  3. 基本配置
    {
      channels: {
        msteams: {
          enabled: true,
          botAppId: "YOUR-BOT-APP-ID",
          botClientSecret: "YOUR-CLIENT-SECRET"
        }
      }
    }

限制:无历史上下文、无文件附件、无频道图像。

历史上下文

Clawdbot 可以从 Teams 拉取历史消息以提供上下文。

方式 1:RSC 权限(推荐)

  • 在 manifest 中添加 ChannelMessage.Read.Group(团队)或 ChatMessage.Read.Chat(群组聊天)。
  • Clawdbot 通过 Graph API 获取历史。
  • 无需用户登录。

方式 2:Graph API

  • 配置 channels.msteams.graph.tenantIdclientIdclientSecret
  • Clawdbot 使用应用程序权限访问聊天历史。

配置:

{
  channels: {
    msteams: {
      historyLimit: 50,  // 每个聊天的消息数
      graph: {
        tenantId: "your-tenant-id",
        clientId: "your-graph-client-id",
        clientSecret: "your-graph-client-secret"
      }
    }
  }
}

注意事项:

  • RSC 权限是按团队/聊天的。需要将应用添加到每个团队/聊天。
  • Graph API 需要应用程序权限(ChannelMessage.Read.AllChat.Read.All)。

RSC 权限

RSC(资源特定许可)允许 bot 访问特定团队或聊天中的消息,而无需用户登录。

示例 Teams manifest:

{
  "permissions": {
    "resourceSpecific": [
      {
        "name": "ChannelMessage.Read.Group",
        "type": "Application"
      }
    ]
  },
  "webApplicationInfo": {
    "id": "YOUR-BOT-APP-ID",
    "resource": "https://RSC_PERMISSION_ID"
  }
}

支持的 RSC 权限:

  • ChannelMessage.Read.Group:读取团队频道中的消息。
  • ChannelMessage.Send.Group:向团队频道发送消息。
  • ChatMessage.Read.Chat:读取群组聊天中的消息。
  • ChatMessage.Send.Chat:向群组聊天发送消息。

更新现有应用

如果您已在 Teams 中安装应用:

  1. 更新 manifest 中的 version
  2. 重新上传 .zip 文件。
  3. 删除并重新添加应用以刷新权限。

RSC vs Graph API 功能

功能RSCGraph API
读取频道消息
读取群组聊天消息
发送消息
读取历史
读取文件/附件
需要 admin 同意

配置

完整的 channels.msteams 配置选项:

{
  channels: {
    msteams: {
      enabled: true,
      botAppId: "your-bot-app-id",
      botClientSecret: "your-bot-client-secret",
      tenantId: "your-tenant-id",  // 可选,用于 Graph API

      // Webhook 服务器
      serve: {
        port: 3334,
        path: "/teams/webhook"
      },

      // 公开暴露(选择一个)
      // publicUrl: "https://example.ngrok.app/teams/webhook",
      // tunnel: { provider: "ngrok" },
      // tailscale: { mode: "funnel", path: "/teams/webhook" }

      // 访问控制
      dmPolicy: "pairing",  // pairing | allowlist | open | disabled
      allowFrom: ["[email protected]"],
      groupPolicy: "allowlist",  // open | allowlist | disabled
      groupAllowFrom: ["[email protected]"],

      // 提及
      requireMention: true,  // 默认 true(群组)
      groups: {
        "*": { requireMention: true },  // 所有群组的默认值
        "19:[email protected]": { requireMention: false }  // 覆盖特定群组
      },

      // 历史
      historyLimit: 50,
      dmHistoryLimit: 20,

      // Graph API(可选,用于历史/文件)
      graph: {
        tenantId: "your-tenant-id",
        clientId: "your-client-id",
        clientSecret: "your-client-secret"
      },

      // 附件/媒体
      mediaMaxMb: 8,
      mediaAllowHosts: ["*.microsoft.com", "*.azureedge.net"],

      // SharePoint(群组文件上传)
      sharePointSiteId: "contoso.sharepoint.com,guid1,guid2"
    }
  }
}

路由与会话

  • Clawdbot 在消息交互后存储会话引用以进行主动消息传递。
  • 目标格式:
    • 用户:user:<aad-object-id>user:<display-name>
    • 群组/频道:conversation:<conversation-id>

回复样式

Clawdbot 支持多种回复样式:

  • plain:纯文本消息。
  • mention:提及用户(默认)。
  • reply:回复特定消息(需要 replyToId)。

配置:

{
  channels: {
    msteams: {
      replyStyle: "mention"  // plain | mention | reply
    }
  }
}

附件(图像/文件)

当前限制:

  • DM:图像和文件附件通过 Teams bot 文件 API 工作。
  • 频道/群组:附件存储在 M365 存储(SharePoint/OneDrive)中。webhook 负载仅包含 HTML 存根,而不是实际文件字节。需要 Graph API 权限才能下载频道附件。

如果没有 Graph 权限,包含图像的频道消息将仅作为文本接收(bot 无法访问图像内容)。 默认情况下,Clawdbot 仅从 Microsoft/Teams 主机名下载媒体。通过 channels.msteams.mediaAllowHosts 覆盖(使用 ["*"] 允许任何主机)。

在群组聊天中发送文件

Bot 可以使用 FileConsentCard 流程在 DM 中发送文件(内置)。但是,在群组聊天/频道中发送文件需要额外设置:

上下文文件发送方式所需设置
DMFileConsentCard → 用户接受 → bot 上传开箱即用
群组聊天/频道上传到 SharePoint → 共享链接需要 sharePointSiteId + Graph 权限
图像(任何上下文)Base64 编码内联开箱即用

为什么群组聊天需要 SharePoint

Bot 没有个人 OneDrive 驱动器(/me/drive Graph API 端点不适用于应用程序标识)。要在群组聊天/频道中发送文件,bot 会上传到 SharePoint 站点并创建共享链接。

设置

  1. 在 Entra ID (Azure AD) → 应用注册中添加 Graph API 权限

    • Sites.ReadWrite.All(应用程序)- 将文件上传到 SharePoint
    • Chat.Read.All(应用程序)- 可选,启用按用户共享链接
  2. 为租户授予 admin 同意

  3. 获取您的 SharePoint 站点 ID

    # 通过 Graph Explorer 或使用有效令牌的 curl:
    curl -H "Authorization: Bearer $TOKEN" \
      "https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}"
    
    # 示例:对于位于 "contoso.sharepoint.com/sites/BotFiles" 的站点
    curl -H "Authorization: Bearer $TOKEN" \
      "https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles"
    
    # 响应包括:"id": "contoso.sharepoint.com,guid1,guid2"
  4. 配置 Clawdbot

    {
      channels: {
        msteams: {
          // ... 其他配置 ...
          sharePointSiteId: "contoso.sharepoint.com,guid1,guid2"
        }
      }
    }

共享行为

权限共享行为
Sites.ReadWrite.All组织范围内共享链接(组织中的任何人都可以访问)
Sites.ReadWrite.All + Chat.Read.All按用户共享链接(仅聊天成员可以访问)

按用户共享更安全,因为只有聊天参与者可以访问文件。如果缺少 Chat.Read.All 权限,bot 将回退到组织范围内共享。

回退行为

场景结果
群组聊天 + 文件 + 配置了 sharePointSiteId上传到 SharePoint,发送共享链接
群组聊天 + 文件 + 无 sharePointSiteId尝试 OneDrive 上传(可能失败),仅发送文本
个人聊天 + 文件FileConsentCard 流程(无需 SharePoint 即可工作)
任何上下文 + 图像Base64 编码内联(无需 SharePoint 即可工作)

文件存储位置

上传的文件存储在配置的 SharePoint 站点的默认文档库中的 /ClawdbotShared/ 文件夹中。

轮询(Adaptive Cards)

Clawdbot 将 Teams 轮询作为 Adaptive Cards 发送(没有原生 Teams 轮询 API)。

  • CLI:clawdbot message poll --channel msteams --target conversation:<id> ...
  • 投票由 gateway 记录在 ~/.clawdbot/msteams-polls.json 中。
  • Gateway 必须保持在线以记录投票。
  • 轮询尚未自动发布结果摘要(如需要,请检查存储文件)。

Adaptive Cards(任意)

使用 message 工具或 CLI 向 Teams 用户或对话发送任何 Adaptive Card JSON。

card 参数接受 Adaptive Card JSON 对象。当提供 card 时,消息文本是可选的。

Agent 工具:

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

CLI:

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

有关 card 架构和示例,请参阅 Adaptive Cards 文档。有关目标格式详细信息,请参阅下面的目标格式

目标格式

MSTeams 目标使用前缀来区分用户和对话:

目标类型格式示例
用户(按 ID)user:<aad-object-id>user:40a1a0ed-4ff2-4164-a219-55518990c197
用户(按名称)user:<display-name>user:John Smith(需要 Graph API)
群组/频道conversation:<conversation-id>conversation:19:[email protected]
群组/频道(原始)<conversation-id>19:[email protected](如果包含 @thread

CLI 示例:

# 按 ID 向用户发送
clawdbot message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"

# 按显示名称向用户发送(触发 Graph API 查找)
clawdbot message send --channel msteams --target "user:John Smith" --message "Hello"

# 向群组聊天或频道发送
clawdbot message send --channel msteams --target "conversation:19:[email protected]" --message "Hello"

# 向对话发送 Adaptive Card
clawdbot message send --channel msteams --target "conversation:19:[email protected]" \
  --card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}'

Agent 工具示例:

{
  "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"}]}
}

注意:没有 user: 前缀,名称默认为群组/团队解析。按显示名称定位人员时,请始终使用 user:

主动消息传递

  • 主动消息仅在用户交互后可能,因为此时我们存储了会话引用。
  • 有关 dmPolicy 和允许列表控制,请参阅 /gateway/configuration

团队和频道 ID(常见陷阱)

Teams URL 中的 groupId 查询参数不是用于配置的团队 ID。而是从 URL 路径中提取 ID:

团队 URL:

https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
                                    └────────────────────────────┘
                                    团队 ID(URL 解码此部分)

频道 URL:

https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
                                      └─────────────────────────┘
                                      频道 ID(URL 解码此部分)

对于配置:

  • 团队 ID = /team/ 之后的路径段(URL 解码,例如 19:[email protected]
  • 频道 ID = /channel/ 之后的路径段(URL 解码)
  • 忽略 groupId 查询参数

私有频道

Bot 在私有频道中的支持有限:

功能标准频道私有频道
Bot 安装有限
实时消息(webhook)可能不工作
RSC 权限可能表现不同
@提及如果 bot 可访问
Graph API 历史是(需要权限)

如果私有频道不工作的解决方法:

  1. 使用标准频道进行 bot 交互
  2. 使用 DM - 用户始终可以直接向 bot 发送消息
  3. 使用 Graph API 进行历史访问(需要 ChannelMessage.Read.All

故障排除

常见问题

  • 频道中未显示图像: 缺少 Graph 权限或 admin 同意。重新安装 Teams 应用并完全退出/重新打开 Teams。
  • 频道中无响应: 默认情况下需要提及;设置 channels.msteams.requireMention=false 或按团队/频道配置。
  • 版本不匹配(Teams 仍显示旧 manifest): 删除并重新添加应用,并完全退出 Teams 以刷新。
  • 来自 webhook 的 401 Unauthorized: 在没有 Azure JWT 的情况下手动测试时预期 - 表示端点可访问但身份验证失败。使用 Azure Web Chat 进行正确测试。

Manifest 上传错误

  • “Icon file cannot be empty”:manifest 引用的图标文件为 0 字节。创建有效的 PNG 图标(outline.png 为 32x32,color.png 为 192x192)。
  • “webApplicationInfo.Id already in use”:应用仍安装在另一个团队/聊天中。首先找到并卸载它,或等待 5-10 分钟以进行传播。
  • 上传时出现 “Something went wrong”:改为通过 https://admin.teams.microsoft.com 上传,打开浏览器 DevTools (F12) → 网络选项卡,并检查响应正文以获取实际错误。
  • 旁加载失败:尝试"将应用上传到组织的应用目录"而不是"上传自定义应用" - 这通常会绕过旁加载限制。

RSC 权限不工作

  1. 验证 webApplicationInfo.id 与您的 bot 的应用 ID 完全匹配
  2. 重新上传应用并在团队/聊天中重新安装
  3. 检查您的组织管理员是否阻止了 RSC 权限
  4. 确认您使用正确的范围:ChannelMessage.Read.Group 用于团队,ChatMessage.Read.Chat 用于群组聊天

参考资料