diff --git a/docs/zh-CN/channels/feishu.md b/docs/zh-CN/channels/feishu.md index 5a3f5c246..7d1d14fca 100644 --- a/docs/zh-CN/channels/feishu.md +++ b/docs/zh-CN/channels/feishu.md @@ -5,10 +5,10 @@ read_when: summary: Feishu 机器人概览、功能和配置 title: Feishu x-i18n: - generated_at: "2026-04-23T06:40:05Z" + generated_at: "2026-04-23T20:12:19Z" model: gpt-5.4 provider: openai - source_hash: 11bf136cecb26dc939c5e78e020c0e6aa3312d9f143af0cab7568743c728cf13 + source_hash: 76565e89007211ba194ea9ab47ba926f5c9b5d12782d046bc94995a075671f02 source_path: channels/feishu.md workflow: 15 --- @@ -23,7 +23,7 @@ Feishu/Lark 是一个一体化协作平台,团队可以在其中聊天、共 ## 快速开始 -> **需要 OpenClaw 2026.4.10 或更高版本。** 运行 `openclaw --version` 进行检查。使用 `openclaw update` 升级。 +> **需要 OpenClaw 2026.4.23 或更高版本。** 运行 `openclaw --version` 进行检查。使用 `openclaw update` 升级。 @@ -48,12 +48,12 @@ Feishu/Lark 是一个一体化协作平台,团队可以在其中聊天、共 配置 `dmPolicy` 以控制谁可以向机器人发送私信: -- `"pairing"` — 未知用户会收到一个配对码;通过 CLI 批准 +- `"pairing"` — 未知用户会收到配对码;通过 CLI 批准 - `"allowlist"` — 只有列在 `allowFrom` 中的用户可以聊天(默认:仅机器人所有者) - `"open"` — 允许所有用户 - `"disabled"` — 禁用所有私信 -**批准配对请求:** +**批准一个配对请求:** ```bash openclaw pairing list feishu @@ -64,9 +64,9 @@ openclaw pairing approve feishu **群组策略**(`channels.feishu.groupPolicy`): -| Value | 行为 | -| ------------- | ------------------------------------------ | -| `"open"` | 响应群组中的所有消息 | +| Value | Behavior | +| ------------- | -------- | +| `"open"` | 在群组中响应所有消息 | | `"allowlist"` | 仅响应 `groupAllowFrom` 中的群组 | | `"disabled"` | 禁用所有群组消息 | @@ -114,7 +114,7 @@ openclaw pairing approve feishu channels: { feishu: { groupPolicy: "allowlist", - // 群组 ID 形如:oc_xxx + // 群组 ID 类似:oc_xxx groupAllowFrom: ["oc_xxx", "oc_yyy"], }, }, @@ -131,7 +131,7 @@ openclaw pairing approve feishu groupAllowFrom: ["oc_xxx"], groups: { oc_xxx: { - // 用户 open_id 形如:ou_xxx + // 用户 open_id 类似:ou_xxx allowFrom: ["ou_user1", "ou_user2"], }, }, @@ -148,13 +148,13 @@ openclaw pairing approve feishu ### 群组 ID(`chat_id`,格式:`oc_xxx`) -在 Feishu/Lark 中打开群组,点击右上角的菜单图标,然后进入 **设置**。群组 ID(`chat_id`)会显示在设置页面中。 +在 Feishu/Lark 中打开群组,点击右上角的菜单图标,然后进入**设置**。群组 ID(`chat_id`)会显示在设置页面中。 ![Get Group ID](/images/feishu-get-group-id.png) ### 用户 ID(`open_id`,格式:`ou_xxx`) -启动 Gateway 网关,向机器人发送一条私信,然后查看日志: +启动 Gateway 网关,向机器人发送一条私信,然后检查日志: ```bash openclaw logs --follow @@ -170,8 +170,8 @@ openclaw pairing list feishu ## 常用命令 -| Command | 说明 | -| --------- | --------------------------- | +| Command | Description | +| --------- | ----------- | | `/status` | 显示机器人状态 | | `/reset` | 重置当前会话 | | `/model` | 显示或切换 AI 模型 | @@ -184,16 +184,16 @@ openclaw pairing list feishu ### 机器人在群聊中没有响应 -1. 确保机器人已被添加到群组中 +1. 确保机器人已加入群组 2. 确保你已 @mention 机器人(默认要求) 3. 验证 `groupPolicy` 不是 `"disabled"` 4. 检查日志:`openclaw logs --follow` ### 机器人没有收到消息 -1. 确保机器人已在 Feishu Open Platform / Lark Developer 中发布并通过审核 +1. 确保机器人已在 Feishu Open Platform / Lark Developer 中发布并获得批准 2. 确保事件订阅包含 `im.message.receive_v1` -3. 确保已选择 **persistent connection**(WebSocket) +3. 确保已选择**持久连接**(WebSocket) 4. 确保已授予所有必需的权限范围 5. 确保 Gateway 网关正在运行:`openclaw gateway status` 6. 检查日志:`openclaw logs --follow` @@ -201,7 +201,7 @@ openclaw pairing list feishu ### App Secret 泄露 1. 在 Feishu Open Platform / Lark Developer 中重置 App Secret -2. 更新你配置中的值 +2. 更新你的配置中的值 3. 重启 Gateway 网关:`openclaw gateway restart` --- @@ -249,20 +249,20 @@ Feishu/Lark 支持通过交互式卡片进行流式回复。启用后,机器 channels: { feishu: { streaming: true, // 启用流式卡片输出(默认:true) - blockStreaming: true, // 启用块级流式传输(默认:true) + blockStreaming: true, // 启用分块流式传输(默认:true) }, }, } ``` -将 `streaming: false` 设为关闭后,会以一条完整消息发送完整回复。 +将 `streaming: false` 设为关闭后,会在一条消息中发送完整回复。 ### 配额优化 使用两个可选标志来减少 Feishu/Lark API 调用次数: -- `typingIndicator`(默认 `true`):设为 `false` 以跳过正在输入反应调用 -- `resolveSenderNames`(默认 `true`):设为 `false` 以跳过发送者资料查询 +- `typingIndicator`(默认 `true`):设为 `false` 可跳过“正在输入”反应调用 +- `resolveSenderNames`(默认 `true`):设为 `false` 可跳过发送者资料查询 ```json5 { @@ -277,7 +277,7 @@ Feishu/Lark 支持通过交互式卡片进行流式回复。启用后,机器 ### ACP 会话 -Feishu/Lark 支持用于私信和群组话题消息的 ACP。Feishu/Lark ACP 由文本命令驱动——没有原生斜杠命令菜单,因此请直接在会话中使用 `/acp ...` 消息。 +Feishu/Lark 支持用于私信和群组话题消息的 ACP。Feishu/Lark ACP 由文本命令驱动——没有原生斜杠命令菜单,因此请直接在对话中使用 `/acp ...` 消息。 #### 持久 ACP 绑定 @@ -335,7 +335,7 @@ Feishu/Lark 支持用于私信和群组话题消息的 ACP。Feishu/Lark ACP 由 ### 多智能体路由 -使用 `bindings` 将 Feishu/Lark 私信或群组路由到不同的智能体。 +使用 `bindings` 将 Feishu/Lark 私信或群组路由到不同智能体。 ```json5 { @@ -371,7 +371,7 @@ Feishu/Lark 支持用于私信和群组话题消息的 ACP。Feishu/Lark ACP 由 - `match.peer.kind`:`"direct"`(私信)或 `"group"`(群聊) - `match.peer.id`:用户 Open ID(`ou_xxx`)或群组 ID(`oc_xxx`) -有关查找提示,请参见[获取群组/用户 ID](#get-groupuser-ids)。 +查找技巧请参见[获取群组/用户 ID](#get-groupuser-ids)。 --- @@ -379,33 +379,33 @@ Feishu/Lark 支持用于私信和群组话题消息的 ACP。Feishu/Lark ACP 由 完整配置:[Gateway 网关配置](/zh-CN/gateway/configuration) -| Setting | 说明 | 默认值 | +| Setting | Description | Default | | ------------------------------------------------- | ------------------------------------------ | ---------------- | -| `channels.feishu.enabled` | 启用/禁用该渠道 | `true` | -| `channels.feishu.domain` | API 域(`feishu` 或 `lark`) | `feishu` | -| `channels.feishu.connectionMode` | 事件传输方式(`websocket` 或 `webhook`) | `websocket` | -| `channels.feishu.defaultAccount` | 用于出站路由的默认账户 | `default` | -| `channels.feishu.verificationToken` | webhook 模式必需 | — | -| `channels.feishu.encryptKey` | webhook 模式必需 | — | -| `channels.feishu.webhookPath` | Webhook 路由路径 | `/feishu/events` | -| `channels.feishu.webhookHost` | Webhook 绑定主机 | `127.0.0.1` | -| `channels.feishu.webhookPort` | Webhook 绑定端口 | `3000` | -| `channels.feishu.accounts..appId` | App ID | — | -| `channels.feishu.accounts..appSecret` | App Secret | — | -| `channels.feishu.accounts..domain` | 按账户覆盖域设置 | `feishu` | -| `channels.feishu.dmPolicy` | 私信策略 | `allowlist` | -| `channels.feishu.allowFrom` | 私信允许列表(`open_id` 列表) | [BotOwnerId] | -| `channels.feishu.groupPolicy` | 群组策略 | `allowlist` | -| `channels.feishu.groupAllowFrom` | 群组允许列表 | — | -| `channels.feishu.requireMention` | 在群组中要求 @mention | `true` | -| `channels.feishu.groups..requireMention` | 按群组覆盖 @mention 要求 | inherited | -| `channels.feishu.groups..enabled` | 启用/禁用特定群组 | `true` | -| `channels.feishu.textChunkLimit` | 消息分块大小 | `2000` | -| `channels.feishu.mediaMaxMb` | 媒体大小限制 | `30` | -| `channels.feishu.streaming` | 流式卡片输出 | `true` | -| `channels.feishu.blockStreaming` | 分块流式传输 | `true` | -| `channels.feishu.typingIndicator` | 发送正在输入反应 | `true` | -| `channels.feishu.resolveSenderNames` | 解析发送者显示名称 | `true` | +| `channels.feishu.enabled` | 启用/禁用该渠道 | `true` | +| `channels.feishu.domain` | API 域(`feishu` 或 `lark`) | `feishu` | +| `channels.feishu.connectionMode` | 事件传输方式(`websocket` 或 `webhook`) | `websocket` | +| `channels.feishu.defaultAccount` | 用于出站路由的默认账户 | `default` | +| `channels.feishu.verificationToken` | webhook 模式必需 | — | +| `channels.feishu.encryptKey` | webhook 模式必需 | — | +| `channels.feishu.webhookPath` | webhook 路由路径 | `/feishu/events` | +| `channels.feishu.webhookHost` | webhook 绑定主机 | `127.0.0.1` | +| `channels.feishu.webhookPort` | webhook 绑定端口 | `3000` | +| `channels.feishu.accounts..appId` | App ID | — | +| `channels.feishu.accounts..appSecret` | App Secret | — | +| `channels.feishu.accounts..domain` | 按账户覆盖域设置 | `feishu` | +| `channels.feishu.dmPolicy` | 私信策略 | `allowlist` | +| `channels.feishu.allowFrom` | 私信允许列表(`open_id` 列表) | [BotOwnerId] | +| `channels.feishu.groupPolicy` | 群组策略 | `allowlist` | +| `channels.feishu.groupAllowFrom` | 群组允许列表 | — | +| `channels.feishu.requireMention` | 在群组中要求 @mention | `true` | +| `channels.feishu.groups..requireMention` | 按群组覆盖 @mention 要求 | 继承 | +| `channels.feishu.groups..enabled` | 启用/禁用特定群组 | `true` | +| `channels.feishu.textChunkLimit` | 消息分块大小 | `2000` | +| `channels.feishu.mediaMaxMb` | 媒体大小限制 | `30` | +| `channels.feishu.streaming` | 流式卡片输出 | `true` | +| `channels.feishu.blockStreaming` | 分块流式传输 | `true` | +| `channels.feishu.typingIndicator` | 发送“正在输入”反应 | `true` | +| `channels.feishu.resolveSenderNames` | 解析发送者显示名称 | `true` | --- @@ -419,7 +419,7 @@ Feishu/Lark 支持用于私信和群组话题消息的 ACP。Feishu/Lark ACP 由 - ✅ 文件 - ✅ 音频 - ✅ 视频/媒体 -- ✅ 贴纸 +- ✅ 表情贴纸 ### 发送 @@ -429,11 +429,11 @@ Feishu/Lark 支持用于私信和群组话题消息的 ACP。Feishu/Lark ACP 由 - ✅ 音频 - ✅ 视频/媒体 - ✅ 交互式卡片(包括流式更新) -- ⚠️ 富文本(post 样式格式;不支持完整的 Feishu/Lark 编辑能力) +- ⚠️ 富文本(post 风格格式;不支持完整的 Feishu/Lark 编辑能力) ### 话题和回复 -- ✅ 内联回复 +- ✅ 行内回复 - ✅ 话题回复 - ✅ 回复话题消息时,媒体回复会保持话题感知 @@ -442,7 +442,7 @@ Feishu/Lark 支持用于私信和群组话题消息的 ACP。Feishu/Lark ACP 由 ## 相关内容 - [渠道概览](/zh-CN/channels) — 所有受支持的渠道 -- [配对](/zh-CN/channels/pairing) — 私信身份验证与配对流程 -- [群组](/zh-CN/channels/groups) — 群聊行为与提及门控 +- [配对](/zh-CN/channels/pairing) — 私信身份验证和配对流程 +- [群组](/zh-CN/channels/groups) — 群聊行为和提及门控 - [渠道路由](/zh-CN/channels/channel-routing) — 消息的会话路由 -- [安全](/zh-CN/gateway/security) — 访问模型与加固措施 +- [安全](/zh-CN/gateway/security) — 访问模型与加固 diff --git a/docs/zh-CN/channels/googlechat.md b/docs/zh-CN/channels/googlechat.md index 6b61dc47e..ba1b85da8 100644 --- a/docs/zh-CN/channels/googlechat.md +++ b/docs/zh-CN/channels/googlechat.md @@ -1,37 +1,37 @@ --- read_when: - - 开发 Google Chat 渠道功能 -summary: Google Chat 应用支持状态、能力和配置 + - 正在开发 Google Chat 渠道功能 +summary: Google Chat 应用支持状态、功能和配置 title: Google Chat x-i18n: - generated_at: "2026-04-05T08:14:34Z" + generated_at: "2026-04-23T20:12:13Z" model: gpt-5.4 provider: openai - source_hash: 570894ed798dd0b9ba42806b050927216379a1228fcd2f96de565bc8a4ac7c2c + source_hash: ff546be5a3e882445f78ee739ca9f55bf4337a3e533ea1e6be0cd588bf40fb19 source_path: channels/googlechat.md workflow: 15 --- # Google Chat(Chat API) -状态:通过 Google Chat API webhook 支持私信和 spaces(仅 HTTP)。 +状态:已可用于通过 Google Chat API webhook 支持私信和空间(仅限 HTTP)。 -## 快速设置(新手) +## 快速开始(新手) 1. 创建一个 Google Cloud 项目并启用 **Google Chat API**。 - 前往:[Google Chat API Credentials](https://console.cloud.google.com/apis/api/chat.googleapis.com/credentials) - - 如果 API 尚未启用,请启用它。 + - 如果 API 尚未启用,请先启用它。 2. 创建一个 **Service Account**: - 点击 **Create Credentials** > **Service Account**。 - - 随意命名(例如 `openclaw-chat`)。 + - 随意命名(例如:`openclaw-chat`)。 - 权限留空(点击 **Continue**)。 - 可访问的主体留空(点击 **Done**)。 3. 创建并下载 **JSON Key**: - - 在服务账号列表中,点击你刚创建的那个账号。 + - 在 Service Account 列表中,点击你刚创建的那个账户。 - 前往 **Keys** 标签页。 - 点击 **Add Key** > **Create new key**。 - - 选择 **JSON** 并点击 **Create**。 -4. 将下载的 JSON 文件存储在你的 gateway 主机上(例如 `~/.openclaw/googlechat-service-account.json`)。 + - 选择 **JSON**,然后点击 **Create**。 +4. 将下载的 JSON 文件存放到你的 Gateway 网关主机上(例如:`~/.openclaw/googlechat-service-account.json`)。 5. 在 [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat) 中创建一个 Google Chat 应用: - 填写 **Application info**: - **App name**:(例如 `OpenClaw`) @@ -40,49 +40,49 @@ x-i18n: - 启用 **Interactive features**。 - 在 **Functionality** 下,勾选 **Join spaces and group conversations**。 - 在 **Connection settings** 下,选择 **HTTP endpoint URL**。 - - 在 **Triggers** 下,选择 **Use a common HTTP endpoint URL for all triggers**,并将其设置为你的 gateway 公网 URL 后跟 `/googlechat`。 - - _提示:运行 `openclaw status` 可找到你的 gateway 公网 URL。_ - - 在 **Visibility** 下,勾选 **Make this Chat app available to specific people and groups in <Your Domain>**。 - - 在文本框中输入你的邮箱地址(例如 `user@example.com`)。 + - 在 **Triggers** 下,选择 **Use a common HTTP endpoint URL for all triggers**,并将其设置为你的 Gateway 网关公网 URL 后接 `/googlechat`。 + - _提示:运行 `openclaw status` 以找到你的 Gateway 网关公网 URL。_ + - 在 **Visibility** 下,勾选 **Make this Chat app available to specific people and groups in ``**。 + - 在文本框中输入你的电子邮件地址(例如 `user@example.com`)。 - 点击底部的 **Save**。 6. **启用应用状态**: - 保存后,**刷新页面**。 - - 找到 **App status** 部分(通常在保存后页面顶部或底部附近)。 + - 找到 **App status** 部分(通常在保存后出现在页面顶部或底部附近)。 - 将状态改为 **Live - available to users**。 - 再次点击 **Save**。 -7. 使用服务账号路径和 webhook audience 配置 OpenClaw: +7. 使用 Service Account 路径和 webhook audience 配置 OpenClaw: - 环境变量:`GOOGLE_CHAT_SERVICE_ACCOUNT_FILE=/path/to/service-account.json` - 或配置:`channels.googlechat.serviceAccountFile: "/path/to/service-account.json"`。 8. 设置 webhook audience 类型和值(需与你的 Chat 应用配置匹配)。 -9. 启动 gateway。Google Chat 将向你的 webhook 路径发送 POST 请求。 +9. 启动 Gateway 网关。Google Chat 将向你的 webhook 路径发送 POST 请求。 ## 添加到 Google Chat -在 gateway 运行且你的邮箱已加入可见性列表后: +一旦 Gateway 网关正在运行,且你的邮箱已添加到可见性列表中: 1. 前往 [Google Chat](https://chat.google.com/)。 2. 点击 **Direct Messages** 旁边的 **+**(加号)图标。 3. 在搜索栏中(通常用于添加联系人),输入你在 Google Cloud Console 中配置的 **App name**。 - **注意**:由于这是私有应用,机器人 _不会_ 出现在 “Marketplace” 浏览列表中。你必须按名称搜索它。 4. 从结果中选择你的机器人。 -5. 点击 **Add** 或 **Chat** 开始一对一对话。 +5. 点击 **Add** 或 **Chat** 开始一对一会话。 6. 发送 “Hello” 来触发助手! -## 公网 URL(仅 webhook) +## 公网 URL(仅限 Webhook) -Google Chat webhook 需要一个公网 HTTPS 端点。出于安全考虑,**只将 `/googlechat` 路径暴露到互联网**。请将 OpenClaw 仪表板和其他敏感端点保留在你的私有网络中。 +Google Chat webhook 需要一个公网 HTTPS 端点。出于安全考虑,**只应将 `/googlechat` 路径暴露到互联网**。请将 OpenClaw 仪表板和其他敏感端点保留在你的私有网络中。 -### 方案 A:Tailscale Funnel(推荐) +### 选项 A:Tailscale Funnel(推荐) -对私有仪表板使用 Tailscale Serve,对公开 webhook 路径使用 Funnel。这样可以保持 `/` 为私有,同时只暴露 `/googlechat`。 +将 Tailscale Serve 用于私有仪表板,将 Funnel 用于公网 webhook 路径。这样可以保持 `/` 为私有,同时只暴露 `/googlechat`。 -1. **检查你的 gateway 绑定到了哪个地址:** +1. **检查你的 Gateway 网关绑定到了哪个地址:** ```bash ss -tlnp | grep 18789 ``` - 记下 IP 地址(例如 `127.0.0.1`、`0.0.0.0`,或你的 Tailscale IP,如 `100.x.x.x`)。 + 记下 IP 地址(例如 `127.0.0.1`、`0.0.0.0`,或你的 Tailscale IP,例如 `100.x.x.x`)。 2. **仅向 tailnet 暴露仪表板(端口 8443):** @@ -94,7 +94,7 @@ Google Chat webhook 需要一个公网 HTTPS 端点。出于安全考虑,**只 tailscale serve --bg --https 8443 http://100.106.161.80:18789 ``` -3. **仅公开暴露 webhook 路径:** +3. **仅将 webhook 路径公开暴露:** ```bash # 如果绑定到 localhost(127.0.0.1 或 0.0.0.0): @@ -105,7 +105,7 @@ Google Chat webhook 需要一个公网 HTTPS 端点。出于安全考虑,**只 ``` 4. **为该节点授权 Funnel 访问:** - 如果出现提示,请访问输出中显示的授权 URL,以在你的 tailnet 策略中为该节点启用 Funnel。 + 如果系统提示,请访问输出中显示的授权 URL,以便在你的 tailnet 策略中为该节点启用 Funnel。 5. **验证配置:** @@ -114,19 +114,19 @@ Google Chat webhook 需要一个公网 HTTPS 端点。出于安全考虑,**只 tailscale funnel status ``` -你的公网 webhook URL 将是: +你的公网 webhook URL 将为: `https://..ts.net/googlechat` -你的私有仪表板将保持仅 tailnet 可访问: +你的私有仪表板将继续仅限 tailnet: `https://..ts.net:8443/` -在 Google Chat 应用配置中,请使用公网 URL(不带 `:8443`)。 +在 Google Chat 应用配置中使用公网 URL(不带 `:8443`)。 -> 注意:此配置在重启后仍会保留。若之后要移除,请运行 `tailscale funnel reset` 和 `tailscale serve reset`。 +> 注意:此配置会在重启后保留。若要稍后移除,请运行 `tailscale funnel reset` 和 `tailscale serve reset`。 -### 方案 B:反向代理(Caddy) +### 选项 B:反向代理(Caddy) -如果你使用类似 Caddy 的反向代理,只代理特定路径: +如果你使用像 Caddy 这样的反向代理,请只代理特定路径: ```caddy your-domain.com { @@ -134,38 +134,38 @@ your-domain.com { } ``` -使用此配置后,对 `your-domain.com/` 的任何请求都会被忽略或返回 404,而 `your-domain.com/googlechat` 会被安全地路由到 OpenClaw。 +使用此配置时,任何对 `your-domain.com/` 的请求都会被忽略或返回 404,而 `your-domain.com/googlechat` 会被安全地路由到 OpenClaw。 -### 方案 C:Cloudflare Tunnel +### 选项 C:Cloudflare Tunnel -将你的 tunnel ingress 规则配置为仅路由 webhook 路径: +将你的 Tunnel ingress 规则配置为只路由 webhook 路径: -- **路径**:`/googlechat` -> `http://localhost:18789/googlechat` -- **默认规则**:HTTP 404(Not Found) +- **Path**:`/googlechat` -> `http://localhost:18789/googlechat` +- **Default Rule**:HTTP 404(未找到) ## 工作原理 -1. Google Chat 会向 gateway 发送 webhook POST 请求。每个请求都包含 `Authorization: Bearer ` 请求头。 +1. Google Chat 向 Gateway 网关发送 webhook POST 请求。每个请求都包含一个 `Authorization: Bearer ` 请求头。 - 当该请求头存在时,OpenClaw 会在读取/解析完整 webhook 请求体之前先验证 bearer 身份验证。 - - 对于在请求体中携带 `authorizationEventObject.systemIdToken` 的 Google Workspace Add-on 请求,也支持通过更严格的预身份验证请求体预算来处理。 -2. OpenClaw 会根据配置的 `audienceType` 和 `audience` 验证 token: + - 对于在请求体中携带 `authorizationEventObject.systemIdToken` 的 Google Workspace Add-on 请求,系统会通过更严格的预认证请求体预算来支持。 +2. OpenClaw 会根据已配置的 `audienceType` 和 `audience` 验证该令牌: - `audienceType: "app-url"` → audience 是你的 HTTPS webhook URL。 - `audienceType: "project-number"` → audience 是 Cloud 项目编号。 -3. 消息按 space 路由: - - 私信使用会话键名 `agent::googlechat:direct:`。 - - spaces 使用会话键名 `agent::googlechat:group:`。 -4. 私信访问默认采用配对。未知发送方会收到一个配对码;使用以下命令批准: +3. 消息按空间路由: + - 私信使用会话键 `agent::googlechat:direct:`。 + - 空间使用会话键 `agent::googlechat:group:`。 +4. 私信访问默认采用配对。未知发送者会收到一个配对码;使用以下命令批准: - `openclaw pairing approve googlechat ` -5. 群组 space 默认要求 @ 提及。若提及检测需要应用的用户名称,请使用 `botUser`。 +5. 群组空间默认需要 @ 提及。若提及检测需要应用的用户名,可使用 `botUser`。 ## 目标 -对交付和 allowlist 使用以下标识符: +对投递和 allowlist 使用以下标识符: - 私信:`users/`(推荐)。 -- 原始邮箱 `name@example.com` 是可变的,且仅在 `channels.googlechat.dangerouslyAllowNameMatching: true` 时用于直接 allowlist 匹配。 +- 原始邮箱 `name@example.com` 是可变的,且仅在 `channels.googlechat.dangerouslyAllowNameMatching: true` 时用于私信 allowlist 匹配。 - 已弃用:`users/` 会被视为用户 ID,而不是邮箱 allowlist。 -- Spaces:`spaces/`。 +- 空间:`spaces/`。 ## 配置要点 @@ -175,11 +175,11 @@ your-domain.com { googlechat: { enabled: true, serviceAccountFile: "/path/to/service-account.json", - // 或 serviceAccountRef: { source: "file", provider: "filemain", id: "/channels/googlechat/serviceAccount" } + // or serviceAccountRef: { source: "file", provider: "filemain", id: "/channels/googlechat/serviceAccount" } audienceType: "app-url", audience: "https://gateway.example.com/googlechat", webhookPath: "/googlechat", - botUser: "users/1234567890", // 可选;有助于提及检测 + botUser: "users/1234567890", // optional; helps mention detection dm: { policy: "pairing", allowFrom: ["users/1234567890"], @@ -203,16 +203,16 @@ your-domain.com { 说明: -- 服务账号凭证也可以通过 `serviceAccount` 内联传入(JSON 字符串)。 -- 也支持 `serviceAccountRef`(env/file SecretRef),包括 `channels.googlechat.accounts..serviceAccountRef` 下的逐账号引用。 +- Service Account 凭证也可以通过 `serviceAccount`(JSON 字符串)内联传入。 +- 也支持 `serviceAccountRef`(env/file SecretRef),包括 `channels.googlechat.accounts..serviceAccountRef` 下的按账户配置引用。 - 如果未设置 `webhookPath`,默认 webhook 路径为 `/googlechat`。 -- `dangerouslyAllowNameMatching` 会重新启用基于可变邮箱主体的 allowlist 匹配(紧急兼容模式)。 -- 启用 `actions.reactions` 后,可通过 `reactions` 工具和 `channels action` 使用表情回应。 -- 消息操作会公开 `send` 用于发送文本,公开 `upload-file` 用于显式发送附件。`upload-file` 接受 `media` / `filePath` / `path`,并支持可选的 `message`、`filename` 和线程目标。 +- `dangerouslyAllowNameMatching` 会重新启用基于可变邮箱主体的 allowlist 匹配(破窗兼容模式)。 +- 当启用 `actions.reactions` 时,可通过 `reactions` 工具和 `channels action` 使用表情回应。 +- 消息操作会暴露用于文本发送的 `send`,以及用于显式发送附件的 `upload-file`。`upload-file` 接受 `media` / `filePath` / `path`,并支持可选的 `message`、`filename` 和线程定向参数。 - `typingIndicator` 支持 `none`、`message`(默认)和 `reaction`(reaction 需要用户 OAuth)。 -- 附件通过 Chat API 下载并存储到媒体管道中(大小受 `mediaMaxMb` 限制)。 +- 附件会通过 Chat API 下载,并存储到媒体管道中(大小受 `mediaMaxMb` 限制)。 -Secrets 参考详情:[Secrets Management](/gateway/secrets)。 +Secrets 引用详情:[Secrets Management](/zh-CN/gateway/secrets)。 ## 故障排除 @@ -224,15 +224,15 @@ Secrets 参考详情:[Secrets Management](/gateway/secrets)。 status code: 405, reason phrase: HTTP error response: HTTP/1.1 405 Method Not Allowed ``` -这表示 webhook 处理器未注册。常见原因有: +这意味着 webhook 处理器尚未注册。常见原因包括: -1. **渠道未配置**:你的配置中缺少 `channels.googlechat` 部分。请通过以下命令验证: +1. **渠道未配置**:你的配置中缺少 `channels.googlechat` 部分。请使用以下命令验证: ```bash openclaw config get channels.googlechat ``` - 如果返回 “Config path not found”,请添加配置(参见[配置要点](#config-highlights))。 + 如果返回 “Config path not found”,请添加配置(参见[配置要点](#配置要点))。 2. **插件未启用**:检查插件状态: @@ -242,36 +242,36 @@ status code: 405, reason phrase: HTTP error response: HTTP/1.1 405 Method Not Al 如果显示 “disabled”,请在配置中添加 `plugins.entries.googlechat.enabled: true`。 -3. **Gateway 网关未重启**:添加配置后,请重启 gateway: +3. **Gateway 网关未重启**:添加配置后,重启 Gateway 网关: ```bash openclaw gateway restart ``` -验证渠道是否正在运行: +验证该渠道是否正在运行: ```bash openclaw channels status -# 应显示:Google Chat default: enabled, configured, ... +# Should show: Google Chat default: enabled, configured, ... ``` ### 其他问题 -- 检查 `openclaw channels status --probe` 是否有认证错误或缺失的 audience 配置。 -- 如果没有收到消息,请确认 Chat 应用的 webhook URL 和事件订阅。 -- 如果提及门控阻止了回复,请将 `botUser` 设置为该应用的用户资源名称,并验证 `requireMention`。 -- 发送测试消息时使用 `openclaw logs --follow`,查看请求是否到达 gateway。 +- 检查 `openclaw channels status --probe`,查看是否存在认证错误或缺失的 audience 配置。 +- 如果没有收到消息,请确认 Chat 应用的 webhook URL 和事件订阅配置正确。 +- 如果提及门控阻止了回复,请将 `botUser` 设置为应用的用户资源名,并验证 `requireMention`。 +- 发送测试消息时使用 `openclaw logs --follow`,查看请求是否到达 Gateway 网关。 相关文档: -- [Gateway 网关配置](/gateway/configuration) -- [安全性](/gateway/security) -- [表情回应](/tools/reactions) +- [Gateway 网关配置](/zh-CN/gateway/configuration) +- [安全性](/zh-CN/gateway/security) +- [Reactions](/zh-CN/tools/reactions) ## 相关内容 -- [渠道概览](/channels) — 所有支持的渠道 -- [配对](/channels/pairing) — 私信身份验证和配对流程 -- [群组](/channels/groups) — 群聊行为和提及门控 -- [渠道路由](/channels/channel-routing) — 消息的会话路由 -- [安全性](/gateway/security) — 访问模型和加固 +- [渠道概览](/zh-CN/channels) — 所有受支持的渠道 +- [配对](/zh-CN/channels/pairing) — 私信认证和配对流程 +- [群组](/zh-CN/channels/groups) — 群聊行为和提及门控 +- [渠道路由](/zh-CN/channels/channel-routing) — 消息的会话路由 +- [安全性](/zh-CN/gateway/security) — 访问模型和加固措施 diff --git a/docs/zh-CN/channels/msteams.md b/docs/zh-CN/channels/msteams.md index fef857fd2..453f49007 100644 --- a/docs/zh-CN/channels/msteams.md +++ b/docs/zh-CN/channels/msteams.md @@ -1,49 +1,47 @@ --- read_when: - - 正在开发 Microsoft Teams 渠道功能 + - 在 Microsoft Teams 渠道上开发功能 summary: Microsoft Teams 机器人支持状态、功能和配置 title: Microsoft Teams x-i18n: - generated_at: "2026-04-23T15:10:14Z" + generated_at: "2026-04-23T20:12:13Z" model: gpt-5.4 provider: openai - source_hash: 872318a0f4ec42efef3c13bc90b4af1b3875ade431bd314583ea2cdc038cdc7c + source_hash: 014fddd2de77a150113bf0f09bebe49ccda5847087929e01d9c0c85e4cd1219e source_path: channels/msteams.md workflow: 15 --- -# Microsoft Teams - -> “进入此处者,放弃一切希望。” - -状态:支持文本和私信附件;频道/群组文件发送需要 `sharePointSiteId` + Graph 权限(参见[在群聊中发送文件](#sending-files-in-group-chats))。投票通过 Adaptive Cards 发送。消息操作会公开明确的 `upload-file`,用于优先发送文件的场景。 +支持文本和私信附件;渠道和群组文件发送需要 `sharePointSiteId` + Graph 权限(参见 [在群组聊天中发送文件](#sending-files-in-group-chats))。投票通过 Adaptive Cards 发送。消息操作公开了显式的 `upload-file`,用于文件优先发送。 ## 内置插件 -Microsoft Teams 在当前的 OpenClaw 版本中作为内置插件提供,因此在常规打包构建中不需要单独安装。 +Microsoft Teams 作为内置插件随当前的 OpenClaw 版本提供,因此在常规打包构建中 +无需单独安装。 -如果你使用的是较旧的构建版本,或是不包含内置 Teams 的自定义安装,请手动安装它: +如果你使用的是较旧版本,或是不包含内置 Teams 的自定义安装, +请手动安装: ```bash openclaw plugins install @openclaw/msteams ``` -本地检出版本(当你从 git 仓库运行时): +本地检出(从 git 仓库运行时): ```bash openclaw plugins install ./path/to/local/msteams-plugin ``` -详情:[Plugins](/zh-CN/tools/plugin) +详情: [Plugins](/zh-CN/tools/plugin) -## 快速设置(新手) +## 快速设置(初学者) 1. 确保 Microsoft Teams 插件可用。 - - 当前打包发布的 OpenClaw 版本已默认内置该插件。 - - 较旧/自定义安装可使用上面的命令手动添加。 + - 当前打包的 OpenClaw 版本已默认内置它。 + - 较旧版本/自定义安装可使用上述命令手动添加。 2. 创建一个 **Azure Bot**(App ID + 客户端密钥 + 租户 ID)。 3. 使用这些凭证配置 OpenClaw。 -4. 通过公共 URL 或隧道暴露 `/api/messages`(默认端口为 3978)。 +4. 通过公共 URL 或隧道公开 `/api/messages`(默认端口为 3978)。 5. 安装 Teams 应用包并启动 Gateway 网关。 最小配置(客户端密钥): @@ -62,15 +60,9 @@ openclaw plugins install ./path/to/local/msteams-plugin } ``` -对于生产部署,建议使用[联合身份验证](#federated-authentication-certificate--managed-identity)(证书或托管身份),而不是客户端密钥。 +对于生产部署,建议考虑使用 [联合身份验证](#federated-authentication-certificate--managed-identity)(证书或托管身份)来替代客户端密钥。 -注意:群聊默认被阻止(`channels.msteams.groupPolicy: "allowlist"`)。若要允许群组回复,请设置 `channels.msteams.groupAllowFrom`(或使用 `groupPolicy: "open"` 以允许任何成员,默认仍需提及)。 - -## 目标 - -- 通过 Teams 私信、群聊或频道与 OpenClaw 交流。 -- 保持路由可预测:回复始终返回到消息到达时所在的渠道。 -- 默认使用安全的渠道行为(除非另有配置,否则必须提及)。 +注意:默认会阻止群组聊天(`channels.msteams.groupPolicy: "allowlist"`)。要允许群组回复,请设置 `channels.msteams.groupAllowFrom`(或者使用 `groupPolicy: "open"` 来允许任何成员,默认仍需提及)。 ## 配置写入 @@ -88,17 +80,17 @@ openclaw plugins install ./path/to/local/msteams-plugin **私信访问** -- 默认:`channels.msteams.dmPolicy = "pairing"`。未知发送者在获得批准前会被忽略。 +- 默认值: `channels.msteams.dmPolicy = "pairing"`。未知发送者在获批前会被忽略。 - `channels.msteams.allowFrom` 应使用稳定的 AAD 对象 ID。 -- UPN/显示名称可能变化;默认禁用直接匹配,仅在 `channels.msteams.dangerouslyAllowNameMatching: true` 时启用。 -- 当凭证权限允许时,向导可通过 Microsoft Graph 将名称解析为 ID。 +- UPN/显示名称是可变的;默认禁用直接匹配,只有在设置 `channels.msteams.dangerouslyAllowNameMatching: true` 时才会启用。 +- 当凭证允许时,向导可以通过 Microsoft Graph 将名称解析为 ID。 **群组访问** -- 默认:`channels.msteams.groupPolicy = "allowlist"`(除非你添加 `groupAllowFrom`,否则会被阻止)。当未设置时,可使用 `channels.defaults.groupPolicy` 覆盖默认值。 -- `channels.msteams.groupAllowFrom` 控制哪些发送者可以在群聊/频道中触发(回退到 `channels.msteams.allowFrom`)。 +- 默认值: `channels.msteams.groupPolicy = "allowlist"`(除非你添加 `groupAllowFrom`,否则会被阻止)。当未设置时,可使用 `channels.defaults.groupPolicy` 覆盖默认值。 +- `channels.msteams.groupAllowFrom` 控制哪些发送者可以在群组聊天/渠道中触发(回退到 `channels.msteams.allowFrom`)。 - 设置 `groupPolicy: "open"` 可允许任何成员(默认仍需提及)。 -- 若要**不允许任何频道**,请设置 `channels.msteams.groupPolicy: "disabled"`。 +- 若要**不允许任何渠道**,请设置 `channels.msteams.groupPolicy: "disabled"`。 示例: @@ -113,13 +105,14 @@ openclaw plugins install ./path/to/local/msteams-plugin } ``` -**Teams + 频道允许列表** +**Teams + 渠道允许列表** -- 通过在 `channels.msteams.teams` 下列出 teams 和 channels,限制群组/频道回复范围。 -- 键应使用稳定的 team ID 和 channel 会话 ID。 -- 当 `groupPolicy="allowlist"` 且存在 teams 允许列表时,仅接受列出的 teams/channels(且需要提及)。 -- 配置向导接受 `Team/Channel` 条目,并会帮你保存。 -- 启动时,OpenClaw 会将 team/channel 和用户允许列表中的名称解析为 ID(当 Graph 权限允许时),并记录映射;未解析的 team/channel 名称会按输入原样保留,但默认会在路由中被忽略,除非启用了 `channels.msteams.dangerouslyAllowNameMatching: true`。 +- 通过在 `channels.msteams.teams` 下列出团队和渠道,限定群组/渠道回复范围。 +- 键应使用稳定的团队 ID 和渠道会话 ID。 +- 当 `groupPolicy="allowlist"` 且存在 teams 允许列表时,仅接受列出的团队/渠道(仍需提及)。 +- 配置向导接受 `Team/Channel` 条目,并会帮你存储它们。 +- 启动时,OpenClaw 会将团队/渠道和用户允许列表中的名称解析为 ID(当 Graph 权限允许时) + 并记录映射;默认情况下,无法解析的团队/渠道名称会按原样保留,但会在路由中被忽略,除非启用 `channels.msteams.dangerouslyAllowNameMatching: true`。 示例: @@ -142,43 +135,43 @@ openclaw plugins install ./path/to/local/msteams-plugin ## Azure Bot 设置 -在配置 OpenClaw 之前,请先创建一个 Azure Bot 资源并保存其凭证。 +在配置 OpenClaw 之前,请先创建 Azure Bot 资源并记录其凭证。 - 前往 [Create Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot),填写 **Basics** 选项卡: + 前往 [创建 Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot),并填写 **Basics** 选项卡: - | Field | Value | - | ------------------ | ------------------------------------------------- | - | **Bot handle** | 你的机器人名称,例如 `openclaw-msteams`(必须唯一) | - | **Subscription** | 你的 Azure 订阅 | - | **Resource group** | 新建或使用现有资源组 | - | **Pricing tier** | 开发/测试请选择 **Free** | - | **Type of App** | 推荐使用 **Single Tenant** | - | **Creation type** | **Create new Microsoft App ID** | + | Field | Value | + | ------------------ | -------------------------------------------------------- | + | **Bot handle** | 你的机器人名称,例如 `openclaw-msteams`(必须唯一) | + | **Subscription** | 你的 Azure 订阅 | + | **Resource group** | 新建或使用现有资源组 | + | **Pricing tier** | 开发/测试请选择 **Free** | + | **Type of App** | **Single Tenant**(推荐) | + | **Creation type** | **Create new Microsoft App ID** | - 新的多租户 bot 已在 2025-07-31 之后弃用。新建 bot 请使用 **Single Tenant**。 + 2025-07-31 之后不再支持新的多租户机器人。新机器人请使用 **Single Tenant**。 - 点击 **Review + create** → **Create**(等待约 1–2 分钟)。 + 点击 **Review + create** → **Create**(等待约 1-2 分钟)。 - + 在 Azure Bot 资源中,进入 **Configuration**: - 复制 **Microsoft App ID** → `appId` - 点击 **Manage Password** → **Certificates & secrets** → **New client secret** → 复制其值 → `appPassword` - - 进入 **Overview** → **Directory (tenant) ID** → `tenantId` + - 点击 **Overview** → **Directory (tenant) ID** → `tenantId` - + Azure Bot → **Configuration** → 设置 **Messaging endpoint**: - - 生产环境:`https://your-domain.com/api/messages` - - 本地开发:使用隧道(参见[本地开发](#local-development-tunneling)) + - 生产环境: `https://your-domain.com/api/messages` + - 本地开发:使用隧道(参见 [本地开发](#local-development-tunneling)) @@ -187,22 +180,20 @@ openclaw plugins install ./path/to/local/msteams-plugin - - ## 联合身份验证 > 添加于 2026.3.24 -对于生产部署,OpenClaw 支持将**联合身份验证**作为比客户端密钥更安全的替代方案。提供两种方式: +对于生产部署,OpenClaw 支持 **联合身份验证**,作为比客户端密钥更安全的替代方案。支持两种方式: ### 选项 A:基于证书的身份验证 -使用在你的 Entra ID 应用注册中登记的 PEM 证书。 +使用已在你的 Entra ID 应用注册中登记的 PEM 证书。 **设置:** -1. 生成或获取证书(带私钥的 PEM 格式)。 -2. 在 Entra ID → App Registration → **Certificates & secrets** → **Certificates** 中上传公开证书。 +1. 生成或获取一个证书(包含私钥的 PEM 格式)。 +2. 在 Entra ID → 应用注册 → **Certificates & secrets** → **Certificates** 中上传公钥证书。 **配置:** @@ -226,24 +217,24 @@ openclaw plugins install ./path/to/local/msteams-plugin - `MSTEAMS_AUTH_TYPE=federated` - `MSTEAMS_CERTIFICATE_PATH=/path/to/cert.pem` -### 选项 B:Azure Managed Identity +### 选项 B:Azure 托管身份 -使用 Azure Managed Identity 实现无密码身份验证。这非常适合部署在 Azure 基础设施上的场景(AKS、App Service、Azure VM),因为这些环境可提供托管身份。 +使用 Azure 托管身份实现无密码身份验证。这非常适合部署在 Azure 基础设施(AKS、App Service、Azure VM)上的场景,此时托管身份可用。 **工作原理:** -1. bot 所在的 pod/VM 拥有托管身份(系统分配或用户分配)。 -2. 一个**联合身份凭证**将该托管身份链接到 Entra ID 应用注册。 +1. 机器人 pod/VM 拥有一个托管身份(系统分配或用户分配)。 +2. 一个 **federated identity credential** 将该托管身份链接到 Entra ID 应用注册。 3. 在运行时,OpenClaw 使用 `@azure/identity` 从 Azure IMDS 端点(`169.254.169.254`)获取令牌。 -4. 该令牌会传递给 Teams SDK,用于 bot 身份验证。 +4. 该令牌会传递给 Teams SDK,用于机器人身份验证。 **前提条件:** - 已启用托管身份的 Azure 基础设施(AKS workload identity、App Service、VM) -- 已在 Entra ID 应用注册上创建联合身份凭证 -- pod/VM 可访问 IMDS(`169.254.169.254:80`) +- 已在 Entra ID 应用注册上创建 federated identity credential +- pod/VM 可以通过网络访问 IMDS(`169.254.169.254:80`) -**配置(系统分配的托管身份):** +**配置(系统分配托管身份):** ```json5 { @@ -260,7 +251,7 @@ openclaw plugins install ./path/to/local/msteams-plugin } ``` -**配置(用户分配的托管身份):** +**配置(用户分配托管身份):** ```json5 { @@ -282,14 +273,14 @@ openclaw plugins install ./path/to/local/msteams-plugin - `MSTEAMS_AUTH_TYPE=federated` - `MSTEAMS_USE_MANAGED_IDENTITY=true` -- `MSTEAMS_MANAGED_IDENTITY_CLIENT_ID=`(仅用户分配场景需要) +- `MSTEAMS_MANAGED_IDENTITY_CLIENT_ID=`(仅适用于用户分配) ### AKS workload identity 设置 对于使用 workload identity 的 AKS 部署: -1. 在 AKS 集群上**启用 workload identity**。 -2. 在 Entra ID 应用注册上**创建联合身份凭证**: +1. 在你的 AKS 集群上**启用 workload identity**。 +2. 在 Entra ID 应用注册上**创建 federated identity credential**: ```bash az ad app federated-credential create --id --parameters '{ @@ -319,147 +310,103 @@ openclaw plugins install ./path/to/local/msteams-plugin azure.workload.identity/use: "true" ``` -5. **确保可以访问 IMDS 网络地址**(`169.254.169.254`)——如果你使用 NetworkPolicy,请添加一条出口规则,允许访问 `169.254.169.254/32` 的 80 端口流量。 +5. **确保网络可访问** IMDS(`169.254.169.254`)—— 如果你使用了 NetworkPolicy,请添加一条出口规则,允许访问 `169.254.169.254/32` 的 80 端口。 ### 身份验证类型对比 -| Method | Config | Pros | Cons | -| -------------------- | ---------------------------------------------- | -------------------- | ------------------------------ | -| **Client secret** | `appPassword` | 设置简单 | 需要轮换密钥,安全性较低 | -| **Certificate** | `authType: "federated"` + `certificatePath` | 无需通过网络共享密钥 | 证书管理有额外开销 | -| **Managed Identity** | `authType: "federated"` + `useManagedIdentity` | 无密码,无需管理密钥 | 需要 Azure 基础设施 | +| Method | Config | Pros | Cons | +| -------------------- | ---------------------------------------------- | ---------------------------------- | ------------------------------------- | +| **Client secret** | `appPassword` | 设置简单 | 需要轮换密钥,安全性较低 | +| **Certificate** | `authType: "federated"` + `certificatePath` | 无需通过网络共享密钥 | 证书管理有额外开销 | +| **Managed Identity** | `authType: "federated"` + `useManagedIdentity` | 无密码,无需管理密钥 | 需要 Azure 基础设施 | **默认行为:** 当未设置 `authType` 时,OpenClaw 默认使用客户端密钥身份验证。现有配置无需修改即可继续工作。 ## 本地开发(隧道) -Teams 无法访问 `localhost`。进行本地开发时请使用隧道: +Teams 无法访问 `localhost`。本地开发时请使用隧道: **选项 A:ngrok** ```bash ngrok http 3978 # 复制 https URL,例如 https://abc123.ngrok.io -# 将消息端点设置为:https://abc123.ngrok.io/api/messages +# 将消息终结点设置为: https://abc123.ngrok.io/api/messages ``` **选项 B:Tailscale Funnel** ```bash tailscale funnel 3978 -# 使用你的 Tailscale funnel URL 作为消息端点 +# 使用你的 Tailscale funnel URL 作为消息终结点 ``` ## Teams Developer Portal(替代方式) -你也可以使用 [Teams Developer Portal](https://dev.teams.microsoft.com/apps),而不是手动创建 manifest ZIP: +除了手动创建 manifest ZIP 之外,你也可以使用 [Teams Developer Portal](https://dev.teams.microsoft.com/apps): 1. 点击 **+ New app** 2. 填写基本信息(名称、描述、开发者信息) -3. 进入 **App features** → **Bot** +3. 前往 **App features** → **Bot** 4. 选择 **Enter a bot ID manually** 并粘贴你的 Azure Bot App ID -5. 勾选作用域:**Personal**、**Team**、**Group Chat** +5. 勾选作用域: **Personal**、**Team**、**Group Chat** 6. 点击 **Distribute** → **Download app package** -7. 在 Teams 中:**Apps** → **Manage your apps** → **Upload a custom app** → 选择该 ZIP +7. 在 Teams 中: **Apps** → **Manage your apps** → **Upload a custom app** → 选择该 ZIP -这通常比手动编辑 JSON manifest 更容易。 +这通常比手动编辑 JSON manifest 更简单。 ## 测试机器人 **选项 A:Azure Web Chat(先验证 webhook)** 1. 在 Azure Portal 中,进入你的 Azure Bot 资源 → **Test in Web Chat** -2. 发送一条消息 —— 你应该能看到回复 -3. 这可以在设置 Teams 之前确认你的 webhook 端点可正常工作 +2. 发送一条消息 —— 你应该能看到响应 +3. 这可以在设置 Teams 之前确认你的 webhook 终结点工作正常 -**选项 B:Teams(安装应用后)** +**选项 B:Teams(安装应用之后)** -1. 安装 Teams 应用(侧载或组织目录) +1. 安装 Teams 应用(旁加载或组织目录) 2. 在 Teams 中找到该机器人并发送一条私信 3. 检查 Gateway 网关日志中的传入 activity -## 设置(最小纯文本配置) + -1. **确保 Microsoft Teams 插件可用** - - 当前打包发布的 OpenClaw 版本已默认内置该插件。 - - 较旧/自定义安装可手动添加: - - 从 npm:`openclaw plugins install @openclaw/msteams` - - 从本地检出:`openclaw plugins install ./path/to/local/msteams-plugin` +任意机器人/身份验证配置键也都可以通过环境变量设置: -2. **机器人注册** - - 创建一个 Azure Bot(见上文),并记录: - - App ID - - 客户端密钥(App password) - - Tenant ID(单租户) +- `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`(联合身份验证 + 托管身份;客户端 ID 仅适用于用户分配) -3. **Teams 应用 manifest** - - 包含一个 `bot` 条目,其中 `botId = `。 - - 作用域:`personal`、`team`、`groupChat`。 - - `supportsFiles: true`(个人作用域文件处理所必需)。 - - 添加 RSC 权限(见下文)。 - - 创建图标:`outline.png`(32x32)和 `color.png`(192x192)。 - - 将这三个文件一起打包为 zip:`manifest.json`、`outline.png`、`color.png`。 - -4. **配置 OpenClaw** - - ```json5 - { - channels: { - msteams: { - enabled: true, - appId: "", - appPassword: "", - tenantId: "", - webhook: { port: 3978, path: "/api/messages" }, - }, - }, - } - ``` - - 你也可以使用环境变量代替配置键: - - `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 需要) - -5. **机器人端点** - - 将 Azure Bot Messaging Endpoint 设置为: - - `https://:3978/api/messages`(或你选择的路径/端口)。 - -6. **运行 Gateway 网关** - - 当内置或手动安装的插件可用,且存在带有凭证的 `msteams` 配置时,Teams 渠道会自动启动。 + ## 成员信息操作 -OpenClaw 为 Microsoft Teams 提供了基于 Graph 的 `member-info` 操作,因此智能体和自动化流程可以直接通过 Microsoft Graph 解析渠道成员详情(显示名称、邮箱、角色)。 +OpenClaw 为 Microsoft Teams 提供了一个由 Graph 支持的 `member-info` 操作,因此智能体和自动化流程可以直接从 Microsoft Graph 解析渠道成员详细信息(显示名称、邮箱、角色)。 要求: - `Member.Read.Group` RSC 权限(已包含在推荐的 manifest 中) -- 对于跨 team 查询:需要带管理员同意的 `User.Read.All` Graph Application 权限 +- 对于跨团队查询:具有管理员同意的 `User.Read.All` Graph 应用程序权限 -该操作受 `channels.msteams.actions.memberInfo` 控制(默认:当 Graph 凭证可用时启用)。 +该操作由 `channels.msteams.actions.memberInfo` 控制(默认:当 Graph 凭证可用时启用)。 ## 历史上下文 -- `channels.msteams.historyLimit` 控制会包装进提示词中的最近频道/群组消息数量。 -- 回退到 `messages.groupChat.historyLimit`。设置为 `0` 可禁用(默认 50)。 -- 获取到的线程历史会按发送者允许列表(`allowFrom` / `groupAllowFrom`)进行过滤,因此线程上下文注入目前只包含来自允许发送者的消息。 -- 引用的附件上下文(由 Teams 回复 HTML 派生的 `ReplyTo*`)目前会按收到时的原样传递。 -- 换句话说,允许列表控制谁可以触发智能体;而当前只有特定的补充上下文路径会被过滤。 -- 私信历史可通过 `channels.msteams.dmHistoryLimit`(用户轮次)进行限制。按用户覆盖:`channels.msteams.dms[""].historyLimit`。 +- `channels.msteams.historyLimit` 控制包装进提示词中的最近渠道/群组消息数量。 +- 回退到 `messages.groupChat.historyLimit`。设置为 `0` 可禁用(默认值为 50)。 +- 获取到的线程历史会经过发送者允许列表过滤(`allowFrom` / `groupAllowFrom`),因此线程上下文预填充目前只包含来自允许发送者的消息。 +- 引用的附件上下文(由 Teams 回复 HTML 派生的 `ReplyTo*`)当前会按接收到的原样传递。 +- 换句话说,允许列表控制谁可以触发智能体;目前只有特定的补充上下文路径会被过滤。 +- 私信历史可以通过 `channels.msteams.dmHistoryLimit`(用户轮次)限制。按用户覆盖:`channels.msteams.dms[""].historyLimit`。 ## 当前 Teams RSC 权限 -这些是我们 Teams 应用 manifest 中**现有的 resourceSpecific 权限**。它们仅在安装了该应用的 team/chat 内生效。 +以下是我们 Teams 应用 manifest 中**现有的 resourceSpecific 权限**。它们只在安装了该应用的团队/聊天内部生效。 -**用于频道(team 作用域):** +**用于渠道(团队范围):** -- `ChannelMessage.Read.Group`(Application)- 无需 @mention 即可接收所有频道消息 +- `ChannelMessage.Read.Group`(Application)- 无需 @mention 即可接收所有渠道消息 - `ChannelMessage.Send.Group`(Application) - `Member.Read.Group`(Application) - `Owner.Read.Group`(Application) @@ -467,13 +414,13 @@ OpenClaw 为 Microsoft Teams 提供了基于 Graph 的 `member-info` 操作, - `TeamMember.Read.Group`(Application) - `TeamSettings.Read.Group`(Application) -**用于群聊:** +**用于群组聊天:** -- `ChatMessage.Read.Chat`(Application)- 无需 @mention 即可接收所有群聊消息 +- `ChatMessage.Read.Chat`(Application)- 无需 @mention 即可接收所有群组聊天消息 ## Teams manifest 示例 -包含所需字段的最小有效示例。请替换 ID 和 URL。 +包含所需字段的最小有效示例。请替换其中的 ID 和 URL。 ```json5 { @@ -525,9 +472,9 @@ OpenClaw 为 Microsoft Teams 提供了基于 Graph 的 `member-info` 操作, - `bots[].botId` **必须**与 Azure Bot App ID 匹配。 - `webApplicationInfo.id` **必须**与 Azure Bot App ID 匹配。 -- `bots[].scopes` 必须包含你计划使用的界面(`personal`、`team`、`groupChat`)。 -- `bots[].supportsFiles: true` 是个人作用域文件处理所必需的。 -- 如果你希望处理频道流量,`authorization.permissions.resourceSpecific` 必须包含频道读取/发送权限。 +- `bots[].scopes` 必须包含你计划使用的界面范围(`personal`、`team`、`groupChat`)。 +- `bots[].supportsFiles: true` 是个人范围内文件处理所必需的。 +- 如果你希望处理渠道流量,`authorization.permissions.resourceSpecific` 必须包含渠道读取/发送权限。 ### 更新现有应用 @@ -535,12 +482,12 @@ OpenClaw 为 Microsoft Teams 提供了基于 Graph 的 `member-info` 操作, 1. 使用新设置更新你的 `manifest.json` 2. **递增 `version` 字段**(例如 `1.0.0` → `1.1.0`) -3. **重新打包为 zip**,包含 manifest 和图标(`manifest.json`、`outline.png`、`color.png`) +3. 使用图标**重新打包 zip**(`manifest.json`、`outline.png`、`color.png`) 4. 上传新的 zip: - - **选项 A(Teams Admin Center):** Teams Admin Center → Teams apps → Manage apps → 找到你的应用 → Upload new version - - **选项 B(侧载):** 在 Teams 中 → Apps → Manage your apps → Upload a custom app -5. **对于 team 频道:** 在每个 team 中重新安装应用,新的权限才会生效 -6. **完全退出并重新启动 Teams**(而不只是关闭窗口),以清除缓存的应用元数据 + - **选项 A(Teams 管理中心):** Teams 管理中心 → Teams 应用 → 管理应用 → 找到你的应用 → 上传新版本 + - **选项 B(旁加载):** 在 Teams 中 → Apps → Manage your apps → Upload a custom app +5. **对于团队渠道:** 在每个团队中重新安装该应用,使新权限生效 +6. **完全退出并重新启动 Teams**(不仅仅是关闭窗口),以清除缓存的应用元数据 ## 功能:仅 RSC 与 Graph @@ -548,63 +495,63 @@ OpenClaw 为 Microsoft Teams 提供了基于 Graph 的 `member-info` 操作, 可用: -- 读取频道消息的**文本**内容。 -- 发送频道消息的**文本**内容。 +- 读取渠道消息**文本**内容。 +- 发送渠道消息**文本**内容。 - 接收**个人(私信)**文件附件。 不可用: -- 频道/群组中的**图片或文件内容**(payload 仅包含 HTML 占位内容)。 +- 渠道/群组中的**图片或文件内容**(负载仅包含 HTML 占位内容)。 - 下载存储在 SharePoint/OneDrive 中的附件。 -- 读取消息历史(超出实时 webhook 事件范围)。 +- 读取消息历史(超出实时 webhook 事件之外)。 -### Teams RSC 加 Microsoft Graph Application 权限 +### Teams RSC 加 Microsoft Graph 应用程序权限 -新增能力: +新增: - 下载托管内容(粘贴到消息中的图片)。 - 下载存储在 SharePoint/OneDrive 中的文件附件。 -- 通过 Graph 读取频道/chat 消息历史。 +- 通过 Graph 读取渠道/聊天消息历史。 -### RSC 与 Graph API 对比 +### RSC 与 Graph API -| Capability | RSC Permissions | Graph API | -| ----------------------- | ----------------- | --------------------------------- | -| **Real-time messages** | 是(通过 webhook) | 否(仅轮询) | -| **Historical messages** | 否 | 是(可查询历史) | -| **Setup complexity** | 仅应用 manifest | 需要管理员同意 + 令牌流程 | -| **Works offline** | 否(必须正在运行) | 是(可随时查询) | +| Capability | RSC Permissions | Graph API | +| ----------------------- | -------------------- | ----------------------------------- | +| **实时消息** | 是(通过 webhook) | 否(仅轮询) | +| **历史消息** | 否 | 是(可查询历史) | +| **设置复杂度** | 仅应用 manifest | 需要管理员同意 + 令牌流程 | +| **离线可用** | 否(必须运行中) | 是(可随时查询) | -**结论:** RSC 用于实时监听;Graph API 用于历史访问。如果你希望在离线期间补回错过的消息,则需要带 `ChannelMessage.Read.All` 的 Graph API(需要管理员同意)。 +**结论:** RSC 用于实时监听;Graph API 用于历史访问。如果你想在离线期间补上错过的消息,就需要启用带有 `ChannelMessage.Read.All` 的 Graph API(需要管理员同意)。 -## 启用 Graph 的媒体 + 历史记录(频道必需) +## 启用 Graph 的媒体 + 历史(渠道必需) -如果你在**频道**中需要图片/文件,或希望获取**消息历史记录**,则必须启用 Microsoft Graph 权限并授予管理员同意。 +如果你需要在**渠道**中处理图片/文件,或想获取**消息历史**,则必须启用 Microsoft Graph 权限并授予管理员同意。 -1. 在 Entra ID(Azure AD)**App Registration** 中,添加 Microsoft Graph **Application permissions**: - - `ChannelMessage.Read.All`(频道附件 + 历史记录) - - `Chat.Read.All` 或 `ChatMessage.Read.All`(群聊) +1. 在 Entra ID(Azure AD)**应用注册**中,添加 Microsoft Graph **应用程序权限**: + - `ChannelMessage.Read.All`(渠道附件 + 历史) + - `Chat.Read.All` 或 `ChatMessage.Read.All`(群组聊天) 2. 为租户**授予管理员同意**。 -3. 提升 Teams 应用 **manifest version**,重新上传,并且**在 Teams 中重新安装应用**。 +3. 提升 Teams 应用 **manifest 版本**,重新上传,并在 **Teams 中重新安装应用**。 4. **完全退出并重新启动 Teams**,以清除缓存的应用元数据。 -**用户提及的附加权限:** 对于当前对话中的用户,用户 @mention 开箱即用。不过,如果你想动态搜索并提及**当前对话之外**的用户,请添加 `User.Read.All`(Application)权限并授予管理员同意。 +**用户提及的附加权限:** 对于会话中的用户,用户 @mentions 可直接使用。但是,如果你想动态搜索并提及**当前会话之外**的用户,请添加 `User.Read.All`(Application)权限并授予管理员同意。 ## 已知限制 ### Webhook 超时 -Teams 通过 HTTP webhook 投递消息。如果处理耗时过长(例如 LLM 响应较慢),你可能会看到: +Teams 通过 HTTP webhook 传递消息。如果处理耗时过长(例如 LLM 响应较慢),你可能会看到: - Gateway 网关超时 -- Teams 重试该消息(导致重复) +- Teams 重试发送消息(导致重复) - 回复丢失 -OpenClaw 会通过快速返回并主动发送回复来处理这种情况,但极慢的响应仍可能引发问题。 +OpenClaw 通过快速返回并主动发送回复来处理这一问题,但响应非常慢时仍可能出现问题。 ### 格式化 -Teams 的 markdown 能力比 Slack 或 Discord 更有限: +Teams 的 markdown 比 Slack 或 Discord 更受限制: - 基本格式可用:**粗体**、_斜体_、`code`、链接 - 复杂 markdown(表格、嵌套列表)可能无法正确渲染 @@ -612,14 +559,14 @@ Teams 的 markdown 能力比 Slack 或 Discord 更有限: ## 配置 -分组设置(共享渠道模式参见 `/gateway/configuration`)。 +按组分类的设置(共享渠道模式参见 `/gateway/configuration`)。 - `channels.msteams.enabled` - `channels.msteams.appId`、`appPassword`、`tenantId`:机器人凭证 - - `channels.msteams.webhook.port`(默认 `3978`) - - `channels.msteams.webhook.path`(默认 `/api/messages`) + - `channels.msteams.webhook.port`(默认值 `3978`) + - `channels.msteams.webhook.path`(默认值 `/api/messages`) @@ -629,17 +576,17 @@ Teams 的 markdown 能力比 Slack 或 Discord 更有限: - - `dmPolicy`:`pairing | allowlist | open | disabled`(默认:pairing) - - `allowFrom`:私信允许列表,推荐使用 AAD 对象 ID;当 Graph 访问可用时,向导会解析名称 - - `dangerouslyAllowNameMatching`:用于可变 UPN/显示名称及 team/channel 名称路由的紧急放行开关 - - `requireMention`:在频道/群组中要求 @mention(默认 `true`) + - `dmPolicy`:`pairing | allowlist | open | disabled`(默认值:pairing) + - `allowFrom`:私信允许列表,优先使用 AAD 对象 ID;当 Graph 访问可用时,向导会解析名称 + - `dangerouslyAllowNameMatching`:用于可变 UPN/显示名称以及团队/渠道名称路由的紧急开关 + - `requireMention`:在渠道/群组中要求 @mention(默认值 `true`) - + 所有这些设置都会覆盖顶层默认值: - `teams..replyStyle`、`.requireMention` - - `teams..tools`、`.toolsBySender`:按 team 设置的工具策略默认值 + - `teams..tools`、`.toolsBySender`:按团队划分的工具策略默认值 - `teams..channels..replyStyle`、`.requireMention` - `teams..channels..tools`、`.toolsBySender` @@ -649,38 +596,38 @@ Teams 的 markdown 能力比 Slack 或 Discord 更有限: - `textChunkLimit`:出站文本分块大小 - - `chunkMode`:`length`(默认)或 `newline`(先按段落边界拆分,再按长度拆分) + - `chunkMode`:`length`(默认)或 `newline`(在按长度分割前先按段落边界分割) - `mediaAllowHosts`:入站附件主机允许列表(默认包含 Microsoft/Teams 域名) - - `mediaAuthAllowHosts`:重试时可接收 Authorization 头的主机(默认包含 Graph + Bot Framework) - - `replyStyle`:`thread | top-level`(参见[回复样式](#reply-style-threads-vs-posts)) - - `actions.memberInfo`:切换基于 Graph 的成员信息操作(默认在 Graph 可用时开启) - - `sharePointSiteId`:群聊/频道中文件上传所必需(参见[在群聊中发送文件](#sending-files-in-group-chats)) + - `mediaAuthAllowHosts`:重试时允许接收 Authorization 头的主机(默认包含 Graph + Bot Framework) + - `replyStyle`:`thread | top-level`(参见 [回复样式](#reply-style-threads-vs-posts)) + - `actions.memberInfo`:切换由 Graph 支持的成员信息操作(默认在 Graph 可用时开启) + - `sharePointSiteId`:群组聊天/渠道中文件上传所必需(参见 [在群组聊天中发送文件](#sending-files-in-group-chats)) -## 路由和会话 +## 路由与会话 - 会话键遵循标准智能体格式(参见 [/concepts/session](/zh-CN/concepts/session)): - 私信共享主会话(`agent::`)。 - - 频道/群组消息使用会话 ID: + - 渠道/群组消息使用会话 ID: - `agent::msteams:channel:` - `agent::msteams:group:` -## 回复样式:线程 vs 发帖 +## 回复样式:线程与帖子 -Teams 最近在相同的底层数据模型之上引入了两种频道 UI 样式: +Teams 最近在相同的底层数据模型之上引入了两种渠道 UI 样式: -| Style | Description | Recommended `replyStyle` | -| ------------------------ | -------------------------------------- | ------------------------ | -| **Posts**(经典) | 消息显示为卡片,下方带线程回复 | `thread`(默认) | -| **Threads**(类似 Slack) | 消息线性流动,更像 Slack | `top-level` | +| Style | Description | Recommended `replyStyle` | +| ------------------------ | --------------------------------------------------------- | ------------------------ | +| **Posts**(经典) | 消息显示为卡片,下方带有线程回复 | `thread`(默认) | +| **Threads**(类似 Slack) | 消息线性流动,更像 Slack | `top-level` | -**问题:** Teams API 不会公开频道使用的是哪种 UI 样式。如果你使用了错误的 `replyStyle`: +**问题在于:** Teams API 不会暴露渠道使用的是哪种 UI 样式。如果你使用了错误的 `replyStyle`: -- 在 Threads 样式的频道中使用 `thread` → 回复会以别扭的嵌套方式显示 -- 在 Posts 样式的频道中使用 `top-level` → 回复会显示为单独的顶层发帖,而不是在线程内 +- 在 Threads 风格的渠道中使用 `thread` → 回复会以不自然的嵌套方式显示 +- 在 Posts 风格的渠道中使用 `top-level` → 回复会显示为独立的顶层帖子,而不是在线程中 -**解决方案:** 根据频道的实际设置,按频道配置 `replyStyle`: +**解决方案:** 根据渠道的设置,为每个渠道配置 `replyStyle`: ```json5 { @@ -705,40 +652,40 @@ Teams 最近在相同的底层数据模型之上引入了两种频道 UI 样式 **当前限制:** -- **私信:** 图片和文件附件可通过 Teams bot 文件 API 正常工作。 -- **频道/群组:** 附件保存在 M365 存储(SharePoint/OneDrive)中。webhook payload 仅包含一个 HTML 占位内容,而不包含实际文件字节。**下载频道附件需要 Graph API 权限**。 -- 对于显式的优先发送文件场景,请使用 `action=upload-file`,并配合 `media` / `filePath` / `path`;可选的 `message` 会作为附带文本/评论,`filename` 会覆盖上传名称。 +- **私信:** 图片和文件附件可通过 Teams 机器人文件 API 正常工作。 +- **渠道/群组:** 附件存储在 M365 存储中(SharePoint/OneDrive)。webhook 负载只包含一个 HTML 占位内容,不包含实际文件字节。**下载渠道附件需要 Graph API 权限**。 +- 对于显式的文件优先发送,请使用 `action=upload-file`,配合 `media` / `filePath` / `path`;可选的 `message` 会作为附带文本/评论,`filename` 会覆盖上传后的名称。 -如果没有 Graph 权限,带图片的频道消息会以纯文本形式接收(机器人无法访问图片内容)。 -默认情况下,OpenClaw 仅从 Microsoft/Teams 主机名下载媒体。可通过 `channels.msteams.mediaAllowHosts` 覆盖(使用 `["*"]` 允许任意主机)。 -Authorization 头只会附加到 `channels.msteams.mediaAuthAllowHosts` 中的主机(默认包含 Graph + Bot Framework 主机)。请保持该列表严格受限(避免多租户后缀)。 +如果没有 Graph 权限,带图片的渠道消息会被作为纯文本接收(机器人无法访问图片内容)。 +默认情况下,OpenClaw 只会从 Microsoft/Teams 主机名下载媒体。可通过 `channels.msteams.mediaAllowHosts` 覆盖(使用 `["*"]` 可允许任意主机)。 +Authorization 头只会附加到 `channels.msteams.mediaAuthAllowHosts` 中的主机(默认包括 Graph + Bot Framework 主机)。请保持此列表严格受限(避免多租户后缀)。 -## 在群聊中发送文件 +## 在群组聊天中发送文件 -机器人可以在私信中使用 FileConsentCard 流程发送文件(内置支持)。但是,**在群聊/频道中发送文件**需要额外设置: +机器人可以使用 FileConsentCard 流程(内置)在私信中发送文件。不过,**在群组聊天/渠道中发送文件**需要额外设置: -| Context | How files are sent | Setup needed | -| ------------------------ | ------------------------------------- | ----------------------------------------------- | -| **私信** | FileConsentCard → 用户接受 → 机器人上传 | 开箱即用 | -| **群聊/频道** | 上传到 SharePoint → 分享链接 | 需要 `sharePointSiteId` + Graph 权限 | -| **图片(任意上下文)** | Base64 编码内联 | 开箱即用 | +| Context | How files are sent | Setup needed | +| ------------------------ | -------------------------------------------- | ----------------------------------------------- | +| **私信** | FileConsentCard → 用户接受 → 机器人上传 | 开箱即用 | +| **群组聊天/渠道** | 上传到 SharePoint → 分享链接 | 需要 `sharePointSiteId` + Graph 权限 | +| **图片(任意上下文)** | Base64 编码内联 | 开箱即用 | -### 为什么群聊需要 SharePoint +### 为什么群组聊天需要 SharePoint -机器人没有个人 OneDrive drive(`/me/drive` Graph API 端点不适用于应用身份)。要在群聊/频道中发送文件,机器人会上传到一个 **SharePoint site** 并创建共享链接。 +机器人没有个人 OneDrive 驱动器(`/me/drive` Graph API 端点不适用于应用身份)。要在群组聊天/渠道中发送文件,机器人需要上传到一个 **SharePoint 站点** 并创建共享链接。 ### 设置 -1. 在 Entra ID(Azure AD)→ App Registration 中**添加 Graph API 权限**: +1. 在 Entra ID(Azure AD)→ 应用注册中**添加 Graph API 权限**: - `Sites.ReadWrite.All`(Application)- 上传文件到 SharePoint - - `Chat.Read.All`(Application)- 可选,用于启用按用户的共享链接 + - `Chat.Read.All`(Application)- 可选,启用按用户的共享链接 2. 为租户**授予管理员同意**。 3. **获取你的 SharePoint site ID:** ```bash - # 通过 Graph Explorer 或使用有效令牌配合 curl: + # 通过 Graph Explorer 或使用有效令牌的 curl: curl -H "Authorization: Bearer $TOKEN" \ "https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}" @@ -746,7 +693,7 @@ Authorization 头只会附加到 `channels.msteams.mediaAuthAllowHosts` 中的 curl -H "Authorization: Bearer $TOKEN" \ "https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles" - # 响应中包含:"id": "contoso.sharepoint.com,guid1,guid2" + # 响应中包含: "id": "contoso.sharepoint.com,guid1,guid2" ``` 4. **配置 OpenClaw:** @@ -764,40 +711,40 @@ Authorization 头只会附加到 `channels.msteams.mediaAuthAllowHosts` 中的 ### 共享行为 -| Permission | Sharing behavior | -| --------------------------------------- | ---------------------------------------------- | -| `Sites.ReadWrite.All` only | 组织范围共享链接(组织内任何人都可访问) | -| `Sites.ReadWrite.All` + `Chat.Read.All` | 按用户共享链接(仅聊天成员可访问) | +| Permission | Sharing behavior | +| --------------------------------------- | --------------------------------------------------------- | +| `Sites.ReadWrite.All` only | 全组织共享链接(组织内任何人都可访问) | +| `Sites.ReadWrite.All` + `Chat.Read.All` | 按用户共享链接(只有聊天成员可访问) | -按用户共享更安全,因为只有聊天参与者可以访问该文件。如果缺少 `Chat.Read.All` 权限,机器人会回退到组织范围共享。 +按用户共享更安全,因为只有聊天参与者可以访问文件。如果缺少 `Chat.Read.All` 权限,机器人会回退为全组织共享。 ### 回退行为 -| Scenario | Result | -| ------------------------------------------------- | -------------------------------------------- | -| 群聊 + 文件 + 已配置 `sharePointSiteId` | 上传到 SharePoint,发送共享链接 | -| 群聊 + 文件 + 无 `sharePointSiteId` | 尝试上传到 OneDrive(可能失败),仅发送文本 | -| 个人聊天 + 文件 | FileConsentCard 流程(无需 SharePoint 即可工作) | -| 任意上下文 + 图片 | Base64 编码内联(无需 SharePoint 即可工作) | +| Scenario | Result | +| ------------------------------------------------- | -------------------------------------------------- | +| 群组聊天 + 文件 + 已配置 `sharePointSiteId` | 上传到 SharePoint,发送共享链接 | +| 群组聊天 + 文件 + 无 `sharePointSiteId` | 尝试上传到 OneDrive(可能失败),仅发送文本 | +| 个人聊天 + 文件 | FileConsentCard 流程(无需 SharePoint 即可工作) | +| 任意上下文 + 图片 | Base64 编码内联(无需 SharePoint 即可工作) | ### 文件存储位置 -上传的文件存储在已配置 SharePoint site 默认文档库中的 `/OpenClawShared/` 文件夹下。 +上传的文件会存储在已配置 SharePoint 站点默认文档库中的 `/OpenClawShared/` 文件夹下。 ## 投票(Adaptive Cards) -OpenClaw 通过 Adaptive Cards 发送 Teams 投票(Teams 没有原生投票 API)。 +OpenClaw 将 Teams 投票作为 Adaptive Cards 发送(Teams 没有原生投票 API)。 -- CLI:`openclaw message poll --channel msteams --target conversation: ...` +- CLI: `openclaw message poll --channel msteams --target conversation: ...` - 投票由 Gateway 网关记录在 `~/.openclaw/msteams-polls.json` 中。 - Gateway 网关必须保持在线才能记录投票。 - 投票目前还不会自动发布结果摘要(如有需要,请检查存储文件)。 ## 展示卡片 -使用 `message` 工具或 CLI 向 Teams 用户或会话发送语义化展示 payload。OpenClaw 会根据通用展示契约将其渲染为 Teams Adaptive Cards。 +可使用 `message` 工具或 CLI 向 Teams 用户或会话发送语义化展示负载。OpenClaw 会根据通用展示契约将其渲染为 Teams Adaptive Cards。 -`presentation` 参数接受语义块。提供 `presentation` 时,消息文本为可选项。 +`presentation` 参数接受语义块。当提供 `presentation` 时,消息文本为可选项。 **智能体工具:** @@ -821,7 +768,7 @@ openclaw message send --channel msteams \ --presentation '{"title":"Hello","blocks":[{"type":"text","text":"Hello!"}]}' ``` -关于目标格式的详细信息,参见下方的[目标格式](#target-formats)。 +有关目标格式的详细信息,参见下文的 [目标格式](#target-formats)。 ## 目标格式 @@ -831,8 +778,8 @@ MSTeams 目标使用前缀来区分用户和会话: | ------------------- | -------------------------------- | --------------------------------------------------- | | 用户(按 ID) | `user:` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` | | 用户(按名称) | `user:` | `user:John Smith`(需要 Graph API) | -| 群组/频道 | `conversation:` | `conversation:19:abc123...@thread.tacv2` | -| 群组/频道(原始) | `` | `19:abc123...@thread.tacv2`(如果包含 `@thread`) | +| 群组/渠道 | `conversation:` | `conversation:19:abc123...@thread.tacv2` | +| 群组/渠道(原始) | `` | `19:abc123...@thread.tacv2`(如果包含 `@thread`) | **CLI 示例:** @@ -840,10 +787,10 @@ MSTeams 目标使用前缀来区分用户和会话: # 按 ID 向用户发送 openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello" -# 按显示名称向用户发送(会触发 Graph API 查找) +# 按显示名称向用户发送(触发 Graph API 查询) openclaw message send --channel msteams --target "user:John Smith" --message "Hello" -# 发送到群聊或频道 +# 发送到群组聊天或渠道 openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" --message "Hello" # 向会话发送展示卡片 @@ -874,31 +821,31 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread. } ``` -注意:如果没有 `user:` 前缀,名称默认按群组/team 解析。按显示名称指定个人时,请始终使用 `user:`。 +注意:如果没有 `user:` 前缀,名称默认会按群组/团队解析。按显示名称定位个人时,请始终使用 `user:`。 ## 主动消息 -- 主动消息只有在用户已经交互**之后**才可发送,因为我们会在那时保存会话引用。 -- 关于 `dmPolicy` 和允许列表限制,参见 `/gateway/configuration`。 +- 只有在用户发生交互**之后**,才可以发送主动消息,因为我们会在那时存储会话引用。 +- 有关 `dmPolicy` 和允许列表控制,请参见 `/gateway/configuration`。 -## Team 和频道 ID +## 团队和渠道 ID -Teams URL 中的 `groupId` 查询参数**不是**配置中使用的 team ID。请从 URL 路径中提取 ID: +Teams URL 中的 `groupId` 查询参数**不是**配置中使用的 team ID。应从 URL 路径中提取 ID: -**Team URL:** +**团队 URL:** ``` https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=... └────────────────────────────┘ - Team ID(对其进行 URL 解码) + Team ID(对此进行 URL 解码) ``` -**频道 URL:** +**渠道 URL:** ``` https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=... └─────────────────────────┘ - Channel ID(对其进行 URL 解码) + Channel ID(对此进行 URL 解码) ``` **用于配置:** @@ -907,61 +854,73 @@ https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?gr - Channel ID = `/channel/` 之后的路径段(URL 解码后) - **忽略** `groupId` 查询参数 -## 私有频道 +## 私有渠道 -机器人在私有频道中的支持有限: +机器人在私有渠道中的支持有限: -| Feature | Standard Channels | Private Channels | -| ---------------------------- | ----------------- | ---------------- | -| 机器人安装 | 是 | 有限 | -| 实时消息(webhook) | 是 | 可能无法工作 | -| RSC 权限 | 是 | 行为可能不同 | -| @mentions | 是 | 如果机器人可访问 | -| Graph API 历史记录 | 是 | 是(有权限时) | +| Feature | Standard Channels | Private Channels | +| ---------------------------- | ----------------- | ------------------ | +| 机器人安装 | 是 | 有限 | +| 实时消息(webhook) | 是 | 可能无法工作 | +| RSC 权限 | 是 | 行为可能不同 | +| @mentions | 是 | 如果机器人可访问 | +| Graph API 历史 | 是 | 是(有权限时) | -**如果私有频道无法工作,可使用以下变通方案:** +**如果私有渠道无法工作,可采用以下变通方法:** -1. 对于机器人交互,使用标准频道 +1. 使用标准渠道进行机器人交互 2. 使用私信 —— 用户始终可以直接给机器人发消息 -3. 使用 Graph API 访问历史记录(需要 `ChannelMessage.Read.All`) +3. 使用 Graph API 获取历史访问(需要 `ChannelMessage.Read.All`) ## 故障排除 ### 常见问题 -- **频道中图片不显示:** 缺少 Graph 权限或管理员同意。请重新安装 Teams 应用,并完全退出后重新打开 Teams。 -- **频道中没有响应:** 默认要求提及;请设置 `channels.msteams.requireMention=false`,或按 team/频道进行配置。 -- **版本不匹配(Teams 仍显示旧 manifest):** 移除并重新添加应用,然后完全退出 Teams 以刷新。 -- **webhook 返回 401 Unauthorized:** 如果你在没有 Azure JWT 的情况下手动测试,这是预期行为 —— 说明端点可达,但身份验证失败。请使用 Azure Web Chat 正确测试。 +- **图片未显示在渠道中:** 缺少 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”:** 该应用仍安装在另一个 team/chat 中。请先找到并卸载它,或等待 5–10 分钟让更改传播。 -- **上传时出现 “Something went wrong”:** 请改用 [https://admin.teams.microsoft.com](https://admin.teams.microsoft.com) 上传,打开浏览器 DevTools(F12)→ Network 选项卡,并检查响应体中的实际错误信息。 -- **侧载失败:** 请尝试使用 “Upload an app to your org's app catalog”,而不是 “Upload a custom app” —— 这通常可以绕过侧载限制。 +- **“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](https://admin.teams.microsoft.com) 上传,打开浏览器 DevTools(F12)→ Network 选项卡,并检查响应体中的实际错误。 +- **旁加载失败:** 尝试使用 “Upload an app to your org's app catalog” 而不是 “Upload a custom app” —— 这通常可以绕过旁加载限制。 ### RSC 权限不起作用 1. 验证 `webApplicationInfo.id` 是否与你机器人的 App ID 完全一致 -2. 重新上传应用,并在 team/chat 中重新安装 +2. 重新上传应用,并在团队/聊天中重新安装 3. 检查你的组织管理员是否阻止了 RSC 权限 -4. 确认你使用了正确的作用域:team 使用 `ChannelMessage.Read.Group`,群聊使用 `ChatMessage.Read.Chat` +4. 确认你使用了正确的范围:团队用 `ChannelMessage.Read.Group`,群组聊天用 `ChatMessage.Read.Chat` ## 参考资料 -- [Create Azure Bot](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot 设置指南 +- [创建 Azure Bot](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot 设置指南 - [Teams Developer Portal](https://dev.teams.microsoft.com/apps) - 创建/管理 Teams 应用 -- [Teams app manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema) - Teams 应用 manifest schema -- [Receive channel messages with RSC](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/channel-messages-with-rsc) - 使用 RSC 接收频道消息 -- [RSC permissions reference](https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/rsc/resource-specific-consent) - RSC 权限参考 -- [Teams bot file handling](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bots-filesv4)(频道/群组需要 Graph) -- [Proactive messaging](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/send-proactive-messages) - 主动消息 +- [Teams 应用 manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema) +- [使用 RSC 接收渠道消息](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/channel-messages-with-rsc) +- [RSC 权限参考](https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/rsc/resource-specific-consent) +- [Teams 机器人文件处理](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bots-filesv4)(渠道/群组需要 Graph) +- [主动消息](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/send-proactive-messages) ## 相关内容 -- [Channels Overview](/zh-CN/channels) — 所有支持的渠道 -- [Pairing](/zh-CN/channels/pairing) — 私信身份验证和配对流程 -- [Groups](/zh-CN/channels/groups) — 群聊行为和提及限制 -- [Channel Routing](/zh-CN/channels/channel-routing) — 消息的会话路由 -- [Security](/zh-CN/gateway/security) — 访问模型和加固 + + + 所有受支持的渠道。 + + + 私信身份验证与配对流程。 + + + 群组聊天行为与提及控制。 + + + 消息的会话路由。 + + + 访问模型与加固。 + + diff --git a/docs/zh-CN/ci.md b/docs/zh-CN/ci.md index 681546933..86cf9defb 100644 --- a/docs/zh-CN/ci.md +++ b/docs/zh-CN/ci.md @@ -1,27 +1,27 @@ --- read_when: - - 你需要了解某个 CI 作业为什么运行了,或者为什么没有运行 + - 你需要了解某个 CI 作业为什么会运行或没有运行 - 你正在调试失败的 GitHub Actions 检查 -summary: CI 作业图、作用域门禁,以及本地等效命令 +summary: CI 作业图、范围门控,以及本地命令对应项 title: CI 流水线 x-i18n: - generated_at: "2026-04-23T19:45:54Z" + generated_at: "2026-04-23T20:12:13Z" model: gpt-5.4 provider: openai - source_hash: 3e250c90d9be13dc25a0b028de5d72cf821387e33c0965cac7b935579e3c6ae7 + source_hash: 601bd170afc217e7e9841c3cc358b941c9706b2cec57f568c0b583888ac0b4e7 source_path: ci.md workflow: 15 --- # CI 流水线 -CI 会在每次推送到 `main` 以及每个拉取请求时运行。它使用智能作用域划分,在仅有无关区域变更时跳过高开销作业。 +CI 会在每次推送到 `main` 以及每个拉取请求上运行。它使用智能范围判定,以便在只改动了无关区域时跳过昂贵作业。 -QA Lab 在主智能作用域工作流之外有专门的 CI 通道。`Parity gate` 工作流会在匹配的 PR 变更以及手动触发时运行;它会构建私有 QA 运行时,并比较模拟的 GPT-5.4 和 Opus 4.6 智能体包。`QA-Lab - All Lanes` 工作流会在 `main` 上每晚运行,并支持手动触发;它会将模拟 parity gate、实时 Matrix 通道和实时 Telegram 通道作为并行作业扇出运行。实时作业使用 `qa-live-shared` 环境,而 Telegram 通道使用 Convex 租约。`OpenClaw Release Checks` 也会在发布批准前运行相同的 QA Lab 通道。 +QA Lab 在主智能范围工作流之外有专用的 CI 通道。`Parity gate` 工作流会在匹配的 PR 变更和手动触发时运行;它会构建私有 QA 运行时,并比较 mock GPT-5.4 和 Opus 4.6 智能体包。`QA-Lab - All Lanes` 工作流会在 `main` 上每晚运行,也支持手动触发;它会将 mock parity gate、实时 Matrix 通道和实时 Telegram 通道作为并行作业扇出运行。实时作业使用 `qa-live-shared` environment,Telegram 通道使用 Convex leases。`OpenClaw Release Checks` 也会在发布批准前运行相同的 QA Lab 通道。 -`Duplicate PRs After Merge` 工作流是一个供维护者使用的手动工作流,用于合并后的重复项清理。它默认使用 dry-run,只有在 `apply=true` 时才会关闭显式列出的 PR。在修改 GitHub 之前,它会验证已合并的 PR 确实已合并,并验证每个重复 PR 要么引用了相同的 issue,要么存在重叠的变更 hunk。 +`Duplicate PRs After Merge` 工作流是一个供维护者在合并后进行重复 PR 清理的手动工作流。它默认是 dry-run,只有在 `apply=true` 时才会关闭明确列出的 PR。在修改 GitHub 之前,它会验证已落地的 PR 确实已合并,并检查每个重复 PR 是否具有共享的引用 issue,或是否存在重叠的改动 hunk。 -`Test Performance Agent` 工作流是一个事件驱动的 Codex 维护通道,用于处理慢测试。它没有纯定时调度:`main` 上一次由非机器人触发且成功的 push CI 运行可以触发它,但如果当天 UTC 时间内已有另一次 workflow-run 调用已经运行或正在运行,它就会跳过。手动触发会绕过这一每日活动门禁。该通道会构建完整测试套件的分组 Vitest 性能报告,允许 Codex 仅进行小范围且不降低覆盖率的测试性能修复,然后重新运行完整测试套件报告,并拒绝任何会降低通过基线测试数量的变更。如果基线中已有失败测试,Codex 只能修复明显的失败项,并且在提交任何内容之前,agent 运行后的完整测试套件报告必须通过。 +`Test Performance Agent` 工作流是一个由事件驱动的 Codex 维护通道,用于处理慢测试。它没有纯定时调度:`main` 上一次成功的、非机器人触发的 push CI 运行可以触发它,但如果当天 UTC 已经有另一个 workflow-run 调用已经运行或正在运行,它就会跳过。手动触发会绕过这个每日活动门控。该通道会构建一份完整测试套件的分组 Vitest 性能报告,让 Codex 只能进行小范围且不降低覆盖率的测试性能修复,然后重新运行完整测试套件报告,并拒绝任何会降低通过基线测试数量的更改。如果基线本身存在失败测试,Codex 只能修复明显的失败,并且只有在 agent 处理后的完整测试套件报告通过时,才会提交任何内容。它使用 GitHub 托管的 Ubuntu,这样 Codex action 就能与 docs agent 保持相同的 drop-sudo 安全策略。 ```bash gh workflow run duplicate-after-merge.yml \ @@ -32,86 +32,86 @@ gh workflow run duplicate-after-merge.yml \ ## 作业概览 -| 作业 | 目的 | 运行时机 | -| -------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------- | -| `preflight` | 检测是否仅为文档变更、变更的作用域、变更的扩展,并构建 CI 清单 | 所有非草稿 push 和 PR | -| `security-scm-fast` | 通过 `zizmor` 进行私钥检测和工作流审计 | 所有非草稿 push 和 PR | -| `security-dependency-audit` | 针对 npm advisories 进行无依赖的生产 lockfile 审计 | 所有非草稿 push 和 PR | -| `security-fast` | 快速安全作业的必需聚合作业 | 所有非草稿 push 和 PR | -| `build-artifacts` | 构建 `dist/`、Control UI、已构建产物检查,以及可复用的下游产物 | 与 Node 相关的变更 | -| `checks-fast-core` | 快速 Linux 正确性通道,例如 bundled/plugin-contract/protocol 检查 | 与 Node 相关的变更 | -| `checks-fast-contracts-channels` | 分片的渠道契约检查,并提供稳定的聚合检查结果 | 与 Node 相关的变更 | -| `checks-node-extensions` | 覆盖整个扩展套件的完整 bundled-plugin 测试分片 | 与 Node 相关的变更 | -| `checks-node-core-test` | Core Node 测试分片,不包括渠道、bundled、contract 和扩展通道 | 与 Node 相关的变更 | -| `extension-fast` | 仅针对已变更 bundled plugin 的定向测试 | 带有扩展变更的拉取请求 | -| `check` | 分片后的主本地门禁等效项:生产类型、lint、guard、测试类型和严格 smoke | 与 Node 相关的变更 | -| `check-additional` | 架构、边界、扩展表面 guard、包边界以及 gateway-watch 分片 | 与 Node 相关的变更 | -| `build-smoke` | 已构建 CLI 的 smoke 测试和启动内存 smoke | 与 Node 相关的变更 | -| `checks` | 用于已构建产物渠道测试的验证器,以及仅在 push 时运行的 Node 22 兼容性检查 | 与 Node 相关的变更 | -| `check-docs` | 文档格式、lint 和损坏链接检查 | 文档有变更时 | -| `skills-python` | 针对 Python 支持的 Skills 运行 Ruff + pytest | 与 Python Skills 相关的变更 | -| `checks-windows` | Windows 专用测试通道 | 与 Windows 相关的变更 | -| `macos-node` | 使用共享已构建产物的 macOS TypeScript 测试通道 | 与 macOS 相关的变更 | -| `macos-swift` | macOS 应用的 Swift lint、构建和测试 | 与 macOS 相关的变更 | -| `android` | 两种 flavor 的 Android 单元测试,以及一个 debug APK 构建 | 与 Android 相关的变更 | -| `test-performance-agent` | 在可信活动之后每日运行的 Codex 慢测试优化 | 主 CI 成功后或手动触发 | +| 作业 | 目的 | 运行时机 | +| -------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------ | +| `preflight` | 检测是否仅有文档变更、已变更范围、已变更扩展,并构建 CI 清单 | 在所有非草稿 push 和 PR 上始终运行 | +| `security-scm-fast` | 通过 `zizmor` 进行私钥检测和工作流审计 | 在所有非草稿 push 和 PR 上始终运行 | +| `security-dependency-audit` | 针对 npm advisories 进行无依赖的生产 lockfile 审计 | 在所有非草稿 push 和 PR 上始终运行 | +| `security-fast` | 快速安全作业的必需聚合作业 | 在所有非草稿 push 和 PR 上始终运行 | +| `build-artifacts` | 构建 `dist/`、Control UI、构建产物检查以及可复用的下游产物 | 与 Node 相关的变更 | +| `checks-fast-core` | 快速 Linux 正确性通道,例如 bundled/plugin-contract/protocol 检查 | 与 Node 相关的变更 | +| `checks-fast-contracts-channels` | 分片的渠道契约检查,并提供稳定的聚合检查结果 | 与 Node 相关的变更 | +| `checks-node-extensions` | 针对扩展套件的完整内置插件测试分片 | 与 Node 相关的变更 | +| `checks-node-core-test` | Core Node 测试分片,不包括渠道、内置、契约和扩展通道 | 与 Node 相关的变更 | +| `extension-fast` | 仅针对已变更内置插件的聚焦测试 | 带有扩展变更的拉取请求 | +| `check` | 分片的主本地门控等价项:生产类型、lint、守卫、测试类型以及严格 smoke | 与 Node 相关的变更 | +| `check-additional` | 架构、边界、扩展表面守卫、包边界以及 gateway-watch 分片 | 与 Node 相关的变更 | +| `build-smoke` | 已构建 CLI 的 smoke 测试和启动内存 smoke | 与 Node 相关的变更 | +| `checks` | 已构建产物渠道测试的校验器,以及仅在 push 上运行的 Node 22 兼容性检查 | 与 Node 相关的变更 | +| `check-docs` | 文档格式、lint 和断链检查 | 文档有变更时 | +| `skills-python` | 面向 Python 支持的 Skills 的 Ruff + pytest | 与 Python Skills 相关的变更 | +| `checks-windows` | Windows 专用测试通道 | 与 Windows 相关的变更 | +| `macos-node` | 使用共享构建产物的 macOS TypeScript 测试通道 | 与 macOS 相关的变更 | +| `macos-swift` | macOS app 的 Swift lint、构建和测试 | 与 macOS 相关的变更 | +| `android` | 两种 flavor 的 Android 单元测试,以及一次 debug APK 构建 | 与 Android 相关的变更 | +| `test-performance-agent` | 在可信活动之后每日运行的 Codex 慢测试优化 | `main` CI 成功后或手动触发 | ## 快速失败顺序 -作业按顺序排列,以便低成本检查先失败,避免高成本作业继续运行: +作业的排列顺序经过设计,使便宜的检查能在昂贵作业开始前先失败: -1. `preflight` 决定哪些通道会存在。`docs-scope` 和 `changed-scope` 逻辑是该作业中的步骤,而不是独立作业。 -2. `security-scm-fast`、`security-dependency-audit`、`security-fast`、`check`、`check-additional`、`check-docs` 和 `skills-python` 会快速失败,而无需等待更重的产物和平台矩阵作业。 -3. `build-artifacts` 与快速 Linux 通道并行运行,这样下游消费者可以在共享构建准备好后立即开始。 -4. 之后扇出更重的平台和运行时通道:`checks-fast-core`、`checks-fast-contracts-channels`、`checks-node-extensions`、`checks-node-core-test`、仅限 PR 的 `extension-fast`、`checks`、`checks-windows`、`macos-node`、`macos-swift` 和 `android`。 +1. `preflight` 决定哪些通道实际存在。`docs-scope` 和 `changed-scope` 逻辑是这个作业中的步骤,而不是独立作业。 +2. `security-scm-fast`、`security-dependency-audit`、`security-fast`、`check`、`check-additional`、`check-docs` 和 `skills-python` 会快速失败,而无需等待更重的构建产物和平台矩阵作业。 +3. `build-artifacts` 与快速 Linux 通道并行运行,这样下游消费者就能在共享构建完成后立即开始。 +4. 更重的平台和运行时通道会在此之后扇出:`checks-fast-core`、`checks-fast-contracts-channels`、`checks-node-extensions`、`checks-node-core-test`、仅 PR 的 `extension-fast`、`checks`、`checks-windows`、`macos-node`、`macos-swift` 和 `android`。 -作用域逻辑位于 `scripts/ci-changed-scope.mjs`,并由 `src/scripts/ci-changed-scope.test.ts` 中的单元测试覆盖。 -CI 工作流编辑会验证 Node CI 图以及工作流 lint,但不会仅因自身变更而强制运行 Windows、Android 或 macOS 原生构建;这些平台通道仍然只对平台源码变更生效。 -Windows Node 检查的作用域仅限于 Windows 专用的进程/路径包装器、npm/pnpm/UI 运行器辅助工具、包管理器配置,以及执行该通道的 CI 工作流表面;无关的源码、plugin、安装 smoke 和纯测试变更仍留在 Linux Node 通道中,这样它们就不会为了已由常规测试分片覆盖的内容而占用一个 16 vCPU 的 Windows worker。 -单独的 `install-smoke` 工作流会通过它自己的 `preflight` 作业复用相同的作用域脚本。它根据更窄的 changed-smoke 信号计算 `run_install_smoke`,因此 Docker/安装 smoke 会针对安装、打包、与容器相关的变更、bundled extension 生产变更,以及 Docker smoke 作业所覆盖的 core plugin/channel/Gateway 网关/插件 SDK 表面运行。纯测试和纯文档编辑不会占用 Docker worker。其 QR package smoke 会强制 Docker 的 `pnpm install` 层重新运行,同时保留 BuildKit 的 pnpm store 缓存,因此它仍能覆盖安装过程,而无需每次运行都重新下载依赖。它的 gateway-network e2e 会复用该作业前面构建的运行时镜像,因此它增加了真实的 container-to-container WebSocket 覆盖,而无需新增一次 Docker 构建。本地 `test:docker:all` 会预先构建一个共享的 live-test 镜像和一个共享的 `scripts/e2e/Dockerfile` built-app 镜像,然后在 `OPENCLAW_SKIP_DOCKER_BUILD=1` 下并行运行 live/E2E smoke 通道;默认并发数为 4,可通过 `OPENCLAW_DOCKER_ALL_PARALLELISM` 调整。本地聚合作业默认会在首次失败后停止调度新的池化通道,并且每个通道都有一个 120 分钟的超时,可通过 `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS` 覆盖。对启动或 provider 敏感的通道会在并行池之后独占运行。可复用的 live/E2E 工作流也遵循共享镜像模式:它会在 Docker 矩阵之前构建并推送一个带 SHA 标签的 GHCR Docker E2E 镜像,然后在 `OPENCLAW_SKIP_DOCKER_BUILD=1` 下运行矩阵。定时的 live/E2E 工作流每天运行完整的发布路径 Docker 套件。QR 和 installer Docker 测试保持各自以安装为重点的 Dockerfile。另有一个独立的 `docker-e2e-fast` 作业,会在 120 秒命令超时内运行有界的 bundled-plugin Docker 配置:setup-entry 依赖修复以及合成的 bundled-loader 故障隔离。完整的 bundled 更新/渠道矩阵仍然是手动/完整套件,因为它会反复执行真实的 npm update 和 doctor repair 流程。 +范围逻辑位于 `scripts/ci-changed-scope.mjs`,并由 `src/scripts/ci-changed-scope.test.ts` 中的单元测试覆盖。 +CI 工作流编辑会验证 Node CI 作业图以及工作流 lint,但仅凭这些编辑本身不会强制运行 Windows、Android 或 macOS 原生构建;这些平台通道仍然只针对对应平台源码变更而运行。 +Windows Node 检查的范围限定在 Windows 专用的进程/路径包装器、npm/pnpm/UI runner 辅助、包管理器配置,以及执行该通道的 CI 工作流表面;无关的源码、插件、install-smoke 和仅测试改动仍会留在 Linux Node 通道中,因此不会为已被常规测试分片覆盖的内容占用一个 16-vCPU 的 Windows worker。 +独立的 `install-smoke` 工作流通过其自己的 `preflight` 作业复用同一个范围脚本。它根据更窄的 changed-smoke 信号计算 `run_install_smoke`,因此 Docker/install smoke 会在安装、打包、与容器相关的变更、内置扩展生产变更,以及 Docker smoke 作业所覆盖的核心 plugin/channel/Gateway 网关/Plugin SDK 表面发生变更时运行。仅测试和仅文档的编辑不会占用 Docker workers。它的 QR 包 smoke 会强制 Docker `pnpm install` 层重新运行,同时保留 BuildKit pnpm store cache,因此仍然能够验证安装流程,而不必在每次运行时重新下载依赖。它的 gateway-network e2e 会复用该作业前面构建的运行时镜像,因此它在不增加额外 Docker 构建的情况下,增加了真实的 container-to-container WebSocket 覆盖。本地 `test:docker:all` 会预先构建一个共享的 live-test 镜像和一个共享的 `scripts/e2e/Dockerfile` built-app 镜像,然后在设置 `OPENCLAW_SKIP_DOCKER_BUILD=1` 的情况下并行运行 live/E2E smoke 通道;默认并发数为 4,可通过 `OPENCLAW_DOCKER_ALL_PARALLELISM` 调整。本地聚合作业默认会在第一次失败后停止调度新的池化通道,并且每个通道都有一个 120 分钟的超时,可通过 `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS` 覆盖。对启动或 provider 敏感的通道会在并行池之后以独占方式运行。可复用的 live/E2E 工作流也采用共享镜像模式:在 Docker 矩阵之前先构建并推送一个带 SHA 标签的 GHCR Docker E2E 镜像,然后在矩阵中设置 `OPENCLAW_SKIP_DOCKER_BUILD=1` 运行。定时的 live/E2E 工作流每天都会运行完整的发布路径 Docker 套件。QR 和 installer Docker 测试保留各自以安装为重点的 Dockerfile。另有一个独立的 `docker-e2e-fast` 作业,会在 120 秒命令超时限制下运行受限的内置插件 Docker 配置:setup-entry 依赖修复,以及 synthetic bundled-loader failure isolation。完整的 bundled update/channel 矩阵仍然保持为手动/完整套件,因为它会执行重复的真实 npm update 和 doctor 修复流程。 -本地 changed-lane 逻辑位于 `scripts/changed-lanes.mjs`,由 `scripts/check-changed.mjs` 执行。这个本地门禁在架构边界方面比宽泛的 CI 平台作用域更严格:core 生产变更会运行 core 生产类型检查以及 core 测试,core 纯测试变更只运行 core 测试类型检查/测试,extension 生产变更会运行 extension 生产类型检查以及 extension 测试,而 extension 纯测试变更只运行 extension 测试类型检查/测试。公共插件 SDK 或 plugin-contract 变更会扩展为 extension 验证,因为扩展依赖这些 core 契约。仅发布元数据的版本变更会运行定向的版本/配置/root-dependency 检查。未知的根目录/配置变更会以安全优先方式落到所有通道。 +本地 changed-lane 逻辑位于 `scripts/changed-lanes.mjs`,并由 `scripts/check-changed.mjs` 执行。这个本地门控在架构边界方面比宽泛的 CI 平台范围更严格:核心生产改动会运行核心生产 typecheck 加核心测试,核心仅测试改动只运行核心测试 typecheck/测试,扩展生产改动会运行扩展生产 typecheck 加扩展测试,而扩展仅测试改动只运行扩展测试 typecheck/测试。公共 Plugin SDK 或 plugin-contract 改动会扩展到扩展校验,因为扩展依赖这些核心契约。仅有发布元数据的版本号变更会运行定向的版本/配置/根依赖检查。未知的根目录/配置改动会以安全优先方式落到所有通道。 -在 push 上,`checks` 矩阵会增加一个仅在 push 时运行的 `compat-node22` 通道。在拉取请求上,该通道会被跳过,矩阵会保持聚焦于常规测试/渠道通道。 +在 push 上,`checks` 矩阵会添加仅限 push 的 `compat-node22` 通道。在拉取请求上,这个通道会被跳过,矩阵将聚焦于常规测试/渠道通道。 -最慢的 Node 测试族会被拆分或平衡,以便每个作业都保持较小规模,同时避免过度预留 runner:渠道契约按权重拆成三个分片,bundled plugin 测试会在六个扩展 worker 间做负载均衡,小型 core 单元通道会成对组合,auto-reply 以三个平衡 worker 运行,而不是六个过小的 worker,agentic Gateway 网关/plugin 配置则分布到现有的仅源码 agentic Node 作业中,而不是等待已构建产物。大范围的浏览器、QA、媒体和杂项 plugin 测试使用各自专用的 Vitest 配置,而不是共享的 plugin 通用兜底配置。扩展分片作业会以单个 Vitest worker 和更大的 Node 堆大小串行运行 plugin 配置组,这样导入开销较大的 plugin 批次就不会让小型 CI runner 过载。宽泛的 agents 通道使用共享的 Vitest 文件级并行调度器,因为它的瓶颈主要在导入/调度,而不是由某个单独的慢测试文件主导。`runtime-config` 会和 infra core-runtime 分片一起运行,以避免共享 runtime 分片独自拖尾。`check-additional` 会把 package-boundary 的编译/canary 工作保持在一起,并将 runtime topology architecture 与 gateway watch 覆盖拆开;boundary guard 分片会在一个作业内部并发运行其小型且相互独立的 guard。Gateway watch、渠道测试以及 core support-boundary 分片会在 `dist/` 和 `dist-runtime/` 已构建完成后,于 `build-artifacts` 内部并发运行;这样既保留了它们原有的检查名称作为轻量验证作业,又避免额外占用两个 Blacksmith worker 和第二条产物消费者队列。 +最慢的 Node 测试家族会被拆分或均衡,以便每个作业都保持较小规模,同时避免过度占用 runner:渠道契约以三个加权分片运行,内置插件测试在六个扩展 worker 之间均衡分配,小型核心单元通道会成对组合,auto-reply 改为三个均衡 worker 而不是六个零碎 worker,agentic Gateway 网关/plugin 配置则分散到现有的仅源码 agentic Node 作业中,而不是等待构建产物。范围较广的 browser、QA、media 和杂项插件测试使用各自专用的 Vitest 配置,而不是共享的插件兜底配置。扩展分片作业会以单个 Vitest worker 和更大的 Node heap 串行运行插件配置组,这样导入开销大的插件批次就不会让小型 CI runner 过载。广泛的 agents 通道使用共享的 Vitest 文件级并行调度器,因为它的瓶颈主要在导入/调度,而不是由某个单独的慢测试文件主导。`runtime-config` 会与 infra core-runtime 分片一起运行,以避免共享 runtime 分片独自拖尾。`check-additional` 会将 package-boundary compile/canary 工作保持在一起,并将 runtime topology architecture 与 gateway watch 覆盖拆开;boundary guard 分片会在单个作业内并发运行其小型且相互独立的守卫检查。Gateway 网关 watch、channel 测试以及核心 support-boundary 分片会在 `dist/` 和 `dist-runtime/` 已经构建完成后,于 `build-artifacts` 中并发运行;这样既保留了它们原有的检查名称作为轻量校验作业,又避免了额外两个 Blacksmith worker 和第二个 artifact-consumer 队列。 -Android CI 会同时运行 `testPlayDebugUnitTest` 和 `testThirdPartyDebugUnitTest`,然后构建 Play debug APK。第三方 flavor 没有单独的 source set 或 manifest;它的单元测试通道仍会使用 SMS/call-log BuildConfig 标志编译该 flavor,同时避免在每次与 Android 相关的 push 上重复执行一个 debug APK 打包作业。 +Android CI 会同时运行 `testPlayDebugUnitTest` 和 `testThirdPartyDebugUnitTest`,然后构建 Play debug APK。第三方 flavor 没有单独的 source set 或 manifest;它的单元测试通道仍会使用 SMS/call-log BuildConfig 标志编译该 flavor,同时避免在每次与 Android 相关的 push 上重复运行一个 debug APK 打包作业。 -`extension-fast` 仅在 PR 上运行,因为 push 运行已经会执行完整的 bundled plugin 分片。这样既能为评审提供已变更 plugin 的反馈,又不会在 `main` 上为 `checks-node-extensions` 已经覆盖的内容额外占用一个 Blacksmith worker。 +`extension-fast` 仅在 PR 上运行,因为 push 运行已经会执行完整的内置插件分片。这样既能为评审提供已变更插件的反馈,又不会在 `main` 上为 `checks-node-extensions` 已经覆盖的内容额外占用一个 Blacksmith worker。 -当同一 PR 或 `main` ref 上有较新的 push 到达时,GitHub 可能会把被替代的作业标记为 `cancelled`。除非同一 ref 上最新的一次运行也失败,否则应将这视为 CI 噪声。聚合分片检查使用 `!cancelled() && always()`,这样它们仍会正常报告分片失败,但不会在整个工作流已被替代后继续排队。 +当同一个 PR 或 `main` ref 上有更新的 push 到达时,GitHub 可能会将被替代的作业标记为 `cancelled`。除非同一 ref 的最新运行也失败,否则应将这视为 CI 噪音。聚合分片检查使用 `!cancelled() && always()`,因此它们仍会正常报告分片失败,但在整个工作流已经被替代后不会再排队运行。 -CI 并发 key 采用版本化形式(`CI-v7-*`),这样 GitHub 端旧队列组中的 zombie 就不会无限期阻塞较新的 main 运行。 +CI 并发键是带版本号的(`CI-v7-*`),因此 GitHub 端旧队列组中的僵尸任务不会无限期阻塞较新的 `main` 运行。 ## Runner -| Runner | 作业 | -| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ubuntu-24.04` | `preflight`、快速安全作业及其聚合(`security-scm-fast`、`security-dependency-audit`、`security-fast`)、快速 protocol/contract/bundled 检查、分片的渠道契约检查、除 lint 以外的 `check` 分片、`check-additional` 分片及聚合、Node 测试聚合验证器、文档检查、Python Skills、workflow-sanity、labeler、auto-response;install-smoke 的 preflight 也使用 GitHub 托管的 Ubuntu,这样 Blacksmith 矩阵可以更早进入排队 | -| `blacksmith-8vcpu-ubuntu-2404` | `build-artifacts`、build-smoke、Linux Node 测试分片、bundled plugin 测试分片、`android` | -| `blacksmith-16vcpu-ubuntu-2404` | `check-lint`,它对 CPU 仍然足够敏感,以至于 8 vCPU 节省下来的成本不如它带来的损失;install-smoke Docker 构建也是如此,32 vCPU 的排队时间成本高于其收益 | -| `blacksmith-16vcpu-windows-2025` | `checks-windows` | -| `blacksmith-6vcpu-macos-latest` | 在 `openclaw/openclaw` 上运行的 `macos-node`;fork 会回退到 `macos-latest` | -| `blacksmith-12vcpu-macos-latest` | 在 `openclaw/openclaw` 上运行的 `macos-swift`;fork 会回退到 `macos-latest` | +| Runner | 作业 | +| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ubuntu-24.04` | `preflight`、快速安全作业及其聚合作业(`security-scm-fast`、`security-dependency-audit`、`security-fast`)、快速 protocol/contract/bundled 检查、分片的渠道契约检查、除 lint 外的 `check` 分片、`check-additional` 分片及其聚合、Node 测试聚合校验器、文档检查、Python Skills、workflow-sanity、labeler、auto-response;install-smoke preflight 也使用 GitHub 托管的 Ubuntu,这样 Blacksmith 矩阵可以更早进入队列 | +| `blacksmith-8vcpu-ubuntu-2404` | `build-artifacts`、build-smoke、Linux Node 测试分片、内置插件测试分片、`android` | +| `blacksmith-16vcpu-ubuntu-2404` | `check-lint`,它仍然对 CPU 足够敏感,以至于 8 vCPU 的成本高于节省;install-smoke Docker 构建,在这里 32 vCPU 的排队时间成本高于节省 | +| `blacksmith-16vcpu-windows-2025` | `checks-windows` | +| `blacksmith-6vcpu-macos-latest` | `openclaw/openclaw` 上的 `macos-node`;fork 会回退到 `macos-latest` | +| `blacksmith-12vcpu-macos-latest` | `openclaw/openclaw` 上的 `macos-swift`;fork 会回退到 `macos-latest` | -## 本地等效命令 +## 本地对应项 ```bash -pnpm changed:lanes # 查看 origin/main...HEAD 的本地 changed-lane 分类器 -pnpm check:changed # 智能本地门禁:按边界通道运行变更相关的类型检查/lint/测试 -pnpm check # 快速本地门禁:生产 tsgo + 分片 lint + 并行快速 guard +pnpm changed:lanes # 检查 origin/main...HEAD 的本地 changed-lane 分类器 +pnpm check:changed # 智能本地门控:按边界通道运行已变更的 typecheck/lint/测试 +pnpm check # 快速本地门控:生产 tsgo + 分片 lint + 并行快速守卫 pnpm check:test-types -pnpm check:timed # 同一套门禁,并输出各阶段耗时 +pnpm check:timed # 相同门控,但带每个阶段的耗时统计 pnpm build:strict-smoke pnpm check:architecture pnpm test:gateway:watch-regression pnpm test # vitest 测试 pnpm test:channels pnpm test:contracts:channels -pnpm check:docs # 文档格式 + lint + 损坏链接检查 -pnpm build # 当 CI 产物/build-smoke 通道相关时,构建 dist -node scripts/ci-run-timings.mjs # 汇总总耗时、排队耗时和最慢作业 +pnpm check:docs # 文档 format + lint + 断链检查 +pnpm build # 当 CI artifact/build-smoke 通道相关时,构建 dist +node scripts/ci-run-timings.mjs # 汇总总耗时、排队时间和最慢作业 node scripts/ci-run-timings.mjs --recent 10 # 对比最近成功的 main CI 运行 pnpm test:perf:groups --full-suite --allow-failures --output .artifacts/test-perf/baseline-before.json pnpm test:perf:groups:compare .artifacts/test-perf/baseline-before.json .artifacts/test-perf/after-agent.json diff --git a/docs/zh-CN/gateway/index.md b/docs/zh-CN/gateway/index.md index d7bef276b..5d8bea655 100644 --- a/docs/zh-CN/gateway/index.md +++ b/docs/zh-CN/gateway/index.md @@ -1,30 +1,30 @@ --- read_when: - 运行或调试 Gateway 网关进程 -summary: Gateway 网关服务、生命周期与运维的操作手册 -title: Gateway 网关操作手册 +summary: Gateway 网关服务、生命周期与运维手册 +title: Gateway 网关运行手册 x-i18n: - generated_at: "2026-04-20T06:30:59Z" + generated_at: "2026-04-23T20:12:15Z" model: gpt-5.4 provider: openai - source_hash: e1004cdd43b1db6794f3ca83da38dbdb231a1976329d9d6d851e2b02405278d8 + source_hash: a024b1250c9e6badd1c72cccbd6d568ea510db829be79ced19a12f293dbe6f2a source_path: gateway/index.md workflow: 15 --- -# Gateway 网关操作手册 +# Gateway 网关运行手册 -将此页面用于 Gateway 网关服务的第 1 天启动和第 2 天运维操作。 +将此页面用于 Gateway 网关服务的第 1 天启动和第 2 天运维。 - 按症状优先的诊断,提供精确的命令步骤和日志特征。 + 按症状优先的诊断指南,包含精确的命令步骤和日志特征。 面向任务的设置指南 + 完整配置参考。 - `SecretRef` 合约、运行时快照行为,以及迁移/重新加载操作。 + SecretRef 合约、运行时快照行为,以及迁移/重载操作。 精确的 `secrets apply` 目标/路径规则,以及仅引用的 auth-profile 行为。 @@ -38,9 +38,9 @@ x-i18n: ```bash openclaw gateway --port 18789 -# debug/trace 镜像输出到 stdio +# 将 debug/trace 镜像输出到 stdio openclaw gateway --port 18789 --verbose -# 强制终止所选端口上的监听器,然后启动 +# 强制终止所选端口上的监听进程,然后启动 openclaw gateway --force ``` @@ -54,7 +54,7 @@ openclaw status openclaw logs --follow ``` -健康基线:`Runtime: running`、`Connectivity probe: ok`,以及与你预期匹配的 `Capability: ...`。当你需要读取范围的 RPC 证明,而不仅仅是可达性时,请使用 `openclaw gateway status --require-rpc`。 +健康基线:`Runtime: running`、`Connectivity probe: ok`,以及与你预期一致的 `Capability: ...`。当你需要读取范围的 RPC 证明,而不仅仅是可达性时,使用 `openclaw gateway status --require-rpc`。 @@ -64,35 +64,35 @@ openclaw logs --follow openclaw channels status --probe ``` -当 Gateway 网关可达时,这会按账户运行实时的渠道探测和可选审计。 -如果 Gateway 网关不可达,CLI 会回退到仅基于配置的渠道摘要, +当 Gateway 网关可达时,这会运行按账户划分的实时渠道探测和可选审计。 +如果 Gateway 网关不可达,CLI 会回退为仅配置的渠道摘要, 而不是实时探测输出。 -Gateway 网关配置重载会监视当前激活的配置文件路径(从 profile/state 默认值解析,或在设置时使用 `OPENCLAW_CONFIG_PATH`)。 -默认模式为 `gateway.reload.mode="hybrid"`。 -首次成功加载后,运行中的进程会提供当前激活的内存配置快照;成功重载后会原子性地替换该快照。 +Gateway 网关配置重载会监视活动配置文件路径(从 profile/state 默认值解析,或者在设置了 `OPENCLAW_CONFIG_PATH` 时使用该路径)。 +默认模式是 `gateway.reload.mode="hybrid"`。 +首次成功加载后,运行中的进程会提供活动的内存中配置快照;成功重载后会以原子方式替换该快照。 ## 运行时模型 - 一个始终运行的进程,用于路由、控制平面和渠道连接。 -- 一个用于以下内容的单一复用端口: +- 单个复用端口用于: - WebSocket 控制/RPC - - HTTP API、OpenAI 兼容接口(`/v1/models`、`/v1/embeddings`、`/v1/chat/completions`、`/v1/responses`、`/tools/invoke`) - - Control UI 和 hooks + - HTTP API,兼容 OpenAI(`/v1/models`、`/v1/embeddings`、`/v1/chat/completions`、`/v1/responses`、`/tools/invoke`) + - 控制 UI 和 hooks - 默认绑定模式:`loopback`。 -- 默认需要认证。共享密钥设置使用 +- 默认要求身份验证。共享密钥设置使用 `gateway.auth.token` / `gateway.auth.password`(或 - `OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD`),而非 `loopback` - 的反向代理设置可以使用 `gateway.auth.mode: "trusted-proxy"`。 + `OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD`),而非 loopback 的 + 反向代理设置可以使用 `gateway.auth.mode: "trusted-proxy"`。 -## OpenAI 兼容端点 +## 兼容 OpenAI 的端点 -OpenClaw 现在最有价值的兼容性接口为: +OpenClaw 当前最具杠杆效应的兼容性接口是: - `GET /v1/models` - `GET /v1/models/{id}` @@ -102,17 +102,17 @@ OpenClaw 现在最有价值的兼容性接口为: 为什么这一组很重要: -- 大多数 Open WebUI、LobeChat 和 LibreChat 集成会先探测 `/v1/models`。 -- 许多 RAG 和 memory 流水线依赖 `/v1/embeddings`。 -- 原生面向智能体的客户端越来越倾向于使用 `/v1/responses`。 +- 大多数 Open WebUI、LobeChat 和 LibreChat 集成都会先探测 `/v1/models`。 +- 许多 RAG 和记忆流水线都期望 `/v1/embeddings`。 +- 原生支持智能体的客户端越来越倾向于使用 `/v1/responses`。 规划说明: - `/v1/models` 以智能体优先:它会返回 `openclaw`、`openclaw/default` 和 `openclaw/`。 - `openclaw/default` 是稳定别名,始终映射到已配置的默认智能体。 -- 如果你想覆盖后端 provider/model,请使用 `x-openclaw-model`;否则将继续由所选智能体的常规模型和 embedding 设置进行控制。 +- 当你想要后端提供商/模型覆盖时,使用 `x-openclaw-model`;否则,所选智能体的常规模型和嵌入设置仍会生效。 -所有这些都运行在 Gateway 网关主端口上,并使用与 Gateway 网关其余 HTTP API 相同的可信操作员认证边界。 +所有这些都运行在主 Gateway 网关端口上,并使用与 Gateway 网关其余 HTTP API 相同的可信操作员身份验证边界。 ### 端口和绑定优先级 @@ -126,15 +126,15 @@ OpenClaw 现在最有价值的兼容性接口为: | `gateway.reload.mode` | 行为 | | --------------------- | ------------------------------------------ | | `off` | 不进行配置重载 | -| `hot` | 仅应用热更新安全的变更 | -| `restart` | 遇到需要重启的变更时重启 | -| `hybrid` (默认) | 安全时热应用,需要时重启 | +| `hot` | 仅应用可安全热更新的更改 | +| `restart` | 在需要重载的更改上重启 | +| `hybrid`(默认) | 安全时热应用,需要时重启 | ## 操作员命令集 ```bash openclaw gateway status -openclaw gateway status --deep # 添加系统级服务扫描 +openclaw gateway status --deep # 增加系统级服务扫描 openclaw gateway status --json openclaw gateway install openclaw gateway restart @@ -145,14 +145,14 @@ openclaw doctor ``` `gateway status --deep` 用于额外的服务发现(LaunchDaemons/systemd 系统 -units/schtasks),而不是更深入的 RPC 健康探测。 +units/schtasks),而不是更深层的 RPC 健康探测。 ## 多个 Gateway 网关(同一主机) -大多数安装应在每台机器上运行一个 Gateway 网关。单个 Gateway 网关可以承载多个 +大多数安装应在每台机器上运行一个 Gateway 网关。单个 Gateway 网关可以托管多个 智能体和渠道。 -只有当你明确需要隔离或一个救援 bot 时,才需要多个 Gateway 网关。 +只有当你有意需要隔离或一个救援机器人时,才需要多个 Gateway 网关。 有用的检查: @@ -161,12 +161,26 @@ openclaw gateway status --deep openclaw gateway probe ``` -预期表现: +预期行为: -- `gateway status --deep` 可能会报告 `Other gateway-like services detected (best effort)` - 并在仍存在陈旧的 launchd/systemd/schtasks 安装时打印清理提示。 -- 当多个目标都有响应时,`gateway probe` 可能会警告 `multiple reachable gateways`。 -- 如果这是有意为之,请为每个 Gateway 网关隔离端口、配置/state 和 workspace 根目录。 +- `gateway status --deep` 可以报告 `Other gateway-like services detected (best effort)` + 并在仍存在过期的 launchd/systemd/schtasks 安装时打印清理提示。 +- 当多个目标有响应时,`gateway probe` 可以警告 `multiple reachable gateways`。 +- 如果这是有意为之,请为每个 Gateway 网关分别隔离端口、配置/state 和工作区根目录。 + +每个实例的检查清单: + +- 唯一的 `gateway.port` +- 唯一的 `OPENCLAW_CONFIG_PATH` +- 唯一的 `OPENCLAW_STATE_DIR` +- 唯一的 `agents.defaults.workspace` + +示例: + +```bash +OPENCLAW_CONFIG_PATH=~/.openclaw/a.json OPENCLAW_STATE_DIR=~/.openclaw-a openclaw gateway --port 19001 +OPENCLAW_CONFIG_PATH=~/.openclaw/b.json OPENCLAW_STATE_DIR=~/.openclaw-b openclaw gateway --port 19002 +``` 详细设置:[/gateway/multiple-gateways](/zh-CN/gateway/multiple-gateways)。 @@ -182,16 +196,16 @@ ssh -N -L 18789:127.0.0.1:18789 user@host 然后让客户端在本地连接到 `ws://127.0.0.1:18789`。 -SSH 隧道不会绕过 Gateway 网关认证。对于共享密钥认证,即使通过隧道,客户端 -仍然必须发送 `token`/`password`。对于带身份的模式, -请求仍然必须满足该认证路径。 +SSH 隧道不会绕过 Gateway 网关身份验证。对于共享密钥身份验证,客户端即使 +通过隧道连接,仍然必须发送 `token`/`password`。对于携带身份的模式, +请求仍然必须满足该身份验证路径。 -参见:[远程 Gateway 网关](/zh-CN/gateway/remote)、[认证](/zh-CN/gateway/authentication)、[Tailscale](/zh-CN/gateway/tailscale)。 +参见:[Remote Gateway](/zh-CN/gateway/remote)、[Authentication](/zh-CN/gateway/authentication)、[Tailscale](/zh-CN/gateway/tailscale)。 -## 监管和服务生命周期 +## 监督与服务生命周期 -为了获得接近生产环境的可靠性,请使用受监管的运行方式。 +对于类生产环境的可靠性,请使用受监督的运行方式。 @@ -215,7 +229,7 @@ systemctl --user enable --now openclaw-gateway[-].service openclaw gateway status ``` -要在登出后保持持久运行,请启用 lingering: +如需在注销后仍保持运行,请启用 lingering: ```bash sudo loginctl enable-linger @@ -262,7 +276,7 @@ openclaw gateway stop -对多用户/始终在线主机,请使用系统 unit。 +对于多用户/始终运行的主机,请使用系统 unit。 ```bash sudo systemctl daemon-reload @@ -276,28 +290,7 @@ sudo systemctl enable --now openclaw-gateway[-].service -## 一台主机上的多个 Gateway 网关 - -大多数设置应只运行**一个** Gateway 网关。 -只有在需要严格隔离/冗余时才使用多个(例如救援 profile)。 - -每个实例的检查清单: - -- 唯一的 `gateway.port` -- 唯一的 `OPENCLAW_CONFIG_PATH` -- 唯一的 `OPENCLAW_STATE_DIR` -- 唯一的 `agents.defaults.workspace` - -示例: - -```bash -OPENCLAW_CONFIG_PATH=~/.openclaw/a.json OPENCLAW_STATE_DIR=~/.openclaw-a openclaw gateway --port 19001 -OPENCLAW_CONFIG_PATH=~/.openclaw/b.json OPENCLAW_STATE_DIR=~/.openclaw-b openclaw gateway --port 19002 -``` - -参见:[多个 Gateway 网关](/zh-CN/gateway/multiple-gateways)。 - -### 开发 profile 快速路径 +## 开发 profile 快速路径 ```bash openclaw --dev setup @@ -305,34 +298,34 @@ openclaw --dev gateway --allow-unconfigured openclaw --dev status ``` -默认值包括隔离的 state/config,以及基础 Gateway 网关端口 `19001`。 +默认值包括隔离的 state/config 和基础 Gateway 网关端口 `19001`。 ## 协议快速参考(操作员视角) -- 第一个客户端帧必须是 `connect`。 +- 客户端的第一帧必须是 `connect`。 - Gateway 网关返回 `hello-ok` 快照(`presence`、`health`、`stateVersion`、`uptimeMs`、limits/policy)。 - `hello-ok.features.methods` / `events` 是保守的发现列表,而不是 - 每个可调用辅助路由的自动生成清单。 + 每个可调用 helper 路由的自动生成清单。 - 请求:`req(method, params)` → `res(ok/payload|error)`。 - 常见事件包括 `connect.challenge`、`agent`、`chat`、 `session.message`、`session.tool`、`sessions.changed`、`presence`、`tick`、 - `health`、`heartbeat`、pairing/approval 生命周期事件以及 `shutdown`。 + `health`、`heartbeat`、配对/批准生命周期事件,以及 `shutdown`。 智能体运行分为两个阶段: 1. 立即接受确认(`status:"accepted"`) 2. 最终完成响应(`status:"ok"|"error"`),其间会流式发送 `agent` 事件。 -查看完整协议文档:[Gateway 协议](/zh-CN/gateway/protocol)。 +完整协议文档参见:[Gateway Protocol](/zh-CN/gateway/protocol)。 ## 运维检查 ### 存活性 - 打开 WS 并发送 `connect`。 -- 预期收到带快照的 `hello-ok` 响应。 +- 预期收到带有快照的 `hello-ok` 响应。 -### 就绪状态 +### 就绪性 ```bash openclaw gateway status @@ -340,26 +333,26 @@ openclaw channels status --probe openclaw health ``` -### 缺口恢复 +### 间隙恢复 -事件不会重放。出现序列缺口时,在继续之前刷新状态(`health`、`system-presence`)。 +事件不会重放。遇到序列间隙时,请先刷新状态(`health`、`system-presence`)再继续。 -## 常见故障特征 +## 常见失败特征 -| 特征 | 可能问题 | +| 特征 | 可能的问题 | | -------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| `refusing to bind gateway ... without auth` | 非 `loopback` 绑定,但没有有效的 Gateway 网关认证路径 | +| `refusing to bind gateway ... without auth` | 非 loopback 绑定但没有有效的 Gateway 网关身份验证路径 | | `another gateway instance is already listening` / `EADDRINUSE` | 端口冲突 | -| `Gateway start blocked: set gateway.mode=local` | 配置被设为远程模式,或损坏配置中缺少本地模式标记 | -| `unauthorized` during connect | 客户端与 Gateway 网关之间的认证不匹配 | +| `Gateway start blocked: set gateway.mode=local` | 配置被设置为远程模式,或受损配置中缺少 local-mode 标记 | +| `unauthorized` during connect | 客户端与 Gateway 网关之间的身份验证不匹配 | -如需完整的诊断步骤,请使用 [Gateway 故障排除](/zh-CN/gateway/troubleshooting)。 +完整诊断步骤请使用 [Gateway Troubleshooting](/zh-CN/gateway/troubleshooting)。 ## 安全保证 -- 当 Gateway 网关不可用时,Gateway 协议客户端会快速失败(不会隐式回退到直接渠道)。 +- 当 Gateway 网关不可用时,Gateway 网关协议客户端会快速失败(不会隐式回退到直接渠道)。 - 无效的/非 `connect` 的首帧会被拒绝并关闭连接。 -- 优雅关闭会在 socket 关闭前发出 `shutdown` 事件。 +- 优雅关闭会在 socket 关闭前发送 `shutdown` 事件。 --- @@ -368,6 +361,6 @@ openclaw health - [故障排除](/zh-CN/gateway/troubleshooting) - [后台进程](/zh-CN/gateway/background-process) - [配置](/zh-CN/gateway/configuration) -- [健康](/zh-CN/gateway/health) +- [健康状态](/zh-CN/gateway/health) - [Doctor](/zh-CN/gateway/doctor) -- [认证](/zh-CN/gateway/authentication) +- [身份验证](/zh-CN/gateway/authentication) diff --git a/docs/zh-CN/help/testing.md b/docs/zh-CN/help/testing.md index 60fd51fbc..c9813971b 100644 --- a/docs/zh-CN/help/testing.md +++ b/docs/zh-CN/help/testing.md @@ -3,20 +3,20 @@ read_when: - 在本地或 CI 中运行测试 - 为模型 / 提供商缺陷添加回归测试 - 调试 Gateway 网关 + 智能体行为 -summary: 测试工具包:单元 / e2e / 实时测试套件、Docker 运行器,以及每类测试覆盖的内容 +summary: 测试工具包:单元测试 / e2e / 实时测试套件、Docker 运行器,以及每类测试覆盖的内容 title: 测试 x-i18n: - generated_at: "2026-04-23T19:51:19Z" + generated_at: "2026-04-23T20:12:20Z" model: gpt-5.4 provider: openai - source_hash: 2aaf23311bb03d8369e45703bd5669bedcbac5a591c63ec8f4d6c11320ba476d + source_hash: 28472f335f77c8e1d126b64b38b4841867aff2edd5f87438fcd904d421ec37cf source_path: help/testing.md workflow: 15 --- -OpenClaw 有三个 Vitest 测试套件(单元 / 集成、e2e、实时),以及一小组 Docker 运行器。本文档是一份“我们如何测试”的指南: +OpenClaw 提供三个 Vitest 测试套件(单元 / 集成、e2e、实时)以及少量 Docker 运行器。本文档是“我们如何测试”的指南: -- 每个测试套件覆盖什么内容(以及它刻意 _不_ 覆盖什么)。 +- 每个套件覆盖什么(以及它刻意 _不_ 覆盖什么)。 - 常见工作流(本地、推送前、调试)应运行哪些命令。 - 实时测试如何发现凭证并选择模型 / 提供商。 - 如何为真实世界中的模型 / 提供商问题添加回归测试。 @@ -26,89 +26,101 @@ OpenClaw 有三个 Vitest 测试套件(单元 / 集成、e2e、实时),以 大多数时候: - 完整门禁(预期在推送前运行):`pnpm build && pnpm check && pnpm check:test-types && pnpm test` -- 在资源充足的机器上更快地运行本地完整测试套件:`pnpm test:max` -- 直接使用 Vitest 观察循环:`pnpm test:watch` -- 直接定位文件现在也会路由扩展 / 渠道路径:`pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts` -- 当你在处理单个失败问题时,优先使用定向运行。 +- 在配置宽裕的机器上更快地运行本地完整测试套件:`pnpm test:max` +- 直接进入 Vitest 观察循环:`pnpm test:watch` +- 直接指定文件现在也会路由 `extensions/` / 渠道路径:`pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts` +- 当你在迭代修复单个失败用例时,优先先运行定向测试。 - 基于 Docker 的 QA 站点:`pnpm qa:lab:up` - 基于 Linux VM 的 QA 通道:`pnpm openclaw qa suite --runner multipass --scenario channel-chat-baseline` -当你修改测试或想要更多信心时: +当你修改了测试,或者想要更高把握时: - 覆盖率门禁:`pnpm test:coverage` -- E2E 测试套件:`pnpm test:e2e` +- E2E 套件:`pnpm test:e2e` -当你调试真实提供商 / 模型时(需要真实凭证): +当你在调试真实提供商 / 模型时(需要真实凭证): -- 实时测试套件(模型 + Gateway 网关工具 / 图像探测):`pnpm test:live` -- 安静地仅针对一个实时文件:`pnpm test:live -- src/agents/models.profiles.live.test.ts` +- 实时套件(模型 + Gateway 网关工具 / 图像探测):`pnpm test:live` +- 安静地只跑一个实时文件:`pnpm test:live -- src/agents/models.profiles.live.test.ts` - Docker 实时模型扫描:`pnpm test:docker:live-models` - - 现在每个选中的模型都会运行一次文本轮次外加一个小型的文件读取风格探测。元数据声明支持 `image` 输入的模型还会运行一个微型图像轮次。隔离提供商故障时,可通过 `OPENCLAW_LIVE_MODEL_FILE_PROBE=0` 或 `OPENCLAW_LIVE_MODEL_IMAGE_PROBE=0` 禁用这些额外探测。 - - CI 覆盖:每日的 `OpenClaw Scheduled Live And E2E Checks` 和手动触发的 `OpenClaw Release Checks` 都会调用可复用的实时 / E2E 工作流,并设置 `include_live_suites: true`,其中包括按提供商分片的独立 Docker 实时模型矩阵任务。 - - 对于聚焦的 CI 重跑,可派发 `OpenClaw Live And E2E Checks (Reusable)`,并设置 `include_live_suites: true` 与 `live_models_only: true`。 - - 添加新的高信号提供商密钥时,还要更新 `scripts/ci-hydrate-live-auth.sh`、`.github/workflows/openclaw-live-and-e2e-checks-reusable.yml` 以及它的 scheduled / release 调用方。 -- Moonshot / Kimi 成本冒烟测试:设置 `MOONSHOT_API_KEY` 后,运行 `openclaw models list --provider moonshot --json`,然后针对 `moonshot/kimi-k2.6` 运行隔离命令 `openclaw agent --local --session-id live-kimi-cost --message 'Reply exactly: KIMI_LIVE_OK' --thinking off --json`。验证 JSON 报告的是 Moonshot / K2.6,并且助手转录中存储了规范化的 `usage.cost`。 + - 现在每个选中的模型都会运行一次文本轮次以及一个小型的类文件读取探测。元数据声明支持 `image` 输入的模型还会再运行一个微型图像轮次。 + 在隔离提供商故障时,可通过 `OPENCLAW_LIVE_MODEL_FILE_PROBE=0` 或 + `OPENCLAW_LIVE_MODEL_IMAGE_PROBE=0` 禁用这些额外探测。 + - CI 覆盖:每日执行的 `OpenClaw Scheduled Live And E2E Checks` 以及手动执行的 + `OpenClaw Release Checks` 都会调用可复用的实时 / E2E 工作流,并设置 + `include_live_suites: true`,其中包含按提供商分片的独立 Docker 实时模型矩阵作业。 + - 对于聚焦的 CI 重跑,可调度 `OpenClaw Live And E2E Checks (Reusable)`, + 并设置 `include_live_suites: true` 和 `live_models_only: true`。 + - 添加新的高信号提供商密钥时,需要同时更新 `scripts/ci-hydrate-live-auth.sh`、 + `.github/workflows/openclaw-live-and-e2e-checks-reusable.yml` 以及其 + scheduled / release 调用方。 +- Moonshot / Kimi 成本冒烟测试:设置 `MOONSHOT_API_KEY` 后,运行 + `openclaw models list --provider moonshot --json`,然后针对 `moonshot/kimi-k2.6` + 运行独立命令 + `openclaw agent --local --session-id live-kimi-cost --message 'Reply exactly: KIMI_LIVE_OK' --thinking off --json`。 + 验证 JSON 报告的是 Moonshot / K2.6,并且 assistant transcript 存储了已规范化的 `usage.cost`。 -提示:如果你只需要一个失败用例,优先使用下文所述的 allowlist 环境变量来缩小实时测试范围。 +提示:当你只需要一个失败用例时,优先使用下文介绍的 allowlist 环境变量来缩小实时测试范围。 ## QA 专用运行器 -当你需要 QA-lab 的真实感时,这些命令与主测试套件并列使用: +当你需要 QA-lab 级别的真实性时,这些命令与主测试套件并列使用: -CI 会在专用工作流中运行 QA Lab。`Parity gate` 会在匹配的 PR 上运行,也可通过手动派发结合 mock 提供商运行。`QA-Lab - All Lanes` 会在 `main` 上每晚运行,也可通过手动派发,以 mock parity gate、实时 Matrix 通道,以及由 Convex 管理的实时 Telegram 通道作为并行任务运行。`OpenClaw Release Checks` 会在发布批准前运行相同的通道。 +CI 会在专用工作流中运行 QA Lab。`Parity gate` 会在匹配的 PR 上运行,也可通过手动调度以 mock 提供商运行。`QA-Lab - All Lanes` 会在 `main` 上每晚运行,也可通过手动调度并行运行 mock parity gate、实时 Matrix 通道以及由 Convex 管理的实时 Telegram 通道。`OpenClaw Release Checks` 会在发布审批前运行同样的通道。 - `pnpm openclaw qa suite` - - 直接在宿主机上运行基于仓库的 QA 场景。 - - 默认会通过隔离的 Gateway 网关 worker 并行运行多个选定场景。`qa-channel` 默认并发数为 4(受所选场景数量限制)。使用 `--concurrency ` 调整 worker 数量,或使用 `--concurrency 1` 运行旧的串行通道。 - - 任何场景失败时都会以非零状态退出。如果你想要保留产物但不以失败退出码结束,请使用 `--allow-failures`。 - - 支持提供商模式 `live-frontier`、`mock-openai` 和 `aimock`。`aimock` 会启动一个本地 AIMock 支持的提供商服务器,用于实验性夹具和协议模拟覆盖,但不会替代具备场景感知的 `mock-openai` 通道。 + - 直接在宿主机上运行仓库支持的 QA 场景。 + - 默认会以隔离的 Gateway 网关 worker 并行运行多个已选场景。`qa-channel` 默认并发数为 4(受所选场景数量限制)。使用 `--concurrency ` 调整 worker 数量,或使用 `--concurrency 1` 回到旧的串行通道。 + - 任一场景失败时以非零状态退出。当你想要保留产物但不希望退出码失败时,使用 `--allow-failures`。 + - 支持提供商模式 `live-frontier`、`mock-openai` 和 `aimock`。 + `aimock` 会启动一个本地的、由 AIMock 驱动的提供商服务器,用于实验性的 fixture 和协议 mock 覆盖,而不会替代具备场景感知能力的 `mock-openai` 通道。 - `pnpm openclaw qa suite --runner multipass` - - 在一次性 Multipass Linux VM 中运行相同的 QA 测试套件。 + - 在一次性 Multipass Linux VM 内运行同一套 QA 测试套件。 - 与宿主机上的 `qa suite` 保持相同的场景选择行为。 - - 复用与 `qa suite` 相同的提供商 / 模型选择标志。 - - 实时运行会转发对来宾机实际可行的受支持 QA 认证输入:基于环境变量的提供商密钥、QA 实时提供商配置路径,以及存在时的 `CODEX_HOME`。 - - 输出目录必须保持在仓库根目录下,以便来宾机可以通过挂载的工作区回写内容。 - - 将常规 QA 报告、摘要以及 Multipass 日志写入 `.artifacts/qa-e2e/...`。 + - 复用与 `qa suite` 相同的提供商 / 模型选择参数。 + - 实时运行会转发对来宾系统可行的受支持 QA 认证输入:基于环境变量的提供商密钥、QA 实时提供商配置路径,以及存在时的 `CODEX_HOME`。 + - 输出目录必须保持在仓库根目录下,这样来宾系统才能通过挂载的工作区回写。 + - 在 `.artifacts/qa-e2e/...` 下写入常规 QA 报告与摘要,以及 Multipass 日志。 - `pnpm qa:lab:up` - - 启动基于 Docker 的 QA 站点,用于面向操作人员风格的 QA 工作。 + - 启动基于 Docker 的 QA 站点,用于偏操作员风格的 QA 工作。 - `pnpm test:docker:npm-onboard-channel-agent` - - 从当前检出构建 npm tarball,在 Docker 中全局安装,运行非交互式 OpenAI API 密钥新手引导,默认配置 Telegram,验证启用插件时会按需安装运行时依赖,运行 doctor,并针对一个模拟的 OpenAI 端点运行一次本地智能体轮次。 - - 使用 `OPENCLAW_NPM_ONBOARD_CHANNEL=discord` 可用 Discord 运行相同的打包安装通道。 + - 从当前 checkout 构建 npm tarball,在 Docker 中全局安装,以非交互方式完成 OpenAI API key 新手引导,默认配置 Telegram,验证启用插件时会按需安装运行时依赖,运行 doctor,并针对一个模拟的 OpenAI 端点执行一次本地智能体轮次。 + - 使用 `OPENCLAW_NPM_ONBOARD_CHANNEL=discord` 可以用 Discord 运行同一条打包安装通道。 - `pnpm test:docker:bundled-channel-deps` - - 在 Docker 中打包并安装当前 OpenClaw 构建,配置 OpenAI 后启动 Gateway 网关,然后通过编辑配置启用内置渠道 / 插件。 - - 验证设置发现流程会让未配置插件的运行时依赖保持未安装状态,首个已配置的 Gateway 网关或 doctor 运行会按需安装每个内置插件的运行时依赖,而第二次重启不会重新安装已激活的依赖。 - - 还会安装一个已知较旧的 npm 基线,在运行 `openclaw update --tag ` 前启用 Telegram,并验证候选版本的更新后 doctor 会修复内置渠道运行时依赖,而无需测试工具侧进行 postinstall 修复。 + - 在 Docker 中打包并安装当前 OpenClaw 构建,启动已配置 OpenAI 的 Gateway 网关,然后通过配置编辑启用内置渠道 / 插件。 + - 验证设置发现流程会让未配置插件的运行时依赖保持未安装状态;首次配置的 Gateway 网关或 doctor 运行会按需安装每个内置插件的运行时依赖;第二次重启不会重新安装已激活的依赖。 + - 还会安装一个已知较旧的 npm 基线,在运行 `openclaw update --tag ` 之前启用 Telegram,并验证候选版本的更新后 doctor 会修复内置渠道运行时依赖,而无需测试框架侧的 postinstall 修复。 - `pnpm openclaw qa aimock` - - 仅启动本地 AIMock 提供商服务器,用于直接协议冒烟测试。 + - 只启动本地 AIMock 提供商服务器,用于直接协议冒烟测试。 - `pnpm openclaw qa matrix` - - 针对一次性的、基于 Docker 的 Tuwunel homeserver 运行 Matrix 实时 QA 通道。 - - 该 QA 宿主目前仅供仓库 / 开发使用。打包后的 OpenClaw 安装不包含 `qa-lab`,因此不会暴露 `openclaw qa`。 - - 仓库检出会直接加载内置运行器;不需要单独安装插件步骤。 - - 会创建三个临时 Matrix 用户(`driver`、`sut`、`observer`)以及一个私有房间,然后以真实 Matrix 插件作为 SUT 传输启动一个 QA gateway 子进程。 - - 默认使用固定稳定版 Tuwunel 镜像 `ghcr.io/matrix-construct/tuwunel:v1.5.1`。如需测试其他镜像,可通过 `OPENCLAW_QA_MATRIX_TUWUNEL_IMAGE` 覆盖。 - - Matrix 不公开共享凭证源标志,因为该通道会在本地创建一次性用户。 - - 将 Matrix QA 报告、摘要、observed-events 产物以及合并的 stdout / stderr 输出日志写入 `.artifacts/qa-e2e/...`。 + - 针对一个一次性的、基于 Docker 的 Tuwunel homeserver 运行 Matrix 实时 QA 通道。 + - 该 QA 宿主今天仅供仓库 / 开发使用。打包后的 OpenClaw 安装不包含 `qa-lab`,因此不会暴露 `openclaw qa`。 + - 仓库 checkout 会直接加载内置运行器;无需单独安装插件。 + - 预配三个临时 Matrix 用户(`driver`、`sut`、`observer`)以及一个私有房间,然后以真实 Matrix 插件作为 SUT 传输层启动一个 QA gateway 子进程。 + - 默认使用固定的稳定 Tuwunel 镜像 `ghcr.io/matrix-construct/tuwunel:v1.5.1`。当你需要测试不同镜像时,可用 `OPENCLAW_QA_MATRIX_TUWUNEL_IMAGE` 覆盖。 + - 由于该通道会在本地预配一次性用户,因此 Matrix 不暴露共享凭证源参数。 + - 在 `.artifacts/qa-e2e/...` 下写入 Matrix QA 报告、摘要、observed-events 产物以及合并后的 stdout / stderr 输出日志。 - `pnpm openclaw qa telegram` - - 使用环境变量中的 driver 和 SUT 机器人令牌,针对真实私有群组运行 Telegram 实时 QA 通道。 - - 需要 `OPENCLAW_QA_TELEGRAM_GROUP_ID`、`OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN` 和 `OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN`。群组 id 必须是 Telegram 聊天的数字 id。 - - 支持 `--credential-source convex` 以使用共享池化凭证。默认使用环境变量模式,或设置 `OPENCLAW_QA_CREDENTIAL_SOURCE=convex` 以选择加入池化租约。 - - 任何场景失败时都会以非零状态退出。如果你想要保留产物但不以失败退出码结束,请使用 `--allow-failures`。 - - 需要同一私有群组中的两个不同机器人,且 SUT 机器人需要公开一个 Telegram 用户名。 - - 为了稳定地观察机器人到机器人的行为,请在 `@BotFather` 中为两个机器人启用 Bot-to-Bot Communication Mode,并确保 driver 机器人能够观察群组中的机器人流量。 - - 将 Telegram QA 报告、摘要以及 observed-messages 产物写入 `.artifacts/qa-e2e/...`。回复场景包含从 driver 发送请求到观察到 SUT 回复的 RTT。 + - 使用环境变量中的 driver 与 SUT bot token,针对真实私有群组运行 Telegram 实时 QA 通道。 + - 需要 `OPENCLAW_QA_TELEGRAM_GROUP_ID`、`OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN` 和 `OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN`。group id 必须是 Telegram chat 的数字 id。 + - 支持 `--credential-source convex` 以使用共享凭证池。默认使用 env 模式,或设置 `OPENCLAW_QA_CREDENTIAL_SOURCE=convex` 以启用池化租约。 + - 任一场景失败时以非零状态退出。当你想要保留产物但不希望退出码失败时,使用 `--allow-failures`。 + - 需要两个不同的 bot 位于同一个私有群组中,并且 SUT bot 需要公开一个 Telegram 用户名。 + - 为了获得稳定的 bot 对 bot 观测结果,请在 `@BotFather` 中为两个 bot 启用 Bot-to-Bot Communication Mode,并确保 driver bot 能观察群组中的 bot 流量。 + - 在 `.artifacts/qa-e2e/...` 下写入 Telegram QA 报告、摘要和 observed-messages 产物。回复场景会包含从 driver 发送请求到观测到 SUT 回复之间的 RTT。 -实时传输通道共享一套标准契约,以防新传输协议发生漂移: +实时传输通道共享一套标准契约,以避免新传输层出现漂移: -`qa-channel` 仍然是广泛的合成式 QA 测试套件,不属于实时传输覆盖矩阵的一部分。 +`qa-channel` 仍然是范围广泛的合成 QA 套件,不属于实时传输覆盖矩阵的一部分。 -| 通道 | 金丝雀 | 提及门禁 | allowlist 阻止 | 顶层回复 | 重启恢复 | 线程后续跟进 | 线程隔离 | 反应观察 | 帮助命令 | +| 通道 | Canary | 提及门控 | allowlist 阻止 | 顶层回复 | 重启恢复 | 线程后续跟进 | 线程隔离 | 反应观测 | 帮助命令 | | -------- | ------ | -------------- | --------------- | --------------- | -------------- | ---------------- | ---------------- | -------------------- | ------------ | | Matrix | x | x | x | x | x | x | x | x | | | Telegram | x | | | | | | | | x | ### 通过 Convex 共享 Telegram 凭证(v1) -当为 `openclaw qa telegram` 启用 `--credential-source convex`(或 `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`)时,QA lab 会从基于 Convex 的池中获取一个独占租约,在通道运行期间持续发送心跳,并在关闭时释放该租约。 +当为 `openclaw qa telegram` 启用 `--credential-source convex`(或 `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`)时,QA lab 会从一个由 Convex 支持的池中获取独占租约,在通道运行期间为该租约发送心跳,并在关闭时释放该租约。 参考 Convex 项目脚手架: @@ -117,12 +129,12 @@ CI 会在专用工作流中运行 QA Lab。`Parity gate` 会在匹配的 PR 上 必需的环境变量: - `OPENCLAW_QA_CONVEX_SITE_URL`(例如 `https://your-deployment.convex.site`) -- 为所选角色提供一个密钥: - - `maintainer` 使用 `OPENCLAW_QA_CONVEX_SECRET_MAINTAINER` - - `ci` 使用 `OPENCLAW_QA_CONVEX_SECRET_CI` +- 为所选角色配置一个 secret: + - `OPENCLAW_QA_CONVEX_SECRET_MAINTAINER` 对应 `maintainer` + - `OPENCLAW_QA_CONVEX_SECRET_CI` 对应 `ci` - 凭证角色选择: - CLI:`--credential-role maintainer|ci` - - 环境变量默认值:`OPENCLAW_QA_CREDENTIAL_ROLE`(在 CI 中默认为 `ci`,否则默认为 `maintainer`) + - 环境变量默认值:`OPENCLAW_QA_CREDENTIAL_ROLE`(在 CI 中默认是 `ci`,否则为 `maintainer`) 可选环境变量: @@ -136,7 +148,8 @@ CI 会在专用工作流中运行 QA Lab。`Parity gate` 会在匹配的 PR 上 正常运行时,`OPENCLAW_QA_CONVEX_SITE_URL` 应使用 `https://`。 -维护者管理员命令(池添加 / 删除 / 列表)必须特别使用 `OPENCLAW_QA_CONVEX_SECRET_MAINTAINER`。 +维护者管理命令(池的添加 / 删除 / 列表)必须明确使用 +`OPENCLAW_QA_CONVEX_SECRET_MAINTAINER`。 面向维护者的 CLI 辅助命令: @@ -146,87 +159,87 @@ pnpm openclaw qa credentials list --kind telegram pnpm openclaw qa credentials remove --credential-id ``` -在脚本和 CI 工具中使用 `--json` 可获得机器可读输出。 +在脚本和 CI 工具中,使用 `--json` 获取机器可读输出。 默认端点契约(`OPENCLAW_QA_CONVEX_SITE_URL` + `/qa-credentials/v1`): - `POST /acquire` - 请求:`{ kind, ownerId, actorRole, leaseTtlMs, heartbeatIntervalMs }` - 成功:`{ status: "ok", credentialId, leaseToken, payload, leaseTtlMs?, heartbeatIntervalMs? }` - - 资源耗尽 / 可重试:`{ status: "error", code: "POOL_EXHAUSTED" | "NO_CREDENTIAL_AVAILABLE", ... }` + - 已耗尽 / 可重试:`{ status: "error", code: "POOL_EXHAUSTED" | "NO_CREDENTIAL_AVAILABLE", ... }` - `POST /heartbeat` - 请求:`{ kind, ownerId, actorRole, credentialId, leaseToken, leaseTtlMs }` - - 成功:`{ status: "ok" }`(或空 `2xx`) + - 成功:`{ status: "ok" }`(或空的 `2xx`) - `POST /release` - 请求:`{ kind, ownerId, actorRole, credentialId, leaseToken }` - - 成功:`{ status: "ok" }`(或空 `2xx`) -- `POST /admin/add`(仅限 maintainer 密钥) + - 成功:`{ status: "ok" }`(或空的 `2xx`) +- `POST /admin/add`(仅限 maintainer secret) - 请求:`{ kind, actorId, payload, note?, status? }` - 成功:`{ status: "ok", credential }` -- `POST /admin/remove`(仅限 maintainer 密钥) +- `POST /admin/remove`(仅限 maintainer secret) - 请求:`{ credentialId, actorId }` - 成功:`{ status: "ok", changed, credential }` - 活跃租约保护:`{ status: "error", code: "LEASE_ACTIVE", ... }` -- `POST /admin/list`(仅限 maintainer 密钥) +- `POST /admin/list`(仅限 maintainer secret) - 请求:`{ kind?, status?, includePayload?, limit? }` - 成功:`{ status: "ok", credentials, count }` Telegram 类型的负载结构: - `{ groupId: string, driverToken: string, sutToken: string }` -- `groupId` 必须是 Telegram 聊天的数字 id 字符串。 -- `admin/add` 会为 `kind: "telegram"` 验证此结构,并拒绝格式错误的负载。 +- `groupId` 必须是 Telegram chat id 的数字字符串。 +- `admin/add` 会为 `kind: "telegram"` 校验此结构,并拒绝格式错误的负载。 ### 向 QA 添加一个渠道 -向 Markdown QA 系统添加一个渠道,严格只需要两件事: +要将一个渠道添加到 Markdown QA 系统中,严格只需要两样东西: 1. 该渠道的传输适配器。 2. 一个用于验证渠道契约的场景包。 -当共享的 `qa-lab` 宿主可以承载流程时,不要新增顶层 QA 命令根。 +当共享的 `qa-lab` 宿主可以承载整个流程时,不要新增一个顶层 QA 命令根。 `qa-lab` 负责共享宿主机制: - `openclaw qa` 命令根 -- 测试套件启动与拆卸 +- 套件启动与拆除 - worker 并发 - 产物写入 - 报告生成 - 场景执行 -- 旧版 `qa-channel` 场景的兼容别名 +- 对旧版 `qa-channel` 场景的兼容别名 运行器插件负责传输契约: -- `openclaw qa ` 如何挂载到共享的 `qa` 根下 +- `openclaw qa ` 如何挂载到共享 `qa` 根之下 - 如何为该传输配置 gateway - 如何检查就绪状态 - 如何注入入站事件 -- 如何观察出站消息 -- 如何暴露转录与规范化的传输状态 +- 如何观测出站消息 +- 如何暴露 transcript 和规范化后的传输状态 - 如何执行由传输支持的操作 - 如何处理传输特定的重置或清理 -新渠道的最低采纳门槛是: +新渠道的最低接入门槛是: -1. 保持由 `qa-lab` 负责共享的 `qa` 根。 -2. 在共享的 `qa-lab` 宿主接缝上实现传输运行器。 -3. 将传输特定机制保留在运行器插件或渠道 harness 内。 -4. 将运行器挂载为 `openclaw qa `,而不是注册一个竞争性的根命令。 - 运行器插件应在 `openclaw.plugin.json` 中声明 `qaRunners`,并从 `runtime-api.ts` 导出匹配的 `qaRunnerCliRegistrations` 数组。 - 保持 `runtime-api.ts` 轻量;延迟 CLI 和运行器执行应留在独立的入口点之后。 -5. 在按主题组织的 `qa/scenarios/` 目录下编写或调整 Markdown 场景。 +1. 继续让 `qa-lab` 作为共享 `qa` 根的拥有者。 +2. 在共享的 `qa-lab` 宿主接缝上实现该传输运行器。 +3. 将传输特定机制保留在运行器插件或渠道 harness 内部。 +4. 将运行器挂载为 `openclaw qa `,而不是注册一个相互竞争的根命令。 + 运行器插件应在 `openclaw.plugin.json` 中声明 `qaRunners`,并从 `runtime-api.ts` 导出匹配的 `qaRunnerCliRegistrations` 数组。 + 保持 `runtime-api.ts` 轻量;惰性 CLI 和运行器执行应留在单独的入口点之后。 +5. 在按主题划分的 `qa/scenarios/` 目录下编写或改造 Markdown 场景。 6. 为新场景使用通用场景辅助函数。 -7. 除非仓库正在进行有意迁移,否则要保持现有兼容别名继续可用。 +7. 除非仓库正在进行有意迁移,否则保持现有兼容别名继续可用。 决策规则很严格: -- 如果某个行为可以在 `qa-lab` 中统一表达一次,就放到 `qa-lab`。 -- 如果某个行为依赖单一渠道传输,就保留在该运行器插件或插件 harness 中。 -- 如果某个场景需要多个渠道都可使用的新能力,应添加通用辅助函数,而不是在 `suite.ts` 中添加渠道特定分支。 +- 如果某个行为可以在 `qa-lab` 中只表达一次,就把它放进 `qa-lab`。 +- 如果某个行为依赖于单一渠道传输,就把它保留在对应的运行器插件或插件 harness 中。 +- 如果某个场景需要多个渠道都可使用的新能力,请添加一个通用辅助函数,而不是在 `suite.ts` 中加入渠道特定分支。 - 如果某个行为只对一种传输有意义,就让该场景保持传输特定,并在场景契约中明确说明。 -新场景优先使用的通用辅助函数名称是: +新场景的首选通用辅助函数名称是: - `waitForTransportReady` - `waitForChannelReady` @@ -249,102 +262,104 @@ Telegram 类型的负载结构: - `formatConversationTranscript` - `resetBus` -新的渠道工作应使用通用辅助函数名称。 -兼容别名的存在是为了避免一次性迁移,不应作为新场景编写的范式。 +新的渠道工作应使用通用辅助函数名称。 +兼容别名的存在是为了避免一次性强制迁移,而不是作为新场景编写的范式。 -## 测试套件(各自运行位置) +## 测试套件(各自在哪里运行) -可以把这些测试套件理解为“真实程度逐步增加”(同时不稳定性 / 成本也逐步增加): +可以把这些套件理解为“真实性逐步增加”(同时波动性 / 成本也逐步增加): ### 单元 / 集成(默认) - 命令:`pnpm test` -- 配置:未定向运行使用 `vitest.full-*.config.ts` 分片集合,并且可能会将多项目分片展开为按项目划分的配置,以便并行调度 +- 配置:未指定目标的运行使用 `vitest.full-*.config.ts` 分片集,并且可能会将多项目分片展开为按项目划分的配置,以便并行调度 - 文件:位于 `src/**/*.test.ts`、`packages/**/*.test.ts`、`test/**/*.test.ts` 下的核心 / 单元测试清单,以及 `vitest.unit.config.ts` 覆盖的白名单 `ui` node 测试 - 范围: - 纯单元测试 - - 进程内集成测试(Gateway 网关认证、路由、工具、解析、配置) + - 进程内集成测试(gateway 认证、路由、工具、解析、配置) - 已知缺陷的确定性回归测试 - 预期: - 在 CI 中运行 - 不需要真实密钥 - 应当快速且稳定 - - 未定向的 `pnpm test` 会运行十二个更小的分片配置(`core-unit-fast`、`core-unit-src`、`core-unit-security`、`core-unit-ui`、`core-unit-support`、`core-support-boundary`、`core-contracts`、`core-bundled`、`core-runtime`、`agentic`、`auto-reply`、`extensions`),而不是一个巨大的原生根项目进程。这样可以降低高负载机器上的峰值 RSS,并避免 auto-reply / 扩展工作拖累无关测试套件。 - `pnpm test --watch` 仍然使用原生根 `vitest.config.ts` 项目图,因为多分片观察循环并不现实。 - `pnpm test`、`pnpm test:watch` 和 `pnpm test:perf:imports` 会先通过定向通道路由显式的文件 / 目录目标,因此 `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts` 可以避免承担完整根项目启动开销。 - 当变更仅涉及可路由的源文件 / 测试文件时,`pnpm test:changed` 会将变更的 git 路径展开到相同的定向通道;配置 / 设置编辑仍会回退到更广泛的根项目重跑。 - `pnpm check:changed` 是针对窄范围工作的常规智能本地门禁。它会将 diff 分类为核心、核心测试、扩展、扩展测试、应用、文档、发布元数据和工具,然后运行对应的 typecheck / lint / 测试通道。公开的 Plugin SDK 和插件契约变更会包含扩展验证,因为扩展依赖这些核心契约。仅包含发布元数据版本提升的变更会运行定向的版本 / 配置 / 根依赖检查,而不是完整套件,并带有一个保护机制,用于拒绝超出顶层版本字段之外的包变更。 - 来自智能体、命令、插件、auto-reply 辅助函数、`plugin-sdk` 以及类似纯工具区域的轻导入单元测试,会通过 `unit-fast` 通道运行,该通道会跳过 `test/setup-openclaw-runtime.ts`;有状态 / 运行时较重的文件则保留在现有通道上。 - 部分选定的 `plugin-sdk` 和 `commands` 辅助源码文件也会在 changed 模式运行时映射到这些轻量通道中的显式同级测试,因此辅助函数编辑可以避免为该目录重跑完整的重型测试套件。 - `auto-reply` 有三个专用桶:顶层核心辅助函数、顶层 `reply.*` 集成测试,以及 `src/auto-reply/reply/**` 子树。这可让最重的 reply harness 工作不影响轻量的状态 / 分块 / token 测试。 + - 未指定目标的 `pnpm test` 会运行十二个更小的分片配置(`core-unit-fast`、`core-unit-src`、`core-unit-security`、`core-unit-ui`、`core-unit-support`、`core-support-boundary`、`core-contracts`、`core-bundled`、`core-runtime`、`agentic`、`auto-reply`、`extensions`),而不是启动一个巨大的原生根项目进程。这能降低高负载机器上的峰值 RSS,并避免 auto-reply / extension 任务拖慢无关测试套件。 - `pnpm test --watch` 仍然使用原生根 `vitest.config.ts` 项目图,因为多分片 watch 循环并不现实。 - `pnpm test`、`pnpm test:watch` 和 `pnpm test:perf:imports` 会优先通过定向通道来路由显式的文件 / 目录目标,因此 `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts` 无需付出完整根项目启动的代价。 - 当变更 diff 仅涉及可路由的源文件 / 测试文件时,`pnpm test:changed` 会将变更的 git 路径展开到同样的定向通道中;配置 / setup 编辑仍会回退到更广泛的根项目重跑。 - `pnpm check:changed` 是针对小范围工作的常规智能本地门禁。它会将 diff 归类为核心、核心测试、extensions、extension 测试、apps、文档、发布元数据和工具,并运行对应的 typecheck / lint / 测试通道。公共插件 SDK 和插件契约的变更会包含 extension 验证,因为 extensions 依赖这些核心契约。仅涉及发布元数据的版本号变更会运行定向的 version / config / root-dependency 检查,而不是完整套件,并带有一个防护规则,拒绝顶层 version 字段之外的 package 变更。 - 来自 agents、commands、plugins、auto-reply 辅助函数、`plugin-sdk` 以及类似纯工具区域的轻导入单元测试,会通过 `unit-fast` 通道路由,并跳过 `test/setup-openclaw-runtime.ts`;有状态 / 运行时较重的文件仍保留在现有通道上。 - 部分 `plugin-sdk` 和 `commands` 辅助源文件也会在 changed 模式下将运行映射到这些轻量通道中的显式同级测试,因此辅助函数的编辑无需为该目录重跑完整的重型套件。 - `auto-reply` 有三个专用桶:顶层核心辅助函数、顶层 `reply.*` 集成测试,以及 `src/auto-reply/reply/**` 子树。这样可以让最重的 reply harness 工作避开廉价的 status / chunk / token 测试。 - - 当你修改消息工具发现输入或压缩运行时上下文时,要同时保持两个层级的覆盖。 + - 当你修改消息工具发现输入或压缩运行时上下文时,请保持两个层级的覆盖。 - 为纯路由和规范化边界添加聚焦的辅助函数回归测试。 - - 保持嵌入式运行器集成测试套件健康: + - 保持嵌入式运行器集成套件健康: `src/agents/pi-embedded-runner/compact.hooks.test.ts`、 - `src/agents/pi-embedded-runner/run.overflow-compaction.test.ts`,以及 + `src/agents/pi-embedded-runner/run.overflow-compaction.test.ts` 和 `src/agents/pi-embedded-runner/run.overflow-compaction.loop.test.ts`。 - - 这些测试套件会验证带作用域的 id 和压缩行为仍然会流经真实的 `run.ts` / `compact.ts` 路径;仅有辅助函数测试不足以替代这些集成路径。 + - 这些套件会验证作用域 id 和压缩行为仍然通过真实的 `run.ts` / `compact.ts` 路径流动;仅有辅助函数测试并不足以替代这些集成路径。 - 基础 Vitest 配置默认使用 `threads`。 - - 共享 Vitest 配置固定 `isolate: false`,并在根项目、e2e 和实时配置中使用非隔离运行器。 - - 根 UI 通道保留其 `jsdom` 设置和优化器,但也在共享的非隔离运行器上运行。 + - 共享 Vitest 配置固定设置 `isolate: false`,并在根项目、e2e 和实时配置中使用非隔离运行器。 + - 根 UI 通道保留其 `jsdom` setup 和优化器,但也运行在共享的非隔离运行器上。 - 每个 `pnpm test` 分片都会从共享 Vitest 配置继承相同的 `threads` + `isolate: false` 默认值。 - - `scripts/run-vitest.mjs` 默认会为 Vitest 子 Node 进程添加 `--no-maglev`,以减少大型本地运行期间的 V8 编译抖动。设置 `OPENCLAW_VITEST_ENABLE_MAGLEV=1` 可与原生 V8 行为进行对比。 + - `scripts/run-vitest.mjs` 默认会为 Vitest 子 Node 进程添加 `--no-maglev`,以减少大型本地运行期间的 V8 编译抖动。 + 设置 `OPENCLAW_VITEST_ENABLE_MAGLEV=1` 可与默认 V8 行为进行对比。 - - `pnpm changed:lanes` 会显示一个 diff 触发了哪些架构通道。 - - 预提交钩子会在 staged 格式化 / lint 之后运行 `pnpm check:changed --staged`,因此仅核心提交不会承担扩展测试成本,除非它们触及面向扩展的公共契约。仅发布元数据提交会保留在定向的版本 / 配置 / 根依赖通道上。 - - 如果完全相同的 staged 变更集已经通过了同等或更强的门禁验证,可以使用 `scripts/committer --fast "" ` 仅跳过 changed-scope 钩子的重跑。staged format / lint 仍会运行。在你的交接说明中提及已完成的门禁。这同样适用于隔离的 flaky 钩子失败在重跑后通过,并具备定向证明的情况。 - - `pnpm test:changed` 会在变更路径能够清晰映射到更小测试套件时,通过定向通道路由。 + - `pnpm changed:lanes` 会显示某个 diff 会触发哪些架构通道。 + - pre-commit hook 会在已暂存的格式化 / lint 之后运行 `pnpm check:changed --staged`,因此纯核心提交不会承担 extension 测试成本,除非它们触及面向 extension 的公共契约。仅发布元数据的提交会保留在定向的 version / config / root-dependency 通道上。 + - 如果完全相同的已暂存变更集已经通过了同等级或更强的门禁验证,可使用 + `scripts/committer --fast "" ` 跳过仅 changed-scope hook 的重跑。已暂存的 format / lint 仍会运行。请在交接说明中提及已完成的门禁。这同样适用于隔离的 flaky hook 失败在重跑后通过,并有定向证据的情况。 + - `pnpm test:changed` 会在变更路径可清晰映射到较小套件时,通过定向通道进行路由。 - `pnpm test:max` 和 `pnpm test:changed:max` 保持相同的路由行为,只是使用更高的 worker 上限。 - - 本地 worker 自动扩缩容有意保持保守;当宿主机负载平均值已经较高时会回退,因此默认情况下多个并发 Vitest 运行带来的影响更小。 - - 基础 Vitest 配置会将项目 / 配置文件标记为 `forceRerunTriggers`,以便在测试接线发生变化时,changed 模式重跑仍然正确。 - - 该配置会在受支持的宿主上保持启用 `OPENCLAW_VITEST_FS_MODULE_CACHE`;如果你希望为直接性能分析指定一个明确的缓存位置,可设置 `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/abs/path`。 + - 本地 worker 自动扩缩容刻意保持保守,并会在宿主机平均负载已经很高时回退,因此默认情况下多个并发 Vitest 运行造成的影响更小。 + - 基础 Vitest 配置会将项目 / 配置文件标记为 `forceRerunTriggers`,从而在测试接线发生变化时,保证 changed 模式重跑依然正确。 + - 配置会在受支持的宿主机上保持启用 `OPENCLAW_VITEST_FS_MODULE_CACHE`;如果你希望为直接性能分析指定一个明确的缓存位置,可设置 `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/abs/path`。 - `pnpm test:perf:imports` 会启用 Vitest 导入耗时报告以及导入拆解输出。 - - `pnpm test:perf:imports:changed` 会将相同的性能分析视图限制在自 `origin/main` 以来变更的文件上。 - - `pnpm test:perf:changed:bench -- --ref ` 会针对该已提交 diff,将路由后的 `test:changed` 与原生根项目路径进行比较,并输出墙钟时间以及 macOS 最大 RSS。 - - `pnpm test:perf:changed:bench -- --worktree` 会通过 `scripts/test-projects.mjs` 和根 Vitest 配置,将变更文件列表路由后,对当前脏工作树进行基准测试。 - - `pnpm test:perf:profile:main` 会为 Vitest / Vite 启动与 transform 开销写入主线程 CPU profile。 - - `pnpm test:perf:profile:runner` 会在禁用文件并行的情况下,为单元测试套件写入运行器 CPU + 堆 profile。 + - `pnpm test:perf:imports:changed` 会将同样的分析视图限定到自 `origin/main` 以来发生变更的文件。 + - `pnpm test:perf:changed:bench -- --ref ` 会将定向的 `test:changed` 与该已提交 diff 的原生根项目路径进行比较,并打印 wall time 与 macOS 最大 RSS。 + - `pnpm test:perf:changed:bench -- --worktree` 会通过 `scripts/test-projects.mjs` 和根 Vitest 配置,将当前有未提交变更的工作树文件列表进行路由,并为其做基准测试。 + - `pnpm test:perf:profile:main` 会写出主线程 CPU profile,用于分析 Vitest / Vite 启动和 transform 开销。 + - `pnpm test:perf:profile:runner` 会在关闭文件并行的情况下,为单元测试套件写出运行器 CPU + heap profile。 -### 稳定性(Gateway 网关) +### 稳定性(gateway) - 命令:`pnpm test:stability:gateway` -- 配置:`vitest.gateway.config.ts`,强制使用一个 worker +- 配置:`vitest.gateway.config.ts`,强制使用单个 worker - 范围: - - 启动一个真实的 loopback Gateway 网关,默认启用诊断 - - 通过诊断事件路径驱动合成的 gateway 消息、内存和大负载 churn + - 启动一个默认启用诊断的真实 loopback Gateway 网关 + - 通过诊断事件路径驱动合成的 gateway 消息、内存和大负载抖动 - 通过 Gateway 网关 WS RPC 查询 `diagnostics.stability` - 覆盖诊断稳定性 bundle 持久化辅助函数 - - 断言记录器保持有界、合成 RSS 采样保持在压力预算之下,并且每个会话的队列深度会回落到零 + - 断言记录器保持有界、合成 RSS 样本保持在压力预算之下,并且每个会话的队列深度会回落到零 - 预期: - 对 CI 安全且无需密钥 - - 这是用于稳定性回归跟进的窄通道,不可替代完整的 Gateway 网关测试套件 + - 这是一个用于稳定性回归跟进的窄通道,不替代完整 Gateway 网关套件 -### E2E(Gateway 网关冒烟) +### E2E(gateway 冒烟) - 命令:`pnpm test:e2e` - 配置:`vitest.e2e.config.ts` - 文件:`src/**/*.e2e.test.ts`、`test/**/*.e2e.test.ts`,以及 `extensions/` 下的内置插件 E2E 测试 - 运行时默认值: - - 使用 Vitest `threads`,并设置 `isolate: false`,与仓库其余部分保持一致。 + - 使用 Vitest `threads` 且设置 `isolate: false`,与仓库其余部分保持一致。 - 使用自适应 worker(CI:最多 2 个,本地:默认 1 个)。 - 默认以静默模式运行,以减少控制台 I/O 开销。 - 常用覆盖项: - - 使用 `OPENCLAW_E2E_WORKERS=` 强制指定 worker 数量(上限为 16)。 - - 使用 `OPENCLAW_E2E_VERBOSE=1` 重新启用详细控制台输出。 + - `OPENCLAW_E2E_WORKERS=` 强制指定 worker 数量(上限为 16)。 + - `OPENCLAW_E2E_VERBOSE=1` 重新启用详细控制台输出。 - 范围: - 多实例 gateway 端到端行为 - - WebSocket / HTTP 接口、节点配对,以及更重的网络行为 + - WebSocket / HTTP 接口、节点配对,以及更重的网络交互 - 预期: - - 会在 CI 中运行(当流水线启用时) + - 会在 CI 中运行(当流水线中启用时) - 不需要真实密钥 - - 比单元测试涉及更多可变部件(可能更慢) + - 比单元测试涉及更多活动部件(可能更慢) ### E2E:OpenShell 后端冒烟测试 @@ -352,146 +367,146 @@ Telegram 类型的负载结构: - 文件:`extensions/openshell/src/backend.e2e.test.ts` - 范围: - 通过 Docker 在宿主机上启动一个隔离的 OpenShell gateway - - 从临时本地 Dockerfile 创建一个沙箱 + - 从一个临时本地 Dockerfile 创建沙箱 - 通过真实的 `sandbox ssh-config` + SSH exec 运行 OpenClaw 的 OpenShell 后端 - 通过沙箱 fs bridge 验证远端规范文件系统行为 - 预期: - - 仅在选择加入时运行;不属于默认 `pnpm test:e2e` 运行的一部分 + - 仅按需启用;不属于默认的 `pnpm test:e2e` 运行 - 需要本地 `openshell` CLI 和可用的 Docker daemon - 使用隔离的 `HOME` / `XDG_CONFIG_HOME`,然后销毁测试 gateway 和沙箱 - 常用覆盖项: - - 在手动运行更广泛的 e2e 套件时,使用 `OPENCLAW_E2E_OPENSHELL=1` 启用该测试 - - 使用 `OPENCLAW_E2E_OPENSHELL_COMMAND=/path/to/openshell` 指向非默认 CLI 二进制或包装脚本 + - `OPENCLAW_E2E_OPENSHELL=1` 在手动运行更广泛的 e2e 套件时启用该测试 + - `OPENCLAW_E2E_OPENSHELL_COMMAND=/path/to/openshell` 指向非默认 CLI 二进制文件或包装脚本 -### 实时(真实提供商 + 真实模型) +### 实时测试(真实提供商 + 真实模型) - 命令:`pnpm test:live` - 配置:`vitest.live.config.ts` - 文件:`src/**/*.live.test.ts`、`test/**/*.live.test.ts`,以及 `extensions/` 下的内置插件实时测试 -- 默认:由 `pnpm test:live` **启用**(设置 `OPENCLAW_LIVE_TEST=1`) +- 默认:由 `pnpm test:live` **启用**(会设置 `OPENCLAW_LIVE_TEST=1`) - 范围: - - “这个提供商 / 模型 _今天_ 使用真实凭证是否真的可用?” - - 捕获提供商格式变更、工具调用怪异行为、认证问题和速率限制行为 + - “这个提供商 / 模型在 _今天_ 使用真实凭证时是否真的可用?” + - 捕获提供商格式变化、工具调用怪癖、认证问题以及速率限制行为 - 预期: - - 按设计不具备 CI 稳定性(真实网络、真实提供商策略、配额、故障) - - 会花钱 / 使用速率配额 - - 优先运行缩小范围的子集,而不是“全部都跑” -- 实时运行会读取 `~/.profile` 以获取缺失的 API 密钥。 -- 默认情况下,实时运行仍会隔离 `HOME`,并将配置 / 认证材料复制到临时测试 home 中,这样单元测试夹具就不会修改你真实的 `~/.openclaw`。 -- 仅当你有意让实时测试使用真实主目录时,才设置 `OPENCLAW_LIVE_USE_REAL_HOME=1`。 -- `pnpm test:live` 现在默认使用更安静的模式:会保留 `[live] ...` 进度输出,但会抑制额外的 `~/.profile` 提示,并静默 gateway 引导日志 / Bonjour 噪声。如果你想恢复完整启动日志,请设置 `OPENCLAW_LIVE_TEST_QUIET=0`。 -- API 密钥轮换(提供商特定):设置 `*_API_KEYS`,使用逗号 / 分号格式,或设置 `*_API_KEY_1`、`*_API_KEY_2`(例如 `OPENAI_API_KEYS`、`ANTHROPIC_API_KEYS`、`GEMINI_API_KEYS`),也可通过 `OPENCLAW_LIVE_*_KEY` 进行每次实时运行的覆盖;测试会在速率限制响应时重试。 + - 按设计不保证在 CI 中稳定(真实网络、真实提供商策略、配额、故障) + - 会花钱 / 消耗速率限制 + - 优先运行缩小范围的子集,而不是“全部” +- 实时运行会 source `~/.profile`,以获取缺失的 API key。 +- 默认情况下,实时运行仍会隔离 `HOME`,并将配置 / 认证材料复制到临时测试 home 中,这样单元测试 fixture 就不会修改你真实的 `~/.openclaw`。 +- 只有当你明确需要让实时测试使用真实 home 目录时,才设置 `OPENCLAW_LIVE_USE_REAL_HOME=1`。 +- `pnpm test:live` 现在默认采用更安静的模式:它会保留 `[live] ...` 进度输出,但会抑制额外的 `~/.profile` 提示,并静音 gateway 启动日志 / Bonjour 杂讯。如果你想恢复完整启动日志,请设置 `OPENCLAW_LIVE_TEST_QUIET=0`。 +- API key 轮换(按提供商):设置 `*_API_KEYS`,使用逗号 / 分号格式,或设置 `*_API_KEY_1`、`*_API_KEY_2`(例如 `OPENAI_API_KEYS`、`ANTHROPIC_API_KEYS`、`GEMINI_API_KEYS`),或者通过 `OPENCLAW_LIVE_*_KEY` 做单次实时覆盖;测试会在遇到速率限制响应时重试。 - 进度 / 心跳输出: - - 实时测试套件现在会将进度行输出到 stderr,因此即使 Vitest 控制台捕获处于安静模式,长时间的提供商调用也能显示为正在活动。 - - `vitest.live.config.ts` 会禁用 Vitest 控制台拦截,因此提供商 / gateway 进度行会在实时运行期间立即流式输出。 - - 使用 `OPENCLAW_LIVE_HEARTBEAT_MS` 调整直连模型心跳。 - - 使用 `OPENCLAW_LIVE_GATEWAY_HEARTBEAT_MS` 调整 gateway / 探测心跳。 + - 实时套件现在会将进度行输出到 stderr,这样即使 Vitest 控制台捕获较安静,长时间的提供商调用也能明显显示为活跃状态。 + - `vitest.live.config.ts` 会禁用 Vitest 的控制台拦截,因此提供商 / gateway 进度行会在实时运行期间立即流式输出。 + - 使用 `OPENCLAW_LIVE_HEARTBEAT_MS` 调整直连模型的心跳。 + - 使用 `OPENCLAW_LIVE_GATEWAY_HEARTBEAT_MS` 调整 gateway / probe 心跳。 -## 我应该运行哪个测试套件? +## 我应该运行哪个套件? -使用这张决策表: +使用下面的决策表: - 编辑逻辑 / 测试:运行 `pnpm test`(如果你改动很多,再加上 `pnpm test:coverage`) -- 涉及 gateway 网络 / WS 协议 / 配对:加跑 `pnpm test:e2e` -- 调试“我的机器人挂了” / 提供商特定故障 / 工具调用:运行缩小范围的 `pnpm test:live` +- 修改 gateway 网络 / WS 协议 / 配对:额外运行 `pnpm test:e2e` +- 调试“我的 bot 挂了” / 提供商特定故障 / 工具调用:运行一个缩小范围的 `pnpm test:live` -## 实时:Android 节点能力扫描 +## 实时测试:Android 节点能力扫描 - 测试:`src/gateway/android-node.capabilities.live.test.ts` - 脚本:`pnpm android:test:integration` -- 目标:调用已连接 Android 节点当前通告的**每一个命令**,并断言命令契约行为。 +- 目标:调用一个已连接 Android 节点当前**发布的每个命令**,并断言命令契约行为。 - 范围: - - 预置 / 手动设置(该套件不会安装 / 运行 / 配对应用)。 - - 针对所选 Android 节点的逐命令 gateway `node.invoke` 验证。 + - 依赖前置条件 / 手动设置(该套件不会安装 / 运行 / 配对 app)。 + - 对所选 Android 节点逐个命令进行 gateway `node.invoke` 验证。 - 必需的预先设置: - - Android 应用已连接并与 gateway 配对。 - - 应用保持在前台。 - - 为你期望通过的能力授予权限 / 捕获同意。 + - Android app 已连接并与 gateway 配对。 + - app 保持在前台。 + - 你期望通过的能力所需的权限 / 捕获授权已授予。 - 可选目标覆盖项: - `OPENCLAW_ANDROID_NODE_ID` 或 `OPENCLAW_ANDROID_NODE_NAME`。 - `OPENCLAW_ANDROID_GATEWAY_URL` / `OPENCLAW_ANDROID_GATEWAY_TOKEN` / `OPENCLAW_ANDROID_GATEWAY_PASSWORD`。 - 完整 Android 设置详情:[Android App](/zh-CN/platforms/android) -## 实时:模型冒烟测试(profile keys) +## 实时测试:模型冒烟测试(profile key) -实时测试分为两层,以便我们隔离故障: +实时测试分为两层,这样我们可以隔离故障: -- “直连模型”告诉我们该提供商 / 模型是否至少能用给定密钥作出响应。 -- “Gateway 网关冒烟测试”告诉我们完整的 gateway + 智能体流水线是否对该模型有效(会话、历史、工具、沙箱策略等)。 +- “直连模型”告诉我们:在给定 key 下,该提供商 / 模型是否至少能响应。 +- “Gateway 网关冒烟测试”告诉我们:该模型的完整 gateway + 智能体链路是否可用(会话、历史、工具、沙箱策略等)。 ### 第 1 层:直连模型补全(无 gateway) - 测试:`src/agents/models.profiles.live.test.ts` - 目标: - 枚举发现到的模型 - - 使用 `getApiKeyForModel` 选择你拥有凭证的模型 - - 对每个模型运行一个小型补全(以及需要时的定向回归测试) + - 使用 `getApiKeyForModel` 选择你有凭证的模型 + - 对每个模型运行一个小型补全(并在需要时运行定向回归) - 启用方式: - - `pnpm test:live`(或者如果你直接调用 Vitest,则设置 `OPENCLAW_LIVE_TEST=1`) -- 设置 `OPENCLAW_LIVE_MODELS=modern`(或 `all`,即 modern 的别名)才会真正运行此套件;否则它会跳过,以便让 `pnpm test:live` 聚焦于 gateway 冒烟测试 + - `pnpm test:live`(或直接调用 Vitest 时设置 `OPENCLAW_LIVE_TEST=1`) +- 设置 `OPENCLAW_LIVE_MODELS=modern`(或 `all`,是 modern 的别名)才会真正运行此套件;否则它会跳过,以便让 `pnpm test:live` 聚焦于 gateway 冒烟测试 - 如何选择模型: - `OPENCLAW_LIVE_MODELS=modern` 运行 modern allowlist(Opus / Sonnet 4.6+、GPT-5.x + Codex、Gemini 3、GLM 4.7、MiniMax M2.7、Grok 4) - `OPENCLAW_LIVE_MODELS=all` 是 modern allowlist 的别名 - - 或 `OPENCLAW_LIVE_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,..."`(逗号分隔的 allowlist) - - modern / all 扫描默认会应用精选的高信号上限;设置 `OPENCLAW_LIVE_MAX_MODELS=0` 可进行完整 modern 扫描,或设置一个正数以使用更小上限。 + - 或者 `OPENCLAW_LIVE_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,..."`(逗号分隔的 allowlist) + - modern / all 扫描默认带有精心挑选的高信号上限;设置 `OPENCLAW_LIVE_MAX_MODELS=0` 可进行完整的 modern 扫描,或设置为正数以使用更小上限。 - 如何选择提供商: - `OPENCLAW_LIVE_PROVIDERS="google,google-antigravity,google-gemini-cli"`(逗号分隔的 allowlist) -- 密钥来源: +- key 来源: - 默认:profile store 和环境变量回退 - - 设置 `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` 以强制**仅使用 profile store** -- 存在原因: - - 将“提供商 API 已损坏 / 密钥无效”和“gateway 智能体流水线已损坏”区分开来 - - 容纳小型、隔离的回归测试(例如:OpenAI Responses / Codex Responses 推理回放 + 工具调用流程) + - 设置 `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` 可强制**仅使用 profile store** +- 这样设计的原因: + - 将“提供商 API 坏了 / key 无效”与“gateway 智能体链路坏了”分离开来 + - 容纳小型、隔离的回归测试(例如:OpenAI Responses / Codex Responses 的推理重放 + 工具调用流程) -### 第 2 层:Gateway 网关 + dev 智能体冒烟测试(也就是 `@openclaw` 实际做的事) +### 第 2 层:Gateway 网关 + dev 智能体冒烟测试(也就是 “@openclaw” 实际做的事情) - 测试:`src/gateway/gateway-models.profiles.live.test.ts` - 目标: - 启动一个进程内 gateway - 创建 / 修补一个 `agent:dev:*` 会话(每次运行按模型覆盖) - - 遍历带密钥的模型并断言: - - “有意义”的响应(无工具) - - 一次真实工具调用可用(读取探针) - - 可选的额外工具探针(exec + 读取探针) - - OpenAI 回归路径(仅工具调用 → 后续跟进)持续可用 -- 探针详情(这样你可以快速解释失败): - - `read` 探针:测试会在工作区写入一个 nonce 文件,并要求智能体 `read` 该文件并回显 nonce。 - - `exec+read` 探针:测试会要求智能体使用 `exec` 将一个 nonce 写入临时文件,然后再 `read` 回来。 - - 图像探针:测试会附加一个生成的 PNG(猫 + 随机代码),并期望模型返回 `cat `。 + - 遍历有 key 的模型,并断言: + - “有意义的”响应(无工具) + - 一次真实工具调用有效(read probe) + - 可选的额外工具探测有效(exec+read probe) + - OpenAI 回归路径(仅工具调用 → 后续跟进)继续有效 +- 探测细节(这样你可以快速解释故障): + - `read` probe:测试会在工作区写入一个 nonce 文件,并要求智能体 `read` 该文件并原样回显 nonce。 + - `exec+read` probe:测试会要求智能体通过 `exec` 将 nonce 写入临时文件,然后再 `read` 回来。 + - image probe:测试会附加一个生成的 PNG(猫 + 随机代码),并期望模型返回 `cat `。 - 实现参考:`src/gateway/gateway-models.profiles.live.test.ts` 和 `src/gateway/live-image-probe.ts`。 - 启用方式: - - `pnpm test:live`(或者如果你直接调用 Vitest,则设置 `OPENCLAW_LIVE_TEST=1`) + - `pnpm test:live`(或直接调用 Vitest 时设置 `OPENCLAW_LIVE_TEST=1`) - 如何选择模型: - 默认:modern allowlist(Opus / Sonnet 4.6+、GPT-5.x + Codex、Gemini 3、GLM 4.7、MiniMax M2.7、Grok 4) - `OPENCLAW_LIVE_GATEWAY_MODELS=all` 是 modern allowlist 的别名 - - 或设置 `OPENCLAW_LIVE_GATEWAY_MODELS="provider/model"`(或逗号列表)以缩小范围 - - modern / all gateway 扫描默认会应用精选的高信号上限;设置 `OPENCLAW_LIVE_GATEWAY_MAX_MODELS=0` 可进行完整 modern 扫描,或设置一个正数以使用更小上限。 -- 如何选择提供商(避免“OpenRouter 什么都测”): + - 或设置 `OPENCLAW_LIVE_GATEWAY_MODELS="provider/model"`(或逗号列表)来缩小范围 + - modern / all 的 gateway 扫描默认带有精心挑选的高信号上限;设置 `OPENCLAW_LIVE_GATEWAY_MAX_MODELS=0` 可进行完整的 modern 扫描,或设置为正数以使用更小上限。 +- 如何选择提供商(避免“OpenRouter 全家桶”): - `OPENCLAW_LIVE_GATEWAY_PROVIDERS="google,google-antigravity,google-gemini-cli,openai,anthropic,zai,minimax"`(逗号分隔的 allowlist) -- 工具 + 图像探针在这个实时测试中始终开启: - - `read` 探针 + `exec+read` 探针(工具压力测试) - - 当模型通告支持图像输入时,会运行图像探针 +- 工具 + 图像探测在此实时测试中始终启用: + - `read` probe + `exec+read` probe(工具压力测试) + - 当模型声明支持图像输入时,会运行 image probe - 流程(高层): - 测试生成一个带有 “CAT” + 随机代码的微型 PNG(`src/gateway/live-image-probe.ts`) - - 通过 `agent` `attachments: [{ mimeType: "image/png", content: "" }]` 发送 - - Gateway 网关将附件解析为 `images[]`(`src/gateway/server-methods/agent.ts` + `src/gateway/chat-attachments.ts`) - - 嵌入式智能体向模型转发一条多模态用户消息 - - 断言:回复包含 `cat` + 该代码(OCR 容错:允许轻微错误) + - 通过 `agent` 发送 `attachments: [{ mimeType: "image/png", content: "" }]` + - Gateway 网关 将附件解析为 `images[]`(`src/gateway/server-methods/agent.ts` + `src/gateway/chat-attachments.ts`) + - 嵌入式智能体将多模态用户消息转发给模型 + - 断言:回复包含 `cat` + 该代码(OCR 容错:允许小错误) -提示:若要查看你的机器上可以测试哪些内容(以及精确的 `provider/model` id),运行: +提示:如果你想查看你的机器上可以测试哪些内容(以及精确的 `provider/model` id),请运行: ```bash openclaw models list openclaw models list --json ``` -## 实时:CLI 后端冒烟测试(Claude、Codex、Gemini 或其他本地 CLI) +## 实时测试:CLI 后端冒烟测试(Claude、Codex、Gemini 或其他本地 CLI) - 测试:`src/gateway/gateway-cli-backend.live.test.ts` -- 目标:在不触碰你的默认配置的情况下,使用本地 CLI 后端验证 Gateway 网关 + 智能体流水线。 -- 后端特定的默认冒烟测试配置位于所属扩展的 `cli-backend.ts` 定义中。 +- 目标:在不触碰默认配置的情况下,使用本地 CLI 后端验证 Gateway 网关 + 智能体链路。 +- 后端特定的冒烟测试默认值位于所属 extension 的 `cli-backend.ts` 定义中。 - 启用: - - `pnpm test:live`(或者如果你直接调用 Vitest,则设置 `OPENCLAW_LIVE_TEST=1`) + - `pnpm test:live`(或直接调用 Vitest 时设置 `OPENCLAW_LIVE_TEST=1`) - `OPENCLAW_LIVE_CLI_BACKEND=1` - 默认值: - 默认提供商 / 模型:`claude-cli/claude-sonnet-4-6` @@ -500,11 +515,11 @@ openclaw models list --json - `OPENCLAW_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.5"` - `OPENCLAW_LIVE_CLI_BACKEND_COMMAND="/full/path/to/codex"` - `OPENCLAW_LIVE_CLI_BACKEND_ARGS='["exec","--json","--color","never","--sandbox","read-only","--skip-git-repo-check"]'` - - `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=1` 发送一个真实图像附件(路径会注入到提示中)。 - - `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_ARG="--image"` 通过 CLI 参数传递图像文件路径,而不是通过提示注入。 - - `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_MODE="repeat"`(或 `"list"`)用于控制在设置了 `IMAGE_ARG` 时图像参数的传递方式。 + - `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=1` 发送真实图像附件(路径会注入到提示中)。 + - `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_ARG="--image"` 将图像文件路径作为 CLI 参数传递,而不是注入到提示中。 + - `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_MODE="repeat"`(或 `"list"`)控制在设置了 `IMAGE_ARG` 时如何传递图像参数。 - `OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1` 发送第二轮消息并验证恢复流程。 - - `OPENCLAW_LIVE_CLI_BACKEND_MODEL_SWITCH_PROBE=0` 禁用默认的 Claude Sonnet -> Opus 同会话连续性探针(当所选模型支持切换目标时,设置为 `1` 可强制启用)。 + - `OPENCLAW_LIVE_CLI_BACKEND_MODEL_SWITCH_PROBE=0` 禁用默认的 Claude Sonnet -> Opus 同会话连续性探测(当所选模型支持切换目标时,设置为 `1` 可强制启用)。 示例: @@ -532,20 +547,20 @@ pnpm test:docker:live-cli-backend:gemini 说明: - Docker 运行器位于 `scripts/test-live-cli-backend-docker.sh`。 -- 它会在仓库 Docker 镜像内、以非 root 的 `node` 用户运行实时 CLI 后端冒烟测试。 -- 它会从所属扩展解析 CLI 冒烟测试元数据,然后将匹配的 Linux CLI 包(`@anthropic-ai/claude-code`、`@openai/codex` 或 `@google/gemini-cli`)安装到可写的缓存前缀 `OPENCLAW_DOCKER_CLI_TOOLS_DIR` 中(默认:`~/.cache/openclaw/docker-cli-tools`)。 -- `pnpm test:docker:live-cli-backend:claude-subscription` 需要可移植的 Claude Code 订阅 OAuth,可通过带有 `claudeAiOauth.subscriptionType` 的 `~/.claude/.credentials.json`,或来自 `claude setup-token` 的 `CLAUDE_CODE_OAUTH_TOKEN` 提供。它首先会在 Docker 中验证直接 `claude -p` 可用,然后在不保留 Anthropic API 密钥环境变量的情况下运行两轮 Gateway 网关 CLI 后端。该订阅通道默认禁用 Claude MCP / 工具和图像探针,因为 Claude 目前会通过额外用量计费来处理第三方应用使用,而不是走普通订阅计划额度。 +- 它会在仓库 Docker 镜像内,以非 root 的 `node` 用户身份运行实时 CLI 后端冒烟测试。 +- 它会从所属 extension 中解析 CLI 冒烟元数据,然后将匹配的 Linux CLI 包(`@anthropic-ai/claude-code`、`@openai/codex` 或 `@google/gemini-cli`)安装到一个可缓存、可写的前缀 `OPENCLAW_DOCKER_CLI_TOOLS_DIR` 中(默认:`~/.cache/openclaw/docker-cli-tools`)。 +- `pnpm test:docker:live-cli-backend:claude-subscription` 需要通过 `~/.claude/.credentials.json` 中的 `claudeAiOauth.subscriptionType`,或来自 `claude setup-token` 的 `CLAUDE_CODE_OAUTH_TOKEN`,来提供可移植的 Claude Code 订阅 OAuth。它会先在 Docker 中验证直接 `claude -p` 可用,然后在不保留 Anthropic API key 环境变量的情况下运行两个 Gateway 网关 CLI 后端轮次。这个订阅通道默认会禁用 Claude MCP / 工具探测和图像探测,因为 Claude 目前会将第三方应用用量计入额外使用计费,而不是普通订阅计划额度。 - 实时 CLI 后端冒烟测试现在会对 Claude、Codex 和 Gemini 执行相同的端到端流程:文本轮次、图像分类轮次,然后通过 gateway CLI 验证 MCP `cron` 工具调用。 -- Claude 的默认冒烟测试还会将会话从 Sonnet 修补到 Opus,并验证恢复的会话仍然记得更早的备注。 +- Claude 的默认冒烟测试还会将会话从 Sonnet 修补为 Opus,并验证恢复后的会话仍记得先前的笔记。 -## 实时:ACP 绑定冒烟测试(`/acp spawn ... --bind here`) +## 实时测试:ACP 绑定冒烟测试(`/acp spawn ... --bind here`) - 测试:`src/gateway/gateway-acp-bind.live.test.ts` - 目标:使用实时 ACP 智能体验证真实的 ACP 会话绑定流程: - 发送 `/acp spawn --bind here` - - 原地绑定一个合成的消息渠道会话 - - 在同一个会话上发送一次普通后续消息 - - 验证该后续消息落入已绑定的 ACP 会话转录中 + - 在原位置绑定一个合成的消息渠道会话 + - 在同一会话上发送一次普通后续消息 + - 验证该后续消息落入已绑定的 ACP 会话 transcript 中 - 启用: - `pnpm test:live src/gateway/gateway-acp-bind.live.test.ts` - `OPENCLAW_LIVE_ACP_BIND=1` @@ -562,8 +577,8 @@ pnpm test:docker:live-cli-backend:gemini - `OPENCLAW_LIVE_ACP_BIND_AGENT_COMMAND='npx -y @agentclientprotocol/claude-agent-acp@'` - `OPENCLAW_LIVE_ACP_BIND_CODEX_MODEL=gpt-5.5` - 说明: - - 该通道使用 gateway 的 `chat.send` 接口,并带有仅管理员可用的合成 originating-route 字段,因此测试可以附加消息渠道上下文,而无需假装进行外部投递。 - - 当未设置 `OPENCLAW_LIVE_ACP_BIND_AGENT_COMMAND` 时,测试会使用嵌入式 `acpx` 插件内置的智能体注册表来选择 ACP harness 智能体。 + - 该通道使用 gateway 的 `chat.send` 接口,并带有仅管理员可用的合成 originating-route 字段,以便测试能够附加消息渠道上下文,而无需假装对外投递。 + - 当未设置 `OPENCLAW_LIVE_ACP_BIND_AGENT_COMMAND` 时,测试会使用内置 `acpx` 插件的内建智能体注册表来选择 ACP harness 智能体。 示例: @@ -590,29 +605,28 @@ pnpm test:docker:live-acp-bind:gemini Docker 说明: - Docker 运行器位于 `scripts/test-live-acp-bind-docker.sh`。 -- 默认情况下,它会按顺序对所有受支持的实时 CLI 智能体运行 ACP 绑定冒烟测试:`claude`、`codex`,然后是 `gemini`。 -- 使用 `OPENCLAW_LIVE_ACP_BIND_AGENTS=claude`、`OPENCLAW_LIVE_ACP_BIND_AGENTS=codex` 或 `OPENCLAW_LIVE_ACP_BIND_AGENTS=gemini` 可缩小矩阵范围。 -- 它会读取 `~/.profile`,将匹配的 CLI 认证材料暂存到容器中,把 `acpx` 安装到可写的 npm 前缀中,然后在缺失时安装所请求的实时 CLI(`@anthropic-ai/claude-code`、`@openai/codex` 或 `@google/gemini-cli`)。 -- 在 Docker 内,运行器会设置 `OPENCLAW_LIVE_ACP_BIND_ACPX_COMMAND=$HOME/.npm-global/bin/acpx`,这样 acpx 就能让从已读取的 profile 中获得的提供商环境变量继续对其子 harness CLI 可用。 +- 默认情况下,它会按顺序对所有受支持的实时 CLI 智能体运行 ACP 绑定冒烟测试:`claude`、`codex`,然后 `gemini`。 +- 使用 `OPENCLAW_LIVE_ACP_BIND_AGENTS=claude`、`OPENCLAW_LIVE_ACP_BIND_AGENTS=codex` 或 `OPENCLAW_LIVE_ACP_BIND_AGENTS=gemini` 可以缩小矩阵范围。 +- 它会 source `~/.profile`,将匹配的 CLI 认证材料暂存进容器,在可写 npm 前缀中安装 `acpx`,然后在缺失时安装所请求的实时 CLI(`@anthropic-ai/claude-code`、`@openai/codex` 或 `@google/gemini-cli`)。 +- 在 Docker 内部,运行器会设置 `OPENCLAW_LIVE_ACP_BIND_ACPX_COMMAND=$HOME/.npm-global/bin/acpx`,这样 `acpx` 就能让来自已 source 的 profile 的提供商环境变量继续对其子 harness CLI 可用。 -## 实时:Codex app-server harness 冒烟测试 +## 实时测试:Codex app-server harness 冒烟测试 -- 目标:通过常规 gateway - `agent` 方法验证由插件负责的 Codex harness: +- 目标:通过常规 gateway `agent` 方法验证由插件拥有的 Codex harness: - 加载内置的 `codex` 插件 - 选择 `OPENCLAW_AGENT_RUNTIME=codex` - - 在强制使用 Codex harness 的情况下,向 `openai/gpt-5.5` 发送第一轮 gateway 智能体消息 - - 向同一个 OpenClaw 会话发送第二轮消息,并验证 app-server 线程可以恢复 - - 通过相同的 gateway 命令路径运行 `/codex status` 和 `/codex models` - - 可选运行两个经 Guardian 审核的提权 shell 探针:一个应该被批准的良性命令,以及一个应该被拒绝并让智能体回问的伪密钥上传命令 + - 使用强制启用的 Codex harness,将第一轮 gateway 智能体消息发送到 `openai/gpt-5.5` + - 向同一个 OpenClaw 会话发送第二轮消息,并验证 app-server 线程能够恢复 + - 通过同一条 gateway 命令路径运行 `/codex status` 和 `/codex models` + - 可选运行两个经 Guardian 审核的提权 shell 探测:一个应被批准的无害命令,以及一个应被拒绝的伪密钥上传命令,以便智能体回问用户 - 测试:`src/gateway/gateway-codex-harness.live.test.ts` - 启用:`OPENCLAW_LIVE_CODEX_HARNESS=1` - 默认模型:`openai/gpt-5.5` -- 可选图像探针:`OPENCLAW_LIVE_CODEX_HARNESS_IMAGE_PROBE=1` -- 可选 MCP / 工具探针:`OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE=1` -- 可选 Guardian 探针:`OPENCLAW_LIVE_CODEX_HARNESS_GUARDIAN_PROBE=1` -- 该冒烟测试会设置 `OPENCLAW_AGENT_HARNESS_FALLBACK=none`,这样损坏的 Codex harness 就不能通过静默回退到 PI 而蒙混过关。 -- 认证:来自 shell / profile 的 `OPENAI_API_KEY`,以及可选复制的 +- 可选图像探测:`OPENCLAW_LIVE_CODEX_HARNESS_IMAGE_PROBE=1` +- 可选 MCP / 工具探测:`OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE=1` +- 可选 Guardian 探测:`OPENCLAW_LIVE_CODEX_HARNESS_GUARDIAN_PROBE=1` +- 该冒烟测试会设置 `OPENCLAW_AGENT_HARNESS_FALLBACK=none`,因此损坏的 Codex harness 不能通过悄悄回退到 PI 来“蒙混过关”。 +- 认证:来自 shell / profile 的 `OPENAI_API_KEY`,再加上可选复制的 `~/.codex/auth.json` 和 `~/.codex/config.toml` 本地配方: @@ -637,41 +651,43 @@ pnpm test:docker:live-codex-harness Docker 说明: - Docker 运行器位于 `scripts/test-live-codex-harness-docker.sh`。 -- 它会读取已挂载的 `~/.profile`,传递 `OPENAI_API_KEY`,在存在时复制 Codex CLI 认证文件,将 `@openai/codex` 安装到可写的已挂载 npm 前缀中,暂存源代码树,然后仅运行 Codex harness 实时测试。 -- Docker 默认启用图像、MCP / 工具以及 Guardian 探针。需要更窄的调试运行时,可设置 `OPENCLAW_LIVE_CODEX_HARNESS_IMAGE_PROBE=0` 或 `OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE=0` 或 `OPENCLAW_LIVE_CODEX_HARNESS_GUARDIAN_PROBE=0`。 -- Docker 还会导出 `OPENCLAW_AGENT_HARNESS_FALLBACK=none`,与实时测试配置保持一致,因此旧别名或回退到 PI 都无法掩盖 Codex harness 回归问题。 +- 它会 source 已挂载的 `~/.profile`,传递 `OPENAI_API_KEY`,在存在时复制 Codex CLI 认证文件,将 `@openai/codex` 安装到一个可写、已挂载的 npm 前缀中,暂存源码树,然后只运行 Codex harness 实时测试。 +- Docker 默认启用图像、MCP / 工具以及 Guardian 探测。当你需要更窄的调试运行时,可设置 + `OPENCLAW_LIVE_CODEX_HARNESS_IMAGE_PROBE=0`、`OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE=0` 或 + `OPENCLAW_LIVE_CODEX_HARNESS_GUARDIAN_PROBE=0`。 +- Docker 还会导出 `OPENCLAW_AGENT_HARNESS_FALLBACK=none`,与实时测试配置保持一致,因此旧别名或回退到 PI 的行为无法掩盖 Codex harness 回归问题。 ### 推荐的实时测试配方 -范围窄、显式的 allowlist 最快,也最不容易出现不稳定问题: +范围窄、显式的 allowlist 最快,也最不容易出现波动: - 单模型,直连(无 gateway): - `OPENCLAW_LIVE_MODELS="openai/gpt-5.5" pnpm test:live src/agents/models.profiles.live.test.ts` -- 单模型,gateway 冒烟测试: +- 单模型,Gateway 网关冒烟测试: - `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts` - 跨多个提供商的工具调用: - `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3-flash-preview,zai/glm-4.7,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts` -- 聚焦 Google(Gemini API 密钥 + Antigravity): - - Gemini(API 密钥):`OPENCLAW_LIVE_GATEWAY_MODELS="google/gemini-3-flash-preview" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts` +- Google 聚焦(Gemini API key + Antigravity): + - Gemini(API key):`OPENCLAW_LIVE_GATEWAY_MODELS="google/gemini-3-flash-preview" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts` - Antigravity(OAuth):`OPENCLAW_LIVE_GATEWAY_MODELS="google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-pro-high" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts` 说明: -- `google/...` 使用 Gemini API(API 密钥)。 +- `google/...` 使用 Gemini API(API key)。 - `google-antigravity/...` 使用 Antigravity OAuth bridge(Cloud Code Assist 风格的智能体端点)。 -- `google-gemini-cli/...` 使用你机器上的本地 Gemini CLI(独立的认证方式 + 工具行为怪异点)。 +- `google-gemini-cli/...` 使用你机器上的本地 Gemini CLI(独立的认证方式 + 工具行为怪癖)。 - Gemini API 与 Gemini CLI: - - API:OpenClaw 通过 HTTP 调用 Google 托管的 Gemini API(API 密钥 / profile 认证);这就是大多数用户理解的 “Gemini”。 - - CLI:OpenClaw 会调用本地 `gemini` 二进制;它有自己的认证方式,而且行为可能不同(流式传输 / 工具支持 / 版本偏差)。 + - API:OpenClaw 通过 HTTP 调用 Google 托管的 Gemini API(使用 API key / profile 认证);这通常是大多数用户所说的 “Gemini”。 + - CLI:OpenClaw 会调用本地 `gemini` 二进制;它有自己的认证方式,并且行为可能不同(流式传输 / 工具支持 / 版本偏差)。 -## 实时:模型矩阵(我们覆盖什么) +## 实时测试:模型矩阵(我们覆盖什么) -没有固定的“CI 模型列表”(实时测试是选择加入的),但这些是在开发机器上、配有密钥时**推荐**定期覆盖的模型。 +没有固定的 “CI 模型列表”(实时测试是按需启用的),但以下是在开发机上、具备 key 时建议定期覆盖的**推荐**模型。 -### 现代冒烟测试集(工具调用 + 图像) +### 现代冒烟测试集合(工具调用 + 图像) 这是我们期望持续保持可用的“常见模型”运行集合: @@ -683,12 +699,12 @@ Docker 说明: - Z.AI(GLM):`zai/glm-4.7` - MiniMax:`minimax/MiniMax-M2.7` -运行带工具 + 图像的 gateway 冒烟测试: +运行带有工具 + 图像的 Gateway 网关冒烟测试: `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3.1-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,zai/glm-4.7,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts` ### 基线:工具调用(Read + 可选 Exec) -每个提供商家族至少选一个: +每个提供商家族至少选择一个: - OpenAI:`openai/gpt-5.5`(或 `openai/gpt-5.4-mini`) - Anthropic:`anthropic/claude-opus-4-6`(或 `anthropic/claude-sonnet-4-6`) @@ -696,44 +712,44 @@ Docker 说明: - Z.AI(GLM):`zai/glm-4.7` - MiniMax:`minimax/MiniMax-M2.7` -可选的额外覆盖(有更好,没有也可): +可选的额外覆盖(有更好,没有也行): - xAI:`xai/grok-4`(或最新可用版本) -- Mistral:`mistral/`…(选择一个你已启用且支持 “tools” 的模型) +- Mistral:`mistral/`…(选择一个你已启用、支持 `tools` 的模型) - Cerebras:`cerebras/`…(如果你有访问权限) - LM Studio:`lmstudio/`…(本地;工具调用取决于 API 模式) -### 视觉:图像发送(附件 → 多模态消息) +### 视觉:发送图像(附件 → 多模态消息) -在 `OPENCLAW_LIVE_GATEWAY_MODELS` 中至少包含一个支持图像的模型(Claude / Gemini / OpenAI 的支持视觉的变体等),以覆盖图像探针。 +在 `OPENCLAW_LIVE_GATEWAY_MODELS` 中至少包含一个支持图像的模型(Claude / Gemini / OpenAI 支持视觉的变体等),以覆盖图像探测。 ### 聚合器 / 替代 Gateway 网关 -如果你已启用对应密钥,我们还支持通过以下方式测试: +如果你已启用相应 key,我们还支持通过以下方式测试: -- OpenRouter:`openrouter/...`(数百个模型;使用 `openclaw models scan` 查找支持工具 + 图像的候选项) -- OpenCode:Zen 使用 `opencode/...`,Go 使用 `opencode-go/...`(通过 `OPENCODE_API_KEY` / `OPENCODE_ZEN_API_KEY` 认证) +- OpenRouter:`openrouter/...`(数百个模型;使用 `openclaw models scan` 找到支持工具 + 图像的候选项) +- OpenCode:`opencode/...` 用于 Zen,`opencode-go/...` 用于 Go(通过 `OPENCODE_API_KEY` / `OPENCODE_ZEN_API_KEY` 认证) -如果你有凭证 / 配置,也可以将更多提供商加入实时矩阵: +如果你有凭证 / 配置,还可以把更多提供商纳入实时矩阵: - 内置:`openai`、`openai-codex`、`anthropic`、`google`、`google-vertex`、`google-antigravity`、`google-gemini-cli`、`zai`、`openrouter`、`opencode`、`opencode-go`、`xai`、`groq`、`cerebras`、`mistral`、`github-copilot` -- 通过 `models.providers`(自定义端点):`minimax`(云 / API),以及任意兼容 OpenAI / Anthropic 的代理(LM Studio、vLLM、LiteLLM 等) +- 通过 `models.providers`(自定义端点):`minimax`(云 / API),以及任何兼容 OpenAI / Anthropic 的代理(LM Studio、vLLM、LiteLLM 等) -提示:不要试图在文档中硬编码“所有模型”。权威列表应以你机器上 `discoverModels(...)` 的返回结果以及可用密钥为准。 +提示:不要试图在文档里硬编码 “所有模型”。权威列表始终是你机器上 `discoverModels(...)` 的返回结果,加上当前可用的 key。 ## 凭证(绝不要提交) 实时测试发现凭证的方式与 CLI 完全相同。实际含义是: -- 如果 CLI 可用,实时测试也应该能找到同样的密钥。 -- 如果实时测试提示 “no creds”,就像排查 `openclaw models list` / 模型选择那样去调试。 +- 如果 CLI 能工作,实时测试通常也应找到相同的 key。 +- 如果实时测试提示 “no creds”,请像调试 `openclaw models list` / 模型选择那样去调试。 -- 每个智能体的认证 profiles:`~/.openclaw/agents//agent/auth-profiles.json`(这就是实时测试里所说的 “profile keys”) +- 每个智能体的认证 profile:`~/.openclaw/agents//agent/auth-profiles.json`(这就是实时测试中 “profile keys” 的含义) - 配置:`~/.openclaw/openclaw.json`(或 `OPENCLAW_CONFIG_PATH`) -- 旧版状态目录:`~/.openclaw/credentials/`(存在时会复制到暂存的实时 home 中,但它不是主要的 profile key 存储) -- 本地实时运行默认会把活动配置、每个智能体的 `auth-profiles.json` 文件、旧版 `credentials/`,以及受支持的外部 CLI 认证目录复制到临时测试 home 中;暂存的实时 home 会跳过 `workspace/` 和 `sandboxes/`,并剥离 `agents.*.workspace` / `agentDir` 路径覆盖,从而让探针不会落到你真实宿主机的工作区上。 +- 旧版状态目录:`~/.openclaw/credentials/`(存在时会复制到暂存的实时测试 home 中,但这不是主 profile-key 存储) +- 本地实时运行默认会把活动配置、每个智能体的 `auth-profiles.json` 文件、旧版 `credentials/` 以及受支持的外部 CLI 认证目录复制到临时测试 home 中;暂存的实时测试 home 会跳过 `workspace/` 和 `sandboxes/`,并且会移除 `agents.*.workspace` / `agentDir` 路径覆盖,这样探测就不会落到你真实宿主机的工作区上。 -如果你想依赖环境变量密钥(例如在你的 `~/.profile` 中导出的那些),请在执行本地测试前运行 `source ~/.profile`,或者使用下面的 Docker 运行器(它们可以把 `~/.profile` 挂载进容器)。 +如果你想依赖环境变量 key(例如在你的 `~/.profile` 中导出的那些),请在 `source ~/.profile` 后运行本地测试,或使用下面的 Docker 运行器(它们可以将 `~/.profile` 挂载进容器)。 ## Deepgram 实时测试(音频转录) @@ -751,9 +767,9 @@ Docker 说明: - 测试:`extensions/comfy/comfy.live.test.ts` - 启用:`OPENCLAW_LIVE_TEST=1 COMFY_LIVE_TEST=1 pnpm test:live -- extensions/comfy/comfy.live.test.ts` - 范围: - - 覆盖内置 comfy 图像、视频和 `music_generate` 路径 - - 除非配置了 `models.providers.comfy.`,否则会跳过各项能力 - - 在修改 comfy 工作流提交、轮询、下载或插件注册后很有用 + - 覆盖内置 comfy 的图像、视频和 `music_generate` 路径 + - 除非配置了 `models.providers.comfy.`,否则会跳过对应能力 + - 适合在修改 comfy 工作流提交、轮询、下载或插件注册后运行 ## 图像生成实时测试 @@ -762,8 +778,8 @@ Docker 说明: - Harness:`pnpm test:live:media image` - 范围: - 枚举每个已注册的图像生成提供商插件 - - 在探测前从你的登录 shell(`~/.profile`)加载缺失的提供商环境变量 - - 默认优先使用实时 / 环境变量 API 密钥,而不是已存储的认证 profiles,因此 `auth-profiles.json` 中过期的测试密钥不会掩盖真实 shell 凭证 + - 在探测前,从你的登录 shell(`~/.profile`)加载缺失的提供商环境变量 + - 默认优先使用实时 / 环境变量 API key,而不是已存储的认证 profile,这样 `auth-profiles.json` 中过期的测试 key 就不会掩盖真实 shell 凭证 - 跳过没有可用认证 / profile / 模型的提供商 - 通过共享运行时能力运行标准图像生成变体: - `google:flash-generate` @@ -782,7 +798,7 @@ Docker 说明: - `OPENCLAW_LIVE_IMAGE_GENERATION_MODELS="openai/gpt-image-2,google/gemini-3.1-flash-image-preview,xai/grok-imagine-image"` - `OPENCLAW_LIVE_IMAGE_GENERATION_CASES="google:flash-generate,google:pro-edit,xai:default-generate,xai:default-edit"` - 可选认证行为: - - `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` 以强制使用 profile store 认证,并忽略仅环境变量的覆盖 + - `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` 强制使用 profile store 认证,并忽略仅来自环境变量的覆盖 ## 音乐生成实时测试 @@ -792,21 +808,21 @@ Docker 说明: - 范围: - 覆盖共享的内置音乐生成提供商路径 - 当前覆盖 Google 和 MiniMax - - 在探测前从你的登录 shell(`~/.profile`)加载提供商环境变量 - - 默认优先使用实时 / 环境变量 API 密钥,而不是已存储的认证 profiles,因此 `auth-profiles.json` 中过期的测试密钥不会掩盖真实 shell 凭证 + - 在探测前,从你的登录 shell(`~/.profile`)加载提供商环境变量 + - 默认优先使用实时 / 环境变量 API key,而不是已存储的认证 profile,这样 `auth-profiles.json` 中过期的测试 key 就不会掩盖真实 shell 凭证 - 跳过没有可用认证 / profile / 模型的提供商 - 在可用时运行两种已声明的运行时模式: - - `generate`,仅使用提示词输入 - - 当提供商声明 `capabilities.edit.enabled` 时运行 `edit` + - `generate`:仅使用 prompt 输入 + - `edit`:当提供商声明 `capabilities.edit.enabled` 时 - 当前共享通道覆盖: - `google`:`generate`、`edit` - `minimax`:`generate` - - `comfy`:单独的 Comfy 实时文件,不属于此共享扫描 + - `comfy`:使用单独的 Comfy 实时测试文件,不在这个共享扫描中 - 可选缩小范围: - `OPENCLAW_LIVE_MUSIC_GENERATION_PROVIDERS="google,minimax"` - `OPENCLAW_LIVE_MUSIC_GENERATION_MODELS="google/lyria-3-clip-preview,minimax/music-2.5+"` - 可选认证行为: - - `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` 以强制使用 profile store 认证,并忽略仅环境变量的覆盖 + - `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` 强制使用 profile store 认证,并忽略仅来自环境变量的覆盖 ## 视频生成实时测试 @@ -815,42 +831,42 @@ Docker 说明: - Harness:`pnpm test:live:media video` - 范围: - 覆盖共享的内置视频生成提供商路径 - - 默认走发布安全的冒烟测试路径:非 FAL 提供商、每个提供商一个 text-to-video 请求、1 秒龙虾提示词,以及来自 `OPENCLAW_LIVE_VIDEO_GENERATION_TIMEOUT_MS` 的每提供商操作上限(默认 `180000`) - - 默认跳过 FAL,因为提供商侧队列延迟可能主导发布时间;传入 `--video-providers fal` 或 `OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS="fal"` 可显式运行它 - - 在探测前从你的登录 shell(`~/.profile`)加载提供商环境变量 - - 默认优先使用实时 / 环境变量 API 密钥,而不是已存储的认证 profiles,因此 `auth-profiles.json` 中过期的测试密钥不会掩盖真实 shell 凭证 + - 默认使用对发布安全的冒烟测试路径:非 FAL 提供商、每个提供商一个 text-to-video 请求、1 秒龙虾 prompt,以及每个提供商由 `OPENCLAW_LIVE_VIDEO_GENERATION_TIMEOUT_MS` 控制的操作上限(默认 `180000`) + - 默认跳过 FAL,因为提供商侧队列延迟可能主导发布时间;传入 `--video-providers fal` 或 `OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS="fal"` 可显式运行 + - 在探测前,从你的登录 shell(`~/.profile`)加载提供商环境变量 + - 默认优先使用实时 / 环境变量 API key,而不是已存储的认证 profile,这样 `auth-profiles.json` 中过期的测试 key 就不会掩盖真实 shell 凭证 - 跳过没有可用认证 / profile / 模型的提供商 - 默认只运行 `generate` - - 设置 `OPENCLAW_LIVE_VIDEO_GENERATION_FULL_MODES=1` 后,如果可用,还会运行已声明的转换模式: - - 当提供商声明 `capabilities.imageToVideo.enabled`,且所选提供商 / 模型在共享扫描中接受基于 buffer 的本地图像输入时,运行 `imageToVideo` - - 当提供商声明 `capabilities.videoToVideo.enabled`,且所选提供商 / 模型在共享扫描中接受基于 buffer 的本地视频输入时,运行 `videoToVideo` - - 当前在共享扫描中“已声明但跳过”的 `imageToVideo` 提供商: - - `vydra`,因为内置 `veo3` 仅支持文本,而内置 `kling` 需要远程图像 URL + - 设置 `OPENCLAW_LIVE_VIDEO_GENERATION_FULL_MODES=1` 后,在可用时还会运行已声明的转换模式: + - `imageToVideo`:当提供商声明 `capabilities.imageToVideo.enabled`,且所选提供商 / 模型在共享扫描中接受基于 buffer 的本地图像输入时 + - `videoToVideo`:当提供商声明 `capabilities.videoToVideo.enabled`,且所选提供商 / 模型在共享扫描中接受基于 buffer 的本地视频输入时 + - 当前在共享扫描中已声明但跳过的 `imageToVideo` 提供商: + - `vydra`,因为内置 `veo3` 仅支持文本,内置 `kling` 需要远程图像 URL - 提供商特定的 Vydra 覆盖: - `OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_VYDRA_VIDEO=1 pnpm test:live -- extensions/vydra/vydra.live.test.ts` - - 该文件会运行 `veo3` text-to-video,以及一个默认使用远程图像 URL 夹具的 `kling` 通道 + - 该文件会运行 `veo3` text-to-video,以及默认使用远程图像 URL fixture 的 `kling` 通道 - 当前 `videoToVideo` 实时覆盖: - 仅 `runway`,且所选模型为 `runway/gen4_aleph` 时 - - 当前在共享扫描中“已声明但跳过”的 `videoToVideo` 提供商: - - `alibaba`、`qwen`、`xai`,因为这些路径当前需要远程 `http(s)` / MP4 参考 URL - - `google`,因为当前共享 Gemini / Veo 通道使用本地基于 buffer 的输入,而共享扫描不接受该路径 - - `openai`,因为当前共享通道无法保证获得组织特定的视频 inpaint / remix 访问权限 + - 当前在共享扫描中已声明但跳过的 `videoToVideo` 提供商: + - `alibaba`、`qwen`、`xai`,因为这些路径目前需要远程 `http(s)` / MP4 参考 URL + - `google`,因为当前共享 Gemini / Veo 通道使用基于本地 buffer 的输入,而该路径在共享扫描中不被接受 + - `openai`,因为当前共享通道缺乏组织特定的视频 inpaint / remix 访问保证 - 可选缩小范围: - `OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS="google,openai,runway"` - `OPENCLAW_LIVE_VIDEO_GENERATION_MODELS="google/veo-3.1-fast-generate-preview,openai/sora-2,runway/gen4_aleph"` - - `OPENCLAW_LIVE_VIDEO_GENERATION_SKIP_PROVIDERS=""` 以在默认扫描中包含所有提供商,包括 FAL - - `OPENCLAW_LIVE_VIDEO_GENERATION_TIMEOUT_MS=60000` 以降低每个提供商的操作上限,用于激进的冒烟测试运行 + - `OPENCLAW_LIVE_VIDEO_GENERATION_SKIP_PROVIDERS=""` 可将所有提供商都包含进默认扫描中,包括 FAL + - `OPENCLAW_LIVE_VIDEO_GENERATION_TIMEOUT_MS=60000` 可降低每个提供商的操作上限,以进行更激进的冒烟测试 - 可选认证行为: - - `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` 以强制使用 profile store 认证,并忽略仅环境变量的覆盖 + - `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` 强制使用 profile store 认证,并忽略仅来自环境变量的覆盖 -## 媒体实时测试 harness +## 媒体实时测试 Harness - 命令:`pnpm test:live:media` - 目的: - - 通过一个仓库原生入口点运行共享的图像、音乐和视频实时测试套件 + - 通过一个仓库原生入口点运行共享的图像、音乐和视频实时套件 - 自动从 `~/.profile` 加载缺失的提供商环境变量 - - 默认自动将每个套件缩小到当前拥有可用认证的提供商 - - 复用 `scripts/test-live.mjs`,因此心跳和安静模式行为保持一致 + - 默认自动将每个套件缩小到当前具备可用认证的提供商 + - 复用 `scripts/test-live.mjs`,因此心跳和静默模式行为保持一致 - 示例: - `pnpm test:live:media` - `pnpm test:live:media image video --providers openai,google,minimax` @@ -859,19 +875,19 @@ Docker 说明: ## Docker 运行器(可选的“在 Linux 中可用”检查) -这些 Docker 运行器分成两类: +这些 Docker 运行器分为两类: -- 实时模型运行器:`test:docker:live-models` 和 `test:docker:live-gateway` 只会在仓库 Docker 镜像中运行各自匹配的 profile key 实时文件(`src/agents/models.profiles.live.test.ts` 和 `src/gateway/gateway-models.profiles.live.test.ts`),并挂载你的本地配置目录和工作区(如果已挂载,也会读取 `~/.profile`)。对应的本地入口点是 `test:live:models-profiles` 和 `test:live:gateway-profiles`。 -- Docker 实时运行器默认使用较小的冒烟测试上限,以便完整 Docker 扫描保持可行: +- 实时模型运行器:`test:docker:live-models` 和 `test:docker:live-gateway` 只会在仓库 Docker 镜像中运行对应的 profile-key 实时测试文件(`src/agents/models.profiles.live.test.ts` 和 `src/gateway/gateway-models.profiles.live.test.ts`),并挂载你的本地配置目录和工作区(如果有挂载,还会 source `~/.profile`)。对应的本地入口点是 `test:live:models-profiles` 和 `test:live:gateway-profiles`。 +- Docker 实时运行器默认使用更小的冒烟测试上限,以便完整 Docker 扫描仍然可行: `test:docker:live-models` 默认设置 `OPENCLAW_LIVE_MAX_MODELS=12`,而 `test:docker:live-gateway` 默认设置 `OPENCLAW_LIVE_GATEWAY_SMOKE=1`、 `OPENCLAW_LIVE_GATEWAY_MAX_MODELS=8`、 `OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=45000`,以及 - `OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=90000`。当你明确需要更大的完整扫描时,再覆盖这些环境变量。 -- `test:docker:all` 会先通过 `test:docker:live-build` 构建一次实时 Docker 镜像,然后在两个实时 Docker 通道中复用它。它还会通过 `test:docker:e2e-build` 构建一个共享的 `scripts/e2e/Dockerfile` 镜像,并在对已构建应用进行验证的 E2E 容器冒烟运行器中复用它。 + `OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=90000`。当你明确想执行更大的完整扫描时,可覆盖这些环境变量。 +- `test:docker:all` 会先通过 `test:docker:live-build` 构建一次实时 Docker 镜像,然后在两个实时 Docker 通道中复用它。它还会通过 `test:docker:e2e-build` 构建一个共享的 `scripts/e2e/Dockerfile` 镜像,并将其复用于针对已构建 app 的 E2E 容器冒烟运行器。 - 容器冒烟运行器:`test:docker:openwebui`、`test:docker:onboard`、`test:docker:npm-onboard-channel-agent`、`test:docker:gateway-network`、`test:docker:mcp-channels`、`test:docker:pi-bundle-mcp-tools`、`test:docker:cron-mcp-cleanup`、`test:docker:plugins`、`test:docker:plugin-update` 和 `test:docker:config-reload` 会启动一个或多个真实容器,并验证更高层级的集成路径。 -实时模型 Docker 运行器还会仅绑定挂载所需的 CLI 认证 home(若运行未缩小范围,则挂载所有受支持的 home),然后在运行前将它们复制到容器 home 中,这样外部 CLI OAuth 就可以刷新令牌,而不会修改宿主机认证存储: +实时模型 Docker 运行器还只会 bind-mount 必需的 CLI 认证 home(如果运行未缩小范围,则挂载所有受支持的认证 home),然后在运行前将其复制到容器 home 中,这样外部 CLI OAuth 就可以刷新 token,而不会修改宿主机上的认证存储: - 直连模型:`pnpm test:docker:live-models`(脚本:`scripts/test-live-models-docker.sh`) - ACP 绑定冒烟测试:`pnpm test:docker:live-acp-bind`(脚本:`scripts/test-live-acp-bind-docker.sh`) @@ -880,18 +896,19 @@ Docker 说明: - Gateway 网关 + dev 智能体:`pnpm test:docker:live-gateway`(脚本:`scripts/test-live-gateway-models-docker.sh`) - Open WebUI 实时冒烟测试:`pnpm test:docker:openwebui`(脚本:`scripts/e2e/openwebui-docker.sh`) - 新手引导向导(TTY,完整脚手架):`pnpm test:docker:onboard`(脚本:`scripts/e2e/onboard-docker.sh`) -- Npm tarball 新手引导 / 渠道 / 智能体冒烟测试:`pnpm test:docker:npm-onboard-channel-agent` 会在 Docker 中全局安装打包后的 OpenClaw tarball,通过 env-ref 新手引导配置 OpenAI,并默认配置 Telegram,验证启用插件时会按需安装其运行时依赖,运行 doctor,然后执行一次模拟的 OpenAI 智能体轮次。可通过 `OPENCLAW_NPM_ONBOARD_PACKAGE_TGZ=/path/to/openclaw-*.tgz` 复用预构建 tarball,通过 `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0` 跳过宿主机构建,或通过 `OPENCLAW_NPM_ONBOARD_CHANNEL=discord` 切换渠道。 -- Bun 全局安装冒烟测试:`bash scripts/e2e/bun-global-install-smoke.sh` 会打包当前代码树,在隔离 home 中使用 `bun install -g` 安装,并验证 `openclaw infer image providers --json` 会返回内置图像提供商,而不是卡住。可通过 `OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ=/path/to/openclaw-*.tgz` 复用预构建 tarball,通过 `OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD=0` 跳过宿主机构建,或通过 `OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE=openclaw-dockerfile-smoke:local` 从已构建的 Docker 镜像中复制 `dist/`。 +- Npm tarball 新手引导 / 渠道 / 智能体冒烟测试:`pnpm test:docker:npm-onboard-channel-agent` 会在 Docker 中全局安装打包后的 OpenClaw tarball,通过 env-ref 新手引导配置 OpenAI,并默认配置 Telegram,验证启用插件时会按需安装其运行时依赖,运行 doctor,并执行一次模拟的 OpenAI 智能体轮次。可通过 `OPENCLAW_NPM_ONBOARD_PACKAGE_TGZ=/path/to/openclaw-*.tgz` 复用已构建 tarball,通过 `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0` 跳过宿主机构建,或通过 `OPENCLAW_NPM_ONBOARD_CHANNEL=discord` 切换渠道。 +- Bun 全局安装冒烟测试:`bash scripts/e2e/bun-global-install-smoke.sh` 会打包当前源码树,在隔离 home 中使用 `bun install -g` 安装,并验证 `openclaw infer image providers --json` 返回的是内置图像提供商,而不是卡住。可通过 `OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ=/path/to/openclaw-*.tgz` 复用已构建 tarball,通过 `OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD=0` 跳过宿主机构建,或通过 `OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE=openclaw-dockerfile-smoke:local` 从已构建 Docker 镜像复制 `dist/`。 +- 安装器 Docker 冒烟测试:`bash scripts/test-install-sh-docker.sh` 会在其 root、update、direct-npm 和 non-root 容器之间共享同一个 npm 缓存。设置 `OPENCLAW_INSTALL_SMOKE_NPM_CACHE_DIR=/path/to/cache` 可在本地重跑之间复用该缓存。 - Gateway 网关网络(两个容器,WS 认证 + 健康检查):`pnpm test:docker:gateway-network`(脚本:`scripts/e2e/gateway-network-docker.sh`) -- OpenAI Responses `web_search` 最小推理回归测试:`pnpm test:docker:openai-web-search-minimal`(脚本:`scripts/e2e/openai-web-search-minimal-docker.sh`)会通过 Gateway 网关运行一个模拟的 OpenAI 服务器,验证 `web_search` 会将 `reasoning.effort` 从 `minimal` 提升为 `low`,然后强制触发提供商 schema 拒绝,并检查原始细节是否出现在 Gateway 网关日志中。 -- MCP 渠道 bridge(带种子数据的 Gateway 网关 + stdio bridge + 原始 Claude notification-frame 冒烟测试):`pnpm test:docker:mcp-channels`(脚本:`scripts/e2e/mcp-channels-docker.sh`) -- Pi 内置 MCP 工具(真实 stdio MCP 服务器 + 嵌入式 Pi profile allow / deny 冒烟测试):`pnpm test:docker:pi-bundle-mcp-tools`(脚本:`scripts/e2e/pi-bundle-mcp-tools-docker.sh`) -- Cron / subagent MCP 清理(真实 Gateway 网关 + stdio MCP 子进程在隔离的 cron 和一次性 subagent 运行后正确终止):`pnpm test:docker:cron-mcp-cleanup`(脚本:`scripts/e2e/cron-mcp-cleanup-docker.sh`) -- 插件(安装冒烟测试 + `/plugin` 别名 + Claude 内置 bundle 重启语义):`pnpm test:docker:plugins`(脚本:`scripts/e2e/plugins-docker.sh`) -- 插件更新未变更冒烟测试:`pnpm test:docker:plugin-update`(脚本:`scripts/e2e/plugin-update-unchanged-docker.sh`) +- OpenAI Responses `web_search` 最小推理回归测试:`pnpm test:docker:openai-web-search-minimal`(脚本:`scripts/e2e/openai-web-search-minimal-docker.sh`)会通过 Gateway 网关运行一个模拟的 OpenAI 服务器,验证 `web_search` 会将 `reasoning.effort` 从 `minimal` 提升到 `low`,然后强制触发提供商 schema 拒绝,并检查原始细节是否出现在 Gateway 网关日志中。 +- MCP 渠道桥接(带种子数据的 Gateway 网关 + stdio bridge + 原始 Claude 通知帧冒烟测试):`pnpm test:docker:mcp-channels`(脚本:`scripts/e2e/mcp-channels-docker.sh`) +- Pi bundle MCP 工具(真实 stdio MCP 服务器 + 嵌入式 Pi profile allow / deny 冒烟测试):`pnpm test:docker:pi-bundle-mcp-tools`(脚本:`scripts/e2e/pi-bundle-mcp-tools-docker.sh`) +- Cron / subagent MCP 清理(真实 Gateway 网关 + 在隔离 cron 和一次性 subagent 运行后清理 stdio MCP 子进程):`pnpm test:docker:cron-mcp-cleanup`(脚本:`scripts/e2e/cron-mcp-cleanup-docker.sh`) +- 插件(安装冒烟测试 + `/plugin` 别名 + Claude bundle 重启语义):`pnpm test:docker:plugins`(脚本:`scripts/e2e/plugins-docker.sh`) +- 插件更新无变更冒烟测试:`pnpm test:docker:plugin-update`(脚本:`scripts/e2e/plugin-update-unchanged-docker.sh`) - 配置热重载元数据冒烟测试:`pnpm test:docker:config-reload`(脚本:`scripts/e2e/config-reload-source-docker.sh`) -- 内置插件运行时依赖:`pnpm test:docker:bundled-channel-deps` 默认会构建一个小型 Docker 运行器镜像,在宿主机上构建并打包一次 OpenClaw,然后把该 tarball 挂载到每个 Linux 安装场景中。可通过 `OPENCLAW_SKIP_DOCKER_BUILD=1` 复用该镜像,在本地刚完成构建后通过 `OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD=0` 跳过宿主机重建,或通过 `OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ=/path/to/openclaw-*.tgz` 指向现有 tarball。 -- 在迭代时,可通过禁用不相关场景来缩小内置插件运行时依赖测试范围,例如: +- 内置插件运行时依赖:`pnpm test:docker:bundled-channel-deps` 默认会构建一个小型 Docker 运行器镜像,在宿主机上构建并打包一次 OpenClaw,然后把该 tarball 挂载到每个 Linux 安装场景中。可通过 `OPENCLAW_SKIP_DOCKER_BUILD=1` 复用镜像,通过 `OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD=0` 在本地刚构建过后跳过宿主机构建,或通过 `OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ=/path/to/openclaw-*.tgz` 指向现有 tarball。 +- 迭代时,如果想缩小内置插件运行时依赖测试范围,可禁用无关场景,例如: `OPENCLAW_BUNDLED_CHANNEL_SCENARIOS=0 OPENCLAW_BUNDLED_CHANNEL_UPDATE_SCENARIO=0 OPENCLAW_BUNDLED_CHANNEL_ROOT_OWNED_SCENARIO=0 OPENCLAW_BUNDLED_CHANNEL_SETUP_ENTRY_SCENARIO=0 pnpm test:docker:bundled-channel-deps`。 手动预构建并复用共享 built-app 镜像: @@ -901,116 +918,82 @@ OPENCLAW_DOCKER_E2E_IMAGE=openclaw-docker-e2e:local pnpm test:docker:e2e-build OPENCLAW_DOCKER_E2E_IMAGE=openclaw-docker-e2e:local OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:mcp-channels ``` -设置后,诸如 `OPENCLAW_GATEWAY_NETWORK_E2E_IMAGE` 这样的套件特定镜像覆盖项仍然优先。当 `OPENCLAW_SKIP_DOCKER_BUILD=1` 指向远端共享镜像时,如果本地尚不存在,该脚本会先拉取它。QR 和安装器 Docker 测试保留各自独立的 Dockerfile,因为它们验证的是打包 / 安装行为,而不是共享的 built-app 运行时。 +当设置了套件特定的镜像覆盖(例如 `OPENCLAW_GATEWAY_NETWORK_E2E_IMAGE`)时,它们仍然优先。若 `OPENCLAW_SKIP_DOCKER_BUILD=1` 指向远端共享镜像,而该镜像尚未在本地存在,脚本会先拉取它。二维码和安装器 Docker 测试保留各自独立的 Dockerfile,因为它们验证的是打包 / 安装行为,而不是共享 built-app 运行时。 -实时模型 Docker 运行器还会以只读方式绑定挂载当前检出内容, -并将其暂存到容器内的临时工作目录中。这样可以保持运行时 -镜像精简,同时仍然针对你本地精确的源码 / 配置运行 Vitest。 -暂存步骤会跳过大型仅本地缓存和应用构建输出,例如 -`.pnpm-store`、`.worktrees`、`__openclaw_vitest__`,以及应用本地 `.build` 或 -Gradle 输出目录,因此 Docker 实时运行不会花上数分钟复制 -机器特定产物。 -它们还会设置 `OPENCLAW_SKIP_CHANNELS=1`,这样 gateway 实时探针就不会在 -容器中启动真实的 Telegram / Discord / 等渠道 worker。 -`test:docker:live-models` 仍然会运行 `pnpm test:live`,因此当你需要缩小范围 -或从该 Docker 通道中排除 gateway 实时覆盖时,也应传入 -`OPENCLAW_LIVE_GATEWAY_*`。 -`test:docker:openwebui` 是更高层级的兼容性冒烟测试:它会启动一个 -启用了 OpenAI 兼容 HTTP 端点的 OpenClaw gateway 容器, -然后针对该 gateway 启动一个固定版本的 Open WebUI 容器,通过 -Open WebUI 登录,验证 `/api/models` 暴露 `openclaw/default`,然后通过 -Open WebUI 的 `/api/chat/completions` 代理发送一次真实聊天请求。 -首次运行可能会明显更慢,因为 Docker 可能需要拉取 -Open WebUI 镜像,而 Open WebUI 也可能需要完成自己的冷启动设置。 -该通道需要一个可用的实时模型密钥,而 `OPENCLAW_PROFILE_FILE` -(默认是 `~/.profile`)是在 Docker 化运行中提供该密钥的主要方式。 -成功运行会打印一个小型 JSON 负载,例如 `{ "ok": true, "model": -"openclaw/default", ... }`。 -`test:docker:mcp-channels` 是有意设计为确定性的,不需要 -真实的 Telegram、Discord 或 iMessage 账号。它会启动一个带种子数据的 Gateway -容器,启动第二个容器并运行 `openclaw mcp serve`,然后 -验证路由会话发现、转录读取、附件元数据、 -实时事件队列行为、出站发送路由,以及 Claude 风格的渠道 + -权限通知是否通过真实 stdio MCP bridge 正常工作。通知检查 -会直接检查原始 stdio MCP 帧,因此该冒烟测试验证的是 bridge 实际发出的内容, -而不仅仅是某个特定客户端 SDK 恰好暴露出来的内容。 -`test:docker:pi-bundle-mcp-tools` 是确定性的,不需要实时 -模型密钥。它会构建仓库 Docker 镜像,在容器中启动一个真实 stdio MCP 探测服务器, -通过嵌入式 Pi bundle MCP 运行时实例化该服务器, -执行该工具,然后验证 `coding` 和 `messaging` 会保留 -`bundle-mcp` 工具,而 `minimal` 和 `tools.deny: ["bundle-mcp"]` 会过滤掉它们。 -`test:docker:cron-mcp-cleanup` 是确定性的,不需要实时模型 -密钥。它会启动一个带种子数据并配有真实 stdio MCP 探测服务器的 Gateway, -运行一次隔离的 cron 轮次和一次 `/subagents spawn` 一次性子智能体轮次,然后 -验证 MCP 子进程会在每次运行后退出。 +实时模型 Docker 运行器还会以只读方式 bind-mount 当前 checkout,并在容器内将其暂存到临时工作目录中。这样既能保持运行时镜像精简,又能让 Vitest 基于你本地的精确源码 / 配置运行。暂存步骤会跳过较大的本地专用缓存和 app 构建输出,例如 `.pnpm-store`、`.worktrees`、`__openclaw_vitest__`,以及 app 本地的 `.build` 或 Gradle 输出目录,因此 Docker 实时运行不会花费数分钟复制机器特定产物。 +它们还会设置 `OPENCLAW_SKIP_CHANNELS=1`,这样 gateway 实时探测就不会在容器内启动真实的 Telegram / Discord 等渠道 worker。 +`test:docker:live-models` 仍然运行 `pnpm test:live`,因此当你需要缩小或排除该 Docker 通道中的 gateway 实时覆盖时,也请一并传入 `OPENCLAW_LIVE_GATEWAY_*`。 +`test:docker:openwebui` 是一个更高层级的兼容性冒烟测试:它会启动一个启用了 OpenAI 兼容 HTTP 端点的 OpenClaw gateway 容器,再针对该 gateway 启动一个固定版本的 Open WebUI 容器,通过 Open WebUI 登录,验证 `/api/models` 会暴露 `openclaw/default`,然后通过 Open WebUI 的 `/api/chat/completions` 代理发送一次真实聊天请求。 +首次运行可能明显更慢,因为 Docker 可能需要拉取 Open WebUI 镜像,而 Open WebUI 也可能需要完成自身的冷启动初始化。 +该通道需要一个可用的实时模型 key,而在 Docker 化运行中,`OPENCLAW_PROFILE_FILE`(默认 `~/.profile`)是提供该 key 的主要方式。 +成功运行会打印一个简短的 JSON 负载,例如 `{ "ok": true, "model": "openclaw/default", ... }`。 +`test:docker:mcp-channels` 刻意保持确定性,不需要真实的 Telegram、Discord 或 iMessage 账号。它会启动一个带种子数据的 Gateway 网关容器,启动第二个容器来运行 `openclaw mcp serve`,然后通过真实的 stdio MCP bridge 验证路由会话发现、transcript 读取、附件元数据、实时事件队列行为、出站发送路由,以及 Claude 风格的渠道 + 权限通知。通知检查会直接检查原始 stdio MCP 帧,因此该冒烟测试验证的是 bridge 实际发出的内容,而不只是某个特定客户端 SDK 恰好暴露出来的内容。 +`test:docker:pi-bundle-mcp-tools` 也是确定性的,不需要实时模型 key。它会构建仓库 Docker 镜像,在容器中启动一个真实的 stdio MCP 探测服务器,通过嵌入式 Pi bundle MCP 运行时将该服务器实例化,执行工具,然后验证 `coding` 和 `messaging` 会保留 `bundle-mcp` 工具,而 `minimal` 和 `tools.deny: ["bundle-mcp"]` 会将其过滤掉。 +`test:docker:cron-mcp-cleanup` 也是确定性的,不需要实时模型 key。它会启动一个带种子数据的 Gateway 网关和一个真实 stdio MCP 探测服务器,运行一次隔离的 cron 轮次和一次 `/subagents spawn` 一次性子智能体轮次,然后验证每次运行后 MCP 子进程都会退出。 -手动 ACP 自然语言线程冒烟测试(不在 CI 中运行): +手动 ACP 自然语言线程冒烟测试(不进 CI): - `bun scripts/dev/discord-acp-plain-language-smoke.ts --channel ...` -- 保留此脚本用于回归 / 调试工作流。它未来可能还会再次用于 ACP 线程路由验证,因此不要删除它。 +- 保留这个脚本用于回归 / 调试工作流。以后它可能还会再次用于 ACP 线程路由验证,所以不要删除它。 常用环境变量: - `OPENCLAW_CONFIG_DIR=...`(默认:`~/.openclaw`)挂载到 `/home/node/.openclaw` - `OPENCLAW_WORKSPACE_DIR=...`(默认:`~/.openclaw/workspace`)挂载到 `/home/node/.openclaw/workspace` -- `OPENCLAW_PROFILE_FILE=...`(默认:`~/.profile`)挂载到 `/home/node/.profile` 并在运行测试前读取 -- `OPENCLAW_DOCKER_PROFILE_ENV_ONLY=1` 用于仅验证从 `OPENCLAW_PROFILE_FILE` 读取的环境变量,并使用临时配置 / 工作区目录且不挂载外部 CLI 认证 -- `OPENCLAW_DOCKER_CLI_TOOLS_DIR=...`(默认:`~/.cache/openclaw/docker-cli-tools`)挂载到 `/home/node/.npm-global`,用于在 Docker 内缓存 CLI 安装 +- `OPENCLAW_PROFILE_FILE=...`(默认:`~/.profile`)挂载到 `/home/node/.profile`,并在运行测试前 source +- `OPENCLAW_DOCKER_PROFILE_ENV_ONLY=1` 仅验证从 `OPENCLAW_PROFILE_FILE` source 得到的环境变量,使用临时配置 / 工作区目录,且不挂载外部 CLI 认证 +- `OPENCLAW_DOCKER_CLI_TOOLS_DIR=...`(默认:`~/.cache/openclaw/docker-cli-tools`)挂载到 `/home/node/.npm-global`,用于 Docker 内部缓存 CLI 安装 - `$HOME` 下的外部 CLI 认证目录 / 文件会以只读方式挂载到 `/host-auth...` 下,然后在测试开始前复制到 `/home/node/...` - 默认目录:`.minimax` - 默认文件:`~/.codex/auth.json`、`~/.codex/config.toml`、`.claude.json`、`~/.claude/.credentials.json`、`~/.claude/settings.json`、`~/.claude/settings.local.json` - - 缩小范围后的提供商运行只会挂载从 `OPENCLAW_LIVE_PROVIDERS` / `OPENCLAW_LIVE_GATEWAY_PROVIDERS` 推断出的所需目录 / 文件 - - 可通过 `OPENCLAW_DOCKER_AUTH_DIRS=all`、`OPENCLAW_DOCKER_AUTH_DIRS=none`,或像 `OPENCLAW_DOCKER_AUTH_DIRS=.claude,.codex` 这样的逗号列表手动覆盖 + - 缩小提供商范围的运行只会挂载根据 `OPENCLAW_LIVE_PROVIDERS` / `OPENCLAW_LIVE_GATEWAY_PROVIDERS` 推断出的必需目录 / 文件 + - 可手动覆盖:`OPENCLAW_DOCKER_AUTH_DIRS=all`、`OPENCLAW_DOCKER_AUTH_DIRS=none`,或逗号列表,例如 `OPENCLAW_DOCKER_AUTH_DIRS=.claude,.codex` - `OPENCLAW_LIVE_GATEWAY_MODELS=...` / `OPENCLAW_LIVE_MODELS=...` 用于缩小运行范围 - `OPENCLAW_LIVE_GATEWAY_PROVIDERS=...` / `OPENCLAW_LIVE_PROVIDERS=...` 用于在容器内筛选提供商 -- `OPENCLAW_SKIP_DOCKER_BUILD=1` 用于复用现有的 `openclaw:local-live` 镜像,以便进行无需重建的重跑 -- `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` 用于确保凭证来自 profile store(而非环境变量) -- `OPENCLAW_OPENWEBUI_MODEL=...` 用于选择 Gateway 网关为 Open WebUI 冒烟测试暴露的模型 -- `OPENCLAW_OPENWEBUI_PROMPT=...` 用于覆盖 Open WebUI 冒烟测试所使用的 nonce 检查提示词 -- `OPENWEBUI_IMAGE=...` 用于覆盖固定的 Open WebUI 镜像标签 +- `OPENCLAW_SKIP_DOCKER_BUILD=1` 复用已有的 `openclaw:local-live` 镜像,以便在不需要重建的重跑中使用 +- `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` 确保凭证来自 profile store(而不是环境变量) +- `OPENCLAW_OPENWEBUI_MODEL=...` 选择暴露给 Open WebUI 冒烟测试的 gateway 模型 +- `OPENCLAW_OPENWEBUI_PROMPT=...` 覆盖 Open WebUI 冒烟测试中使用的 nonce 检查 prompt +- `OPENWEBUI_IMAGE=...` 覆盖固定的 Open WebUI 镜像标签 ## 文档完整性检查 -在编辑文档后运行文档检查:`pnpm check:docs`。 -当你还需要进行页内标题检查时,运行完整的 Mintlify 锚点验证:`pnpm docs:check-links:anchors`。 +文档修改后运行文档检查:`pnpm check:docs`。 +当你还需要页面内标题检查时,运行完整的 Mintlify anchor 验证:`pnpm docs:check-links:anchors`。 ## 离线回归测试(对 CI 安全) -这些是在不接入真实提供商情况下的“真实流水线”回归测试: +这些是“不接入真实提供商”的“真实链路”回归测试: - Gateway 网关工具调用(模拟 OpenAI,真实 gateway + 智能体循环):`src/gateway/gateway.test.ts`(用例:“runs a mock OpenAI tool call end-to-end via gateway agent loop”) - Gateway 网关向导(WS `wizard.start` / `wizard.next`,强制写入配置 + 认证):`src/gateway/gateway.test.ts`(用例:“runs wizard over ws and writes auth token config”) ## 智能体可靠性评估(Skills) -我们已经有一些对 CI 安全的测试,它们的行为类似“智能体可靠性评估”: +我们已经有一些对 CI 安全、行为类似“智能体可靠性评估”的测试: -- 通过真实 gateway + 智能体循环进行模拟工具调用(`src/gateway/gateway.test.ts`)。 -- 验证会话接线和配置影响的端到端向导流程(`src/gateway/gateway.test.ts`)。 +- 通过真实 gateway + 智能体循环的模拟工具调用(`src/gateway/gateway.test.ts`)。 +- 用于验证会话接线和配置效果的端到端向导流程(`src/gateway/gateway.test.ts`)。 -对于 Skills(参见 [Skills](/zh-CN/tools/skills)),目前仍缺少的内容: +对于 Skills(见 [Skills](/zh-CN/tools/skills)),目前仍缺少: -- **决策**:当提示中列出 Skills 时,智能体是否会选择正确的 Skills(或避开无关项)? -- **合规性**:智能体是否会在使用前读取 `SKILL.md`,并遵循要求的步骤 / 参数? -- **工作流契约**:断言工具顺序、会话历史延续和沙箱边界的多轮场景。 +- **决策能力:** 当 prompt 中列出 Skills 时,智能体是否会选择正确的 skill(或避免选择无关 skill)? +- **合规性:** 智能体是否会在使用前读取 `SKILL.md`,并遵循所要求的步骤 / 参数? +- **工作流契约:** 多轮场景,用于断言工具顺序、会话历史延续以及沙箱边界。 -未来的评估应优先保持确定性: +未来的评估应当优先保持确定性: -- 一个使用模拟提供商的场景运行器,用于断言工具调用 + 顺序、skill 文件读取和会话接线。 -- 一小组聚焦 skill 的场景(使用与避免、门禁、提示注入)。 -- 只有在对 CI 安全的测试套件就位之后,才考虑可选的实时评估(选择加入、受环境变量门控)。 +- 一个使用 mock 提供商的场景运行器,用于断言工具调用及其顺序、skill 文件读取以及会话接线。 +- 一小套聚焦 skill 的场景(使用与避免、门控、prompt 注入)。 +- 只有在对 CI 安全的套件落地后,才增加可选的实时评估(按需启用、受环境变量门控)。 ## 契约测试(插件和渠道形状) -契约测试会验证每个已注册插件和渠道是否符合其 -接口契约。它们会遍历所有发现到的插件,并运行一组 -形状与行为断言。默认的 `pnpm test` 单元测试通道会有意 -跳过这些共享接缝与冒烟测试文件;当你修改共享的渠道或提供商接口时, -请显式运行这些契约命令。 +契约测试会验证每个已注册的插件和渠道都符合其接口契约。它们会遍历所有已发现的插件,并运行一组针对形状和行为的断言。默认的 `pnpm test` 单元测试通道会刻意跳过这些共享接缝与冒烟测试文件;当你修改共享渠道或提供商接口时,请显式运行契约测试命令。 ### 命令 -- 全部契约测试:`pnpm test:contracts` +- 所有契约测试:`pnpm test:contracts` - 仅渠道契约测试:`pnpm test:contracts:channels` - 仅提供商契约测试:`pnpm test:contracts:plugins` @@ -1018,14 +1001,14 @@ Open WebUI 镜像,而 Open WebUI 也可能需要完成自己的冷启动设置 位于 `src/channels/plugins/contracts/*.contract.test.ts`: -- **plugin** - 基本插件结构(id、名称、能力) +- **plugin** - 基础插件形状(id、名称、能力) - **setup** - 设置向导契约 - **session-binding** - 会话绑定行为 - **outbound-payload** - 消息负载结构 - **inbound** - 入站消息处理 -- **actions** - 渠道操作处理器 +- **actions** - 渠道动作处理器 - **threading** - 线程 ID 处理 -- **directory** - 目录 / 成员清单 API +- **directory** - 目录 / roster API - **group-policy** - 群组策略执行 ### 提供商状态契约测试 @@ -1033,7 +1016,7 @@ Open WebUI 镜像,而 Open WebUI 也可能需要完成自己的冷启动设置 位于 `src/plugins/contracts/*.contract.test.ts`。 - **status** - 渠道状态探测 -- **registry** - 插件注册表结构 +- **registry** - 插件注册表形状 ### 提供商契约测试 @@ -1045,26 +1028,26 @@ Open WebUI 镜像,而 Open WebUI 也可能需要完成自己的冷启动设置 - **discovery** - 插件发现 - **loader** - 插件加载 - **runtime** - 提供商运行时 -- **shape** - 插件结构 / 接口 +- **shape** - 插件形状 / 接口 - **wizard** - 设置向导 ### 何时运行 -- 在更改 Plugin SDK 导出或子路径之后 -- 在添加或修改渠道或提供商插件之后 -- 在重构插件注册或发现机制之后 +- 修改 `plugin-sdk` 导出或子路径之后 +- 添加或修改渠道插件或提供商插件之后 +- 重构插件注册或发现逻辑之后 -契约测试会在 CI 中运行,且不需要真实 API 密钥。 +契约测试会在 CI 中运行,不需要真实 API key。 ## 添加回归测试(指导) -当你修复一个在实时测试中发现的提供商 / 模型问题时: +当你修复了一个在实时测试中发现的提供商 / 模型问题时: -- 如果可能,添加一个对 CI 安全的回归测试(模拟 / 存根提供商,或捕获精确的请求结构转换) -- 如果问题本质上只能在实时环境中复现(速率限制、认证策略),就让实时测试保持范围狭窄,并通过环境变量选择启用 -- 优先瞄准能捕获该缺陷的最小层级: - - 提供商请求转换 / 回放缺陷 → 直连模型测试 - - gateway 会话 / 历史 / 工具流水线缺陷 → Gateway 网关实时冒烟测试或对 CI 安全的 gateway 模拟测试 -- SecretRef 遍历保护机制: +- 如果可能,添加一个对 CI 安全的回归测试(模拟 / stub 提供商,或捕获精确的请求形状转换) +- 如果问题天然只能通过实时测试复现(速率限制、认证策略),请将实时测试保持为范围很窄,并通过环境变量按需启用 +- 优先定位到能捕获该缺陷的最小层级: + - 提供商请求转换 / 重放缺陷 → 直连模型测试 + - gateway 会话 / 历史 / 工具链路缺陷 → gateway 实时冒烟测试或对 CI 安全的 gateway mock 测试 +- SecretRef 遍历防护规则: - `src/secrets/exec-secret-ref-id-parity.test.ts` 会从注册表元数据(`listSecretTargetRegistryEntries()`)中为每个 SecretRef 类派生一个采样目标,然后断言遍历段 exec id 会被拒绝。 - - 如果你在 `src/secrets/target-registry-data.ts` 中新增一个 `includeInPlan` SecretRef 目标家族,请更新该测试中的 `classifyTargetClass`。该测试会在遇到未分类目标 id 时故意失败,因此新的类别不能被静默跳过。 + - 如果你在 `src/secrets/target-registry-data.ts` 中新增了一个 `includeInPlan` SecretRef 目标族,请更新该测试中的 `classifyTargetClass`。该测试会在遇到未分类的目标 id 时故意失败,这样新的类别就无法被悄悄跳过。 diff --git a/docs/zh-CN/plugins/sdk-runtime.md b/docs/zh-CN/plugins/sdk-runtime.md index 7cd7e31e3..5705e6d2c 100644 --- a/docs/zh-CN/plugins/sdk-runtime.md +++ b/docs/zh-CN/plugins/sdk-runtime.md @@ -1,26 +1,26 @@ --- read_when: - - 你需要从插件中调用核心辅助函数(TTS、STT、图像生成、Web 搜索、子智能体) + - 你需要从插件调用核心辅助工具(TTS、STT、图像生成、网络搜索、子智能体) - 你想了解 `api.runtime` 暴露了什么 - - 你正在从插件代码中访问配置、智能体或媒体辅助函数 + - 你正在从插件代码中访问配置、智能体或媒体辅助工具 sidebarTitle: Runtime Helpers -summary: '`api.runtime` —— 可供插件使用的注入式运行时辅助函数' -title: 插件运行时辅助函数 +summary: api.runtime —— 可供插件使用的注入式运行时辅助工具 +title: 插件运行时辅助工具 x-i18n: - generated_at: "2026-04-15T16:36:52Z" + generated_at: "2026-04-23T20:12:16Z" model: gpt-5.4 provider: openai - source_hash: c77a6e9cd48c84affa17dce684bbd0e072c8b63485e4a5d569f3793a4ea4f9c8 + source_hash: 78672fff3f850854a68c9fa2494e595cec9177c49dc426c96e11f169a44fa76e source_path: plugins/sdk-runtime.md workflow: 15 --- -# 插件运行时辅助函数 +# 插件运行时辅助工具 -这是对在注册期间注入到每个插件中的 `api.runtime` 对象的参考说明。使用这些辅助函数,而不是直接导入宿主内部实现。 +这是对在注册期间注入到每个插件中的 `api.runtime` 对象的参考说明。请使用这些辅助工具,而不是直接导入宿主内部实现。 - **想看使用演示?** 请参阅 [渠道插件](/zh-CN/plugins/sdk-channel-plugins) 或 [提供商插件](/zh-CN/plugins/sdk-provider-plugins) 中的分步指南,它们会在具体上下文中展示这些辅助函数的用法。 + **想看操作演示?** 请参阅 [渠道插件](/zh-CN/plugins/sdk-channel-plugins) 或 [提供商插件](/zh-CN/plugins/sdk-provider-plugins) 中的分步指南,这些指南会结合上下文展示这些辅助工具的用法。 ```typescript @@ -36,25 +36,25 @@ register(api) { 智能体身份、目录和会话管理。 ```typescript -// 解析智能体的工作目录 +// Resolve the agent's working directory const agentDir = api.runtime.agent.resolveAgentDir(cfg); -// 解析智能体工作区 +// Resolve agent workspace const workspaceDir = api.runtime.agent.resolveAgentWorkspaceDir(cfg); -// 获取智能体身份 +// Get agent identity const identity = api.runtime.agent.resolveAgentIdentity(cfg); -// 获取默认思考级别 +// Get default thinking level const thinking = api.runtime.agent.resolveThinkingDefault(cfg, provider, model); -// 获取智能体超时时间 +// Get agent timeout const timeoutMs = api.runtime.agent.resolveAgentTimeoutMs(cfg); -// 确保工作区存在 +// Ensure workspace exists await api.runtime.agent.ensureAgentWorkspace(cfg); -// 运行一个嵌入式智能体轮次 +// Run an embedded agent turn const agentDir = api.runtime.agent.resolveAgentDir(cfg); const result = await api.runtime.agent.runEmbeddedAgent({ sessionId: "my-plugin:task-1", @@ -66,11 +66,11 @@ const result = await api.runtime.agent.runEmbeddedAgent({ }); ``` -`runEmbeddedAgent(...)` 是一个中立的辅助函数,用于从插件代码启动一个普通的 OpenClaw 智能体轮次。它使用与渠道触发回复相同的 provider / model 解析逻辑和智能体 harness 选择方式。 +`runEmbeddedAgent(...)` 是用于从插件代码启动常规 OpenClaw 智能体轮次的中立辅助工具。它使用与渠道触发回复相同的提供商 / 模型解析和智能体 harness 选择逻辑。 -`runEmbeddedPiAgent(...)` 仍保留作为兼容性别名。 +`runEmbeddedPiAgent(...)` 仍然作为兼容性别名保留。 -**会话存储辅助函数** 位于 `api.runtime.agent.session` 下: +**会话存储辅助工具** 位于 `api.runtime.agent.session` 下: ```typescript const storePath = api.runtime.agent.session.resolveStorePath(cfg); @@ -81,50 +81,49 @@ const filePath = api.runtime.agent.session.resolveSessionFilePath(cfg, sessionId ### `api.runtime.agent.defaults` -默认 model 和 provider 常量: +默认模型和提供商常量: ```typescript -const model = api.runtime.agent.defaults.model; // 例如 "anthropic/claude-sonnet-4-6" -const provider = api.runtime.agent.defaults.provider; // 例如 "anthropic" +const model = api.runtime.agent.defaults.model; // e.g. "anthropic/claude-sonnet-4-6" +const provider = api.runtime.agent.defaults.provider; // e.g. "anthropic" ``` ### `api.runtime.subagent` -启动并管理后台子智能体运行。 +启动和管理后台子智能体运行。 ```typescript -// 启动一次子智能体运行 +// Start a subagent run const { runId } = await api.runtime.subagent.run({ sessionKey: "agent:main:subagent:search-helper", message: "Expand this query into focused follow-up searches.", - provider: "openai", // 可选覆盖 - model: "gpt-4.1-mini", // 可选覆盖 + provider: "openai", // optional override + model: "gpt-4.1-mini", // optional override deliver: false, }); -// 等待完成 +// Wait for completion const result = await api.runtime.subagent.waitForRun({ runId, timeoutMs: 30000 }); -// 读取会话消息 +// Read session messages const { messages } = await api.runtime.subagent.getSessionMessages({ sessionKey: "agent:main:subagent:search-helper", limit: 10, }); -// 删除一个会话 +// Delete a session await api.runtime.subagent.deleteSession({ sessionKey: "agent:main:subagent:search-helper", }); ``` - model 覆盖(`provider` / `model`)需要操作员在配置中显式启用 `plugins.entries..subagent.allowModelOverride: true`。 - 不受信任的插件仍然可以运行子智能体,但其覆盖请求会被拒绝。 + 模型覆盖(`provider` / `model`)需要操作员在配置中通过 `plugins.entries..subagent.allowModelOverride: true` 显式启用。不受信任的插件仍然可以运行子智能体,但其覆盖请求会被拒绝。 ### `api.runtime.taskFlow` -将一个 Task Flow 运行时绑定到现有的 OpenClaw 会话键或受信任的工具上下文,然后在不必每次调用都传入 owner 的情况下创建和管理 Task Flows。 +将 Task Flow 运行时绑定到现有的 OpenClaw 会话键或受信任的工具上下文,然后在每次调用时无需传入所有者即可创建和管理 Task Flows。 ```typescript const taskFlow = api.runtime.taskFlow.fromToolContext(ctx); @@ -151,70 +150,70 @@ const waiting = taskFlow.setWaiting({ }); ``` -当你已经从自己的绑定层拿到了一个受信任的 OpenClaw 会话键时,请使用 `bindSession({ sessionKey, requesterOrigin })`。不要从原始用户输入中进行绑定。 +当你已经从自己的绑定层获得受信任的 OpenClaw 会话键时,请使用 `bindSession({ sessionKey, requesterOrigin })`。不要基于原始用户输入进行绑定。 ### `api.runtime.tts` 文本转语音合成。 ```typescript -// 标准 TTS +// Standard TTS const clip = await api.runtime.tts.textToSpeech({ text: "Hello from OpenClaw", cfg: api.config, }); -// 面向电话场景优化的 TTS +// Telephony-optimized TTS const telephonyClip = await api.runtime.tts.textToSpeechTelephony({ text: "Hello from OpenClaw", cfg: api.config, }); -// 列出可用音色 +// List available voices const voices = await api.runtime.tts.listVoices({ provider: "elevenlabs", cfg: api.config, }); ``` -使用核心 `messages.tts` 配置和 provider 选择逻辑。返回 PCM 音频缓冲区和采样率。 +使用核心 `messages.tts` 配置和提供商选择。返回 PCM 音频缓冲区 + 采样率。 ### `api.runtime.mediaUnderstanding` 图像、音频和视频分析。 ```typescript -// 描述一张图片 +// Describe an image const image = await api.runtime.mediaUnderstanding.describeImageFile({ filePath: "/tmp/inbound-photo.jpg", cfg: api.config, agentDir: "/tmp/agent", }); -// 转录音频 +// Transcribe audio const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({ filePath: "/tmp/inbound-audio.ogg", cfg: api.config, - mime: "audio/ogg", // 可选,用于无法推断 MIME 的情况 + mime: "audio/ogg", // optional, for when MIME cannot be inferred }); -// 描述一个视频 +// Describe a video const video = await api.runtime.mediaUnderstanding.describeVideoFile({ filePath: "/tmp/inbound-video.mp4", cfg: api.config, }); -// 通用文件分析 +// Generic file analysis const result = await api.runtime.mediaUnderstanding.runFile({ filePath: "/tmp/inbound-file.pdf", cfg: api.config, }); ``` -当没有生成输出时(例如输入被跳过),返回 `{ text: undefined }`。 +当未产生输出时(例如输入被跳过),会返回 `{ text: undefined }`。 - `api.runtime.stt.transcribeAudioFile(...)` 仍保留作为 `api.runtime.mediaUnderstanding.transcribeAudioFile(...)` 的兼容性别名。 + `api.runtime.stt.transcribeAudioFile(...)` 仍然作为 `api.runtime.mediaUnderstanding.transcribeAudioFile(...)` 的兼容性别名保留。 ### `api.runtime.imageGeneration` @@ -232,7 +231,7 @@ const providers = api.runtime.imageGeneration.listProviders({ cfg: api.config }) ### `api.runtime.webSearch` -Web 搜索。 +网络搜索。 ```typescript const providers = api.runtime.webSearch.listProviders({ config: api.config }); @@ -254,11 +253,16 @@ const kind = api.runtime.media.mediaKindFromMime("image/jpeg"); // "image" const isVoice = api.runtime.media.isVoiceCompatibleAudio(filePath); const metadata = await api.runtime.media.getImageMetadata(filePath); const resized = await api.runtime.media.resizeToJpeg(buffer, { maxWidth: 800 }); +const terminalQr = await api.runtime.media.renderQrTerminal("https://openclaw.ai"); +const pngQr = await api.runtime.media.renderQrPngBase64("https://openclaw.ai", { + scale: 6, // 1-12 + marginModules: 4, // 0-16 +}); ``` ### `api.runtime.config` -配置加载与写入。 +加载和写入配置。 ```typescript const cfg = await api.runtime.config.loadConfig(); @@ -300,7 +304,7 @@ const childLogger = api.runtime.logging.getChildLogger({ plugin: "my-plugin" }, ### `api.runtime.modelAuth` -model 和 provider 的凭证解析。 +模型和提供商凭证解析。 ```typescript const auth = await api.runtime.modelAuth.getApiKeyForModel({ model, cfg }); @@ -330,7 +334,7 @@ api.runtime.tools.registerMemoryCli(/* ... */); ### `api.runtime.channel` -渠道特定的运行时辅助函数(在加载渠道插件时可用)。 +渠道专用运行时辅助工具(在加载渠道插件时可用)。 `api.runtime.channel.mentions` 是供使用运行时注入的内置渠道插件共享的入站提及策略接口: @@ -359,7 +363,7 @@ const decision = api.runtime.channel.mentions.resolveInboundMentionDecision({ }); ``` -可用的提及辅助函数: +可用的提及辅助工具包括: - `buildMentionRegexes` - `matchesMentionPatterns` @@ -367,7 +371,7 @@ const decision = api.runtime.channel.mentions.resolveInboundMentionDecision({ - `implicitMentionKindWhen` - `resolveInboundMentionDecision` -`api.runtime.channel.mentions` 有意不暴露较旧的 `resolveMentionGating*` 兼容性辅助函数。请优先使用规范化的 `{ facts, policy }` 路径。 +`api.runtime.channel.mentions` 有意不暴露较旧的 `resolveMentionGating*` 兼容性辅助工具。请优先使用标准化的 `{ facts, policy }` 路径。 ## 存储运行时引用 @@ -382,7 +386,7 @@ const store = createPluginRuntimeStore({ errorMessage: "my-plugin runtime not initialized", }); -// 在你的入口点中 +// In your entry point export default defineChannelPluginEntry({ id: "my-plugin", name: "My Plugin", @@ -391,17 +395,17 @@ export default defineChannelPluginEntry({ setRuntime: store.setRuntime, }); -// 在其他文件中 +// In other files export function getRuntime() { - return store.getRuntime(); // 如果尚未初始化则抛出异常 + return store.getRuntime(); // throws if not initialized } export function tryGetRuntime() { - return store.tryGetRuntime(); // 如果尚未初始化则返回 null + return store.tryGetRuntime(); // returns null if not initialized } ``` -对于运行时存储标识,优先使用 `pluginId`。更底层的 `key` 形式适用于少见场景,即某个插件有意需要多个运行时槽位。 +对于 runtime-store 标识,优先使用 `pluginId`。较底层的 `key` 形式适用于不常见的场景,即某个插件有意需要多个运行时槽位。 ## 其他顶层 `api` 字段 @@ -411,14 +415,14 @@ export function tryGetRuntime() { | ------------------------ | ------------------------- | ------------------------------------------------------------------------------------------- | | `api.id` | `string` | 插件 id | | `api.name` | `string` | 插件显示名称 | -| `api.config` | `OpenClawConfig` | 当前配置快照(可用时为活动的内存中运行时快照) | +| `api.config` | `OpenClawConfig` | 当前配置快照(可用时为处于活动状态的内存中运行时快照) | | `api.pluginConfig` | `Record` | 来自 `plugins.entries..config` 的插件专用配置 | | `api.logger` | `PluginLogger` | 作用域日志记录器(`debug`、`info`、`warn`、`error`) | -| `api.registrationMode` | `PluginRegistrationMode` | 当前加载模式;`"setup-runtime"` 是完整入口启动 / 设置前的轻量级窗口 | +| `api.registrationMode` | `PluginRegistrationMode` | 当前加载模式;`"setup-runtime"` 是完整入口启动 / 设置之前的轻量级窗口 | | `api.resolvePath(input)` | `(string) => string` | 解析相对于插件根目录的路径 | ## 相关内容 -- [SDK 概览](/zh-CN/plugins/sdk-overview) -- 子路径参考 -- [插件入口点](/zh-CN/plugins/sdk-entrypoints) -- `definePluginEntry` 选项 -- [插件内部机制](/zh-CN/plugins/architecture) -- 能力模型和注册表 +- [SDK 概览](/zh-CN/plugins/sdk-overview) —— 子路径参考 +- [插件入口点](/zh-CN/plugins/sdk-entrypoints) —— `definePluginEntry` 选项 +- [插件内部机制](/zh-CN/plugins/architecture) —— 能力模型和注册表 diff --git a/docs/zh-CN/reference/test.md b/docs/zh-CN/reference/test.md index 591388c35..fddf39f0e 100644 --- a/docs/zh-CN/reference/test.md +++ b/docs/zh-CN/reference/test.md @@ -1,53 +1,53 @@ --- read_when: - 运行或修复测试 -summary: 如何在本地运行测试(`vitest`),以及何时使用 `force`/`coverage` 模式 +summary: 如何在本地运行测试(vitest),以及何时使用 force/coverage 模式 title: 测试 x-i18n: - generated_at: "2026-04-23T19:45:54Z" + generated_at: "2026-04-23T20:12:12Z" model: gpt-5.4 provider: openai - source_hash: 078ba2d49ffc117b5069304113ab4b08d734ced76f8e0ac491e79375d6f3fde4 + source_hash: b00be94a548ca75ab0f2531186a2bf7928655454352ac6a38fd8a6ffa47e8451 source_path: reference/test.md workflow: 15 --- # 测试 -- 完整测试工具包(测试套件、实时测试、Docker):[测试](/zh-CN/help/testing) +- 完整测试工具包(测试套件、live、Docker):[测试](/zh-CN/help/testing) -- `pnpm test:force`:终止任何仍在占用默认控制端口的残留 Gateway 网关进程,然后使用隔离的 Gateway 网关端口运行完整的 Vitest 测试套件,以避免服务端测试与正在运行的实例发生冲突。当之前的 Gateway 网关运行遗留并占用了端口 `18789` 时,请使用此命令。 -- `pnpm test:coverage`:通过 `vitest.unit.config.ts` 运行带 V8 覆盖率的单元测试套件。这是一个基于已加载文件的单元覆盖率门禁,不是整个仓库所有文件的覆盖率。阈值为:行数/函数/语句 70%,分支 55%。由于 `coverage.all` 为 false,该门禁只衡量单元覆盖率测试套件中已加载的文件,而不会将拆分测试分组中的所有源码文件都视为未覆盖。 -- `pnpm test:coverage:changed`:仅对相对于 `origin/main` 有变更的文件运行单元覆盖率。 -- `pnpm test:changed`:当 diff 只涉及可路由的源码/测试文件时,将变更的 git 路径展开为有作用域的 Vitest 分组。配置或 setup 变更仍会回退到原生根项目运行,以便在需要时对 wiring 改动进行更广泛的重跑。 -- `pnpm changed:lanes`:显示相对于 `origin/main` 的 diff 所触发的架构分组。 -- `pnpm check:changed`:对相对于 `origin/main` 的 diff 运行智能变更门禁。它会将 core 变更与 core 测试分组一起运行,将扩展变更与扩展测试分组一起运行,将仅测试变更限制为测试 typecheck/测试,针对公共插件 SDK 或 plugin-contract 的变更扩展到扩展验证,并让仅含发布元数据的版本变更保持在定向的版本/配置/根依赖检查中。 -- `pnpm test`:通过有作用域的 Vitest 分组来路由显式的文件/目录目标。未指定目标的运行会使用固定的分片组,并展开为叶子配置以进行本地并行执行;扩展组始终会展开到每个扩展/插件的分片配置,而不是单个巨大的根项目进程。 -- 完整测试和扩展分片运行会更新 `.artifacts/vitest-shard-timings.json` 中的本地计时数据;后续运行会使用这些计时数据来平衡较慢和较快的分片。设置 `OPENCLAW_TEST_PROJECTS_TIMINGS=0` 可忽略本地计时产物。 -- 部分 `plugin-sdk` 和 `commands` 测试文件现在会通过专用的轻量分组进行路由,这些分组仅保留 `test/setup.ts`,而将运行时较重的用例留在它们原有的分组中。 -- 部分 `plugin-sdk` 和 `commands` 辅助源码文件也会将 `pnpm test:changed` 映射到这些轻量分组中的显式同级测试,因此小型辅助函数改动无需重跑依赖重型运行时的测试套件。 -- `auto-reply` 现在也被拆分为三个专用配置(`core`、`top-level`、`reply`),这样 reply harness 就不会主导更轻量的顶层状态/token/helper 测试。 -- 基础 Vitest 配置现在默认使用 `pool: "threads"` 和 `isolate: false`,并在整个仓库配置中启用了共享的非隔离运行器。 -- `pnpm test:channels`:运行 `vitest.channels.config.ts`。 -- `pnpm test:extensions` 和 `pnpm test extensions`:运行所有扩展/插件分片。较重的渠道扩展和 OpenAI 会作为专用分片运行;其他扩展组保持批量执行。对单个内置插件分组使用 `pnpm test extensions/`。 -- `pnpm test:perf:imports`:启用 Vitest 导入耗时 + 导入明细报告,同时仍对显式文件/目录目标使用有作用域的分组路由。 -- `pnpm test:perf:imports:changed`:与上面相同的导入性能分析,但仅针对相对于 `origin/main` 有变更的文件。 -- `pnpm test:perf:changed:bench -- --ref `:对同一份已提交 git diff 的变更模式路由路径与原生根项目运行进行基准对比。 -- `pnpm test:perf:changed:bench -- --worktree`:无需先提交,直接对当前工作树的变更集进行基准测试。 +- `pnpm test:force`:终止任何仍在占用默认控制端口的残留 Gateway 网关进程,然后使用隔离的 Gateway 网关端口运行完整的 Vitest 测试套件,避免服务端测试与正在运行的实例发生冲突。当先前的 Gateway 网关运行导致端口 `18789` 仍被占用时,请使用此命令。 +- `pnpm test:coverage`:通过 `vitest.unit.config.ts` 使用 V8 覆盖率运行单元测试套件。这是针对已加载文件的单元覆盖率门禁,不是整个仓库所有文件的覆盖率。阈值为:行数 / 函数 / 语句覆盖率 70%,分支覆盖率 55%。由于 `coverage.all` 为 false,此门禁只统计被单元覆盖率测试套件加载的文件,而不会把拆分测试通道中的每个源文件都视为未覆盖。 +- `pnpm test:coverage:changed`:仅针对自 `origin/main` 以来发生变更的文件运行单元覆盖率。 +- `pnpm test:changed`:当 diff 仅涉及可路由的源文件 / 测试文件时,将变更的 Git 路径展开为有范围限制的 Vitest 测试通道。配置 / 设置变更仍会回退到原生根项目运行,以便在需要时更广泛地重新运行布线相关改动。 +- `pnpm changed:lanes`:显示针对 `origin/main` 的 diff 所触发的架构测试通道。 +- `pnpm check:changed`:对比 `origin/main` 的 diff 运行智能变更门禁。它会将核心代码工作与核心测试通道一起运行,将扩展工作与扩展测试通道一起运行,将仅测试改动限制为仅测试类型检查 / 测试,针对公开的插件 SDK 或插件契约变更扩展到扩展验证,并让仅发布元数据的版本号变更保持在有针对性的版本 / 配置 / 根依赖检查范围内。 +- `pnpm test`:将显式指定的文件 / 目录目标路由到有范围限制的 Vitest 测试通道。未指定目标的运行会使用固定的分片组,并展开到叶子配置以便在本地并行执行;扩展组始终会展开为按扩展划分的分片配置,而不是一个巨大的根项目进程。 +- 完整测试和扩展分片运行会更新 `.artifacts/vitest-shard-timings.json` 中的本地时序数据;后续运行会使用这些时序数据来平衡慢分片和快分片。设置 `OPENCLAW_TEST_PROJECTS_TIMINGS=0` 可忽略本地时序工件。 +- 部分 `plugin-sdk` 和 `commands` 测试文件现在会通过专用轻量测试通道路由,这些通道仅保留 `test/setup.ts`,而运行时负载较重的用例仍保留在现有测试通道中。 +- 部分 `plugin-sdk` 和 `commands` 辅助源文件也会把 `pnpm test:changed` 映射到这些轻量测试通道中的显式同级测试,因此小型辅助函数改动无需重新运行重量级、依赖运行时的测试套件。 +- `auto-reply` 现在也被拆分为三个专用配置(`core`、`top-level`、`reply`),这样回复测试框架就不会主导较轻量的顶层状态 / token / helper 测试。 +- 基础 Vitest 配置现在默认使用 `pool: "threads"` 和 `isolate: false`,并在整个仓库配置中启用共享的非隔离运行器。 +- `pnpm test:channels` 运行 `vitest.channels.config.ts`。 +- `pnpm test:extensions` 和 `pnpm test extensions` 运行所有扩展 / 插件分片。重量级渠道扩展和 OpenAI 会作为专用分片运行;其他扩展组保持批量运行。对某一个内置插件测试通道,请使用 `pnpm test extensions/`。 +- `pnpm test:perf:imports`:启用 Vitest 导入耗时 + 导入明细报告,同时仍对显式文件 / 目录目标使用有范围限制的测试通道路由。 +- `pnpm test:perf:imports:changed`:与上面相同的导入性能分析,但仅针对自 `origin/main` 以来发生变更的文件。 +- `pnpm test:perf:changed:bench -- --ref `:针对同一份已提交的 Git diff,对比变更模式路由路径与原生根项目运行的基准表现。 +- `pnpm test:perf:changed:bench -- --worktree`:无需先提交,即可对当前工作区改动集进行基准测试。 - `pnpm test:perf:profile:main`:为 Vitest 主线程写入 CPU profile(`.artifacts/vitest-main-profile`)。 -- `pnpm test:perf:profile:runner`:为单元测试运行器写入 CPU + heap profile(`.artifacts/vitest-runner-profile`)。 -- `pnpm test:perf:groups --full-suite --allow-failures --output .artifacts/test-perf/baseline-before.json`:串行运行完整测试套件中的每个 Vitest 叶子配置,并写入分组时长数据以及每个配置的 JSON/日志产物。测试性能智能体会在尝试修复慢测试之前,将它作为基线。 +- `pnpm test:perf:profile:runner`:为单元测试运行器写入 CPU + 堆 profile(`.artifacts/vitest-runner-profile`)。 +- `pnpm test:perf:groups --full-suite --allow-failures --output .artifacts/test-perf/baseline-before.json`:串行运行完整测试套件中的每个 Vitest 叶子配置,并写入分组耗时数据以及每个配置对应的 JSON / 日志工件。测试性能智能体会将其用作尝试修复慢测试之前的基线。 - `pnpm test:perf:groups:compare .artifacts/test-perf/baseline-before.json .artifacts/test-perf/after-agent.json`:比较性能优化改动前后的分组报告。 - Gateway 网关集成:通过 `OPENCLAW_TEST_INCLUDE_GATEWAY=1 pnpm test` 或 `pnpm test:gateway` 选择启用。 -- `pnpm test:e2e`:运行 Gateway 网关端到端冒烟测试(多实例 WS/HTTP/node 配对)。在 `vitest.e2e.config.ts` 中默认使用 `threads` + `isolate: false` 和自适应 worker;可通过 `OPENCLAW_E2E_WORKERS=` 调整,并设置 `OPENCLAW_E2E_VERBOSE=1` 获取详细日志。 -- `pnpm test:live`:运行 provider 实时测试(minimax/zai)。需要 API key,并且需要 `LIVE=1`(或 provider 专属的 `*_LIVE_TEST=1`)才能取消 skip。 -- `pnpm test:docker:all`:先构建共享的实时测试镜像和 Docker E2E 镜像一次,然后默认以并发数 4 使用 `OPENCLAW_SKIP_DOCKER_BUILD=1` 运行 Docker 冒烟分组。可通过 `OPENCLAW_DOCKER_ALL_PARALLELISM=` 调整。除非设置了 `OPENCLAW_DOCKER_ALL_FAIL_FAST=0`,否则运行器会在首次失败后停止调度新的池化分组;每个分组默认有 120 分钟超时,可通过 `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS` 覆盖。对启动或 provider 敏感的分组会在并行池之后以独占方式运行。每个分组的日志会写入 `.artifacts/docker-tests//`。 -- `pnpm test:docker:openwebui`:启动 Docker 化的 OpenClaw + Open WebUI,通过 Open WebUI 登录,检查 `/api/models`,然后通过 `/api/chat/completions` 运行一次真实的代理聊天。需要可用的实时模型 key(例如 `~/.profile` 中的 OpenAI),会拉取外部 Open WebUI 镜像,并不像常规单元/e2e 测试套件那样要求具备 CI 稳定性。 -- `pnpm test:docker:mcp-channels`:启动一个预置数据的 Gateway 网关容器和第二个客户端容器,后者会启动 `openclaw mcp serve`,然后验证路由会话发现、转录读取、附件元数据、实时事件队列行为、出站发送路由,以及通过真实 stdio bridge 传输的 Claude 风格渠道和权限通知。Claude 通知断言会直接读取原始 stdio MCP 帧,因此该冒烟测试能反映 bridge 实际发出的内容。 +- `pnpm test:e2e`:运行 Gateway 网关端到端冒烟测试(多实例 WS/HTTP/节点配对)。在 `vitest.e2e.config.ts` 中默认使用 `threads` + `isolate: false` 以及自适应 worker;可通过 `OPENCLAW_E2E_WORKERS=` 调整,并设置 `OPENCLAW_E2E_VERBOSE=1` 以输出详细日志。 +- `pnpm test:live`:运行提供商 live 测试(minimax/zai)。需要 API key,并设置 `LIVE=1`(或提供商专用的 `*_LIVE_TEST=1`)才能取消跳过。 +- `pnpm test:docker:all`:先构建一次共享的 live-test 镜像和 Docker E2E 镜像,然后默认以并发度 4 运行 Docker 冒烟测试通道,并设置 `OPENCLAW_SKIP_DOCKER_BUILD=1`。可通过 `OPENCLAW_DOCKER_ALL_PARALLELISM=` 调整。除非设置了 `OPENCLAW_DOCKER_ALL_FAIL_FAST=0`,否则运行器会在首次失败后停止调度新的池化测试通道;每个测试通道默认有 120 分钟超时,可通过 `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS` 覆盖。对启动或提供商敏感的测试通道会在并行池之后以独占方式运行。每个测试通道的日志都会写入 `.artifacts/docker-tests//`。 +- `pnpm test:docker:openwebui`:启动 Docker 化的 OpenClaw + Open WebUI,通过 Open WebUI 登录,检查 `/api/models`,然后通过 `/api/chat/completions` 运行一次真实的代理聊天。需要可用的 live 模型 key(例如 `~/.profile` 中的 OpenAI),会拉取外部 Open WebUI 镜像,并不像普通 unit/e2e 测试套件那样被期望具有 CI 稳定性。 +- `pnpm test:docker:mcp-channels`:启动一个带种子数据的 Gateway 网关容器,以及第二个会启动 `openclaw mcp serve` 的客户端容器,然后通过真实的 stdio bridge 验证路由会话发现、转录读取、附件元数据、live 事件队列行为、出站发送路由,以及类似 Claude 的渠道与权限通知。Claude 通知断言会直接读取原始 stdio MCP 帧,因此该冒烟测试能反映 bridge 实际发出的内容。 ## 本地 PR 门禁 -对于本地 PR 合入/门禁检查,请运行: +在本地执行 PR 合入 / 门禁检查时,运行: - `pnpm check:changed` - `pnpm check` @@ -56,12 +56,12 @@ x-i18n: - `pnpm test` - `pnpm check:docs` -如果 `pnpm test` 在负载较高的主机上出现偶发失败,在将其视为回归之前先重跑一次,然后使用 `pnpm test ` 进行定位。对于内存受限的主机,可使用: +如果 `pnpm test` 在负载较高的主机上出现偶发失败,在视其为回归之前先重跑一次,然后用 `pnpm test ` 进行定位。对于内存受限的主机,请使用: - `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test` - `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/tmp/openclaw-vitest-cache pnpm test:changed` -## 模型延迟基准测试(本地 keys) +## 模型延迟基准测试(本地 key) 脚本:[`scripts/bench-model.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/bench-model.ts) @@ -103,15 +103,15 @@ x-i18n: - `real`:`health`、`status`、`status --json`、`sessions`、`sessions --json`、`agents list --json`、`gateway status`、`gateway status --json`、`gateway health --json`、`config get gateway.port` - `all`:同时包含两个预设 -输出会包含每个命令的 `sampleCount`、平均值、p50、p95、最小/最大值、退出码/信号分布,以及最大 RSS 汇总。可选的 `--cpu-prof-dir` / `--heap-prof-dir` 会为每次运行写入 V8 profile,这样计时与 profile 捕获会使用同一套 harness。 +输出会包含每个命令的 `sampleCount`、平均值、p50、p95、最小值 / 最大值、exit-code / signal 分布,以及最大 RSS 摘要。可选的 `--cpu-prof-dir` / `--heap-prof-dir` 会为每次运行写入 V8 profile,使计时与 profile 捕获使用同一套测试框架。 保存输出约定: -- `pnpm test:startup:bench:smoke` 会将定向冒烟产物写入 `.artifacts/cli-startup-bench-smoke.json` -- `pnpm test:startup:bench:save` 会使用 `runs=5` 和 `warmup=1` 将完整测试套件产物写入 `.artifacts/cli-startup-bench-all.json` -- `pnpm test:startup:bench:update` 会使用 `runs=5` 和 `warmup=1` 刷新已检入的基线 fixture:`test/fixtures/cli-startup-bench.json` +- `pnpm test:startup:bench:smoke` 会将目标冒烟工件写入 `.artifacts/cli-startup-bench-smoke.json` +- `pnpm test:startup:bench:save` 会使用 `runs=5` 和 `warmup=1` 将完整测试套件工件写入 `.artifacts/cli-startup-bench-all.json` +- `pnpm test:startup:bench:update` 会使用 `runs=5` 和 `warmup=1` 刷新已纳入版本控制的基线 fixture:`test/fixtures/cli-startup-bench.json` -已检入的 fixture: +已纳入版本控制的 fixture: - `test/fixtures/cli-startup-bench.json` - 使用 `pnpm test:startup:bench:update` 刷新 @@ -119,7 +119,7 @@ x-i18n: ## 新手引导 E2E(Docker) -Docker 是可选的;只有在需要容器化新手引导冒烟测试时才需要它。 +Docker 是可选的;只有在进行容器化新手引导冒烟测试时才需要。 在干净的 Linux 容器中运行完整冷启动流程: @@ -127,11 +127,11 @@ Docker 是可选的;只有在需要容器化新手引导冒烟测试时才需 scripts/e2e/onboard-docker.sh ``` -该脚本会通过 pseudo-tty 驱动交互式向导,验证配置/工作区/会话文件,然后启动 Gateway 网关并运行 `openclaw health`。 +该脚本会通过 pseudo-tty 驱动交互式向导,验证 config/workspace/session 文件,然后启动 Gateway 网关并运行 `openclaw health`。 -## 二维码导入冒烟测试(Docker) +## QR 导入冒烟测试(Docker) -确保 `qrcode-terminal` 能在受支持的 Docker Node 运行时中加载(默认 Node 24,兼容 Node 22): +确保维护中的 QR 运行时 helper 能在受支持的 Docker Node 运行时下加载(默认 Node 24,兼容 Node 22): ```bash pnpm test:docker:qr