---
sidebar_position: 5
title: "Microsoft Teams"
description: "将 Hermes Agent 设置为 Microsoft Teams 机器人"
---

# Microsoft Teams 设置

将 Hermes Agent 作为机器人接入 Microsoft Teams。与 Slack 的 Socket Mode 不同，Teams 通过调用**公开 HTTPS webhook**（钩子）来投递消息，因此你的实例需要一个可公开访问的端点——本地开发时使用开发隧道，生产环境使用真实域名。

如果你需要的是来自 Microsoft Graph 事件的会议摘要，而非普通的机器人对话，请使用专用设置页面：[Teams 会议](/user-guide/messaging/teams-meetings)。

## 机器人的响应方式

| 场景 | 行为 |
|------|------|
| **个人聊天（私信）** | 机器人响应每一条消息，无需 @提及。 |
| **群聊** | 机器人仅在被 @提及时响应。 |
| **频道** | 机器人仅在被 @提及时响应。 |

Teams 将 @提及作为普通消息投递，其中包含 `<at>BotName</at>` 标签，Hermes 在处理前会自动去除这些标签。

---

## 第一步：安装 Teams CLI

`@microsoft/teams.cli` 可自动完成机器人注册，无需进入 Azure 门户。

```bash
npm install -g @microsoft/teams.cli@preview
teams login
```

验证登录状态并查找你自己的 AAD 对象 ID（`TEAMS_ALLOWED_USERS` 需要用到）：

```bash
teams status --verbose
```

---

## 第二步：暴露 Webhook 端口

Teams 无法向 `localhost` 投递消息。本地开发时，使用任意隧道工具获取一个公开的 HTTPS URL。默认端口为 `3978`，如需更改可通过 `TEAMS_PORT` 设置。

```bash
# devtunnel（Microsoft 官方）
devtunnel create hermes-bot --allow-anonymous
devtunnel port create hermes-bot -p 3978 --protocol https  # 如已修改 TEAMS_PORT，请替换 3978
devtunnel host hermes-bot

# ngrok
ngrok http 3978  # 如已修改 TEAMS_PORT，请替换 3978

# cloudflared
cloudflared tunnel --url http://localhost:3978  # 如已修改 TEAMS_PORT，请替换 3978
```

从输出中复制 `https://` URL——下一步会用到。开发期间保持隧道运行。

