第一个 Bot 开发教程
- 04-user-manual/01-bot-developer-onboarding.md v0.1(接入前准备/概念/鉴权概览)
- 06-api-integration/OCTO-Bot-API接入快速上手指南-v0.1.md v0.1(curl骨架/字段/禁止项)
- 06-api-integration/Bot-API概览.md v0.1(端点分组)
- source-repos/openclaw-channel-octo/src/api-fetch.ts(鉴权header/sendMessage/register/heartbeat/events 调用点)
- source-repos/octo-server/modules/bot_api/(bot_api.go 路由/auth.go/events.go/groups.go/file.go/card_profile.go)
- 02-architecture/OCTO总体架构.md v1.0(请求5步/ACK/at-least-once)
- 03-deployment-ops/OCTO部署形态总览.md v1.0(apiBase/端口/HTTPS)
- 源码研究/schema边界汇总卡-v0.4.md / nonBot入站路径一致性验证小卡-v0.1.md(硬校验/拒卡/OBO边界)
创建第一个 Bot 实操 Walkthrough v0.1
状态:`draft` / v0.1。confidence:medium。
本文带你从零开始,在 OCTO 上创建第一个 Bot、发出第一条消息、收到第一条回复。本文基于 [01-bot-developer-onboarding.md](01-bot-developer-onboarding.md) 的概念,给出 step-by-step 实操。
所有未确认的 UI 路径、URL 前缀、数字常量一律用 `{{占位}}` 或 `⚠️ 待确认` 标注,并对应到 061 提问稿问题编号;等产品管家回答后回填,不编造可运行的完整 curl。本文不替代官方 API 文档,不作为客户交付手册。
适用场景与前置假设
- 你走哪条路径:本文默认走 路径② 直接调 Bot REST API(用 curl/HTTP 客户端),不依赖 OpenClaw 插件;这是最通用、最能看清机制的路径。走完本文你也能理解路径①插件背后做了什么。
- 你需要准备:
- 一套能访问的 OCTO 部署,知道 HTTP/HTTPS 入口地址(参考 `03-deployment-ops/OCTO部署形态总览.md` v1.0 端口表)。
- 一个能登录 OCTO 客户端(Web/PC)的普通用户账号,用来测试。
- OCTO 管理员配合创建 Bot 身份。
- 一个 HTTP 客户端(本文用 curl;Postman/Node/Python/Go 等价)。
ℹ️ 如果你要做带 AI 能力的 Agent,路径① openclaw-channel-octo 插件会自动帮你做本文里的 register/heartbeat/events 轮询/ACK/card profile 协商/降级,不用自己写。走完本文理解机制后再切插件路径。
Step 1:获取 botToken
1.1 请管理员创建 Bot 身份
- 需要提供给管理员的信息:Bot 名字(中英文均可)、头像(可选)、简介(可选)、Bot 用途说明。
⚠️ 待确认(对应 061 P0-API与鉴权 Q2 / onboarding K1-K2):
- Bot 创建入口是在 octo-admin 管理后台、普通客户端的"BotFather"页面,还是通过 API 自助注册——源码 `modules/botfather` 存在但具体 UI 路径未确认。
- 创建 Bot 需要什么角色权限(superAdmin?space 管理员?普通开发者是否可自助创建?)。
- botToken 是在创建成功后立即展示一次,还是可以在管理页反复查看/重置。
- botToken 形态(长度/前缀/JWT 还是 opaque 字符串)、是否会过期、吊销/重置流程。
1.2 保存 botToken 并确认 apiBase
botToken:字符串,类似ob-xxxxxxxxxxxxxxxx(示例形态,实际以管理员给的为准,本文不编具体前缀)。- Bot 的
uid(Bot 在 OCTO 里的用户 ID,32 位 hex)。
| 部署形态 | apiBase(占位) |
|---|---|
| Docker 单机 HTTP | http:// |
| Docker 单机 HTTPS | https:// |
| K8s + Ingress | https:// |
| 前置反代 | https:// |
⚠️ 待确认(onboarding K3/A1):URL 前缀是直接挂根 `/v1/bot/...` 还是带 `/api/v1/bot/...`——openclaw-channel-octo 源码直接打 `/v1/bot/*`,docker nginx 同时反代 `/v1/` 和 `/api/v1/`;生产以实际 ingress 配置为准。本文统一用占位符 `{{apiBase}}`。
1.3 (可选)把 Bot 拉进一个测试群
⚠️ 待确认:Bot 能否自己建群、拉人(061 P0-API与鉴权 Q4)——本文先让人工把 Bot 拉进群,不假设 Bot 有建群 API。
1.4 确认 channel_id
- 方式 A(推荐先走):先让 Bot 跑起来,在群里 @Bot 发一句话,Bot 从
/v1/bot/events拉到的入站消息事件里会带channel_id和channel_type(见 Step 4)。 - 方式 B:调
GET {{apiBase}}/v1/bot/groups(路由源码 bot_api.go:416 已见)拿 Bot 所在群列表,从返回里读group_no作为channel_id。
⚠️ 待确认(A3):`channel_type` 数字常量值——群聊/私聊/Thread 分别对应哪个数字,源码 constants.ts 有定义但当前未精读;本文用 `{{channelType}}` 占位。实操建议先走方式 A,从入站事件里读服务端给的 `channel_type`,直接回发即可。
Step 2:鉴权连通性测试(register + heartbeat)
2.1 调 heartbeat 验证 token
curl -i -X POST "{{apiBase}}/v1/bot/heartbeat" \
-H "Authorization: Bearer {{botToken}}"
ℹ️ 路径①插件会自动调 heartbeat,间隔由插件内部实现;路径②自实现时建议周期性调用(具体间隔待确认 A7,先按 30s 起步,观察服务端是否返回 401/掉线信号)。
2.2 (可选)调 register 刷 IM token
curl -i -X POST "{{apiBase}}/v1/bot/register" \
-H "Authorization: Bearer {{botToken}}" \
-H "Content-Type: application/json" \
-d '{}'
⚠️ 不要把 register 当"登录接口"在每次发消息前都调——它是限流豁免的重连入口,正常在线状态用 heartbeat 保活即可。
2.3 (可选)查询 Bot 卡片能力 profile
curl "{{apiBase}}/v1/bot/card/profile" \
-H "Authorization: Bearer {{botToken}}"
Step 3:发送第一条文本消息
3.1 准备请求体
Authorization: Bearer {{botToken}}Content-Type: application/json
{
"channel_id": "{{channelId}}",
"channel_type": {{channelType}},
"payload": {
"type": "text",
"content": "👋 你好,我是第一个 Bot!收到请回复。"
},
"client_msg_no": "walkthrough-msg-001"
}
channel_id:目标会话 ID(群填 group_no,私聊填对方 uid)。channel_type:数字常量,待确认 A3(群/私聊/Thread 分别是几);先走方式 A 从入站事件读。payload.type:文本消息类型字符串,待确认 A4(源码sendTextMessage构造 type 字段,精读后可确认是"text"还是数字)。payload.content:文本内容。client_msg_no:客户端消息唯一 ID(UUID 或递增序号),用于幂等去重(具体去重语义待确认),建议每次发送生成新值。
3.2 发 curl
curl -i -X POST "{{apiBase}}/v1/bot/sendMessage" \
-H "Authorization: Bearer {{botToken}}" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "{{channelId}}",
"channel_type": {{channelType}},
"payload": { "type": "text", "content": "👋 你好,我是第一个 Bot!收到请回复。" },
"client_msg_no": "walkthrough-msg-001"
}'
- 401:token 错/过期;重新走 Step 2。
- 400 Bad Request:
- 是否漏了必填字段;
- 是否把
channel_type写成字符串(数字常量,不加引号); - 若发卡片(本步不用):检查 type/profile/card_version/card 四必填、是否双传 card+template_ref、是否伪造了 catalog_provenance(详见 059 指南第 4 章 10 条禁止项)。
- 403 Forbidden:Bot 不在目标群里,或对该会话无发消息权限。
- 429 Too Many Requests:被限流;等待
Retry-After秒数(具体阈值待确认 A8)再重试。 - 客户端看不到消息:确认客户端版本是否支持;检查你是往正确的 channel_id/channel_type 发;可能 WuKongIM 长连接抖动,稍等几秒。
⚠️ 注意硬边界(源码已验证):
- 用 bot_token + `/v1/bot/sendMessage` 才能发;普通用户 token 发消息的接口是另一套,且普通用户 API 绝对拒绝卡片 payload(046 事实)。
- 不要同时在 payload 里传 `card` 和 `template_ref`(XOR,双传 400)。
- 不要伪造 `catalog_provenance`(入站 loud 4xx 拒绝)。
3.3 验证消息已送达
- 消息应该出现在会话里,Bot 的名字/头像正确显示;
- 如果是群,其他群成员也能看到。
Step 4:接收并回复消息(events 长轮询 + ACK)
4.1 启动事件轮询
curl -i -X POST "{{apiBase}}/v1/bot/events" \
-H "Authorization: Bearer {{botToken}}" \
-H "Content-Type: application/json" \
-d '{}'
{
"events": [
{
"event_id": "evt-xxxxxxx",
"type": "message",
"channel_id": "...",
"channel_type": ...,
"message": {
"message_id": "...",
"sender_uid": "...",
"content": { "type": "text", "text": "@Bot 你好" }
}
}
],
"cursor": "next-cursor-value"
}
⚠️ 待确认(A8):完整事件 JSON schema(事件类型枚举、字段名、@提及格式、附件格式)、长轮询超时秒数、cursor 传参方式。
4.2 让普通用户在群里 @Bot 发一句话
event_id:用于 ACK;message.sender_uid:发消息的用户 uid(不是你 Bot 自己);message.content:消息文本;channel_id/channel_type:这个会话的 ID 和类型——这正是你在 Step 3 需要的,可以从这里读出来直接回发。
ℹ️ 除了 `message` 事件,你还可能收到:
- `card_action`:用户点击你发的卡片按钮时产生(Action.Submit 回调);
- 成员变更事件(加群/退群);
- 其他系统事件(具体清单待确认)。
4.3 ACK 事件,避免重复投递
curl -i -X POST "{{apiBase}}/v1/bot/events/{{event_id}}/ack" \
-H "Authorization: Bearer {{botToken}}"
- 拉到 events;
- 处理业务(解析、回消息);
- 处理成功后再 ACK;
- 你的业务逻辑要按 `event_id` 做幂等(重复事件到来时不要重复回复)。
⚠️ 不要先 ACK 再处理——如果 ACK 后处理崩溃,消息就丢了。也不要长时间不 ACK——会重复投递,但不会丢。
4.4 回消息
{
"channel_id": "<从事件里抄来的 channel_id>",
"channel_type": <从事件里抄来的 channel_type>,
"payload": {
"type": "text",
"content": "收到!你刚刚说的是:<原消息内容>"
},
"client_msg_no": "walkthrough-reply-001"
}
4.5 保持轮询循环
loop:
resp = POST /v1/bot/events (长轮询,阻塞)
for evt in resp.events:
if 已处理过 evt.event_id: continue
处理 evt(业务逻辑 / 回消息 / 调其他接口)
POST /v1/bot/events/{evt.event_id}/ack
带上 resp.cursor 进入下一轮(具体传参方式待确认)
ℹ️ 路径①插件已经帮你写好这个循环了,包含单线程并发、ACK 去重、重试、错误处理、心跳保活。
Step 5:验证收发成功
| 检查项 | 如何验证 |
|---|---|
| ✅ 鉴权通 | Step 2 heartbeat 返回 200 |
| ✅ 能拿到群列表/入站事件 | GET /v1/bot/groups 或 events 能看到 Bot 所在群 |
| ✅ 发出的消息对端可见 | 普通用户账号能在群里看到 Bot 发的消息 |
| ✅ 能收到 @ 消息 | 普通用户 @Bot 后,events 长轮询拿到 message 事件 |
| ✅ 能回复 | Bot 回的消息普通用户能看到 |
| ✅ ACK 生效 | 下一次轮询不重复收到已 ACK 的事件 |
| ✅ 心跳保活 | 跑 5 分钟不重启,Bot 仍能收到新事件 |
常见报错速查
| 现象 | 可能原因 | 排查 |
|---|---|---|
| heartbeat 返回 401 | botToken 错/过期/被吊销 | 核对 token,联系管理员 |
| sendMessage 返回 403 | Bot 不在目标群,或对会话无权 | 把 Bot 拉进群;核对 channel_id |
| sendMessage 返回 400 | 字段缺失/类型错/双传 card+template/伪造 catalog_provenance | 对照 059 第 4 章 10 条禁止项 |
| sendMessage 返回 429 | 触发限流 | 等 Retry-After,检查是否同 IP 其他 Bot 挤占(2026-08-05 事故) |
| events 一直空转无消息 | Bot 没在群里、没人 @Bot、长轮询 cursor 未正确续传 | 换种方式触发(手动 @);核对 cursor 传参 |
| 事件重复投递 | 没 ACK / ACK 前崩溃 | 确保处理成功后 ACK;做 event_id 幂等 |
| 发卡片显示纯文本 | 客户端版本过旧,或 Bot profile 没开对应 surface | 先 GET card/profile 看支持情况 |
| 客户端看不到 Bot 消息 | channel_id/channel_type 错;WuKongIM 长连接抖动 | 用入站事件的 channel_id/channel_type 回发,不要自己拼 |
进阶:发你的第一张卡片(可选)
curl -X POST "{{apiBase}}/v1/bot/sendMessage" \
-H "Authorization: Bearer {{botToken}}" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "{{channelId}}",
"channel_type": {{channelType}},
"payload": {
"type": 17,
"profile": "octo-chat/v1",
"card_version": 1,
"card": {
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{ "type": "TextBlock", "text": "👋 第一张卡片", "weight": "Bolder" },
{ "type": "TextBlock", "text": "Hello from Bot walkthrough!" }
]
}
},
"client_msg_no": "walkthrough-card-001"
}'
待确认项(等 061 提问稿回答后回填)
| # | 待确认项 | 对应 061 问题 / onboarding 编号 |
|---|---|---|
| W1 | Bot 创建入口页面/URL、所需角色权限 | P0-API与鉴权 Q2 / K1 |
| W2 | botToken 形态/生命周期/吊销/重置方式 | P0-API与鉴权 Q2 / K2 |
| W3 | {{apiBase}} 标准 URL 前缀(/v1 vs /api/v1) |
P0-API与鉴权 Q1 / K3 / A1 |
| W4 | channel_type 数字常量值(群/私聊/Thread) |
P0-API与鉴权 Q3 / A3 |
| W5 | payload.type 文本消息类型字符串/数字 |
P0-API与鉴权 Q3 / A4 |
| W6 | sendMessage 完整响应字段(message_id/ts 等) | P0-API与鉴权 Q3 / A5 |
| W7 | register 完整请求/响应、何时需要调用 | P0-API与鉴权 Q2 / A6 |
| W8 | heartbeat 推荐间隔、超时/401 处理策略 | P0-API与鉴权 Q2 / A7 |
| W9 | events 完整 JSON schema、事件类型枚举、cursor 传参、长轮询超时、ACK 语义细节 | P0-API与鉴权 Q3 / A8 |
| W10 | 完整错误码表与 429 Retry-After 行为 | P0-API与鉴权 Q2-Q3 / A8 |
| W11 | 文件上传/图片/语音/表情/引用回复/@mention 等消息类型的 payload 格式 | P0-API与鉴权 Q3 / K9 |
| W12 | Bot 建群/改群信息/加人/Thread 创建等写操作 API 是否开放 | P0-API与鉴权 Q4 / K6 |
相关文档
- [01-bot-developer-onboarding.md](01-bot-developer-onboarding.md) v0.1 —— 动手前的概念、鉴权机制、常见坑
- [06-api-integration/OCTO-Bot-API接入快速上手指南-v0.1.md](../04-api-integration/OCTO-Bot-API接入快速上手指南-v0.1.md) v0.1 —— 三种接入路径与完整 curl 骨架
- [06-api-integration/Bot-API概览.md](../04-api-integration/Bot-API概览.md) v0.1 —— 9 组 40+ 端点分组
- [02-architecture/OCTO总体架构.md](../02-architecture/OCTO总体架构.md) v1.0 —— 请求 5 步 / ACK / at-least-once
- [03-deployment-ops/OCTO部署形态总览.md](../03-deployment-ops/OCTO部署形态总览.md) v1.0 —— apiBase/端口/HTTPS
- [售前交付/openclaw-channel-octo卡片能力正式口径-v1.0.md](../08-presales-delivery/openclaw-channel-octo卡片能力正式口径-v1.0.md) —— 卡片能力边界与红线
v0.1 骨架边界说明
- 本文"发送文本消息"的请求体字段(channel_id/channel_type/payload/content/client_msg_no)有 openclaw-channel-octo
api-fetch.ts:381-393直接源码证据。 - register/heartbeat/events/ack/card/profile 端点路径有 api-fetch.ts 与 bot_api.go 路由双重证据。
- 卡片四必填/raw+template XOR/catalog_provenance 拒卡/render_profile 覆盖 有 044A/044C/046 源码验证事实。
- UI 入口、URL 前缀、channel_type 常量、payload.type 字符串、响应字段、错误码、心跳间隔、事件 schema 未在当前源码精读范围直接验证的项一律用
{{占位}}或 ⚠️ 标注,不编造可运行值。 - 本文不是官方 API 参考文档;等 061 产品管家回答回填 + 官方 API 文档开放后升 v0.2。