@sumeai/sumeclaw 1.1.19 → 1.1.20

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.
@@ -0,0 +1,457 @@
1
+ # sumeclaw Harness 调试经验
2
+
3
+ 本文记录友虾名片通过 `sumeclaw` 插件对接 OpenClaw 的调试经验,用于后续开发类似 Channel / Harness 集成时避坑。
4
+
5
+ ## 一句话结论
6
+
7
+ Channel 插件不是简单 WebSocket 转发,而是三套状态机的对接:
8
+
9
+ - 友虾后端的智能体、名片、客户、会话状态
10
+ - sumeclaw 插件的连接、请求、回包状态
11
+ - OpenClaw Agent runtime 的消息分发、回复、流式输出状态
12
+
13
+ 任何一层边界不清,都会表现成连接成功但无法对话、能对话但超时、能流式但截断、非流式只返回第一句等问题。
14
+
15
+ ## 最终职责划分
16
+
17
+ 推荐采用以下架构边界:
18
+
19
+ ```text
20
+ 友虾后端:上下文编排层
21
+ sumeclaw 插件:通道适配层
22
+ OpenClaw:推理执行层
23
+ ```
24
+
25
+ 友虾后端负责:
26
+
27
+ - 识别用户、智能体、名片、客户和会话
28
+ - 生成 `requestId` 与稳定的 `sessionKey`
29
+ - 组装 `route / card / customer / conversation / policy / context_pack`
30
+ - 处理普通模式和海报模式的不同返回方式
31
+
32
+ sumeclaw 插件负责:
33
+
34
+ - 维护到友虾后端的 WebSocket 连接
35
+ - 接收 `hooks_agent`
36
+ - 将消息投递到 OpenClaw runtime
37
+ - 将 OpenClaw 回复转换为 `agent_reply / agent_delta / agent_done`
38
+
39
+ OpenClaw 负责:
40
+
41
+ - 根据插件注入的上下文执行 Agent
42
+ - 生成回复
43
+ - 通过 Channel delivery 回调输出文本块
44
+
45
+ ## 协议生命周期
46
+
47
+ 以后设计类似集成时,应先明确完整生命周期:
48
+
49
+ ```text
50
+ connect
51
+ -> register_info
52
+ -> heartbeat
53
+ -> backend sends hooks_agent
54
+ -> plugin dispatches to OpenClaw runtime
55
+ -> OpenClaw delivers one or more reply blocks
56
+ -> plugin returns agent_delta or buffered agent_reply
57
+ -> plugin sends explicit done for streaming
58
+ -> backend matches requestId
59
+ -> backend persists and returns result
60
+ ```
61
+
62
+ 不要只验证 WebSocket 是否连接成功。连接成功只能证明通道可用,不能证明 Agent 能收到消息、能生成回复、能完整回传。
63
+
64
+ ## requestId 与 sessionKey
65
+
66
+ 必须分清两个字段的职责。
67
+
68
+ ```text
69
+ requestId = 单次请求匹配
70
+ sessionKey = 长期对话上下文隔离
71
+ ```
72
+
73
+ `requestId` 用于:
74
+
75
+ - 后端 pending future / stream queue 匹配
76
+ - 插件回包关联
77
+ - 并发请求不串包
78
+
79
+ `sessionKey` 用于:
80
+
81
+ - OpenClaw 会话记忆隔离
82
+ - 名片与客户的长期会话上下文
83
+ - 多名片、多客户场景下防止串上下文
84
+
85
+ 推荐格式:
86
+
87
+ ```text
88
+ sumeclaw:conversation:{conversation_id}
89
+ ```
90
+
91
+ 不要使用粗粒度默认值,例如:
92
+
93
+ ```text
94
+ http_user
95
+ agent:{agent_id}:user:http_user
96
+ ```
97
+
98
+ 这些值在多名片、多客户、多智能体场景下很容易导致上下文串线。
99
+
100
+ ## Runtime 能力探测
101
+
102
+ 不要假设 OpenClaw runtime 一定暴露某个 API。
103
+
104
+ 本次早期误判了 `runtime.channel.turn.run` 可用,实际 OpenClaw 2026.4.21 没有该入口,导致插件收到消息后无法分发。
105
+
106
+ 正确做法是在插件启动或异常时打印能力矩阵:
107
+
108
+ ```text
109
+ apiKeys
110
+ runtimeKeys
111
+ runtime.channel keys
112
+ reply dispatcher availability
113
+ direct-dm helper availability
114
+ ```
115
+
116
+ 本次最终走通的入口是:
117
+
118
+ ```text
119
+ dispatchInboundDirectDmWithRuntime
120
+ ```
121
+
122
+ 以后接入新版 OpenClaw 时,应先确认可用 runtime 分发路径,再实现业务协议。
123
+
124
+ ## 插件管理命令不能启动业务连接
125
+
126
+ 执行类似命令时:
127
+
128
+ ```bash
129
+ openclaw plugins update sumeclaw
130
+ ```
131
+
132
+ OpenClaw 也可能加载插件并调用 `register()`。如果此时插件启动业务 WebSocket,会出现临时进程连上后台,然后马上断开,甚至替换真实连接。
133
+
134
+ 典型现象:
135
+
136
+ ```text
137
+ connected
138
+ disconnected
139
+ replaced by a newer OpenClaw plugin connection
140
+ ```
141
+
142
+ 处理原则:
143
+
144
+ - 识别插件安装、更新、管理命令
145
+ - 管理命令中跳过业务 WebSocket 连接
146
+ - 正常 gateway 进程中才启动连接
147
+
148
+ ## 连接必须幂等
149
+
150
+ OpenClaw 可能多次 register / reload 插件。
151
+
152
+ 插件连接逻辑必须保证:
153
+
154
+ - 同一 account 已有 `OPEN` 或 `CONNECTING` 连接时复用
155
+ - 新连接替换旧连接时,旧连接不再自动重连
156
+ - 后端注销旧连接时不要误删新连接
157
+
158
+ 后端连接池应按 `agent_config_id` 管理当前 WebSocket,并在注销时校验对象身份:
159
+
160
+ ```text
161
+ 只有当前连接对象才能注销当前连接
162
+ 旧连接断开时忽略旧连接注销
163
+ ```
164
+
165
+ ## 非流式也可能多块 deliver
166
+
167
+ 这是最容易忽视的坑。
168
+
169
+ 即使友虾名片处于普通非流式模式,OpenClaw 也可能多次触发 delivery:
170
+
171
+ ```text
172
+ 第一句
173
+ 第二段
174
+ 第三段
175
+ ...
176
+ ```
177
+
178
+ 如果插件收到第一块就发送 `agent_reply`,后端非流式 `send()` 会立即完成任务,用户只能看到第一句。
179
+
180
+ 正确协议:
181
+
182
+ ```text
183
+ 非流式:
184
+ OpenClaw deliver 多块文本
185
+ 插件全部缓冲
186
+ OpenClaw dispatch 完成
187
+ 插件发送一次完整 agent_reply
188
+ ```
189
+
190
+ 关键日志应表现为:
191
+
192
+ ```text
193
+ outbound reply buffered
194
+ OpenClaw Direct DM dispatched
195
+ agent_reply sent textLength=完整长度
196
+ ```
197
+
198
+ ## 流式必须显式 done
199
+
200
+ 不要用静默窗口猜测流式结束。
201
+
202
+ 本次尝试过 12 秒、15 秒、45 秒静默窗口,但 OpenClaw 中间可能长时间进行检索、工具调用或模型思考。任何固定时间窗口都有误判风险。
203
+
204
+ 正确协议:
205
+
206
+ ```text
207
+ agent_delta
208
+ agent_delta
209
+ agent_delta
210
+ agent_done
211
+ ```
212
+
213
+ 后端只有收到 `agent_done` 才能:
214
+
215
+ - 向 SSE 输出 `done`
216
+ - 清理 stream queue
217
+ - 完成请求状态
218
+
219
+ 不要因为收到某个 final/future 就提前清理流队列,否则会出现:
220
+
221
+ ```text
222
+ OpenClaw 增量无匹配流队列
223
+ ```
224
+
225
+ ## 流式队列要按顺序消费
226
+
227
+ 流式请求中,`agent_delta` 和 `agent_done` 必须按队列顺序处理。
228
+
229
+ 注意不要让 pending future 抢跑队列,否则可能出现:
230
+
231
+ ```text
232
+ future 先完成
233
+ stream queue 被清理
234
+ 后续 delta 到达时找不到队列
235
+ ```
236
+
237
+ 推荐做法:
238
+
239
+ - `agent_delta` 进入 stream queue
240
+ - `agent_done` 也进入同一个 stream queue
241
+ - stream generator 按队列顺序 yield
242
+ - 非流式 future 只用于普通 `agent_reply`
243
+
244
+ ## SSE 不等于真实流式
245
+
246
+ 前端可以模拟打字机效果,但这不代表服务端真实流式。
247
+
248
+ 判断真实流式要看后端是否持续输出:
249
+
250
+ ```text
251
+ agent_delta received
252
+ OpenClaw 流式输出 delta
253
+ chat_stream event=content
254
+ ```
255
+
256
+ 如果只在最后一次性输出一大段,前端再拆字显示,那只是前端模拟。
257
+
258
+ ## 多租户设计原则
259
+
260
+ 一个 OpenClaw 可以连接多个友虾智能体;一个友虾智能体可以绑定多个名片;每个名片又服务多个客户。
261
+
262
+ 因此 payload 不能只传 `message`。
263
+
264
+ 推荐 payload 分层:
265
+
266
+ ```json
267
+ {
268
+ "requestId": "req_xxx",
269
+ "message": "用户输入",
270
+ "agentId": "main",
271
+ "sessionKey": "sumeclaw:conversation:xxx",
272
+ "stream": true,
273
+ "route": {},
274
+ "card": {},
275
+ "customer": {},
276
+ "conversation": {},
277
+ "policy": {},
278
+ "context_pack": {}
279
+ }
280
+ ```
281
+
282
+ 各层职责:
283
+
284
+ - `route`:发给哪个 OpenClaw、哪个智能体、哪个名片、哪个客户会话
285
+ - `card`:名片身份、角色定位、语气、人设、约束
286
+ - `customer`:客户画像、风险偏好、关系阶段、已知需求
287
+ - `conversation`:历史摘要、当前场景、语言、时区
288
+ - `policy`:合规等级、敏感领域、免责声明、最大长度
289
+ - `context_pack`:后端压缩后的 OpenClaw 可用上下文
290
+
291
+ 插件不应直接理解友虾业务数据库。友虾后端负责生成 `context_pack`,插件只负责透传。
292
+
293
+ ## 推荐调试顺序
294
+
295
+ 后续做类似集成时,建议按以下顺序推进:
296
+
297
+ 1. WebSocket 连接成功
298
+ 2. `heartbeat / heartbeat_ack` 正常
299
+ 3. `register_info` 上报 agentId 和插件版本
300
+ 4. 后端发送 `hooks_agent`,插件确认收到
301
+ 5. 插件打印 runtime 能力矩阵
302
+ 6. 插件用可用 runtime 入口分发到 OpenClaw
303
+ 7. 非流式缓冲多块 deliver,最终返回一次 `agent_reply`
304
+ 8. 流式逐块返回 `agent_delta`
305
+ 9. 流式结束返回明确 `agent_done`
306
+ 10. 后端按 `requestId` 匹配 pending future / stream queue
307
+ 11. 普通名片轮询结果成功
308
+ 12. 海报名片 SSE 流式成功
309
+ 13. 多客户、多名片、多智能体并发验证
310
+
311
+ ## 必须保留的诊断日志
312
+
313
+ 插件侧建议保留:
314
+
315
+ ```text
316
+ register called
317
+ connected
318
+ heartbeat_ack
319
+ hooks_agent received
320
+ runtime keys / channel keys
321
+ OpenClaw Direct DM dispatched
322
+ outbound reply buffered
323
+ agent_reply sent
324
+ agent_delta sent
325
+ agent_done sent
326
+ ```
327
+
328
+ 后端侧建议保留:
329
+
330
+ ```text
331
+ send_to_openclaw request_id
332
+ stream_to_openclaw request_id
333
+ agent_reply received
334
+ agent_delta received
335
+ agent_done received
336
+ stream queue matched
337
+ stream queue done
338
+ chat_task completed
339
+ ```
340
+
341
+ 这些日志要包含:
342
+
343
+ - `agent_config_id`
344
+ - `request_id`
345
+ - `session_id`
346
+ - `card_id`
347
+ - `conversation_id`
348
+ - `text_len`
349
+
350
+ 不要只打印“成功”或“失败”,否则定位不了串包、截断和超时。
351
+
352
+ ## 常见故障模式
353
+
354
+ ### 连接成功但不能对话
355
+
356
+ 可能原因:
357
+
358
+ - runtime 分发入口不存在
359
+ - 插件只连上 WebSocket,没有把消息投递给 OpenClaw
360
+ - `hooks_agent` 类型未处理
361
+
362
+ 排查:
363
+
364
+ - 看插件是否打印 `hooks_agent`
365
+ - 看是否打印 runtime 能力矩阵
366
+ - 看是否打印 `OpenClaw Direct DM dispatched`
367
+
368
+ ### 后端超时
369
+
370
+ 可能原因:
371
+
372
+ - 插件没有回传 `requestId`
373
+ - 后端 pending key 和插件回包 key 不一致
374
+ - 插件 dispatch 卡住
375
+ - 插件连接被管理命令替换
376
+
377
+ 排查:
378
+
379
+ - 对比后端发送和插件回包的 `requestId`
380
+ - 看是否有 `replaced by newer connection`
381
+
382
+ ### 流式只输出第一段
383
+
384
+ 可能原因:
385
+
386
+ - 后端提前发 `done`
387
+ - 插件用定时器猜 final
388
+ - future 抢跑 stream queue
389
+
390
+ 修复:
391
+
392
+ - 使用显式 `agent_done`
393
+ - 按 stream queue 顺序消费
394
+
395
+ ### 非流式只输出第一句
396
+
397
+ 可能原因:
398
+
399
+ - OpenClaw 多块 deliver
400
+ - 插件第一块就发 `agent_reply`
401
+
402
+ 修复:
403
+
404
+ - 非流式也缓冲 deliver
405
+ - dispatch 完成后一次性发完整 `agent_reply`
406
+
407
+ ## 最小正确协议
408
+
409
+ 非流式:
410
+
411
+ ```text
412
+ backend -> hooks_agent(stream=false)
413
+ plugin -> OpenClaw
414
+ OpenClaw -> deliver block 1
415
+ plugin -> buffer
416
+ OpenClaw -> deliver block 2
417
+ plugin -> buffer
418
+ OpenClaw dispatch completed
419
+ plugin -> agent_reply(full_text)
420
+ backend -> complete task
421
+ ```
422
+
423
+ 流式:
424
+
425
+ ```text
426
+ backend -> hooks_agent(stream=true)
427
+ plugin -> OpenClaw
428
+ OpenClaw -> deliver block 1
429
+ plugin -> agent_delta(block 1)
430
+ OpenClaw -> deliver block 2
431
+ plugin -> agent_delta(block 2)
432
+ OpenClaw dispatch completed
433
+ plugin -> agent_done(full_text)
434
+ backend -> SSE done
435
+ ```
436
+
437
+ ## 后续演进建议
438
+
439
+ 下一阶段应把上下文构建收敛到统一模块,例如:
440
+
441
+ ```text
442
+ OpenClawRequestContextBuilder
443
+ ```
444
+
445
+ 它负责生成:
446
+
447
+ - route
448
+ - card
449
+ - customer
450
+ - conversation
451
+ - policy
452
+ - context_pack
453
+ - requestId
454
+ - sessionKey
455
+
456
+ 不要在 `chat.py`、API handler、插件代码里分散拼上下文。上下文编排应该是后端的显式能力,插件只做稳定的通道适配。
457
+
@@ -24,5 +24,63 @@
24
24
  }
