openclaw-xiaoyou 1.3.3 → 1.3.5

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/docs/SKILL.md CHANGED
@@ -5,106 +5,220 @@ description: Use when users want to install, upgrade, uninstall, configure, or t
5
5
 
6
6
  # 小优 Channel 安装入口
7
7
 
8
- 小优 channel 安装、升级、卸载统一使用远程脚本�?
8
+ ## ⚠️ 强制约束(必须遵守)
9
9
 
10
- ```bash
11
- https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh
12
- ```
10
+ 1. **禁止使用 pip install、npm install 或任何其他方式安装小优**,唯一合法的安装方式是执行下方的 `curl | bash` 命令。
11
+ 2. **禁止自行编造命令**,只能使用本文档中列出的命令。
12
+ 3. **必须在终端中实际执行命令**,不能只是展示给用户看。
13
+ 4. **安装完成后必须按照"安装结论输出"格式汇报结果**。
14
+ 5. 不要把 OpenClaw 的底层安装命令拆散执行。除非远程脚本失败且需要排障,否则直接调用 `install-xiaoyou.sh`。
13
15
 
14
- 不要�?OpenClaw 的底层安装命令拆散执行。除非远程脚本失败且需要排障,否则直接调用 `install-xiaoyou.sh`�?
15
-
16
- ## 触发�?
16
+ ## 触发词
17
17
 
18
18
  - "安装小优" / "小优安装"
19
19
  - "小优接入" / "接入小优"
20
20
  - "配置 xiaoyou" / "xiaoyou 配置"
21
21
  - "升级小优" / "更新小优"
22
22
  - "卸载小优" / "移除小优"
23
- - "我想用小�? / "启用小优"
23
+ - "我想用小优" / "启用小优"
24
24
  - "小优 channel" / "xiaoyou channel"
25
25
 
26
- ## 标准执行
26
+ ## 执行顺序
27
27
 
28
- 优先直接通过 `curl | bash` 执行远程脚本�?
28
+ ### 第一步:卸载旧版本(幂等操作,未安装时不会报错)
29
29
 
30
30
  ```bash
31
- curl -fsSL https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh | bash -s -- --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw --token <AccessToken>
31
+ curl -fsSL https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh | bash -s -- uninstall
32
32
  ```
33
33
 
34
- ### 参数说明
34
+ ### 第二步:安装
35
35
 
36
- | 参数 | 必填 | 说明 |
37
- |------|------|------|
38
- | `--ws-url <url>` | �?首次安装 | 企业 WebSocket 服务地址,默�?`ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw` |
39
- | `--token <token>` | �?首次安装 | 企业服务认证 Token |
40
- | `--version <ver>` | �?| 指定 openclaw-xiaoyou 版本,默�?latest |
41
- | `--check-only` | �?| 只输出计划,不执行真实改�?|
42
- | `uninstall` | �?| 卸载小优 channel |
36
+ ```bash
37
+ curl -fsSL https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh | bash -s -- --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw --token <AccessToken>
38
+ ```
39
+
40
+ `<AccessToken>` 替换为用户提供的实际 Token 值。
43
41
 
44
- ### 首次安装
42
+ ### 第三步:验证
45
43
 
46
- 用户必须提供 `--ws-url` �?`--token`�?
44
+ 安装完成后执行以下命令确认状态:
47
45
 
48
46
  ```bash
49
- curl -fsSL https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh | bash -s -- --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw --token my-enterprise-token
47
+ openclaw plugins list
48
+ openclaw gateway status
50
49
  ```
51
50
 
52
- ### 升级
51
+ ### 第四步:输出结论
53
52
 
54
- 已安装后的升级可以不传参数,脚本会复用现有配置:
53
+ **必须**按照下方"安装结论输出"格式汇报结果。
55
54
 
56
- ```bash
57
- curl -fsSL https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh | bash -s --
58
- ```
55
+ ## 参数说明
59
56
 
60
- 如果升级时传�?`--ws-url` �?`--token`,会覆盖现有配置�?
57
+ | 参数 | 必填 | 说明 |
58
+ |------|------|------|
59
+ | `--ws-url <url>` | 是(首次安装) | 企业 WebSocket 服务地址,默认 `ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw` |
60
+ | `--token <token>` | 是(首次安装) | 企业服务认证 Token(用户必须提供) |
61
+ | `--version <ver>` | 否 | 指定 openclaw-xiaoyou 版本,默认 1.3.4 |
62
+ | `--check-only` | 否 | 只输出计划,不执行真实改动 |
63
+ | `uninstall` | 否 | 卸载小优 channel |
61
64
 
62
- ### 指定版本
65
+ ## 升级
66
+
67
+ 升级同样先卸载再安装:
63
68
 
64
69
  ```bash
65
- curl -fsSL https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh | bash -s -- --version 1.0.2 --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw --token my-token
70
+ curl -fsSL https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh | bash -s -- uninstall
71
+ curl -fsSL https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh | bash -s -- --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw --token <AccessToken>
66
72
  ```
67
73
 
68
74
  ## 卸载
69
75
 
