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)

1
GET /aid/api/bot/poll

请求:

参数 位置 类型 必填 说明
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)

1
POST /aid/api/bot/ack

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 推进) │
└─────────────────────────────────────────────────┘

设计要点:

  1. 队列存储:可以用 Redis List/Stream 或数据库表,按 bot_id 分区
  2. 消息过期:超过 TTL(如 24 小时)未被拉取的消息自动丢弃
  3. 挂起机制:用户发消息时,如果对应 bot 有挂起的 poll 请求,立即唤醒返回(不必等到超时)
  4. 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.tsbot.tsreply-dispatcher.ts 等完全不变。