---
docStatus: current
---

# 渠道与会话消息

从真实成员和渠道目录取得外部会话，通过 channel gateway 给同事发送企业微信私聊，并在工作流中调用。

channel gateway 提供已绑定渠道会话的消息发送能力。向指定同事的企业微信私聊发送消息时，
使用 `sendExternalSessionMessage`，提供接收人的百积木用户 ID、真实外部会话键及内容。
它直接发送渠道消息，不调用 Agent，也不要求创建新的内部 Agent 会话。

## 先选择发送语义

| 目标                          | 方法与关键参数                                                                       |
| --------------------------- | ----------------------------------------------------------------------------- |
| 发给明确的外部会话，例如某位同事与某个企微机器人的私聊 | channel gateway 的 `sendExternalSessionMessage`：`internalUserId + sessionKey`。 |
| 把执行结果发回已关联渠道的内部 Agent 会话    | channel gateway 的 `sendSessionMessage`：`sessionId`。                           |
| 发到工作区已绑定群聊                  | 工作区管理模块的 `sendGroupChatMessage`：优先使用群聊通讯录 `contactId` 对应的 `id`。               |

不同模块可能有同名方法。本文的 `sendSessionMessage` 属于 channel gateway，含义是向渠道下行发送，
不能与 Agent 模块中驱动 Agent 执行的方法混用。

## 调用前准备

- 在目标工作区安装提供 channel gateway 与目标企业微信渠道的 Bundle，确认方法已在 Runtime 可见。
- 开启目标渠道，完成同事的外部账号与百积木用户绑定。
- 同事已向目标企微机器人发送过消息，平台已有该外部私聊的入站记录及回复上下文。
- 当前调用入口有权调用相关方法；Workflow 直接引用的 Module 必须属于本包清单；外部渠道能力通过本包 Module 的接口属性绑定。

从[概览与调用约定](/development/organization-capabilities/)发现服务及方法。本文不提供固定 Runtime `businessId`，
也不把示例机器人、渠道和用户当作实际配置。

## 1. 确认同事与真实渠道

先调用工作区管理模块的 `listWorkspaceMembers({})`，找到接收人的真实用户 ID。
有重名时结合组织信息确认；不要取查询结果中的第一人。继续通过 `listContactMethods` 查看企微联系方式，
具体步骤见[通讯录与联系方式](/development/organization-capabilities/contacts/)。

再读取 channel gateway 的 `listWorkspaceChannels` 定义，用以下业务参数查询启用渠道：

```json
{
  "enabledOnly": true
}
```

从返回目录选择明确的目标企业微信渠道实例。接下来 `channel` 的值必须使用目录返回的真实实例标识，
不能只填渠道类型 `qiwei`，也不能根据名称或 `sessionKey` 猜测一个服务 ID。

使用 `getInternalUserBinding` 核对这个渠道实例下的接收人绑定：

```json
{
  "channel": "<目录返回的真实企微渠道实例标识>",
  "internalUserId": "<接收人的百积木用户ID>"
}
```

这里 `internalUserId` 为字符串。联系方式查询的 `userId` 当前为整数；两者都指向同一平台用户，
但要按各方法合同传递正确类型。

## 2. 选择已有的外部私聊

调用 channel gateway 的 `listSessionGroups`：

```json
{
  "channel": "<目录返回的真实企微渠道实例标识>"
}
```

读取返回的 `sessionKey`、`scope`、`channelUserId`、`internalUserId` 等会话字段，结合上一步的绑定核对目标。
选择私聊记录（`scope=dm`），并确认接收人与目标机器人。`channelUserId` 是外部账号，不能直接和百积木用户 ID 比较。

没有匹配记录时先检查入站消息和绑定；多个匹配记录时明确选择机器人及会话。
不要为了得到一个可用结果，改选群聊、其他同事的会话或任意最近活跃会话。

## 企业微信的 sessionKey 表示什么

企微机器人适配器生成的典型私聊会话键如下。格式用于理解，业务调用应原样使用会话查询返回值：

```text
qiwei:bot:<botKey>:dm:<企业微信用户ID>
```

- `botKey` 标识目标机器人，不是 channel gateway 的 Runtime 服务 ID。
- 最后的用户 ID 是企业微信用户 ID，不是姓名、手机号或百积木用户 ID。
- 同一个人与两个机器人私聊，会得到不同会话键。
- `sessionKey` 表示外部会话；`sessionId` 表示内部 Agent 会话，不能互换。

例如 `qiwei:bot:example-bot:dm:example-user` 只是格式示意，不是可用发送目标。
群聊键包含群聊身份，不能把群聊键用于个人通知。