70
76
  ```bash
71
- curl -fsSL https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh | bash -s -- uninstall
77
+ curl -fsSL https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh | bash -s -- uninstall
72
78
  ```
73
79
 
74
- 卸载会删�?xiaoyou channel 配置、插件记录和扩展目录�?
80
+ ## 交互约束(最少用户操作)
75
81
 
76
- ## 预检
82
+ - 不要求用户手动执行 CLI 命令,代理直接在终端执行。
83
+ - 不一次性抛给用户多步说明;直接执行。
84
+ - 仅在以下场景请求用户参与:
85
+ - 用户未提供 AccessToken 时,要求用户提供。
86
+ - 脚本执行失败需要用户确认时。
87
+ - 在每次用户提供信息后,立刻继续自动步骤。
77
88
 
78
- ```bash
79
- curl -fsSL https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh | bash -s -- --check-only
80
- curl -fsSL https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh | bash -s -- uninstall --check-only
89
+ ## 脚本做了什么
90
+
91
+ 1. 检测 OpenClaw 是否已安装并运行
92
+ 2. 安装/升级 `openclaw-xiaoyou` 插件(`openclaw plugins install openclaw-xiaoyou@1.3.4`)
93
+ 3. 写入 channel 配置到 `~/.openclaw/openclaw.json`:
94
+ - `channels.xiaoyou.enabled = true`
95
+ - `channels.xiaoyou.wsUrl = <用户提供的地址>`
96
+ - `channels.xiaoyou.authToken = <用户提供的 token>`
97
+ - `channels.xiaoyou.dmSecurity = open`
98
+ - `channels.xiaoyou.blockStreaming = true`
99
+ - `channels.xiaoyou.blockStreamingChunk = {"minChars": 1, "maxChars": 20}`
100
+ - `channels.xiaoyou.blockStreamingCoalesce = {"minChars": 1, "maxChars": 20, "idleMs": 30}`
101
+ 4. 重启 gateway 并检查插件状态
102
+
103
+ ## 完成定义
104
+
105
+ - 脚本退出码为 0
106
+ - 日志显示安装流程完成
107
+ - `openclaw plugins list` 中可见 `openclaw-xiaoyou`
108
+ - Gateway 处于运行状态
109
+ - 企业服务端能收到来自 xiaoyou 插件的 WebSocket 连接
110
+
111
+ ## 安装结论输出
112
+
113
+ **安装完成后,必须严格按以下格式输出结论摘要(不可省略):**
114
+
115
+ ```
116
+ Gateway 已经重启成功,运行正常。
117
+
118
+ 小优 channel 配置已完成:
119
+ ✅ 插件 openclaw-xiaoyou@<版本号> 已安装
120
+ ✅ AccessToken 已写入配置
121
+ ✅ xiaoyou channel 已启用
122
+ ✅ dmSecurity 设为 open
123
+ ✅ blockStreaming 已配置
124
+ ✅ Gateway 已重启并正常运行
125
+
126
+ 现在可以在设备上试试唤醒小优啦~
127
+ 对着设备说句话看看能不能接通 🎤
81
128
  ```
82
129
 
130
+ 如果某一步失败,对应项改为 ❌ 并附上失败原因。例如:
131
+
132
+ ```
133
+ 小优 channel 配置结果:
134
+ ✅ 插件 openclaw-xiaoyou@1.3.4 已安装
135
+ ✅ AccessToken 已写入配置
136
+ ✅ xiaoyou channel 已启用
137
+ ✅ dmSecurity 设为 open
138
+ ✅ blockStreaming 已配置
139
+ ❌ Gateway 重启失败:请手动执行 openclaw gateway restart
140
+ ```
141
+
142
+ ## 执行确认清单
143
+
144
+ 配置完成前,必须确认以下各项已执行:
145
+
146
+ - [ ] 卸载旧版本(第一步)
147
+ - [ ] 安装新版本(第二步)
148
+ - [ ] 插件安装状态确认(openclaw plugins list)
149
+ - [ ] Channel 配置写入确认
150
+ - [ ] Gateway 重启确认
151
+ - [ ] 按格式输出结论摘要
152
+
83
153
  ## 失败处理
84
154
 
85
- 如果脚本执行失败,优先让用户提供完整 `[xiaoyou-install]` 日志。常见判断:
155
+ 如果脚本执行失败,优先查看完整 `[xiaoyou-install]` 日志输出。常见判断:
86
156
 
87
157
  | 日志现象 | 处理 |
88
158
  |----------|------|
89
159
  | 缺少 `--token` | 要求用户提供企业服务认证 Token |
90
160
  | 未检测到 OpenClaw | 提示用户先安装并启动 OpenClaw |
91
- | npm latest 查询失败 | 可让脚本继续幂等安装,或指定 `--version` |
92
- | wsUrl 格式不合�?| 地址必须�?`ws://` �?`wss://` 开�?|
161
+ | npm 提示 404 / not found | 执行 `npm config set registry https://registry.npmmirror.com` 后重试 |
162
+ | wsUrl 格式不合法 | 地址必须以 `ws://` 或 `wss://` 开头 |
93
163
 
