Skip to main content
状态:支持文本 + DM 附件;频道/群组文件发送需要 sharePointSiteId + Graph 权限(参见 在群聊中发送文件)。投票通过 Adaptive Cards 发送。消息操作显式提供 upload-file,用于先文件后发送的场景。

Bundled plugin

Microsoft Teams 在当前 OpenClaw 版本中作为捆绑插件随附提供,因此在正常的打包构建中无需单独安装。 如果你使用的是较旧的构建版本,或是一个排除了捆绑 Teams 的自定义安装, 请直接安装 npm 包:
openclaw plugins install @openclaw/msteams
使用裸包以跟随当前官方发布标签。只有在你需要可复现安装时,才固定到确切 版本。 本地检出(从 git 仓库运行时):
openclaw plugins install ./path/to/local/msteams-plugin
详情: 插件

快速设置

@microsoft/teams.cli 通过一个命令即可完成机器人注册、清单创建和凭据生成。 1. 安装并登录
npm install -g @microsoft/teams.cli@preview
teams login
teams status   # 验证你已登录并查看你的租户信息
Teams CLI 目前处于预览版。命令和标志在不同版本之间可能会变化。
2. 启动隧道(Teams 无法访问 localhost) 如果你还没有安装并验证 devtunnel CLI,请先安装并认证它(入门指南)。
# 一次性设置(跨会话保持固定 URL):
devtunnel create my-openclaw-bot --allow-anonymous
devtunnel port create my-openclaw-bot -p 3978 --protocol auto

# 每个开发会话:
devtunnel host my-openclaw-bot
# 你的端点:https://<tunnel-id>.devtunnels.ms/api/messages
需要 --allow-anonymous,因为 Teams 无法使用 devtunnels 进行身份验证。每个传入的机器人请求仍会由 Teams SDK 自动验证。
替代方案:ngrok http 3978tailscale funnel 3978(但这些可能会在每次会话中更改 URL)。 3. 创建应用
teams app create \
  --name "OpenClaw" \
  --endpoint "https://<your-tunnel-url>/api/messages"
这条单命令会:
  • 创建一个 Entra ID(Azure AD)应用
  • 生成一个客户端密钥
  • 构建并上传一个 Teams 应用清单(包含图标)
  • 注册机器人(默认由 Teams 托管 - 无需 Azure 订阅)