25
25
  },
26
26
  "required": []
27
+ },
28
+
29
+ "channelConfigs": {
30
+ "sumeclaw": {
31
+ "label": "友虾名片",
32
+ "description": "通过 WebSocket 连接友虾名片平台,接收客户消息并调用 AI 名片回复",
33
+ "schema": {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "properties": {
37
+ "token": {
38
+ "type": "string",
39
+ "description": "从友虾平台获取的连接令牌"
40
+ },
41
+ "wsUrl": {
42
+ "type": "string",
43
+ "description": "友虾名片平台 WebSocket 地址,默认为 wss://api.gixin.cc"
44
+ },
45
+ "allowFrom": {
46
+ "type": "array",
47
+ "items": { "type": "string" },
48
+ "description": "允许接收消息的用户 ID 白名单,空表示不限制"
49
+ },
50
+ "dmSecurity": {
51
+ "type": "string",
52
+ "description": "私聊安全策略,例如 open / closed / allowlist"
53
+ }
54
+ },
55
+ "required": ["token"]
56
+ },
57
+ "uiHints": {
58
+ "token": {
59
+ "label": "连接令牌",
60
+ "placeholder": "从友虾小程序获取的 token",
61
+ "sensitive": true,
62
+ "help": "用于鉴权 WebSocket 连接,请妥善保管"
63
+ },
64
+ "wsUrl": {
65
+ "label": "WebSocket 地址",
66
+ "placeholder": "wss://api.gixin.cc",
67
+ "advanced": true
68
+ },
69
+ "allowFrom": {
70
+ "label": "允许的用户",
71
+ "placeholder": "用户 ID,多个用英文逗号分隔",
72
+ "help": "为空表示允许所有用户;填写后仅白名单内用户可触发对话"
73
+ },
74
+ "dmSecurity": {
75
+ "label": "私聊安全策略",
76
+ "placeholder": "open / closed / allowlist",
77
+ "advanced": true
78
+ }
79
+ },
80
+ "commands": {
81
+ "nativeCommandsAutoEnabled": true,
82
+ "nativeSkillsAutoEnabled": true
83
+ }
84
+ }
27
85
  }
28
86
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sumeai/sumeclaw",
3
- "version": "1.1.19",
3
+ "version": "1.1.20",
4
4
  "description": "友虾名片 OpenClaw Channel 插件 — 将 OpenClaw 连接到友虾名片平台,通过 AI 名片为客户提供服务",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",