94
- ## 脚本做了什�?
164
+ ## 排障流程
95
165
 
96
- 1. 检�?OpenClaw 是否已安装并运行
97
- 2. 安装/升级 `openclaw-xiaoyou` 插件(`openclaw plugins install openclaw-xiaoyou@latest`�?
98
- 3. 写入 channel 配置�?`~/.openclaw/openclaw.json`�?
99
- - `channels.xiaoyou.enabled = true`
100
- - `channels.xiaoyou.wsUrl = <用户提供的地址>`
101
- - `channels.xiaoyou.authToken = <用户提供�?token>`
102
- - `channels.xiaoyou.dmSecurity = open`
103
- 4. 检查插件状态和 gateway 运行状�?
166
+ 当安装脚本失败或安装后连接异常时,按以下流程排查:
104
167
 
105
- ## 完成定义
168
+ ### 检查前置条件
169
+
170
+ ```bash
171
+ openclaw --version
172
+ openclaw gateway status
173
+ openclaw plugins list
174
+ ```
175
+
176
+ ### 安装时找不到插件
177
+
178
+ ```bash
179
+ npm config set registry https://registry.npmmirror.com
180
+ curl -fsSL https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh | bash -s -- --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw --token <AccessToken>
181
+ ```
182
+
183
+ ### WebSocket 连接不上
184
+
185
+ ```bash
186
+ tail -n 100 ~/.openclaw/logs/xiaoyou.log
187
+ ```
188
+
189
+ | 现象 | 原因 | 处理 |
190
+ |------|------|------|
191
+ | `invalid or expired token` | Token 无效或已过期 | 重新申请 Token |
192
+ | `token already connected` | 同一 Token 已有其他连接 | 断开旧连接或申请新 Token |
193
+ | 连接超时 | 网络不通或防火墙拦截 | 确认 11055 端口可达 |
194
+ | `ECONNREFUSED` | 企业服务未启动 | 确认 ai-websocket 服务正常 |
195
+
196
+ ### 手动清理(仅脚本卸载失败时使用)
197
+
198
+ ```bash
199
+ openclaw config unset channels.xiaoyou
200
+ openclaw config unset plugins.entries.openclaw-xiaoyou
201
+ openclaw config unset plugins.installs.openclaw-xiaoyou
202
+ rm -rf ~/.openclaw/extensions/xiaoyou
203
+ rm -rf ~/.openclaw/extensions/openclaw-xiaoyou
204
+ rm -rf ~/.openclaw/extensions/.openclaw-*
205
+ openclaw gateway restart
206
+ ```
106
207
 
107
- - 脚本退出码�?0�?
108
- - 日志显示安装、升级或卸载流程完成�?
109
- - 安装场景下,`openclaw xiaoyou status` 显示 connected�?
110
- - 企业服务端能收到来自 xiaoyou 插件�?WebSocket 连接�?
208
+ ### 更换 Token
209
+
210
+ ```bash
211
+ curl -fsSL https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh | bash -s -- --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw --token <新Token>
212
+ ```
213
+
214
+ ### 常用命令清单
215
+
216
+ ```bash
217
+ openclaw --version
218
+ openclaw plugins list
219
+ openclaw gateway status
220
+ cat ~/.openclaw/openclaw.json | python3 -m json.tool
221
+ tail -n 100 ~/.openclaw/logs/xiaoyou.log
222
+ tail -f ~/.openclaw/logs/xiaoyou.log
223
+ openclaw gateway restart
224
+ ```
@@ -347,7 +347,9 @@ xiaoyou 插件采用 **Bridge 模式**:由插件主动向企业服务发起 We
347
347
  "text": "项目 Alpha 当前进度 85%,预计下周三完成。",
348
348
  "mediaUrls": ["https://openclaw-local/media/chart.png"],
349
349
  "agentId": "main",
350
- "timestamp": 1730000000000
350
+ "timestamp": 1730000000000,
351
+ "streamStatus": "chunk",
352
+ "seq": 1
351
353
  }
352
354
  ```
353
355
 
@@ -356,10 +358,12 @@ xiaoyou 插件采用 **Bridge 模式**:由插件主动向企业服务发起 We
356
358
  | conversationId | string | 对应入站消息的会话 ID |
357
359
  | messageId | string | 回复消息的唯一 ID(由插件生成) |
358
360
  | replyToMessageId | string? | 对应入站 message 的 messageId,用于请求-回复一一绑定 |
359
- | text | string | 回复文本 |
361
+ | text | string | 回复文本(chunk 时为增量,end 时为完整文本) |
360
362
  | mediaUrls | string[]? | 附带的媒体文件 URL |
361
363
  | agentId | string? | 处理该消息的 Agent ID |
362
364
  | timestamp | number | 毫秒时间戳 |
365
+ | streamStatus | string? | 流式标记:`"chunk"`=增量片段,`"end"`=流结束,不传=一次性完整回复 |
366
+ | seq | number? | 片段序号(从 1 开始),仅 streamStatus="chunk" 时有值 |
363
367
 
364
368
  #### ping / pong
365
369
 
@@ -596,7 +600,145 @@ async def on_user_message(ws, user_msg):
596
600
  - xiaoyou 的重连机制可以应对单节点故障
597
601
  ```
