claw-subagent-service 0.0.69 → 0.0.71
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/README.md +130 -14
- package/package.json +1 -1
- package/service/rongcloud/message-handler.js +45 -17
package/README.md
CHANGED
|
@@ -549,33 +549,149 @@ npm uninstall -g claw-subagent-service
|
|
|
549
549
|
|
|
550
550
|
## 日志查看
|
|
551
551
|
|
|
552
|
-
###
|
|
552
|
+
### 日志文件说明
|
|
553
|
+
|
|
554
|
+
服务运行过程中会产生以下日志文件,均位于安装目录的 `logs/` 子文件夹中:
|
|
555
|
+
|
|
556
|
+
| 日志文件 | 说明 | 关键内容 |
|
|
557
|
+
|----------|------|----------|
|
|
558
|
+
| `worker-YYYY-MM-DD.log` | Worker 进程日志 | 融云消息收发、OpenClaw SSE 流式调用、消息处理流程 |
|
|
559
|
+
| `daemon-YYYY-MM-DD.log` | Daemon 进程日志 | 服务启动/停止、进程监控、自动更新、端口管理 |
|
|
560
|
+
| `updater-YYYY-MM-DD.log` | 自动更新日志 | 版本检查、下载更新、安装结果 |
|
|
561
|
+
|
|
562
|
+
### 日志目录位置
|
|
563
|
+
|
|
564
|
+
| 安装方式 | 日志目录路径 |
|
|
565
|
+
|----------|-------------|
|
|
566
|
+
| npm 全局安装(Linux/macOS) | `$(npm root -g)/claw-subagent-service/logs/` |
|
|
567
|
+
| npm 全局安装(Windows) | `%APPDATA%\npm\node_modules\claw-subagent-service\logs\` |
|
|
568
|
+
| 本地源码运行 | `./logs/`(项目根目录) |
|
|
569
|
+
| Docker 容器内 | `/usr/lib/node_modules/claw-subagent-service/logs/` 或 `/data/node_cli/logs/` |
|
|
570
|
+
|
|
571
|
+
### Linux / macOS 查看命令
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
# 1. 确定安装目录
|
|
575
|
+
INSTALL_DIR=$(npm root -g)/claw-subagent-service
|
|
576
|
+
# 如果是本地源码运行,替换为实际路径,如:
|
|
577
|
+
# INSTALL_DIR=/data/node_cli
|
|
578
|
+
|
|
579
|
+
# 2. 查看当天 worker 日志(实时跟踪,调试用)
|
|
580
|
+
tail -f $INSTALL_DIR/logs/worker-$(date +%Y-%m-%d).log
|
|
581
|
+
|
|
582
|
+
# 3. 查看 worker 日志最后 200 行
|
|
583
|
+
tail -n 200 $INSTALL_DIR/logs/worker-$(date +%Y-%m-%d).log
|
|
584
|
+
|
|
585
|
+
# 4. 查看 daemon 日志
|
|
586
|
+
tail -n 100 $INSTALL_DIR/logs/daemon-$(date +%Y-%m-%d).log
|
|
587
|
+
|
|
588
|
+
# 5. 查看 updater 日志
|
|
589
|
+
tail -n 50 $INSTALL_DIR/logs/updater-$(date +%Y-%m-%d).log
|
|
590
|
+
|
|
591
|
+
# 6. 列出所有日志文件及大小
|
|
592
|
+
ls -lah $INSTALL_DIR/logs/
|
|
593
|
+
|
|
594
|
+
# 7. 搜索包含特定关键词的日志(如错误、SSE、融云)
|
|
595
|
+
grep -i "error\|sse\|融云\|rongcloud\|claw" $INSTALL_DIR/logs/worker-$(date +%Y-%m-%d).log
|
|
596
|
+
|
|
597
|
+
# 8. 搜索今天的所有 ERROR 级别日志
|
|
598
|
+
grep "$(date +%Y-%m-%d)" $INSTALL_DIR/logs/worker-$(date +%Y-%m-%d).log | grep "\[ERROR\]"
|
|
599
|
+
|
|
600
|
+
# 9. 合并 worker + daemon 日志并按时间排序(完整时间线)
|
|
601
|
+
cat $INSTALL_DIR/logs/worker-$(date +%Y-%m-%d).log $INSTALL_DIR/logs/daemon-$(date +%Y-%m-%d).log | sort
|
|
602
|
+
|
|
603
|
+
# 10. 实时查看所有组件日志(使用 multitail,需安装)
|
|
604
|
+
# multitail $INSTALL_DIR/logs/worker-$(date +%Y-%m-%d).log $INSTALL_DIR/logs/daemon-$(date +%Y-%m-%d).log
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Windows 查看命令
|
|
553
608
|
|
|
554
609
|
```powershell
|
|
555
|
-
#
|
|
556
|
-
|
|
610
|
+
# 1. 确定安装目录
|
|
611
|
+
$installDir = (npm root -g) + "\claw-subagent-service"
|
|
612
|
+
|
|
613
|
+
# 2. 查看当天 worker 日志
|
|
614
|
+
Get-Content "$installDir\logs\worker-$(Get-Date -Format yyyy-MM-dd).log" -Tail 100
|
|
615
|
+
|
|
616
|
+
# 3. 查看 daemon 日志
|
|
617
|
+
Get-Content "$installDir\logs\daemon-$(Get-Date -Format yyyy-MM-dd).log" -Tail 100
|
|
557
618
|
|
|
558
|
-
#
|
|
559
|
-
|
|
619
|
+
# 4. 搜索错误关键词
|
|
620
|
+
Select-String -Path "$installDir\logs\*.log" -Pattern "ERROR|error|失败|异常"
|
|
560
621
|
|
|
561
|
-
#
|
|
622
|
+
# 5. 查看 wrapper 日志(node-windows 服务生成)
|
|
623
|
+
Get-Content "$env:APPDATA\npm\node_modules\claw-subagent-service\service\daemon\clawsubagentservice.wrapper.log" -Tail 50
|
|
624
|
+
|
|
625
|
+
# 6. SYSTEM 账户下运行的日志(如果服务以 SYSTEM 运行)
|
|
562
626
|
Get-Content "C:\Windows\System32\config\systemprofile\claw-subagent-service\logs\worker-$(Get-Date -Format yyyy-MM-dd).log" -Tail 50
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### Docker 查看命令
|
|
563
630
|
|
|
564
|
-
|
|
565
|
-
|
|
631
|
+
```bash
|
|
632
|
+
# 1. 查看容器内日志(实时)
|
|
633
|
+
docker exec -it <容器名> sh -c "tail -f \$(npm root -g)/claw-subagent-service/logs/worker-\$(date +%Y-%m-%d).log"
|
|
634
|
+
|
|
635
|
+
# 2. 直接在宿主机查看容器日志文件
|
|
636
|
+
docker exec <容器名> cat /usr/lib/node_modules/claw-subagent-service/logs/worker-$(date +%Y-%m-%d).log
|
|
637
|
+
|
|
638
|
+
# 3. 查看容器标准输出(非日志文件,是控制台输出)
|
|
639
|
+
docker logs -f <容器名> --tail 200
|
|
640
|
+
|
|
641
|
+
# 4. 将容器日志复制到宿主机
|
|
642
|
+
docker cp <容器名>:/usr/lib/node_modules/claw-subagent-service/logs/ ./claw-logs/
|
|
566
643
|
```
|
|
567
644
|
|
|
568
|
-
###
|
|
645
|
+
### 按运行模式查看日志
|
|
646
|
+
|
|
647
|
+
#### systemd 模式(Linux 服务器)
|
|
569
648
|
|
|
570
649
|
```bash
|
|
571
|
-
# 查看 systemd
|
|
650
|
+
# 查看 systemd 管理的实时日志
|
|
572
651
|
sudo journalctl -u claw-subagent-service -f
|
|
573
652
|
|
|
574
|
-
#
|
|
575
|
-
|
|
653
|
+
# 查看最近 100 条日志
|
|
654
|
+
sudo journalctl -u claw-subagent-service -n 100
|
|
655
|
+
|
|
656
|
+
# 查看今天所有日志
|
|
657
|
+
sudo journalctl -u claw-subagent-service --since today
|
|
658
|
+
|
|
659
|
+
# 查看指定时间段的日志
|
|
660
|
+
sudo journalctl -u claw-subagent-service --since "2026-05-11 06:00:00" --until "2026-05-11 07:00:00"
|
|
661
|
+
|
|
662
|
+
# 查看包含特定关键词的日志
|
|
663
|
+
sudo journalctl -u claw-subagent-service -g "SSE|error|融云"
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
#### 用户级守护进程模式(无 systemd / Docker)
|
|
667
|
+
|
|
668
|
+
```bash
|
|
669
|
+
# 查找日志目录(全局搜索)
|
|
670
|
+
find / -name "worker-*.log" -path "*/claw-subagent-service/logs/*" 2>/dev/null
|
|
671
|
+
|
|
672
|
+
# 常见路径:
|
|
673
|
+
# /usr/lib/node_modules/claw-subagent-service/logs/
|
|
674
|
+
# /usr/local/lib/node_modules/claw-subagent-service/logs/
|
|
675
|
+
# /root/.clawmessenger/logs/
|
|
676
|
+
# /data/node_cli/logs/
|
|
677
|
+
|
|
678
|
+
# 设置日志目录变量并实时查看
|
|
679
|
+
LOG_DIR=/usr/lib/node_modules/claw-subagent-service/logs
|
|
680
|
+
tail -f $LOG_DIR/worker-$(date +%Y-%m-%d).log
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
#### 前台运行模式(调试开发)
|
|
684
|
+
|
|
685
|
+
```bash
|
|
686
|
+
# 直接运行,日志输出到终端控制台
|
|
687
|
+
claw-subagent-service --run
|
|
688
|
+
|
|
689
|
+
# 后台运行并重定向到文件
|
|
690
|
+
nohup claw-subagent-service --run > /tmp/claw-subagent.log 2>&1 &
|
|
691
|
+
tail -f /tmp/claw-subagent.log
|
|
576
692
|
|
|
577
|
-
#
|
|
578
|
-
|
|
693
|
+
# 使用 tee 同时输出到终端和文件
|
|
694
|
+
claw-subagent-service --run 2>&1 | tee /tmp/claw-subagent-$(date +%Y%m%d).log
|
|
579
695
|
```
|
|
580
696
|
|
|
581
697
|
---
|
package/package.json
CHANGED
|
@@ -14,6 +14,8 @@ class MessageHandler {
|
|
|
14
14
|
this.nodeId = config.accountId || '';
|
|
15
15
|
this.handleNormalMessage = handleNormalMessage;
|
|
16
16
|
this._streamQueue = Promise.resolve();
|
|
17
|
+
// 存储流式消息的 RongCloud messageUID:streamId -> messageUID
|
|
18
|
+
this._streamMessageUIDs = new Map();
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -222,26 +224,31 @@ class MessageHandler {
|
|
|
222
224
|
// 2. 调用 OpenClaw SSE
|
|
223
225
|
let hasSentChunk = false;
|
|
224
226
|
try {
|
|
227
|
+
// 确保传入的内容是字符串(claw 类型消息 content 可能是对象)
|
|
228
|
+
const chatContent = typeof msg.content === 'string' ? msg.content : (msg.content?.content || JSON.stringify(msg.content));
|
|
229
|
+
this.log?.info(`[MessageHandler] 调用 chatStream, content_type=${typeof msg.content}, chatContent=${chatContent.substring(0, 50)}`);
|
|
225
230
|
await this.openclawClient.chatStream(
|
|
226
|
-
|
|
231
|
+
chatContent,
|
|
227
232
|
msg.senderUserId,
|
|
228
233
|
async (delta) => {
|
|
229
234
|
buffer += delta;
|
|
230
235
|
seq += 1;
|
|
231
236
|
this.log?.info(`[MessageHandler] onDelta: seq=${seq}, delta_len=${delta.length}, buffer_len=${buffer.length}`);
|
|
232
|
-
|
|
237
|
+
// 发送增量(delta),让前端做增量拼接,避免内容重复
|
|
238
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, delta, streamId, seq === 1, false, seq);
|
|
233
239
|
hasSentChunk = true;
|
|
234
240
|
},
|
|
235
241
|
async (fullText) => {
|
|
236
242
|
this.log?.info(`[MessageHandler] onDone 触发, fullText.length=${fullText.length}, buffer.length=${buffer.length}, hasSentChunk=${hasSentChunk}`);
|
|
237
243
|
if (buffer.trim()) {
|
|
244
|
+
// 发送尾流:空字符串表示流结束,前端保留已拼接的完整内容
|
|
238
245
|
seq += 1;
|
|
239
|
-
await this._sendStreamChunk(fromUserId, targetId, conversationType,
|
|
246
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, '', streamId, false, true, seq);
|
|
240
247
|
hasSentChunk = true;
|
|
241
248
|
} else if (hasSentChunk) {
|
|
242
249
|
// 已经发送过内容,单独发送结束标记
|
|
243
250
|
seq += 1;
|
|
244
|
-
await this._sendStreamChunk(fromUserId, targetId, conversationType,
|
|
251
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, '', streamId, false, true, seq);
|
|
245
252
|
} else {
|
|
246
253
|
// 完全没有收到内容,发送错误提示
|
|
247
254
|
await this._sendStreamChunk(fromUserId, targetId, conversationType, '抱歉,AI 暂时没有回复内容。', streamId, true, true, 1);
|
|
@@ -253,11 +260,17 @@ class MessageHandler {
|
|
|
253
260
|
}
|
|
254
261
|
|
|
255
262
|
this.log?.info(`[MessageHandler] 流式消息完成,streamId=${streamId}, 总长度: ${fullText.length}`);
|
|
263
|
+
|
|
264
|
+
// 清理已存储的 messageUID,防止内存泄漏
|
|
265
|
+
this._streamMessageUIDs.delete(streamId);
|
|
256
266
|
}
|
|
257
267
|
);
|
|
258
268
|
} catch (err) {
|
|
259
269
|
this.log?.error(`[MessageHandler] 流式处理错误: ${err.message}`);
|
|
260
270
|
await this._sendStreamChunk(fromUserId, targetId, conversationType, '抱歉,AI 响应出现错误,请稍后重试。', streamId, true, true, 1);
|
|
271
|
+
|
|
272
|
+
// 错误时也要清理
|
|
273
|
+
this._streamMessageUIDs.delete(streamId);
|
|
261
274
|
throw err;
|
|
262
275
|
}
|
|
263
276
|
}
|
|
@@ -289,7 +302,8 @@ class MessageHandler {
|
|
|
289
302
|
* 发送流式消息片段(通过 Python 后端代理)
|
|
290
303
|
*/
|
|
291
304
|
async _sendStreamChunk(fromUserId, targetId, conversationType, content, streamId, isFirstChunk, isLastChunk, seq = 1) {
|
|
292
|
-
|
|
305
|
+
const contentPreview = typeof content === 'string' ? content.substring(0, 100) : JSON.stringify(content).substring(0, 100);
|
|
306
|
+
this.log?.info(`[MessageHandler] _sendStreamChunk ENTRY: target=${targetId}, streamId=${streamId}, seq=${seq}, first=${isFirstChunk}, last=${isLastChunk}, content_len=${content?.length || 0}, content_preview=${contentPreview}`);
|
|
293
307
|
if (!this.isStreamingEnabled) {
|
|
294
308
|
this.log?.warn('[MessageHandler] _sendStreamChunk skipped: isStreamingEnabled=false');
|
|
295
309
|
return;
|
|
@@ -298,25 +312,39 @@ class MessageHandler {
|
|
|
298
312
|
// 使用队列确保流式消息片段串行发送,避免并发导致后端处理错乱
|
|
299
313
|
this._streamQueue = this._streamQueue.then(async () => {
|
|
300
314
|
try {
|
|
315
|
+
// 获取已存储的 RongCloud messageUID(首流响应返回的)
|
|
316
|
+
const messageUID = this._streamMessageUIDs.get(streamId);
|
|
317
|
+
|
|
318
|
+
const payload = {
|
|
319
|
+
fromUserId,
|
|
320
|
+
targetId,
|
|
321
|
+
content,
|
|
322
|
+
streamId,
|
|
323
|
+
isFirstChunk,
|
|
324
|
+
isLastChunk,
|
|
325
|
+
conversationType,
|
|
326
|
+
seq,
|
|
327
|
+
messageUID
|
|
328
|
+
};
|
|
329
|
+
this.log?.info(`[MessageHandler] _sendStreamChunk 请求体: ${JSON.stringify(payload).substring(0, 300)}`);
|
|
301
330
|
const resp = await axios.post(
|
|
302
331
|
`${this.config.apiBaseUrl}/im/api/proxy/stream/publish`,
|
|
303
|
-
|
|
304
|
-
fromUserId,
|
|
305
|
-
targetId,
|
|
306
|
-
content,
|
|
307
|
-
streamId,
|
|
308
|
-
isFirstChunk,
|
|
309
|
-
isLastChunk,
|
|
310
|
-
conversationType,
|
|
311
|
-
seq
|
|
312
|
-
},
|
|
332
|
+
payload,
|
|
313
333
|
{ timeout: 10000 }
|
|
314
334
|
);
|
|
315
|
-
|
|
335
|
+
|
|
336
|
+
// 首流时存储 RongCloud 返回的 messageUID
|
|
337
|
+
if (isFirstChunk && resp.data?.messageUID) {
|
|
338
|
+
this._streamMessageUIDs.set(streamId, resp.data.messageUID);
|
|
339
|
+
this.log?.info(`[MessageHandler] 首流 messageUID 已存储: ${resp.data.messageUID}, streamId=${streamId}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
this.log?.info(`[MessageHandler] _sendStreamChunk 成功: status=${resp.status}, seq=${seq}, response=${JSON.stringify(resp.data).substring(0, 200)}`);
|
|
316
343
|
} catch (err) {
|
|
317
344
|
const url = `${this.config.apiBaseUrl}/im/api/proxy/stream/publish`;
|
|
318
345
|
const status = err.response?.status;
|
|
319
|
-
|
|
346
|
+
const responseData = err.response?.data ? JSON.stringify(err.response.data).substring(0, 200) : 'N/A';
|
|
347
|
+
this.log?.warn(`[MessageHandler] 发送流式消息失败: ${err.message}, url=${url}, status=${status || 'N/A'}, response=${responseData}, seq=${seq}`);
|
|
320
348
|
}
|
|
321
349
|
});
|
|
322
350
|
|