> **拼接字符串不会创建私聊**
>
> `sendExternalSessionMessage` 要求已有入站上下文和有效用户绑定。即使知道机器人标识和企微用户 ID，
> 也不能只拼出一个键就给从未与机器人互动过的人首次发消息。网关将会话键作为不透明字符串处理，
> 不要在工作流里解析它来替代渠道、成员或机器人目录。

## 3. 发送文本

调用 channel gateway 的 `sendExternalSessionMessage`，常用参数如下：

| 参数               | 必填               | 含义                 |
| ---------------- | ---------------- | ------------------ |
| `internalUserId` | 是                | 接收同事的百积木用户 ID，字符串。 |
| `sessionKey`     | 是                | 上一步选定外部会话的真实键。     |
| `message`        | 与 `content` 至少一个 | 非空文本。              |
| `content`        | 与 `message` 至少一个 | 统一渠道内容数组。          |

简单文本调用：

```json
{
  "internalUserId": "<接收人的百积木用户ID>",
  "sessionKey": "<查询返回的真实私聊sessionKey>",
  "message": "报告已生成，请查看并处理。"
}
```

或用结构化文本内容替换 `message`：

```json
{
  "internalUserId": "<接收人的百积木用户ID>",
  "sessionKey": "<查询返回的真实私聊sessionKey>",
  "content": [
    { "type": "text", "text": "报告已生成，请查看并处理。" }
  ]
}
```

一次调用选择一种内容写法。当前统一内容协议还声明 image、audio、video、file；实际支持的媒体格式、
资源访问和大小限制需同时核对所选渠道的合同，不能从统一类型名推断所有渠道都能投递同一种媒体。

本方法不需要额外传 `channel`、`channelUserId`、`workspaceId`、`projectId` 或内部 `sessionId`。
工作区由调用入口建立，平台根据接收人、外部会话和已启用绑定核验实际投递目标。
未绑定或解析到多个不同目标时，请求失败，不会自动猜测接收人。

## 4. 在工作流中调用

下面是可加入业务 Workflow 的发送节点。Module 的 `bundleId`、`moduleId` 必须替换为目录返回的作者身份，
不能填 Runtime `businessId`。示例不自动查找或选择接收人：三个输入必须由前置业务步骤或启动前校验取得。

```json
{
  "type": "module_method",
  "id": "notify_colleague",
  "name": "向同事发送企微私聊",
  "module": {
    "bundleId": "<目录返回的来源Bundle身份>",
    "moduleId": "<目录返回的channel gateway模块身份>"
  },
  "method": "sendExternalSessionMessage",
  "params": {
    "internalUserId": "{{input.recipientUserId}}",
    "sessionKey": "{{input.recipientSessionKey}}",
    "message": "{{input.notificationText}}"
  },
  "timeoutSecs": 30
}
```

完整流程连接 `start → notify_colleague → end`，将 `recipientUserId`、`recipientSessionKey` 和
`notificationText` 声明为必填字符串输入。`recipientUserId` 是接收人，不是默认的工作流发起人。
如需运行时查询，在前置 `module_method` 中查询成员、绑定和会话，并将明确选择后的值映射到发送节点；
不要让选择逻辑在多结果时默认取第一条。

完整作者定义、输入类型和连线格式见[工作流](/features/workflows/)。
示例没有配置自动重试，因为一次请求超时不能证明渠道没有收到消息；重试前需要核实目标方法的幂等与投递查询合同。

## 5. 验证结果与排查

公开 API 遵循[CModel 错误模型](/integration/cmodel-error-model/)，业务成功以字符串 `errorCode` 为 `"0"` 判断，
不能只检查 HTTP 200。CLI 和 Runtime 工具可能已展开响应，按入口返回合同读取结果。

发送结果包含接收人、渠道、外部会话及 `delivered` 等字段时，应与所选目标核对，并查看工作流节点执行记录。
`delivered: true` 表示该次发送链路报告成功，不证明用户已读或人工事项已完成。

| 现象          | 处理                               |
| ----------- | -------------------------------- |
| 方法不存在       | 核对提供能力的 Bundle、已安装版本和当前 Runtime。 |
| 用户绑定不存在或有歧义 | 核对接收人的百积木身份、外部账号以及所选会话，不猜测替代用户。  |
| 会话不存在或上下文缺失 | 确认同事向目标机器人发过消息，并检查入站与绑定。         |
| 渠道或工作区停用    | 由相应管理员确认配置和启用状态。                 |
| 权限不足        | 核对调用入口、执行主体和方法授权，不将接收人身份当作凭据。    |
| 超时或外部渠道拒绝   | 查看节点及渠道返回的实际错误和投递证据，再决定是否重试。     |

需要同事完成业务处理时，继续接入[事项与人工协作](/development/organization-capabilities/issues/)。
需要统一收件箱、偏好或多渠道路由时，先阅读[通知与投递](/development/organization-capabilities/notifications/)。