598
602
 
599
- ## 10. 快速对照表
603
+ ## 10. 流式推送(Block Streaming)
604
+
605
+ ### 10.1 流式机制概述
606
+
607
+ xiaoyou 插件支持流式推送 LLM 回复,实现"边生成边推送"的效果。
608
+ 企业服务端可以实时展示 Agent 正在生成的内容,而非等待完整回复。
609
+
610
+ ```
611
+ LLM 生成过程 xiaoyou 插件 企业服务端
612
+ │ │ │
613
+ │ block 1 生成完毕 │ │
614
+ │─────────────────────▶│ reply (chunk, seq=1) │
615
+ │ │─────────────────────────────▶│ 实时展示
616
+ │ │ │
617
+ │ block 2 生成完毕 │ │
618
+ │─────────────────────▶│ reply (chunk, seq=2) │
619
+ │ │─────────────────────────────▶│ 追加展示
620
+ │ │ │
621
+ │ block 3 生成完毕 │ │
622
+ │─────────────────────▶│ reply (chunk, seq=3) │
623
+ │ │─────────────────────────────▶│ 追加展示
624
+ │ │ │
625
+ │ 全部生成完毕 │ │
626
+ │─────────────────────▶│ reply (end) │
627
+ │ │─────────────────────────────▶│ 渲染完成
628
+ │ │ │
629
+ ```
630
+
631
+ ### 10.2 流式帧格式
632
+
633
+ 流式回复统一使用 `type: "reply"`,通过 `streamStatus` 字段区分:
634
+
635
+ #### chunk 帧(增量片段)
636
+
637
+ ```json
638
+ {
639
+ "type": "reply",
640
+ "conversationId": "conv-123",
641
+ "messageId": "xiaoyou-1730000000000",
642
+ "replyToMessageId": "msg-001",
643
+ "text": "今天天气晴,",
644
+ "streamStatus": "chunk",
645
+ "seq": 1,
646
+ "timestamp": 1730000000001
647
+ }
648
+ ```
649
+
650
+ #### end 帧(流结束)
651
+
652
+ ```json
653
+ {
654
+ "type": "reply",
655
+ "conversationId": "conv-123",
656
+ "messageId": "xiaoyou-1730000000000",
657
+ "replyToMessageId": "msg-001",
658
+ "text": "今天天气晴,气温25度,适合外出。",
659
+ "streamStatus": "end",
660
+ "timestamp": 1730000000500
661
+ }
662
+ ```
663
+
664
+ #### 非流式帧(向后兼容)
665
+
666
+ ```json
667
+ {
668
+ "type": "reply",
669
+ "conversationId": "conv-123",
670
+ "messageId": "xiaoyou-1730000000000",
671
+ "text": "完整回复内容",
672
+ "timestamp": 1730000000000
673
+ }
674
+ ```
675
+
676
+ ### 10.3 字段说明
677
+
678
+ | 字段 | 类型 | 说明 |
679
+ |------|------|------|
680
+ | streamStatus | `"chunk"` \| `"end"` \| 不传 | chunk=增量片段,end=流结束(text 为完整文本),不传=一次性完整回复 |
681
+ | seq | number | 片段序号,从 1 开始递增,仅 chunk 帧有值 |
682
+ | messageId | string | 同一次回复的所有 chunk 和 end 帧共享相同的 messageId |
683
+ | text | string | chunk 时为增量文本,end 时为完整文本 |
684
+
685
+ ### 10.4 企业服务端处理逻辑
686
+
687
+ ```python
688
+ elif frame["type"] == "reply":
689
+ stream_status = frame.get("streamStatus")
690
+
691
+ if stream_status == "chunk":
692
+ # 流式片段 — 追加到缓冲区,实时推送给前端
693
+ append_to_buffer(
694
+ message_id=frame["messageId"],
695
+ delta=frame["text"],
696
+ seq=frame["seq"]
697
+ )
698
+ push_partial_to_user(frame["conversationId"], frame["text"])
699
+
700
+ elif stream_status == "end":
701
+ # 流结束 — 可用 text 校验拼接结果,完成渲染
702
+ finalize_reply(
703
+ message_id=frame["messageId"],
704
+ full_text=frame["text"],
705
+ media_urls=frame.get("mediaUrls", [])
706
+ )
707
+
708
+ else:
709
+ # 非流式 — 一次性完整回复(向后兼容)
710
+ deliver_to_user(
711
+ conversation_id=frame["conversationId"],
712
+ text=frame["text"],
713
+ media_urls=frame.get("mediaUrls", [])
714
+ )
715
+ ```
716
+
717
+ ### 10.5 流式配置(OpenClaw Gateway 侧)
718
+
719
+ | 配置项 | 值 | 说明 |
720
+ |--------|-----|------|
721
+ | `agents.defaults.blockStreamingDefault` | `"on"` | 全局开启 block streaming |
722
+ | `agents.defaults.blockStreamingBreak` | `"text_end"` | 每个 text block 结束就推送 |
723
+ | `agents.defaults.blockStreamingChunk.minChars` | `1` | 不等积累,有内容就推 |
724
+ | `agents.defaults.blockStreamingChunk.maxChars` | `20` | 每 20 字符触发一次推送(约 10 个汉字) |
725
+ | `agents.defaults.blockStreamingCoalesce.minChars` | `1` | 不合并小片段 |
726
+ | `agents.defaults.blockStreamingCoalesce.maxChars` | `20` | 合并上限 20 字符 |
727
+ | `agents.defaults.blockStreamingCoalesce.idleMs` | `30` | 30ms 无新内容就立即 flush |
728
+ | `channels.xiaoyou.blockStreaming` | `true` | xiaoyou channel 启用流式 |
729
+
730
+ ### 10.6 拆分规则
731
+
732
+ 插件在收到 Gateway 推送的每个 block 后,会按以下规则进一步拆分为更细的 chunk:
733
+
734
+ - 中文标点:`。` `!` `?` `;`
735
+ - 英文标点后跟空格:`. ` `! ` `? ` `; `
736
+ - 换行符:`\n`
737
+
738
+ 每个拆分出的片段作为独立的 chunk 帧推送,保证企业服务端能逐句展示。
739
+
740
+
741
+ ## 11. 快速对照表
600
742
 
