Helix 后端长轮询方案详解
一、当前问题
1 2 3 4 5 6
| 当前 Webhook 模式(需要公网 IP): Helix ──POST /metis/events──▶ OpenClaw:8765 ❌ OpenClaw 必须有公网可达地址
长轮询模式(不需要公网 IP): OpenClaw ──GET /poll──▶ Helix ✅ 客户端主动出站 OpenClaw ──POST /bot_callback──▶ Helix ✅ 已有(send.ts 不变)
|
核心思路:反转连接方向。不再由 Helix 推消息给 OpenClaw,而是 OpenClaw 主动向 Helix 拉消息。
二、长轮询通信流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 时间轴 ─────────────────────────────────────────────────────────────▶
OpenClaw Helix 后端 │ │ │ ① GET /aid/api/bot/poll?timeout=30 │ │ Authorization: Bearer <bot_token> │ │ Bot-Id: openclaw_bot │ │ ─────────────────────────────────────────────────▶ │ │ │ │ (Helix 挂起请求,等待新消息...) │ │ (最长等 30 秒) │ │ │ │ ② 用户在 Helix 上发了一条消息 │ │ │ │ ③ 200 OK { events: [{...}] } │ │ ◀───────────────────────────────────────────────── │ │ │ │ ④ OpenClaw 处理消息 → Agent 生成回复 │ │ │ │ ⑤ POST /aid/api/task/bot_callback (已有) │ │ ─────────────────────────────────────────────────▶ │ │ │ │ ⑥ 立即发起下一轮 GET /poll │ │ ─────────────────────────────────────────────────▶ │ │ ...循环... │
|
关键机制:
- 长轮询挂起:如果没有新消息,Helix 不立即返回,而是保持连接打开最多
timeout 秒,有消息时立即返回
- 超时空响应:超时后返回空 events 数组,OpenClaw 立即重新发起请求
- 断线续传:通过
last_event_id / cursor 确保消息不丢
三、Helix 后端需要实现的接口
接口 1:长轮询获取消息(核心,P0)
请求:
| 参数 |
位置 |
类型 |
必填 |
说明 |
Authorization |
Header |
string |
是 |
Bearer <bot_token> Bot 认证 |
Bot-Id |
Header |
string |
是 |
Bot 标识 |
timeout |
Query |
int |
否 |
长轮询超时秒数,默认 30,最大 60 |
cursor |
Query |
string |
否 |
上次返回的 next_cursor,用于续传 |
响应 200 OK:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { "ok": true, "events": [ { "event_id": "evt_20260314_001", "type": "message", "timestamp": 1710400000000, "aid_context": { "User-Id": "user_3c0a8d30ff7d4e9fa497e5c0ef737c59", "Env-Id": "test_env_001", "Task-Id": "test_task_001", "Bot-Id": "openclaw_bot" }, "content": "你好,请问现在使用的是什么模型?" } ], "next_cursor": "cursor_20260314_002" }
|
无新消息(超时返回):
1 2 3 4 5
| { "ok": true, "events": [], "next_cursor": "cursor_20260314_001" }
|
错误响应:
| HTTP Status |
说明 |
OpenClaw 行为 |
| 200 |
正常(可能 events 为空) |
处理消息,立即重新 poll |
| 401 |
Token 无效 |
停止轮询,报错 |
| 429 |
请求过于频繁 |
等待 Retry-After 后重试 |
| 500/502/503 |
服务器错误 |
指数退避重试 |
Helix 后端实现要点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 伪代码:
function handlePollRequest(req): botId = req.header("Bot-Id") token = req.header("Authorization").removePrefix("Bearer ") timeout = min(req.query.timeout ?? 30, 60) cursor = req.query.cursor
// 1. 验证 token if !validateBotToken(botId, token): return 401
// 2. 先查是否有积压的未投递消息(cursor 之后的) events = messageQueue.getEventsAfter(botId, cursor)
if events.isNotEmpty(): // 有积压消息,立即返回 return { ok: true, events, next_cursor: events.last().event_id }
// 3. 没有消息,挂起等待 newEvents = messageQueue.waitForEvents(botId, timeout: timeout * 1000ms)
if newEvents.isNotEmpty(): return { ok: true, events: newEvents, next_cursor: newEvents.last().event_id } else: // 超时,返回空 return { ok: true, events: [], next_cursor: cursor }
|
接口 2:消息确认(可选,P1)
Bot 确认已成功处理某些消息,Helix 可以从队列中移除。不实现也可以——用 cursor 机制替代。
请求:
1 2 3 4
| { "bot_id": "openclaw_bot", "event_ids": ["evt_20260314_001", "evt_20260314_002"] }
|
响应:
1
| { "ok": true, "acked": 2 }
|
接口 3:Bot 回复回调(已有,不变)
1
| POST /aid/api/task/bot_callback
|
当前 send.ts 已实现,无需改动。OpenClaw 处理完消息后通过此接口回复。
四、Helix 后端消息队列设计建议
Helix 需要为每个 Bot 维护一个 待投递消息队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ┌─────────────────────────────────────────────────┐ │ Helix 后端 │ │ │ │ 用户发消息 ──▶ ┌──────────────────────┐ │ │ │ Bot 消息队列 │ │ │ │ (per bot_id) │ │ │ │ │ │ │ │ evt_001 [pending] │◀── cursor│ │ │ evt_002 [pending] │ │ │ │ evt_003 [pending] │ │ │ └──────────────────────┘ │ │ │ │ │ ▼ │ │ GET /poll 请求 ◀── 取出 pending events │ │ ──▶ 返回给 OpenClaw │ │ │ │ POST /ack ──▶ 标记 delivered │ │ (或 cursor 推进) │ └─────────────────────────────────────────────────┘
|
设计要点:
- 队列存储:可以用 Redis List/Stream 或数据库表,按
bot_id 分区
- 消息过期:超过 TTL(如 24 小时)未被拉取的消息自动丢弃
- 挂起机制:用户发消息时,如果对应 bot 有挂起的 poll 请求,立即唤醒返回(不必等到超时)
- cursor 语义:cursor 指向”最后已读事件”,poll 返回 cursor 之后的所有事件
五、总结:接口清单
| # |
接口路径 |
方法 |
功能 |
优先级 |
复杂度 |
| 1 |
/aid/api/bot/poll |
GET |
长轮询拉取新消息 |
P0 |
中(需要消息队列+挂起机制) |
| 2 |
/aid/api/task/bot_callback |
POST |
Bot 回复(已有) |
已完成 |
- |
| 3 |
/aid/api/bot/ack |
POST |
消息确认 |
P1(可选) |
低 |
Helix 后端最小实现:只需一个新接口 /aid/api/bot/poll,加上已有的 /aid/api/task/bot_callback,就能完成完整的双向通信。
对应 Metis 插件侧,需要新增一个 monitor.polling.ts 模块(参考飞书的传输层设计),根据配置的 connectionMode 切换 webhook/polling 模式,其余 send.ts、bot.ts、reply-dispatcher.ts 等完全不变。