输出将显示 CLIENT_IDCLIENT_SECRETTENANT_ID 和一个 Teams App ID - 请在下一步中记下这些信息。它还会提供直接在 Teams 中安装应用的选项。 4. 使用输出中的凭据配置 OpenClaw
{
  channels: {
    msteams: {
      enabled: true,
      appId: "<CLIENT_ID>",
      appPassword: "<CLIENT_SECRET>",
      tenantId: "<TENANT_ID>",
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}
或者直接使用环境变量:MSTEAMS_APP_IDMSTEAMS_APP_PASSWORDMSTEAMS_TENANT_ID 5. 在 Teams 中安装应用 teams app create 会提示你安装应用 - 选择 “Install in Teams”。如果你跳过了它,之后可以获取链接:
teams app get <teamsAppId> --install-link
6. 验证一切正常
teams app doctor <teamsAppId>
这会跨机器人注册、AAD 应用配置、清单有效性和 SSO 设置运行诊断。 对于生产部署,建议使用 联合身份验证(证书或托管身份)而不是客户端密钥。
群聊默认被阻止(channels.msteams.groupPolicy: "allowlist")。要允许群组回复,请设置 channels.msteams.groupAllowFrom,或使用 groupPolicy: "open" 允许任何成员(默认仍需提及)。

目标

  • 通过 Teams 私信、群聊或频道与 OpenClaw 对话。
  • 保持路由确定性:回复始终回到它们进入时所在的频道。
  • 默认采用安全的频道行为(除非另有配置,否则需要提及)。

配置写入

默认情况下,Microsoft Teams 允许由 /config set|unset 触发的配置更新写入(需要 commands.config: true)。 可通过以下方式禁用:
{
  channels: { msteams: { configWrites: false } },
}

访问控制(DM + 群组)

DM 访问
  • 默认:channels.msteams.dmPolicy = "pairing"。未知发送者会被忽略,直到通过审批。
  • channels.msteams.allowFrom 应使用稳定的 AAD 对象 ID 或静态发送者访问组,例如 accessGroup:core-team
  • 不要依赖 UPN/显示名称匹配来做允许列表——它们可能会变化。OpenClaw 默认会禁用直接名称匹配;如需启用,请显式设置 channels.msteams.dangerouslyAllowNameMatching: true
  • 当凭据允许时,向导可以通过 Microsoft Graph 将名称解析为 ID。
群组访问
  • 默认:channels.msteams.groupPolicy = "allowlist"(除非你添加 groupAllowFrom,否则会被阻止)。当未设置时,可使用 channels.defaults.groupPolicy 覆盖默认值。
  • channels.msteams.groupAllowFrom 控制哪些发送者或静态发送者访问组可以在群聊/频道中触发(回退到 channels.msteams.allowFrom)。
  • 设置 groupPolicy: "open" 以允许任何成员(默认仍需提及门控)。
  • 要允许没有任何频道,请设置 channels.msteams.groupPolicy: "disabled"
示例:
{
  channels: {
    msteams: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["00000000-0000-0000-0000-000000000000", "accessGroup:core-team"],
    },
  },
}
Teams + 频道允许列表
  • 通过在 channels.msteams.teams 下列出 teams 和 channels 来限定群组/频道回复范围。
  • 键应使用来自 Teams 链接的稳定 Teams 会话 ID,而不是可变的显示名称。
  • groupPolicy="allowlist" 且存在 teams 允许列表时,只有列出的 teams/channels 会被接受(仍需提及)。
  • 配置向导接受 Team/Channel 条目,并会为你保存它们。
  • 启动时,OpenClaw 会将 team/channel 和用户允许列表名称解析为 ID(当 Graph 权限允许时) 并记录映射;无法解析的 team/channel 名称会按输入保留,但默认会被忽略用于路由,除非启用了 channels.msteams.dangerouslyAllowNameMatching: true
示例:
{
  channels: {
    msteams: {
      groupPolicy: "allowlist",
      teams: {
        "My Team": {
          channels: {
            General: { requireMention: true },
          },
        },
      },
    },
  },
}

联邦认证(证书 + 托管标识)

添加于 2026.4.11
对于生产环境部署,OpenClaw 支持 联邦认证,作为比客户端密钥更安全的替代方案。可用两种方式:

选项 A:基于证书的认证

使用与你的 Entra ID 应用注册关联的 PEM 证书。 设置:
  1. 生成或获取一个证书(包含私钥的 PEM 格式)。
  2. 在 Entra ID → 应用注册 → 证书和密码证书 → 上传公钥证书。
配置:
{
  channels: {
    msteams: {
      enabled: true,
      appId: "<APP_ID>",
      tenantId: "<TENANT_ID>",
      authType: "federated",
      certificatePath: "/path/to/cert.pem",
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}
环境变量:
  • MSTEAMS_AUTH_TYPE=federated
  • MSTEAMS_CERTIFICATE_PATH=/path/to/cert.pem

选项 B:Azure 托管标识

使用 Azure 托管标识进行无密码认证。这非常适合部署在 Azure 基础设施(AKS、App Service、Azure 虚拟机)上且可用托管标识的场景。 工作原理:
  1. Bot Pod/虚拟机具有托管标识(系统分配或用户分配)。
  2. 一个 联邦身份凭据 将该托管标识链接到 Entra ID 应用注册。
  3. 运行时,OpenClaw 使用 @azure/identity 从 Azure IMDS 端点(169.254.169.254)获取令牌。
  4. 该令牌被传递给 Teams SDK 用于 bot 认证。
前置条件:
  • 已启用托管标识的 Azure 基础设施(AKS 工作负载标识、App Service、虚拟机)
  • 已在 Entra ID 应用注册上创建联邦身份凭据
  • Pod/虚拟机可访问 IMDS(169.254.169.254:80
配置(系统分配托管标识):
{
  channels: {
    msteams: {
      enabled: true,
      appId: "<APP_ID>",
      tenantId: "<TENANT_ID>",
      authType: "federated",
      useManagedIdentity: true,
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}
配置(用户分配托管标识):
{
  channels: {
    msteams: {
      enabled: true,
      appId: "<APP_ID>",
      tenantId: "<TENANT_ID>",
      authType: "federated",
      useManagedIdentity: true,
      managedIdentityClientId: "<MI_CLIENT_ID>",
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}
环境变量:
  • MSTEAMS_AUTH_TYPE=federated
  • MSTEAMS_USE_MANAGED_IDENTITY=true
  • MSTEAMS_MANAGED_IDENTITY_CLIENT_ID=<client-id>(仅适用于用户分配)

AKS 工作负载标识设置

对于使用工作负载标识的 AKS 部署:
  1. 在你的 AKS 集群上启用工作负载标识
  2. 在 Entra ID 应用注册上创建联邦身份凭据
    az ad app federated-credential create --id <APP_OBJECT_ID> --parameters '{
      "name": "my-bot-workload-identity",
      "issuer": "<AKS_OIDC_ISSUER_URL>",
      "subject": "system:serviceaccount:<NAMESPACE>:<SERVICE_ACCOUNT>",
      "audiences": ["api://AzureADTokenExchange"]
    }'
    
  3. 使用应用客户端 ID 为 Kubernetes service account 添加注解
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: my-bot-sa
      annotations:
        azure.workload.identity/client-id: "<APP_CLIENT_ID>"
    
  4. 为工作负载标识注入为 Pod 添加标签
    metadata:
      labels:
        azure.workload.identity/use: "true"
    
  5. 确保网络可访问 IMDS(169.254.169.254)- 如果使用 NetworkPolicy,请添加一条允许到 169.254.169.254/32 的 80 端口流量的 egress 规则。

认证类型对比

方式配置优点缺点
客户端密钥appPassword设置简单需要轮换密钥,安全性较低
证书authType: "federated" + certificatePath网络中不传共享密钥需要管理证书
托管标识authType: "federated" + useManagedIdentity无密码,不需要管理密钥需要 Azure 基础设施
默认行为: 当未设置 authType 时,OpenClaw 默认使用客户端密钥认证。现有配置无需更改即可继续工作。

本地开发(隧道)

Teams 无法访问 localhost。请使用持久化开发隧道,以便你的 URL 在不同会话之间保持不变:
# 一次性设置:
devtunnel create my-openclaw-bot --allow-anonymous
devtunnel port create my-openclaw-bot -p 3978 --protocol auto

# 每次开发会话:
devtunnel host my-openclaw-bot
替代方案:ngrok http 3978tailscale funnel 3978(每次会话 URL 可能会变化)。 如果你的隧道 URL 发生变化,请更新端点:
teams app update <teamsAppId> --endpoint "https://<new-url>/api/messages"

测试 Bot

运行诊断:
teams app doctor <teamsAppId>
一次性检查 bot 注册、AAD 应用、清单和 SSO 配置。 发送测试消息:
  1. 安装 Teams 应用(使用 teams app get <id> --install-link 返回的安装链接)
  2. 在 Teams 中找到 bot 并发送一条私信
  3. 检查网关日志是否有传入活动

环境变量

所有配置键都可以改为通过环境变量设置:
  • MSTEAMS_APP_ID
  • MSTEAMS_APP_PASSWORD
  • MSTEAMS_TENANT_ID
  • MSTEAMS_AUTH_TYPE(可选:"secret" "federated"
  • MSTEAMS_CERTIFICATE_PATH(联邦 + 证书)
  • MSTEAMS_CERTIFICATE_THUMBPRINT(可选,认证不需要)
  • MSTEAMS_USE_MANAGED_IDENTITY(联邦 + 托管标识)
  • MSTEAMS_MANAGED_IDENTITY_CLIENT_ID(仅用户分配 MI)

成员信息操作

OpenClaw 提供一个由 Graph 支持的 Microsoft Teams member-info 操作,使 agents 和自动化系统能够直接从 Microsoft Graph 中解析频道成员详细信息(显示名称、电子邮件、角色)。 要求:
  • Member.Read.Group RSC 权限(已包含在推荐清单中)
  • 对于跨团队查找:User.Read.All Graph 应用权限并获得管理员同意
该操作受 channels.msteams.actions.memberInfo 控制(默认:当 Graph 凭据可用时启用)。

历史上下文

  • channels.msteams.historyLimit 控制会封装进 prompt 的最近频道/群聊消息数量。
  • 回退到 messages.groupChat.historyLimit。设为 0 可禁用(默认 50)。
  • 获取到的线程历史会按发送者 allowlist(allowFrom / groupAllowFrom)过滤,因此线程上下文注入只包含来自允许发送者的消息。
  • 引用附件上下文(从 Teams 回复 HTML 中派生的 ReplyTo*)当前会按接收到的内容传递。
  • 换句话说,allowlist 仅用于限制哪些人可以触发 agent;目前只有特定的补充上下文路径会被过滤。
  • 可通过 channels.msteams.dmHistoryLimit 限制 DM 历史(用户轮次)。按用户覆盖:channels.msteams.dms["<user_id>"].historyLimit

当前 Teams RSC 权限(清单)

这些是我们 Teams 应用清单中现有的 resourceSpecific 权限。它们仅适用于应用已安装的团队/聊天内部。 适用于频道(团队范围):
  • ChannelMessage.Read.Group (Application) - 在无需 @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)
适用于群聊:
  • ChatMessage.Read.Chat (Application) - 在无需 @mention 的情况下接收所有群聊消息
通过 Teams CLI 添加 RSC 权限:
teams app rsc add <teamsAppId> ChannelMessage.Read.Group --type Application

Teams 清单示例(已脱敏)

包含所需字段的最小有效示例。请替换 ID 和 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: "Teams 中的 OpenClaw", full: "Teams 中的 OpenClaw" },
  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" },
      ],
    },
  },
}

清单注意事项(必需字段)

  • bots[].botId 必须 与 Azure Bot App ID 匹配。
  • webApplicationInfo.id 必须 与 Azure Bot App ID 匹配。
  • bots[].scopes 必须包含你计划使用的入口(personalteamgroupChat)。
  • 在个人范围内进行文件处理时需要 bots[].supportsFiles: true
  • 如果你希望接收频道流量,authorization.permissions.resourceSpecific 必须包含频道读/发权限。

更新现有应用

要更新一个已安装的 Teams 应用(例如添加 RSC 权限):
# 下载、编辑并重新上传清单
teams app manifest download <teamsAppId> manifest.json
# 在本地编辑 manifest.json...
teams app manifest upload manifest.json <teamsAppId>
# 如果内容发生变化,版本会自动提升
更新后,请在每个团队中重新安装该应用以使新权限生效,并且完全退出并重新启动 Teams(而不只是关闭窗口)以清除缓存的应用元数据。

功能:仅 RSC vs Graph

使用 Teams 仅 RSC(已安装应用,无 Graph API 权限)

可用:
  • 读取频道消息的 文本 内容。
  • 发送频道消息的 文本 内容。
  • 接收 个人(DM) 文件附件。
不可用:
  • 频道/群组的 图片或文件内容(payload 只包含 HTML 占位片段)。
  • 下载存储在 SharePoint/OneDrive 中的附件。
  • 读取消息历史记录(仅限实时 webhook 事件)。

使用 Teams RSC + Microsoft Graph 应用程序权限

新增:
  • 下载托管内容(粘贴到消息中的图片)。
  • 下载存储在 SharePoint/OneDrive 中的文件附件。
  • 通过 Graph 读取频道/聊天消息历史记录。

RSC vs Graph API

功能RSC 权限Graph API
实时消息是(通过 webhook)否(仅轮询)
历史消息是(可查询历史)
配置复杂度仅需应用清单需要管理员同意 + token 流程
离线可用否(必须运行中)是(可随时查询)
结论: RSC 用于实时监听;Graph API 用于历史访问。若要在离线期间补抓漏掉的消息,你需要带有 ChannelMessage.Read.All 的 Graph API(需要管理员同意)。

启用 Graph 的媒体 + 历史记录(频道必需)

如果你需要在 频道 中处理图片/文件,或者想获取 消息历史记录,必须启用 Microsoft Graph 权限并授予管理员同意。
  1. 在 Entra ID(Azure AD)应用注册中,添加 Microsoft Graph 应用程序权限
    • ChannelMessage.Read.All(频道附件 + 历史记录)
    • Chat.Read.AllChatMessage.Read.All(群聊)
  2. 为租户 授予管理员同意
  3. 提升 Teams 应用 manifest 版本,重新上传,并 在 Teams 中重新安装应用
  4. 完全退出并重新启动 Teams,以清除缓存的应用元数据。
用户提及的额外权限: 对于对话中的用户,用户 @mention 开箱即用。但如果你想动态搜索并提及 不在当前对话中的 用户,请添加 User.Read.All(Application)权限并授予管理员同意。

已知限制

Webhook 超时

Teams 通过 HTTP webhook 传递消息。如果处理耗时过长(例如 LLM 响应较慢),你可能会看到:
  • 网关超时
  • Teams 重试消息(导致重复)
  • 回复丢失
OpenClaw 通过快速返回并主动发送回复来处理这一点,但过慢的响应仍可能引发问题。

Teams cloud and service URL support

This SDK-backed Teams path is live-validated for Microsoft Teams public cloud. Inbound replies use the incoming Teams SDK turn context. Out-of-context proactive operations - sends, edits, deletes, cards, polls, file-consent messages, and queued long-running replies - use the stored conversation reference serviceUrl. Public cloud defaults to the Teams SDK public cloud environment and allows stored references on the public Teams Connector host: https://smba.trafficmanager.net/. Public cloud is the default. You do not need to set channels.msteams.cloud or channels.msteams.serviceUrl for normal public-cloud bots. For non-public Teams clouds, set cloud and the matching proactive boundary when Microsoft publishes one:
  • channels.msteams.cloud selects the Teams SDK cloud preset for authentication, JWT validation, token services, and Graph scope.
  • channels.msteams.serviceUrl selects the Bot Connector endpoint boundary used to validate stored conversation references before proactive sends, edits, deletes, cards, polls, file-consent messages, and queued long-running replies. It is required for USGov and DoD SDK clouds. For China/21Vianet, OpenClaw uses the SDK China preset and accepts stored/configured service URLs only on Azure China Bot Framework channel hosts.
Microsoft publishes the global proactive Bot Connector endpoints in the Create the conversation section of the Teams proactive messaging docs. Use the incoming activity’s serviceUrl when available; if you need a global proactive endpoint, use Microsoft’s table.
Teams environmentOpenClaw configProactive serviceUrl
Publicno cloud/serviceUrl config neededhttps://smba.trafficmanager.net/teams
GCCset serviceUrl; no separate Teams SDK cloud preset existshttps://smba.infra.gcc.teams.microsoft.com/teams
GCC Highcloud: "USGov" + serviceUrlhttps://smba.infra.gov.teams.microsoft.us/teams
DoDcloud: "USGovDoD" + serviceUrlhttps://smba.infra.dod.teams.microsoft.us/teams
China/21Vianetcloud: "China"use the incoming activity’s serviceUrl
Example for GCC, where Microsoft documents a separate proactive service URL but the Teams SDK does not expose a separate GCC cloud preset:
{
  "channels": {
    "msteams": {
      "serviceUrl": "https://smba.infra.gcc.teams.microsoft.com/teams"
    }
  }
}
Example for GCC High:
{
  "channels": {
    "msteams": {
      "cloud": "USGov",
      "serviceUrl": "https://smba.infra.gov.teams.microsoft.us/teams"
    }
  }
}
channels.msteams.serviceUrl is restricted to supported Microsoft Teams Bot Connector hosts. When a service URL is configured, OpenClaw checks that the stored conversation serviceUrl uses the same host before proactive sends, edits, deletes, cards, polls, or queued long-running replies run. With the default public-cloud config, OpenClaw fails closed if a stored conversation points outside the public Teams Connector host. Receive a fresh message from the conversation after changing cloud/service URL settings so the stored conversation reference is current. China/21Vianet does not have a separate global proactive smba URL in Microsoft’s Teams proactive endpoint table. Configure cloud: "China" so the Teams SDK uses Azure China auth, token, and JWT endpoints. Proactive sends then require a stored conversation reference from an incoming China Teams activity, or an explicitly configured service URL, on the Azure China Bot Framework channel boundary (*.botframework.azure.cn). Graph-backed Teams helpers are currently disabled for cloud: "China" until OpenClaw routes Graph requests through the Azure China Graph endpoint.

格式

Teams 的 markdown 比 Slack 或 Discord 更受限:
  • 基本格式可用:粗体斜体代码、链接
  • 复杂 markdown(表格、嵌套列表)可能无法正确渲染
  • Adaptive Cards 支持用于投票和语义化展示发送(见下文)

Configuration

Key settings (see /gateway/configuration for shared-channel mode):
  • channels.msteams.enabled: enable/disable the channel.
  • channels.msteams.appId, channels.msteams.appPassword, channels.msteams.tenantId: bot credentials.
  • channels.msteams.cloud: Teams SDK cloud environment (Public, USGov, USGovDoD, or China; default Public). Set this with serviceUrl for USGov/DoD SDK clouds; China uses the SDK preset and stored Azure China Bot Framework conversation references, with Graph-backed helpers disabled until Azure China Graph routing is implemented.
  • channels.msteams.serviceUrl: Bot Connector service URL boundary for SDK proactive operations. Public cloud uses the SDK default; set this for GCC (https://smba.infra.gcc.teams.microsoft.com/teams), GCC High, or DoD. China accepts Azure China Bot Framework channel hosts when the stored conversation reference comes from Teams operated by 21Vianet.
  • channels.msteams.webhook.port (default 3978)
  • channels.msteams.webhook.path (default /api/messages)
  • channels.msteams.dmPolicy: pairing | allowlist | open | disabled (default: pairing)
  • channels.msteams.allowFrom: DM allowlist (AAD object IDs recommended). The wizard resolves names to IDs during setup when Graph access is available.
  • channels.msteams.dangerouslyAllowNameMatching: break-glass toggle to re-enable mutable UPN/display-name matching and direct team/channel name routing.
  • channels.msteams.textChunkLimit: outbound text chunk size.
  • channels.msteams.chunkMode: length (default) or newline to split on blank lines (paragraph boundaries) before length chunking.
  • channels.msteams.mediaAllowHosts: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains).
  • channels.msteams.mediaAuthAllowHosts: allowlist for attaching Authorization headers on media retries (defaults to Graph + Bot Framework hosts).
  • channels.msteams.requireMention: require @mention in channels/groups (default true).
  • channels.msteams.replyStyle: thread | top-level (see Reply Style).
  • channels.msteams.teams.<teamId>.replyStyle: per-team override.
  • channels.msteams.teams.<teamId>.requireMention: per-team override.
  • channels.msteams.teams.<teamId>.tools: default per-team tool policy overrides (allow/deny/alsoAllow) used when a channel override is missing.
  • channels.msteams.teams.<teamId>.toolsBySender: default per-team per-sender tool policy overrides ("*" wildcard supported).
  • channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle: per-channel override.
  • channels.msteams.teams.<teamId>.channels.<conversationId>.requireMention: per-channel override.
  • channels.msteams.teams.<teamId>.channels.<conversationId>.tools: per-channel tool policy overrides (allow/deny/alsoAllow).
  • channels.msteams.teams.<teamId>.channels.<conversationId>.toolsBySender: per-channel per-sender tool policy overrides ("*" wildcard supported).
  • toolsBySender keys should use explicit prefixes: channel:, id:, e164:, username:, name: (legacy unprefixed keys still map to id: only).
  • channels.msteams.actions.memberInfo: enable or disable the Graph-backed member info action (default: enabled when Graph credentials are available).
  • channels.msteams.authType: authentication type - "secret" (default) or "federated".
  • channels.msteams.certificatePath: path to PEM certificate file (federated + certificate auth).
  • channels.msteams.certificateThumbprint: certificate thumbprint (optional, not required for auth).
  • channels.msteams.useManagedIdentity: enable managed identity auth (federated mode).
  • channels.msteams.managedIdentityClientId: client ID for user-assigned managed identity.
  • channels.msteams.sharePointSiteId: SharePoint site ID for file uploads in group chats/channels (see Sending files in group chats).

Routing and Sessions

  • Session keys follow the standard agent format (see /concepts/session):
    • Direct messages share the main session (agent:<agentId>:<mainKey>).
    • Channel/group messages use the conversation id:
      • agent:<agentId>:msteams:channel:<conversationId>
      • agent:<agentId>:msteams:group:<conversationId>

Reply Style: Threads vs Posts

Teams recently introduced two channel UI styles on top of the same underlying data model:
StyleDescriptionRecommended replyStyle
Posts(经典)Messages appear as cards with threaded replies underneaththread(default)
Threads(类似 Slack)Messages flow linearly, more like Slacktop-level
Problem: The Teams API does not expose which UI style a channel uses. If you use the wrong replyStyle:
  • In Threads-style channels, using thread → replies nest awkwardly
  • In Posts-style channels, using top-level → replies appear as separate top-level posts instead of in-thread replies
Solution: Set replyStyle per channel based on the channel’s configuration:
{
  channels: {
    msteams: {
      replyStyle: "thread",
      teams: {
        "19:[email protected]": {
          channels: {
            "19:[email protected]": {
              replyStyle: "top-level",
            },
          },
        },
      },
    },
  },
}

Priority Order

When the bot sends a reply to a channel, replyStyle is resolved from the most specific override down to the default. The first non-undefined value wins:
  1. Per channelchannels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle
  2. Per teamchannels.msteams.teams.<teamId>.replyStyle
  3. Globalchannels.msteams.replyStyle
  4. Implicit default — inferred from requireMention:
    • requireMention: truethread
    • requireMention: falsetop-level
If you set requireMention: false globally but do not explicitly set replyStyle, then mention messages in Posts-style channels will appear as top-level posts, even if the incoming message was a thread reply. To avoid surprises, pin replyStyle: "thread" at the global, team, or channel level.

Thread Context Preservation

When replyStyle: "thread" is in effect and the bot is mentioned inside a channel thread, OpenClaw reattaches the original thread root to the outbound conversation reference (19:…@thread.tacv2;messageid=<root>), so replies land in the same thread. This applies both to in-turn sends and proactive sends after the Bot Framework turn context has expired (for example, long-running agents or tool-call replies queued through mcp__openclaw__message). The thread root is taken from the threadId stored on the conversation reference. Older references that do not yet include threadId fall back to activityId (the most recent inbound activity that provided context for the conversation), so existing deployments keep working without reseeding. When replyStyle: "top-level" is in effect, inbound messages from channel threads are intentionally replied to as new top-level posts — no thread suffix is attached. This is correct behavior for Threads-style channels; if you see a message that should have been a thread reply become a top-level post, the channel’s replyStyle is misconfigured.

Attachments and Images

Current limitations:
  • DMs: Images and file attachments can be used via the Teams bot file API.
  • Channels/Groups: Attachments are stored in M365 storage (SharePoint/OneDrive). The webhook payload includes only an HTML placeholder fragment, not the actual file bytes. Downloading channel attachments requires Graph API permissions.
  • For explicit “file-first” sends, use action=upload-file with media / filePath / path; optional message becomes the accompanying text/comment, and filename overrides the upload name.
Without Graph permissions, channel messages with images are received as plain text (the bot cannot access the image content). By default, OpenClaw downloads media only from Microsoft/Teams hostnames. Override via channels.msteams.mediaAllowHosts (use ["*"] to allow any host). Only hosts listed in channels.msteams.mediaAuthAllowHosts get an Authorization header attached (Graph + Bot Framework hosts by default). Keep this list strict (avoid multi-tenant suffixes).

Sending Files in Group Chats

The bot can send files in DMs using the FileConsentCard flow (built in). However, sending files in group chats/channels requires extra configuration:
ScenarioFile sending methodRequired config
DMsFileConsentCard → user accepts → bot uploadsWorks out of the box
Group chats/ChannelsUpload to SharePoint → share linkRequires sharePointSiteId + Graph permissions
Images (any scenario)Base64 inline encodingWorks out of the box

Why Group Chats Need SharePoint

The bot does not have a personal OneDrive (/me/drive Graph API endpoints do not work with application identity). To send files in group chats/channels, the bot uploads the file to a SharePoint site and creates a share link.

Setup

  1. In Entra ID (Azure AD) → App registrations, add Graph API permissions:
    • Sites.ReadWrite.All (Application) - upload files to SharePoint
    • Chat.Read.All (Application) - optional, enables user-specific sharing links
  2. Grant admin consent for the tenant.
  3. Get your SharePoint site ID:
    # Via Graph Explorer or using curl with a valid token:
    curl -H "Authorization: Bearer $TOKEN" \
      "https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}"
    
    # Example: site is located at "contoso.sharepoint.com/sites/BotFiles"
    curl -H "Authorization: Bearer $TOKEN" \
      "https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles"
    
    # Response includes: "id": "contoso.sharepoint.com,guid1,guid2"
    
  4. Configure OpenClaw:
    {
      channels: {
        msteams: {
          // ... other configuration ...
          sharePointSiteId: "contoso.sharepoint.com,guid1,guid2",
        },
      },
    }
    

Sharing Behavior

PermissionSharing behavior
Sites.ReadWrite.All onlyOrganization-wide share link (anyone in org can access)
Sites.ReadWrite.All + Chat.Read.AllUser-specific share link (only chat members can access)
User-specific sharing is more secure because only chat participants can access the file. If Chat.Read.All is missing, the bot falls back to organization-wide sharing.

Fallback Behavior

ScenarioResult
Group chat + file + configured sharePointSiteIdUpload to SharePoint, send share link
Group chat + file + no sharePointSiteIdTry OneDrive upload (may fail), send text only
Personal chat + fileFileConsentCard flow (works without SharePoint too)
Any scenario + imageBase64 inline encoding (works without SharePoint too)

File Storage Location

Uploaded files are stored in the /OpenClawShared/ folder in the default document library of the configured SharePoint site.

Polls (Adaptive Cards)

OpenClaw sends Teams polls as Adaptive Cards (no native Teams poll API).
  • CLI: openclaw message poll --channel msteams --target conversation:<id> ...
  • 投票由 gateway 记录到 OpenClaw 插件状态 SQLite 中,位于 state/openclaw.sqlite
  • Existing msteams-polls.json files are imported by openclaw doctor --fix, not by the running plugin.
  • The gateway must stay online to record votes.
  • Polls do not auto-post result summaries yet, and there is no supported poll-results CLI yet.

Presentation Cards

Use the message tool, CLI, or a normal reply to deliver semantic presentation payloads to Teams users or conversations. OpenClaw renders them as Teams Adaptive Cards based on the common presentation contract. presentation parameters accept semantic blocks. When presentation is provided, message text is optional. Buttons render as Adaptive Card submit or URL actions. Choice menus are not natively supported by the Teams renderer yet, so OpenClaw degrades them to readable text before delivery. Agent tool:
{
  action: "send",
  channel: "msteams",
  target: "user:<id>",
  presentation: {
    title: "你好",
    blocks: [{ type: "text", text: "你好!" }],
  },
}
CLI:
openclaw message send --channel msteams \
  --target "conversation:19:[email protected]" \
  --presentation '{"title":"你好","blocks":[{"type":"text","text":"你好!"}]}'
For details on target formats, see Target Formats below.

目标格式

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 向用户发送
openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "你好"

# 通过显示名称向用户发送(会触发 Graph API 查找)
openclaw message send --channel msteams --target "user:John Smith" --message "你好"

# 向群聊或频道发送
openclaw message send --channel msteams --target "conversation:19:[email protected]" --message "你好"

# 向会话发送演示卡片
openclaw message send --channel msteams --target "conversation:19:[email protected]" \
  --presentation '{"title":"你好","blocks":[{"type":"text","text":"你好"}]}'
Agent tool 示例:
{
  action: "send",
  channel: "msteams",
  target: "user:John Smith",
  message: "你好!",
}
{
  action: "send",
  channel: "msteams",
  target: "conversation:19:[email protected]",
  presentation: {
    title: "你好",
    blocks: [{ type: "text", text: "你好" }],
  },
}
如果没有 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 解码)
用于配置:
  • Team key = /team/ 后的路径段(URL 解码,例如 19:[email protected];较旧的租户可能显示 @thread.skype,这也是有效的)
  • Channel key = /channel/ 后的路径段(URL 解码)
  • 忽略 OpenClaw 路由中的 groupId 查询参数。它是 Microsoft Entra 组 ID,不是传入 Teams 活动中使用的 Bot Framework 会话 ID。

私有频道

机器人在私有频道中的支持有限:
功能标准频道私有频道
机器人安装有限
实时消息(webhook)可能不起作用
RSC 权限行为可能不同
@提及如果机器人可访问
Graph API 历史记录是(需要权限)
如果私有频道不起作用,可尝试以下变通方案:
  1. 使用标准频道进行机器人交互
  2. 使用私信 - 用户始终可以直接向机器人发消息
  3. 使用 Graph API 访问历史记录(需要 ChannelMessage.Read.All

故障排查

常见问题

  • 频道中不显示图片: 缺少 Graph 权限或管理员同意。重新安装 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 上传,打开浏览器开发者工具(F12)→ Network 选项卡,并检查响应体中的实际错误。
  • 侧载失败: 尝试选择“Upload an app to your org’s app catalog”,而不是“Upload a custom app”——这通常可以绕过侧载限制。

RSC 权限不生效

  1. 确认 webApplicationInfo.id 与你的机器人 App ID 完全一致
  2. 重新上传应用,并在团队/聊天中重新安装
  3. 检查组织管理员是否已阻止 RSC 权限
  4. 确认你使用的是正确的作用域:团队使用 ChannelMessage.Read.Group,群聊使用 ChatMessage.Read.Chat

参考资料

相关内容