601
743
  | 你想知道... | 看这里 |
602
744
  |------------|--------|
@@ -610,3 +752,4 @@ async def on_user_message(ws, user_msg):
610
752
  | 出了问题怎么处理? | §7 异常处理与容错 |
611
753
  | 企业侧要实现什么? | §8 企业服务端实现要点 |
612
754
  | 怎么部署? | §9 部署拓扑 |
755
+ | 流式推送怎么用? | §10 流式推送 |
@@ -332,45 +332,56 @@ patch_config() {
332
332
  die "配置文件不存在: $config_file"
333
333
  fi
334
334
 
335
- # 使用 python3 直接写入配置,绕过 openclaw config set 的 channel id 校验
336
- python3 -c "
337
- import json, sys
335
+ # 使用 node 直接写入配置(OpenClaw 依赖 node,必定存在)
336
+ node -e "
337
+ const fs = require('fs');
338
+ const configFile = '$config_file';
339
+ const wsUrl = '$WS_URL';
340
+ const token = '$TOKEN';
341
+
342
+ let cfg;
343
+ try {
344
+ cfg = JSON.parse(fs.readFileSync(configFile, 'utf8'));
345
+ } catch (e) {
346
+ console.error('[xiaoyou-install][error] 无法读取配置文件: ' + e.message);
347
+ process.exit(1);
348
+ }
338
349
 
339
- config_file = '$config_file'
340
- ws_url = '$WS_URL'
341
- token = '$TOKEN'
342
-
343
- try:
344
- with open(config_file, 'r') as f:
345
- cfg = json.load(f)
346
- except Exception as e:
347
- print(f'[xiaoyou-install][error] 无法读取配置文件: {e}', file=sys.stderr)
348
- sys.exit(1)
349
-
350
- # 确保 channels 对象存在
351
- cfg.setdefault('channels', {})
352
-
353
- # 写入 xiaoyou channel 配置
354
- xiaoyou = cfg['channels'].get('xiaoyou', {})
355
- xiaoyou['enabled'] = True
356
- if ws_url:
357
- xiaoyou['wsUrl'] = ws_url
358
- if token:
359
- xiaoyou['authToken'] = token
360
- xiaoyou.setdefault('dmSecurity', 'open')
361
- xiaoyou.setdefault('allowFrom', ['*'])
362
- xiaoyou.setdefault('reconnectIntervalMs', 3000)
363
- xiaoyou.setdefault('heartbeatIntervalMs', 30000)
364
- xiaoyou.setdefault('heartbeatTimeoutMs', 10000)
365
- cfg['channels']['xiaoyou'] = xiaoyou
366
-
367
- try:
368
- with open(config_file, 'w') as f:
369
- json.dump(cfg, f, indent=2, ensure_ascii=False)
370
- print('[xiaoyou-install] 配置写入成功')
371
- except Exception as e:
372
- print(f'[xiaoyou-install][error] 写入配置失败: {e}', file=sys.stderr)
373
- sys.exit(1)
350
+ // 确保 channels 对象存在
351
+ if (!cfg.channels) cfg.channels = {};
352
+
353
+ // 写入 xiaoyou channel 配置
354
+ const xiaoyou = cfg.channels.xiaoyou || {};
355
+ xiaoyou.enabled = true;
356
+ if (wsUrl) xiaoyou.wsUrl = wsUrl;
357
+ if (token) xiaoyou.authToken = token;
358
+ if (!xiaoyou.dmSecurity) xiaoyou.dmSecurity = 'open';
359
+ if (!xiaoyou.allowFrom) xiaoyou.allowFrom = ['*'];
360
+ if (!xiaoyou.reconnectIntervalMs) xiaoyou.reconnectIntervalMs = 3000;
361
+ if (!xiaoyou.heartbeatIntervalMs) xiaoyou.heartbeatIntervalMs = 30000;
362
+ if (!xiaoyou.heartbeatTimeoutMs) xiaoyou.heartbeatTimeoutMs = 10000;
363
+ // 开启 block streaming(仅 xiaoyou channel)
364
+ xiaoyou.blockStreaming = true;
365
+ xiaoyou.blockStreamingChunk = { minChars: 1, maxChars: 20 };
366
+ xiaoyou.blockStreamingCoalesce = { minChars: 1, maxChars: 20, idleMs: 30 };
367
+ cfg.channels.xiaoyou = xiaoyou;
368
+
369
+ // 同时写入 agents.defaults 作为全局 fallback(确保流式生效)
370
+ if (!cfg.agents) cfg.agents = {};
371
+ if (!cfg.agents.defaults) cfg.agents.defaults = {};
372
+ const defaults = cfg.agents.defaults;
373
+ if (!defaults.blockStreamingDefault) defaults.blockStreamingDefault = 'on';
374
+ if (!defaults.blockStreamingBreak) defaults.blockStreamingBreak = 'text_end';
375
+ if (!defaults.blockStreamingChunk) defaults.blockStreamingChunk = { minChars: 1, maxChars: 20 };
376
+ if (!defaults.blockStreamingCoalesce) defaults.blockStreamingCoalesce = { minChars: 1, maxChars: 20, idleMs: 30 };
377
+
378
+ try {
379
+ fs.writeFileSync(configFile, JSON.stringify(cfg, null, 2));
380
+ console.log('[xiaoyou-install] 配置写入成功');
381
+ } catch (e) {
382
+ console.error('[xiaoyou-install][error] 写入配置失败: ' + e.message);
383
+ process.exit(1);
384
+ }
374
385
  " || die "写入配置失败"
375
386
 
376
387
  log "已写入配置: $config_file"
@@ -1,25 +1,25 @@
1
1
 
2
- # 小优 Channel 安装话术与部署路径
2
+ # 小优 Channel 安装话术与部署路�?
3
3
 
4
4
  ## 安装话术
5
5
 
6
- 按照 https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh 文档完成小优channel配置,AccessToken为xiaoyou-025b07e43ae84cd6
6
+ 按照 https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh 文档完成小优channel配置,AccessToken为xiaoyou-025b07e43ae84cd6
7
7
 
8
8
  ## 脚本位置
9
9
 
10
- https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh
10
+ https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh
11
11
 
12
12
  ## 卸载+重装命令
13
13
 
14
14
  ```bash
15
- curl -fsSL https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh | bash -s -- uninstall && \
16
- curl -fsSL https://d10.tfdl.net/public/2026-05-06/14dbf63d-d0aa-4b5d-bdd6-47eddb455f36/install-xiaoyou.sh | bash -s -- \
15
+ curl -fsSL https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh | bash -s -- uninstall && \
16
+ curl -fsSL https://d10.tfdl.net/public/2026-05-08/7c0c5f98-9ee5-47ee-8dd9-54b12dbe8f4d/install-xiaoyou.sh | bash -s -- \
17
17
  --version 1.2.5 \
18
18
  --ws-url ws://aiws-sim.haiersmarthomes.com:11055/xiaoyou/claw \
19
19
  --token xiaoyou-025b07e43ae84cd6
20
20
  ```
21
21
 
22
- ## 验证发消息
22
+ ## 验证发消�?
23
23
 
24
24
  ```bash
25
25
  curl -X POST http://172.16.4.35:11480/ai-connection/xiaoyou/message/send \
@@ -46,11 +46,11 @@ journalctl --user -u openclaw-gateway.service --since "2 min ago" --no-pager | g
46
46
 
47
47
  ## 部署路径
48
48
 
49
- 验收环境位置:
49
+ 验收环境位置�?
50
50
  /aiysnfs/nginx/share/skills/upload/claw
51
51
 
52
- 仿真环境位置:
52
+ 仿真环境位置�?
53
53
  /ai-sim/export/nginx/share/skills/upload/claw
54
54
 
55
- 生产环境位置:
55
+ 生产环境位置�?
56
56
  /ai-prod/export/nginx/share/skills/upload/claw
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-xiaoyou",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "type": "module",
5
5
  "description": "Xiaoyou channel plugin for OpenClaw — connects enterprise services via persistent outbound WebSocket",
6
6
  "openclaw": {
package/src/channel.ts CHANGED
@@ -23,13 +23,12 @@ export function getRuntime() { return _runtime; }
23
23
  * 每个片段以标点或换行结尾(保留标点在片段内)。
24
24
  */
25
25
  function splitBySentence(text: string): string[] {
26
- // 匹配:中文标点(。!?;)、英文标点(.!?;)后跟空格或结尾、换行符
27
- const parts = text.split(/(?<=[。!?;\n])|(?<=[.!?;]\s)/);
26
+ // 匹配:中文标点(。!?;,:)、英文标点(.!?;,:)后跟空格或结尾、换行符
27
+ const parts = text.split(/(?<=[。!?;,:\n])|(?<=[.!?;,:]\s)/);
28
28
  const result: string[] = [];
29
29
  for (const part of parts) {
30
- const trimmed = part;
31
- if (trimmed.length > 0) {
32
- result.push(trimmed);
30
+ if (part.length > 0) {
31
+ result.push(part);
33
32
  }
34
33
  }
35
34
  return result.length > 0 ? result : [text];
@@ -205,7 +204,7 @@ export const xiayouPlugin = {
205
204
  // 我们在 deliver 内部进一步按标点/换行拆分,实现更细粒度的流式推送。
206
205
  //
207
206
  const replyMessageId = `xiaoyou-${Date.now()}`;
208
- let chunkSeq = 0;
207
+ let chunkSeq = 1;
209
208
  let fullText = "";
210
209
  let replyEndSent = false;
211
210
 
@@ -257,7 +256,7 @@ export const xiayouPlugin = {
257
256
  });
258
257
 
259
258
  // dispatch resolve 后,如果 onComplete 未被调用(兼容),手动发 end
260
- if (!replyEndSent && chunkSeq > 0 && _client && _client.isConnected()) {
259
+ if (!replyEndSent && chunkSeq > 1 && _client && _client.isConnected()) {
261
260
  _client.sendReply({
262
261
  type: "reply",
263
262
  conversationId,
@@ -268,7 +267,7 @@ export const xiayouPlugin = {
268
267
  timestamp: Date.now(),
269
268
  });
270
269
  logger.info(`[xiaoyou] stream end (fallback) sent to ${conversationId} (${chunkSeq} chunks)${inboundMessageId ? ` (replyTo=${inboundMessageId})` : ""}`);
271
- } else if (chunkSeq === 0) {
270
+ } else if (chunkSeq === 1) {
272
271
  logger.warn(`[xiaoyou] no reply generated for ${conversationId}`);
273
272
  }
274
273
  },
@@ -1,93 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 920" font-family="'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif">
3
- <defs>
4
- <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
5
- <polygon points="0 0, 10 3.5, 0 7" fill="#555"/>
6
- </marker>
7
- <filter id="shadow" x="-2%" y="-2%" width="104%" height="104%">
8
- <feDropShadow dx="2" dy="2" stdDeviation="3" flood-opacity="0.1"/>
9
- </filter>
10
- </defs>
11
-
12
- <!-- 外框 -->
13
- <rect x="30" y="20" width="840" height="880" rx="16" fill="#f8fafc" stroke="#94a3b8" stroke-width="2" filter="url(#shadow)"/>
14
- <text x="450" y="55" text-anchor="middle" font-size="20" font-weight="bold" fill="#1e293b">xiaoyou 插件内部架构</text>
15
-
16
- <!-- index.ts 模块 -->
17
- <rect x="60" y="80" width="780" height="180" rx="12" fill="#ffffff" stroke="#3b82f6" stroke-width="2" filter="url(#shadow)"/>
18
- <rect x="60" y="80" width="780" height="36" rx="12" fill="#3b82f6"/>
19
- <rect x="60" y="104" width="780" height="12" fill="#3b82f6"/>
20
- <text x="80" y="104" font-size="14" font-weight="bold" fill="#ffffff">index.ts (入口)</text>
21
-
22
- <text x="90" y="145" font-size="13" fill="#334155" font-family="'Consolas', 'Courier New', monospace">register(api) {</text>
23
- <text x="110" y="170" font-size="13" fill="#334155" font-family="'Consolas', 'Courier New', monospace">setRuntime(api.runtime)</text>
24
- <text x="430" y="170" font-size="12" fill="#6366f1">──▶ 注入 runtime 引用</text>
25
- <text x="110" y="195" font-size="13" fill="#334155" font-family="'Consolas', 'Courier New', monospace">api.registerChannel()</text>
26
- <text x="430" y="195" font-size="12" fill="#6366f1">──▶ 注册到 PluginRegistry</text>
27
- <text x="110" y="220" font-size="13" fill="#334155" font-family="'Consolas', 'Courier New', monospace">api.registerCli()</text>
28
- <text x="430" y="220" font-size="12" fill="#6366f1">──▶ 注册 CLI 子命令</text>
29
- <text x="90" y="245" font-size="13" fill="#334155" font-family="'Consolas', 'Courier New', monospace">}</text>
30
-
31
- <!-- 箭头: index -> channel -->
32
- <line x1="450" y1="260" x2="450" y2="295" stroke="#555" stroke-width="2" marker-end="url(#arrowhead)"/>
33
-
34
- <!-- channel.ts 模块 -->
35
- <rect x="60" y="300" width="780" height="320" rx="12" fill="#ffffff" stroke="#10b981" stroke-width="2" filter="url(#shadow)"/>
36
- <rect x="60" y="300" width="780" height="36" rx="12" fill="#10b981"/>
37
- <rect x="60" y="324" width="780" height="12" fill="#10b981"/>
38
- <text x="80" y="324" font-size="14" font-weight="bold" fill="#ffffff">channel.ts (ChannelPlugin)</text>
39
-
40
- <!-- config 区域 -->
41
- <text x="90" y="365" font-size="13" font-weight="bold" fill="#1e293b">config</text>
42
- <text x="170" y="365" font-size="12" fill="#6366f1">──▶ listAccountIds / resolveAccount / isConfigured / describeAccount</text>
43
-
44
- <!-- security 区域 -->
45
- <text x="90" y="395" font-size="13" font-weight="bold" fill="#1e293b">security</text>
46
- <text x="170" y="395" font-size="12" fill="#6366f1">──▶ DM 策略 (默认 open,允许所有用户)</text>
47
-
48
- <!-- gateway 区域 -->
49
- <text x="90" y="430" font-size="13" font-weight="bold" fill="#1e293b">gateway.startAccount(ctx)</text>
50
- <text x="110" y="455" font-size="12" fill="#475569">1. 从 ctx.account.config 读取 wsUrl / authToken</text>
51
- <text x="110" y="478" font-size="12" fill="#475569">2. 创建 EnterpriseClient 并连接</text>
52
- <text x="110" y="501" font-size="12" fill="#475569">3. onMessage 收到消息时:</text>
53
- <text x="140" y="521" font-size="11" fill="#64748b" font-family="'Consolas', 'Courier New', monospace">rt.channel.routing.resolveAgentRoute()</text>
54
- <text x="140" y="539" font-size="11" fill="#64748b" font-family="'Consolas', 'Courier New', monospace">rt.channel.reply.formatInboundEnvelope()</text>
55
- <text x="140" y="557" font-size="11" fill="#64748b" font-family="'Consolas', 'Courier New', monospace">rt.channel.reply.finalizeInboundContext()</text>
56
- <text x="140" y="575" font-size="11" fill="#64748b" font-family="'Consolas', 'Courier New', monospace">rt.channel.reply.dispatchReplyWith...()</text>
57
- <text x="110" y="598" font-size="12" fill="#475569">4. deliver() 回调将 LLM 回复通过 WebSocket 发出</text>
58
-
59
- <!-- outbound & status -->
60
- <text x="90" y="555" font-size="13" font-weight="bold" fill="#1e293b" transform="translate(520, -195)">outbound</text>
61
- <text x="170" y="555" font-size="12" fill="#6366f1" transform="translate(520, -195)">──▶ 将主动推送通过</text>
62
- <text x="170" y="573" font-size="12" fill="#6366f1" transform="translate(520, -195)"> EnterpriseClient 发出</text>
63
-
64
- <text x="90" y="598" font-size="13" font-weight="bold" fill="#1e293b" transform="translate(520, -195)">status</text>
65
- <text x="170" y="598" font-size="12" fill="#6366f1" transform="translate(520, -195)">──▶ 报告连接健康状态</text>
66
-
67
- <!-- 箭头: channel -> enterprise-client -->
68
- <line x1="450" y1="620" x2="450" y2="655" stroke="#555" stroke-width="2" marker-end="url(#arrowhead)"/>
69
-
70
- <!-- enterprise-client.ts 模块 -->
71
- <rect x="60" y="660" width="780" height="220" rx="12" fill="#ffffff" stroke="#f59e0b" stroke-width="2" filter="url(#shadow)"/>
72
- <rect x="60" y="660" width="780" height="36" rx="12" fill="#f59e0b"/>
73
- <rect x="60" y="684" width="780" height="12" fill="#f59e0b"/>
74
- <text x="80" y="684" font-size="14" font-weight="bold" fill="#ffffff">enterprise-client.ts (WebSocket 连接管理)</text>
75
-
76
- <text x="90" y="725" font-size="13" font-weight="bold" fill="#1e293b">connect()</text>
77
- <text x="260" y="725" font-size="12" fill="#6366f1">──▶ 建立 WebSocket → 发送 auth</text>
78
-
79
- <text x="90" y="755" font-size="13" font-weight="bold" fill="#1e293b">handleFrame()</text>
80
- <text x="260" y="755" font-size="12" fill="#6366f1">──▶ 解析 JSON 帧 → 分发到对应处理器</text>
81
-
82
- <text x="90" y="785" font-size="13" font-weight="bold" fill="#1e293b">startHeartbeat()</text>
83
- <text x="260" y="785" font-size="12" fill="#6366f1">──▶ 定时 ping,超时触发重连</text>
84
-
85
- <text x="90" y="815" font-size="13" font-weight="bold" fill="#1e293b">scheduleReconnect()</text>
86
- <text x="260" y="815" font-size="12" fill="#6366f1">──▶ 指数退避重连</text>
87
-
88
- <text x="90" y="845" font-size="13" font-weight="bold" fill="#1e293b">sendReply()</text>
89
- <text x="260" y="845" font-size="12" fill="#6366f1">──▶ 发送出站 reply 帧</text>
90
-
91
- <text x="90" y="875" font-size="13" font-weight="bold" fill="#1e293b">disconnect()</text>
92
- <text x="260" y="875" font-size="12" fill="#6366f1">──▶ 优雅关闭</text>
93
- </svg>