@xmoxmo/bncr 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +350 -0
- package/index.ts +43 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +30 -0
- package/src/channel.ts +1761 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xmoxmo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# bncr-channel
|
|
2
|
+
|
|
3
|
+
OpenClaw 的 Bncr WebSocket Bridge 频道插件(`channelId=bncr`)。
|
|
4
|
+
|
|
5
|
+
这份文档按“拿到插件就能对接”的目标编写:你只看本 README,也能完成接入。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 概览
|
|
10
|
+
|
|
11
|
+
- **OpenClaw Channel ID**:`bncr`
|
|
12
|
+
- **Bridge Version**:`2`
|
|
13
|
+
- **出站事件名**:`bncr.push`
|
|
14
|
+
- **出站模式**:`push-only`(不依赖 pull 轮询)
|
|
15
|
+
- **活动心跳方法**:`bncr.activity`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 2. 工作模式(重点)
|
|
20
|
+
|
|
21
|
+
当前是 **push-only**:
|
|
22
|
+
|
|
23
|
+
- Bncr 在线:OpenClaw 通过 WS `event=bncr.push` 直接下发回复。
|
|
24
|
+
- Bncr 离线:消息进入 outbox;重连后自动冲队列。
|
|
25
|
+
- `bncr.activity` 仅用于在线保活,不承载拉取。
|
|
26
|
+
- `bncr.ack` 兼容保留,当前不是必需链路(fire-and-forget)。
|
|
27
|
+
|
|
28
|
+
> 结论:客户端最小实现只需两件事:
|
|
29
|
+
> 1) 发 `bncr.inbound`;2) 监听 `bncr.push`。
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 3. SessionKey 规则(严格)
|
|
34
|
+
|
|
35
|
+
严格格式:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
agent:main:bncr:direct:<hexScope>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
其中:
|
|
42
|
+
|
|
43
|
+
- `<hexScope>` = `platform:groupId:userId` 的 UTF-8 hex(小写)。
|
|
44
|
+
- 仅允许 `0-9a-f` 且长度为偶数。
|
|
45
|
+
- 旧格式(如 `...:<hexScope>:0`)不再作为标准形态。
|
|
46
|
+
|
|
47
|
+
示例:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
scope = qq:0:888888
|
|
51
|
+
hexScope = 71713a303a383838383838
|
|
52
|
+
sessionKey= agent:main:bncr:direct:71713a303a383838383838
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 4. Gateway Methods
|
|
58
|
+
|
|
59
|
+
插件注册:
|
|
60
|
+
|
|
61
|
+
- `bncr.connect`
|
|
62
|
+
- `bncr.inbound`
|
|
63
|
+
- `bncr.activity`
|
|
64
|
+
- `bncr.ack`
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 5. 接入流程(最小可用)
|
|
69
|
+
|
|
70
|
+
### Step A:建立 WS 并发送 `bncr.connect`
|
|
71
|
+
|
|
72
|
+
请求示例:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"type": "req",
|
|
77
|
+
"id": "c1",
|
|
78
|
+
"method": "bncr.connect",
|
|
79
|
+
"params": {
|
|
80
|
+
"accountId": "Primary",
|
|
81
|
+
"clientId": "bncr-client-1"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
响应示例:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"type": "res",
|
|
91
|
+
"id": "c1",
|
|
92
|
+
"ok": true,
|
|
93
|
+
"result": {
|
|
94
|
+
"channel": "bncr",
|
|
95
|
+
"accountId": "Primary",
|
|
96
|
+
"bridgeVersion": 2,
|
|
97
|
+
"pushEvent": "bncr.push",
|
|
98
|
+
"online": true,
|
|
99
|
+
"isPrimary": true,
|
|
100
|
+
"activeConnections": 1,
|
|
101
|
+
"pending": 0,
|
|
102
|
+
"deadLetter": 0,
|
|
103
|
+
"now": 1772476800000
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> `accountId` 示例是 `Primary`,请按你实际配置替换;不传默认使用 `Primary`。
|
|
109
|
+
|
|
110
|
+
### Step B:上行消息用 `bncr.inbound`
|
|
111
|
+
|
|
112
|
+
文本请求示例(`msg` 形态):
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"type": "req",
|
|
117
|
+
"id": "i1",
|
|
118
|
+
"method": "bncr.inbound",
|
|
119
|
+
"params": {
|
|
120
|
+
"accountId": "Primary",
|
|
121
|
+
"platform": "qq",
|
|
122
|
+
"groupId": "0",
|
|
123
|
+
"userId": "888888",
|
|
124
|
+
"sessionKey": "agent:main:bncr:direct:71713a303a383838383838",
|
|
125
|
+
"msgId": "msg-1001",
|
|
126
|
+
"type": "text",
|
|
127
|
+
"msg": "你好"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
媒体请求示例(字段是 `base64`):
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"type": "req",
|
|
137
|
+
"id": "i2",
|
|
138
|
+
"method": "bncr.inbound",
|
|
139
|
+
"params": {
|
|
140
|
+
"accountId": "Primary",
|
|
141
|
+
"platform": "qq",
|
|
142
|
+
"groupId": "0",
|
|
143
|
+
"userId": "888888",
|
|
144
|
+
"sessionKey": "agent:main:bncr:direct:71713a303a383838383838",
|
|
145
|
+
"msgId": "msg-1002",
|
|
146
|
+
"type": "image/png",
|
|
147
|
+
"msg": "",
|
|
148
|
+
"base64": "<BASE64_PAYLOAD>",
|
|
149
|
+
"mimeType": "image/png",
|
|
150
|
+
"fileName": "demo.png"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
响应示例:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"type": "res",
|
|
160
|
+
"id": "i1",
|
|
161
|
+
"ok": true,
|
|
162
|
+
"result": {
|
|
163
|
+
"accepted": true,
|
|
164
|
+
"accountId": "Primary",
|
|
165
|
+
"sessionKey": "agent:main:bncr:direct:71713a303a383838383838",
|
|
166
|
+
"msgId": "msg-1001",
|
|
167
|
+
"taskKey": null
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
> `bncr.inbound` 先快速 ACK,再异步处理,最终回复经 `bncr.push` 回推。
|
|
173
|
+
|
|
174
|
+
### Step C:消费 `bncr.push`
|
|
175
|
+
|
|
176
|
+
事件示例:
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"type": "event",
|
|
181
|
+
"event": "bncr.push",
|
|
182
|
+
"payload": {
|
|
183
|
+
"type": "message.outbound",
|
|
184
|
+
"messageId": "3f8b1f9b-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
185
|
+
"idempotencyKey": "3f8b1f9b-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
186
|
+
"sessionKey": "agent:main:bncr:direct:71713a303a383838383838",
|
|
187
|
+
"message": {
|
|
188
|
+
"platform": "qq",
|
|
189
|
+
"groupId": "0",
|
|
190
|
+
"userId": "888888",
|
|
191
|
+
"type": "text",
|
|
192
|
+
"msg": "收到,已处理。",
|
|
193
|
+
"path": "",
|
|
194
|
+
"base64": "",
|
|
195
|
+
"fileName": ""
|
|
196
|
+
},
|
|
197
|
+
"ts": 1772476801234
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 6. 字段协议
|
|
205
|
+
|
|
206
|
+
### 6.1 Bncr -> OpenClaw(`bncr.inbound`)
|
|
207
|
+
|
|
208
|
+
常用字段:
|
|
209
|
+
|
|
210
|
+
- `accountId`:可选(默认 `Primary`)
|
|
211
|
+
- `platform`:必填
|
|
212
|
+
- `groupId`:可选,默认 `"0"`(私聊)
|
|
213
|
+
- `userId`:建议填写(私聊/群聊都建议带上)
|
|
214
|
+
- `sessionKey`:可选,建议传严格 sessionKey(见第 3 节)
|
|
215
|
+
- `msgId`:建议传(便于短窗口去重)
|
|
216
|
+
- `type`:`text/image/video/file/...`
|
|
217
|
+
- `msg`:文本
|
|
218
|
+
- `base64`:媒体 base64
|
|
219
|
+
- `mimeType` / `fileName`:媒体元数据(可选)
|
|
220
|
+
|
|
221
|
+
校验失败常见错误:
|
|
222
|
+
|
|
223
|
+
- `platform/groupId/userId required`
|
|
224
|
+
|
|
225
|
+
#### 6.1.1 openclawclient.js(发送端)对齐说明
|
|
226
|
+
|
|
227
|
+
基于你当前附件版本(`openclawclient` 注释版本 `0.0.2`)核对结果:
|
|
228
|
+
|
|
229
|
+
- `inboundSend()` 使用 `sessionKey`,与当前插件入站字段一致。
|
|
230
|
+
- 文本消息使用 `msg`,与当前插件读取一致。
|
|
231
|
+
- 媒体字段使用 `base64`(可带 `mimeType/fileName`),与当前插件读取一致。
|
|
232
|
+
- 默认账号建议使用 `Primary`,并与网关账户 ID 保持大小写一致。
|
|
233
|
+
|
|
234
|
+
### 6.2 OpenClaw -> Bncr(`bncr.push`)
|
|
235
|
+
|
|
236
|
+
关键字段:
|
|
237
|
+
|
|
238
|
+
- `messageId`
|
|
239
|
+
- `idempotencyKey`(当前等于 `messageId`)
|
|
240
|
+
- `sessionKey`
|
|
241
|
+
- `message.platform/groupId/userId`
|
|
242
|
+
- `message.type/msg/path/base64/fileName`
|
|
243
|
+
- `ts`
|
|
244
|
+
|
|
245
|
+
说明:
|
|
246
|
+
|
|
247
|
+
- 主类型固定为 `type="message.outbound"`。
|
|
248
|
+
- 仅输出嵌套结构 `message.{...}`,不再输出平铺兼容字段。
|
|
249
|
+
- 不附带 webchat 的 `stream/state/data` 语义字段。
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 7. `message.send(channel=bncr)` 目标解析规则(重要)
|
|
254
|
+
|
|
255
|
+
插件发送目标支持三种输入:
|
|
256
|
+
|
|
257
|
+
1. 严格 `sessionKey`
|
|
258
|
+
2. `platform:groupId:userId`
|
|
259
|
+
3. `Bncr-platform:groupId:userId`
|
|
260
|
+
|
|
261
|
+
但发送前会做**反查校验**:
|
|
262
|
+
|
|
263
|
+
- 必须在已知会话路由里反查到真实 `sessionKey` 才会发送。
|
|
264
|
+
- 禁止拼凑 key 直接发;查不到会报:`target not found in known sessions`。
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 8. 重试与可靠性
|
|
269
|
+
|
|
270
|
+
- 离线入队 + 重连自动冲队列。
|
|
271
|
+
- 指数退避:`1s,2s,4s,8s...`
|
|
272
|
+
- 最大重试次数:`10`
|
|
273
|
+
- 超限进入 dead-letter。
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 9. 状态判定与观测
|
|
278
|
+
|
|
279
|
+
- 实际链路在线:`linked`
|
|
280
|
+
- 已配置但离线:`configured`
|
|
281
|
+
- 账户卡片中离线模式会显示 `Status`(展示口径)
|
|
282
|
+
|
|
283
|
+
常用状态字段:
|
|
284
|
+
|
|
285
|
+
- `pending`
|
|
286
|
+
- `deadLetter`
|
|
287
|
+
- `lastSessionKey`
|
|
288
|
+
- `lastSessionScope`(`Bncr-platform:group:user`)
|
|
289
|
+
- `lastSessionAt`
|
|
290
|
+
- `lastActivityAt`
|
|
291
|
+
- `lastInboundAt`
|
|
292
|
+
- `lastOutboundAt`
|
|
293
|
+
|
|
294
|
+
> 已知现象:`openclaw status` 顶层与 `status --deep` 在个别版本可能出现口径不一致;排障时优先看 `status --deep` 的 Health。
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 10. 兼容接口说明
|
|
299
|
+
|
|
300
|
+
### `bncr.activity`
|
|
301
|
+
|
|
302
|
+
用于活动保活,建议节流(例如 60s 一次)。
|
|
303
|
+
|
|
304
|
+
请求示例:
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"type": "req",
|
|
309
|
+
"id": "a1",
|
|
310
|
+
"method": "bncr.activity",
|
|
311
|
+
"params": {
|
|
312
|
+
"accountId": "Primary",
|
|
313
|
+
"clientId": "bncr-client-1",
|
|
314
|
+
"reason": "heartbeat"
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`reason` 为可选自定义字段,插件会忽略业务外字段。
|
|
320
|
+
|
|
321
|
+
### `bncr.ack`
|
|
322
|
+
|
|
323
|
+
可调用,但当前模式下不是必需链路。
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 11. FAQ
|
|
328
|
+
|
|
329
|
+
### Q1:为什么看不到回复?
|
|
330
|
+
|
|
331
|
+
1. 先确认 `bncr.connect` 成功。
|
|
332
|
+
2. 客户端确认监听的是 `bncr.push`。
|
|
333
|
+
3. `sessionKey` 是否符合严格格式。
|
|
334
|
+
4. 若用 `message.send`,目标是否能反查到已知会话。
|
|
335
|
+
|
|
336
|
+
### Q2:为什么不需要 `bncr.pull`?
|
|
337
|
+
|
|
338
|
+
因为当前是 push-only,统一走 `bncr.push`。
|
|
339
|
+
|
|
340
|
+
### Q3:如何避免重复消息?
|
|
341
|
+
|
|
342
|
+
- 入站带稳定 `msgId`。
|
|
343
|
+
- 出站按 `idempotencyKey` 幂等处理。
|
|
344
|
+
- 客户端侧建议只消费 `message.outbound` 主链路。
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## 12. 版本提示
|
|
349
|
+
|
|
350
|
+
历史版本接入过的话,请以当前文档(push-only + strict sessionKey + 目标反查)为准。
|
package/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import {
|
|
3
|
+
emptyPluginConfigSchema,
|
|
4
|
+
type GatewayRequestHandlerOptions,
|
|
5
|
+
} from "openclaw/plugin-sdk";
|
|
6
|
+
import { createBncrBridge, createBncrChannelPlugin } from "./src/channel.js";
|
|
7
|
+
|
|
8
|
+
const plugin = {
|
|
9
|
+
id: "bncr",
|
|
10
|
+
name: "Bncr",
|
|
11
|
+
description: "Bncr channel plugin",
|
|
12
|
+
configSchema: emptyPluginConfigSchema(),
|
|
13
|
+
register(api: OpenClawPluginApi) {
|
|
14
|
+
const bridge = createBncrBridge(api);
|
|
15
|
+
|
|
16
|
+
api.registerService({
|
|
17
|
+
id: "bncr-bridge-service",
|
|
18
|
+
start: bridge.startService,
|
|
19
|
+
stop: bridge.stopService,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
api.registerChannel({ plugin: createBncrChannelPlugin(bridge) });
|
|
23
|
+
|
|
24
|
+
api.registerGatewayMethod(
|
|
25
|
+
"bncr.connect",
|
|
26
|
+
(opts: GatewayRequestHandlerOptions) => bridge.handleConnect(opts),
|
|
27
|
+
);
|
|
28
|
+
api.registerGatewayMethod(
|
|
29
|
+
"bncr.inbound",
|
|
30
|
+
(opts: GatewayRequestHandlerOptions) => bridge.handleInbound(opts),
|
|
31
|
+
);
|
|
32
|
+
api.registerGatewayMethod(
|
|
33
|
+
"bncr.activity",
|
|
34
|
+
(opts: GatewayRequestHandlerOptions) => bridge.handleActivity(opts),
|
|
35
|
+
);
|
|
36
|
+
api.registerGatewayMethod(
|
|
37
|
+
"bncr.ack",
|
|
38
|
+
(opts: GatewayRequestHandlerOptions) => bridge.handleAck(opts),
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default plugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xmoxmo/bncr",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/xmoxmo/openclaw-bncr-channel.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/xmoxmo/openclaw-bncr-channel#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/xmoxmo/openclaw-bncr-channel/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"index.ts",
|
|
20
|
+
"openclaw.plugin.json",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"src"
|
|
24
|
+
],
|
|
25
|
+
"openclaw": {
|
|
26
|
+
"extensions": [
|
|
27
|
+
"./index.ts"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|