生产环境请将机器人端点指向服务器的公开域名（参见[生产部署](#production-deployment)）。

---

## 第三步：创建机器人

```bash
teams app create \
  --name "Hermes" \
  --endpoint "https://<your-tunnel-url>/api/messages"
```

CLI 会输出你的 `CLIENT_ID`、`CLIENT_SECRET` 和 `TENANT_ID`，以及第六步所需的安装链接。请保存客户端密钥——它不会再次显示。

---

## 第四步：配置环境变量

添加到 `~/.hermes/.env`：

```bash
# 必填
TEAMS_CLIENT_ID=<your-client-id>
TEAMS_CLIENT_SECRET=<your-client-secret>
TEAMS_TENANT_ID=<your-tenant-id>

# 限制特定用户访问（推荐）
# 使用 `teams status --verbose` 获取 AAD 对象 ID
TEAMS_ALLOWED_USERS=<your-aad-object-id>
```

---

## 第五步：启动 Gateway

```bash
HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d gateway
```

此命令启动 gateway。默认 webhook 端口为 `3978`（可通过 `TEAMS_PORT` 覆盖）。检查运行状态：

```bash
curl http://localhost:3978/health   # 应返回：ok
docker logs -f hermes
```

查找以下日志：
```
[teams] Webhook server listening on 0.0.0.0:3978/api/messages
```

---

## 第六步：在 Teams 中安装应用

```bash
teams app get <teamsAppId> --install-link
```

在浏览器中打开输出的链接——它会直接在 Teams 客户端中打开。安装完成后，向机器人发送一条私信，即可开始使用。

---

## 配置参考

### 环境变量

| 变量 | 说明 |
|------|------|
| `TEAMS_CLIENT_ID` | Azure AD 应用（客户端）ID |
| `TEAMS_CLIENT_SECRET` | Azure AD 客户端密钥 |
| `TEAMS_TENANT_ID` | Azure AD 租户 ID |
| `TEAMS_ALLOWED_USERS` | 允许使用机器人的 AAD 对象 ID，逗号分隔 |
| `TEAMS_ALLOW_ALL_USERS` | 设为 `true` 可跳过白名单，允许所有人使用 |
| `TEAMS_HOME_CHANNEL` | 用于 cron/主动消息投递的会话 ID |
| `TEAMS_HOME_CHANNEL_NAME` | 主频道的显示名称 |
| `TEAMS_PORT` | Webhook 端口（默认：`3978`） |

### config.yaml

也可通过 `~/.hermes/config.yaml` 进行配置：

```yaml
platforms:
  teams:
    enabled: true
    extra:
      client_id: "your-client-id"
      client_secret: "your-secret"
      tenant_id: "your-tenant-id"
      port: 3978
```

---

## 功能特性

### 交互式审批卡片

当 Agent 需要执行可能存在风险的命令时，它会发送一张带有四个按钮的 Adaptive Card，而不是要求你输入 `/approve`：

- **Allow Once**——仅批准此次特定命令
- **Allow Session**——在本次会话期间批准此模式
- **Always Allow**——永久批准此模式
- **Deny**——拒绝该命令

点击按钮即可内联完成审批，卡片会被替换为决策结果。

### 会议摘要投递（Teams 会议 Pipeline）

当 [Teams 会议 pipeline 插件](/user-guide/messaging/msgraph-webhook)启用后，此适配器同时负责会议摘要的出站投递——一个 Teams 集成面，而非两个。会议转录摘要生成后，写入器会将摘要发布到你指定的 Teams 目标。

Pipeline 摘要投递在 `teams` 平台条目下与机器人配置并列配置：

```yaml
platforms:
  teams:
    enabled: true
    extra:
      # 现有机器人配置（client_id、client_secret、tenant_id、port）...

      # 会议摘要投递（仅在 teams_pipeline 插件启用时生效）
      delivery_mode: "graph"       # 或 "incoming_webhook"
      # 对于 delivery_mode: graph — 选择其中一项：
      chat_id: "19:meeting_..."    # 发布到 Teams 聊天
      # team_id: "..."             # 或发布到频道
      # channel_id: "..."
      # access_token: "..."        # 可选；回退到 MSGRAPH_* 应用凭据
      # 对于 delivery_mode: incoming_webhook：
      # incoming_webhook_url: "https://outlook.office.com/webhook/..."
```

| 模式 | 适用场景 | 权衡 |
|------|----------|------|
| `incoming_webhook` | 使用 Teams 生成的静态 URL，简单地将摘要发布到某个频道。 | 不支持回复线程和表情回应，显示为 webhook 配置的身份。 |
| `graph` | 通过 Microsoft Graph 以机器人身份发布带线程的频道帖子或 1:1/群聊消息。 | 需要完成 [Graph 应用注册](/guides/microsoft-graph-app-registration)，并具备 `ChannelMessage.Send`（频道）或 `Chat.ReadWrite.All`（聊天）应用权限。 |

如果 `teams_pipeline` 插件**未启用**，这些设置不会生效——它们仅在 pipeline 运行时绑定到 Graph webhook 入口时才会激活。

---

## 生产部署

对于永久服务器，跳过 devtunnel，使用服务器的公开 HTTPS 端点注册机器人：

```bash
teams app create \
  --name "Hermes" \
  --endpoint "https://your-domain.com/api/messages"
```

如果机器人已创建，只需更新端点：

```bash
teams app update --id <teamsAppId> --endpoint "https://your-domain.com/api/messages"
```

确保你配置的端口（`TEAMS_PORT`，默认 `3978`）可从互联网访问，且 TLS 证书有效——Teams 会拒绝自签名证书。

---

## 故障排查

| 问题 | 解决方案 |
|------|----------|
| `health` 端点正常但机器人不响应 | 检查隧道是否仍在运行，以及机器人的消息端点是否与隧道 URL 匹配 |
| 日志中出现 `KeyError: 'teams'` | 重启容器——此问题已在当前版本中修复 |
| 机器人响应时出现认证错误 | 验证 `TEAMS_CLIENT_ID`、`TEAMS_CLIENT_SECRET` 和 `TEAMS_TENANT_ID` 是否均已正确设置 |
| `No inference provider configured` | 检查 `~/.hermes/.env` 中是否设置了 `ANTHROPIC_API_KEY`（或其他提供商密钥） |
| 机器人收到消息但忽略它们 | 你的 AAD 对象 ID 可能不在 `TEAMS_ALLOWED_USERS` 中。运行 `teams status --verbose` 查找 |
| 隧道 URL 在重启后变更 | 使用命名隧道（`devtunnel create hermes-bot`）时，devtunnel URL 是持久的。ngrok 和 cloudflared 每次运行都会生成新 URL（除非你有付费计划）——URL 变更时请用 `teams app update` 更新机器人端点 |
| Teams 显示"此机器人未响应" | Webhook 返回了错误。检查 `docker logs hermes` 中的错误堆栈 |
| 日志中出现 `[teams] Failed to connect` | SDK 认证失败。仔细检查凭据，并确认租户 ID 与 `teams login` 时使用的账户匹配 |

---

## 安全性

:::warning
**务必设置 `TEAMS_ALLOWED_USERS`**，填入授权用户的 AAD 对象 ID。否则，任何能找到或安装你的机器人的人都可以与其交互。

将 `TEAMS_CLIENT_SECRET` 视同密码对待——定期通过 Azure 门户或 Teams CLI 进行轮换。
:::

- 将凭据存储在权限为 `600` 的 `~/.hermes/.env` 中（`chmod 600 ~/.hermes/.env`）
- 机器人仅接受 `TEAMS_ALLOWED_USERS` 中用户的消息；未授权的消息会被静默丢弃
- 你的公开端点（`/api/messages`）由 Teams Bot Framework 进行认证——不含有效 JWT 的请求会被拒绝

## 相关文档

- [Teams 会议](/user-guide/messaging/teams-meetings)
- [运营 Teams 会议 Pipeline](/guides/operate-teams-meeting-pipeline)