消息行为
How replies are routed and threaded, and how attachments and files move in and out of Teams.
路由与会话¶
- 会话键遵循标准 agent 格式(参见 /concepts/session):
- 直接消息默认共享主会话(
agent:<agentId>:main)。 - 频道/群组消息使用会话 ID:
agent:<agentId>:msteams:channel:<conversationId>agent:<agentId>:msteams:group:<conversationId>
频道元数据¶
消息工具的 channel-list 操作需要 teamId。channel-info 操作需要 teamId 和 channelId。请使用 Microsoft Teams 的团队 ID,即使同时配置了 Slack 也是如此;共享的 teamId 字段接受各提供方的 ID 格式。这些操作保留已配置的 Teams 访问规则。
Graph 操作¶
在 Teams 操作处理器启动后,置顶、取消置顶、添加或移除表情回应、更改参与者以及重命名会话会在每次 Graph 请求前重新检查其调用者。在处理器准备 token、目标授权或参与者查找期间取消,会停止其下一个请求。取消不会撤销 Microsoft 已接受的变更。现有的账户、目标和所有者/管理员规则仍然适用;置顶和取消置顶仍仅限于聊天。
回复样式:线程与帖子¶
Teams 在同一底层数据模型上有两种频道 UI 样式:
| 样式 | 描述 | 推荐 replyStyle |
|---|---|---|
| Posts(经典) | 消息以卡片形式显示,下方为线程回复 | thread(默认) |
| Threads(类似 Slack) | 消息线性流动,更接近 Slack | top-level |
问题: Teams API 不会公开频道使用哪种 UI 样式。如果使用错误的 replyStyle:
- 在线程样式频道中使用
thread→ 回复会以尴尬的方式嵌套显示。 - 在帖子样式频道中使用
top-level→ 回复会显示为独立的顶级帖子,而不是线程内回复。
解决方案: 根据频道的设置方式,按频道配置 replyStyle:
{
channels: {
msteams: {
replyStyle: "thread",
teams: {
"19:abc...@thread.tacv2": {
channels: {
"19:xyz...@thread.tacv2": {
replyStyle: "top-level",
},
},
},
},
},
},
}
解析优先级¶
当机器人向频道发送回复时,replyStyle 会从最具体的覆盖项解析到默认值。第一个非 undefined 值生效:
- 按频道 -
channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle - 按团队 -
channels.msteams.teams.<teamId>.replyStyle - 全局 -
channels.msteams.replyStyle - 隐式默认 - 由
requireMention派生: requireMention: true→threadrequireMention: false→top-level
如果全局设置 requireMention: false 且没有显式 replyStyle,帖子样式频道中的提及会显示为顶级帖子,即使入站消息是线程回复。为避免意外,请在全局、团队或频道级别固定 replyStyle: "thread"。
对于发送到已存储频道会话的主动发送(排队的工具调用回复、长时间运行的 agent),应用相同的团队/频道解析;无论 replyStyle 如何,群组聊天和个人(DM)会话的主动发送始终解析为 top-level。
线程上下文保留¶
当 replyStyle: "thread" 生效,且机器人从频道线程内部被 @提及 时,OpenClaw 会将原始线程根重新附加到出站会话引用(19:...@thread.tacv2;messageid=<root>),使回复落在同一线程内。这既适用于实时(回合内)发送,也适用于 Bot Framework 回合上下文过期后进行的主动发送(例如长时间运行的 agent、通过 mcp__openclaw__message 排队的工具调用回复)。
线程根取自会话引用上存储的 threadId。早于 threadId 的旧存储引用会回退到 activityId(最后为会话播种的入站活动),因此现有部署无需重新播种即可继续工作。
当 replyStyle: "top-level" 生效时,频道线程入站消息会被有意作为新的顶级帖子回复;不会附加线程后缀。对于线程样式频道,这是正确的;如果你期望线程回复却看到顶级帖子,说明该频道的 replyStyle 设置不正确。
投递取消与重试¶
文本、图片、文件和演示卡片的任务与队列投递会在每次 Teams 请求前检查其当前投递权限,包括在获取 token 和等待速率限制之后。取消会停止尚未开始的请求。Teams 已接受的消息和文件卡片保留其投递回执;部分完成的文本与媒体发送保留其已接受的部分。
必需的 SharePoint 上传、成员查找、共享链接和重定向在继续前会检查相同的权限。已上传的 SharePoint 文件是为 Teams 文件卡片做准备;仅上传本身不会确认已投递到聊天。
出站提及¶
在出站文本中使用 @[Name](id),将 id 替换为 Teams 用户/机器人 ID 或 Microsoft Entra 对象 ID。将显示名称中的方括号转义为 \[ 和 \]:Alice \[Ops\] 在原生提及中变为 Alice [Ops]。转义的反斜杠使用 \\。相同的格式适用于消息编辑和文件说明。
附件与图片¶
当前限制:
- DM: 图片和文件附件可通过 Teams 机器人文件 API 工作。
- 频道/群组: 附件存储在 M365 存储(SharePoint/OneDrive)中。webhook 负载仅包含 HTML 存根,而不是实际文件字节。下载频道附件需要 Graph API 权限。
- 对于显式的文件优先发送,请使用
action=upload-file并配合media/filePath/path;可选的message会成为伴随文本/注释,filename(或title)会覆盖上传名称。
没有 Graph 权限时,包含图片的频道消息会以纯文本形式到达(图片内容对机器人不可访问)。
默认情况下,OpenClaw 仅从 Microsoft/Teams 主机名下载媒体。使用 channels.msteams.mediaAllowHosts 覆盖(使用 ["*"] 允许任意主机)。
授权头仅附加到 channels.msteams.mediaAuthAllowHosts 中的主机(默认为 Graph + Bot Framework 主机)。请保持此列表严格(避免多租户后缀)。
在群聊中发送文件¶
机器人可以使用内置的 FileConsentCard 流程在私聊中发送文件。在群聊/频道中发送文件需要额外设置:
| 上下文 | 文件发送方式 | 所需设置 |
|---|---|---|
| 私聊 | FileConsentCard → 用户接受 → 机器人上传 | 开箱即用 |
| 群聊/频道 | 上传到 SharePoint → 原生文件卡片 | 需要 sharePointSiteId + Graph 权限 |
| 图片(任意上下文) | Base64 编码内联 | 开箱即用 |
为什么群聊需要 SharePoint¶
机器人使用应用程序身份,而 Microsoft Graph 的 /me 资源需要已登录用户。要在群聊/频道中发送文件,机器人会上传到 SharePoint 站点并创建共享链接。
设置¶
- 在 Entra ID(Azure AD)→ 应用注册中添加 Graph API 权限:
Sites.ReadWrite.All(应用程序)- 上传文件到 SharePoint。ChatMember.Read.All(应用程序)- 用于群聊文件发送的最小权限租户范围权限。Chat.Read.All也可用,并且在启用群聊历史记录时已覆盖此权限。作为按聊天替代方案,使用ChatMember.Read.Chat资源特定同意权限。- 为租户授予管理员同意。
- 获取你的 SharePoint 站点 ID:
# Via Graph Explorer or curl with a valid token:
curl -H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}"
# Example: for a site 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"
- 配置 OpenClaw:
{
channels: {
msteams: {
// ... other config ...
sharePointSiteId: "contoso.sharepoint.com,guid1,guid2",
},
},
}
共享行为¶
| 上下文和权限 | 共享行为 |
|---|---|
频道 + Sites.ReadWrite.All |
组织范围共享链接(组织内任何人可访问) |
群聊 + Sites.ReadWrite.All + 受支持的聊天成员读取授权 |
按用户共享链接(仅聊天成员可访问) |
| 群聊没有受支持的聊天成员读取授权 | 发送失败(安全失败) |
按用户共享更安全,因为只有聊天参与者可以访问文件。OpenClaw 要求群聊成功查找成员;超时、传输失败、空结果和 Graph API 拒绝会导致发送失败,而不是将访问范围扩大到整个组织。
回退行为¶
| 场景 | 结果 |
|---|---|
| 群聊 + 文件 + 已配置 SharePoint 和成员权限 | 上传到 SharePoint,发送原生文件卡片 |
| 群聊 + 文件 + 缺少 SharePoint 或成员权限 | 以可操作的配置错误失败 |
频道 + 文件 + 已配置 sharePointSiteId |
上传到 SharePoint,发送原生文件卡片 |
| 个人聊天 + 文件 | FileConsentCard 流程(无需 SharePoint 也可用) |
| 任意上下文 + 图片 | Base64 编码内联(无需 SharePoint 也可用) |
文件存储位置¶
上传的文件存储在已配置 SharePoint 站点的默认文档库中的 /OpenClawShared/ 文件夹中。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw