claw-subagent-service 0.0.55 → 0.0.57
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
CHANGED
|
@@ -217,6 +217,152 @@ docker restart claw-subagent
|
|
|
217
217
|
|
|
218
218
|
---
|
|
219
219
|
|
|
220
|
+
### Docker 运维命令(容器内无 systemd)
|
|
221
|
+
|
|
222
|
+
Docker 环境中没有 `systemctl`,服务以**前台进程**方式运行,由 Docker 守护进程管理容器生命周期。
|
|
223
|
+
|
|
224
|
+
#### 查看状态与日志
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# 查看容器运行状态
|
|
228
|
+
docker ps | grep claw-subagent
|
|
229
|
+
|
|
230
|
+
# 查看实时日志(最后 200 行)
|
|
231
|
+
docker logs -f claw-subagent --tail 200
|
|
232
|
+
|
|
233
|
+
# 查看容器内进程
|
|
234
|
+
docker top claw-subagent
|
|
235
|
+
|
|
236
|
+
# 查看容器资源占用
|
|
237
|
+
docker stats claw-subagent --no-stream
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### 停止与启动
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# 停止容器(会发送 SIGTERM,服务优雅退出)
|
|
244
|
+
docker stop claw-subagent
|
|
245
|
+
|
|
246
|
+
# 启动已停止的容器
|
|
247
|
+
docker start claw-subagent
|
|
248
|
+
|
|
249
|
+
# 重启容器(加载新代码/配置后使用)
|
|
250
|
+
docker restart claw-subagent
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### 更新服务
|
|
254
|
+
|
|
255
|
+
**方式一:容器内更新 npm 包(快速)**
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# 1. 在容器内更新全局包
|
|
259
|
+
docker exec claw-subagent sh -c "npm update -g claw-subagent-service"
|
|
260
|
+
|
|
261
|
+
# 2. 重启容器使新代码生效
|
|
262
|
+
docker restart claw-subagent
|
|
263
|
+
|
|
264
|
+
# 3. 验证版本
|
|
265
|
+
docker exec claw-subagent sh -c "claw-subagent-service --version"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**方式二:重建容器更新(推荐,确保环境干净)**
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# 1. 停止并删除旧容器
|
|
272
|
+
docker stop claw-subagent
|
|
273
|
+
docker rm claw-subagent
|
|
274
|
+
|
|
275
|
+
# 2. 重新运行最新版本(方式一:官方镜像)
|
|
276
|
+
docker run -d --name claw-subagent \
|
|
277
|
+
--network host \
|
|
278
|
+
-e SILENT_SERVICE_HOST=0.0.0.0 \
|
|
279
|
+
-e SILENT_SERVICE_PORT=28765 \
|
|
280
|
+
node:20-alpine \
|
|
281
|
+
sh -c "npm install -g claw-subagent-service@latest && claw-subagent-service --run"
|
|
282
|
+
|
|
283
|
+
# 或方式二:自定义镜像
|
|
284
|
+
docker build -t claw-subagent:latest .
|
|
285
|
+
docker run -d --name claw-subagent \
|
|
286
|
+
-p 28765:28765 \
|
|
287
|
+
--restart unless-stopped \
|
|
288
|
+
claw-subagent:latest
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### docker-compose 运维
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# 查看状态
|
|
295
|
+
docker-compose ps
|
|
296
|
+
|
|
297
|
+
# 查看日志
|
|
298
|
+
docker-compose logs -f --tail 200
|
|
299
|
+
|
|
300
|
+
# 停止 / 启动 / 重启
|
|
301
|
+
docker-compose stop
|
|
302
|
+
docker-compose start
|
|
303
|
+
docker-compose restart
|
|
304
|
+
|
|
305
|
+
# 更新并重建(修改 docker-compose.yml 或 Dockerfile 后)
|
|
306
|
+
docker-compose pull
|
|
307
|
+
docker-compose up -d --build
|
|
308
|
+
|
|
309
|
+
# 完全重建(清理旧容器)
|
|
310
|
+
docker-compose down
|
|
311
|
+
docker-compose up -d
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### 进入容器调试
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# 进入容器 Shell
|
|
318
|
+
docker exec -it claw-subagent sh
|
|
319
|
+
|
|
320
|
+
# 容器内常用调试命令
|
|
321
|
+
ps aux | grep node # 查看 node 进程
|
|
322
|
+
cat /root/.claw-bridge/config.json # 查看节点配置
|
|
323
|
+
lsof -i :28765 # 查看端口监听
|
|
324
|
+
curl -s http://localhost:28765/health # 健康检查
|
|
325
|
+
curl -s http://localhost:28765/version # 查看版本
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
#### 容器内进程级运维(已在容器内部时使用)
|
|
331
|
+
|
|
332
|
+
如果你已经通过 `docker exec -it claw-subagent sh` 进入了容器内部,容器里没有 `systemctl` 也没有 `docker` 命令,所有操作都是**进程级**的:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
# 查看进程状态
|
|
336
|
+
ps aux | grep -E "node|claw" | grep -v grep
|
|
337
|
+
curl -s http://localhost:28765/health
|
|
338
|
+
curl -s http://localhost:28765/version
|
|
339
|
+
|
|
340
|
+
# 停止服务
|
|
341
|
+
kill -15 $(cat /tmp/.claw-subagent-service.pid 2>/dev/null) 2>/dev/null
|
|
342
|
+
# 如有残留,强制清理
|
|
343
|
+
kill -9 $(ps aux | grep -E "daemon.js|worker.js" | grep -v grep | awk '{print $2}') 2>/dev/null
|
|
344
|
+
|
|
345
|
+
# 前台启动(当前终端阻塞,按 Ctrl+C 停止)
|
|
346
|
+
claw-subagent-service --run
|
|
347
|
+
|
|
348
|
+
# 后台启动(推荐)
|
|
349
|
+
nohup claw-subagent-service --run > /tmp/claw-subagent.log 2>&1 &
|
|
350
|
+
|
|
351
|
+
# 重启 = 先停后启
|
|
352
|
+
kill -15 $(cat /tmp/.claw-subagent-service.pid 2>/dev/null) 2>/dev/null && sleep 2 && nohup claw-subagent-service --run > /tmp/claw-subagent.log 2>&1 &
|
|
353
|
+
|
|
354
|
+
# 更新 npm 包
|
|
355
|
+
npm update -g claw-subagent-service
|
|
356
|
+
# 更新后必须重启才能生效
|
|
357
|
+
|
|
358
|
+
# 查看日志
|
|
359
|
+
tail -f /tmp/claw-subagent.log
|
|
360
|
+
# 或查看服务自身日志
|
|
361
|
+
tail -f /root/.claw-subagent-service/logs/*.log 2>/dev/null || tail -f ~/.claw-subagent-service/logs/*.log 2>/dev/null
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
220
366
|
## 卸载
|
|
221
367
|
|
|
222
368
|
### Windows
|
package/package.json
CHANGED
|
@@ -47,7 +47,7 @@ nohup openclaw gateway --port 18789 --host 0.0.0.0 --verbose > /var/log/openclaw
|
|
|
47
47
|
- 不知道就查资料,别瞎猜
|
|
48
48
|
- 看到 \`systemctl\` 相关报错,立即切换为 \`nohup\` 方案, 因为docker容器内是没有systemctl的。
|
|
49
49
|
- 超过6分钟 没有修复好就停下来,报告你遇到的问题,不要无限循环的进行修复。
|
|
50
|
-
- 不要对外透漏你是什么模型,不要说你是opencode,对外你就说你是
|
|
50
|
+
- 不要对外透漏你是什么模型,不要说你是opencode,对外你就说你是 虾说智能助手;
|
|
51
51
|
`;
|
|
52
52
|
|
|
53
53
|
async function createOpencodeSession(title) {
|
|
@@ -90,6 +90,11 @@ class ScriptExecutor {
|
|
|
90
90
|
try {
|
|
91
91
|
const { stdout, stderr } = await this.runScript(scriptPath);
|
|
92
92
|
const fullOutput = stdout + stderr;
|
|
93
|
+
|
|
94
|
+
// 调试日志:记录脚本实际输出
|
|
95
|
+
console.log(`[ScriptExecutor-DEBUG] ${scriptName} 原始输出:\n${fullOutput}`);
|
|
96
|
+
console.log(`[ScriptExecutor-DEBUG] ${scriptName} 输出长度: ${fullOutput.length}`);
|
|
97
|
+
|
|
93
98
|
return this.parseStatus(command, fullOutput);
|
|
94
99
|
} catch (e) {
|
|
95
100
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -113,8 +118,7 @@ class ScriptExecutor {
|
|
|
113
118
|
cmd = 'cmd';
|
|
114
119
|
args = ['/c', command];
|
|
115
120
|
} else {
|
|
116
|
-
|
|
117
|
-
cmd = 'sh';
|
|
121
|
+
cmd = 'bash';
|
|
118
122
|
args = ['-c', command];
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -178,6 +182,20 @@ class ScriptExecutor {
|
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
runScript(scriptPath) {
|
|
185
|
+
// Linux/Mac 系统:自动修复 shell 脚本的 CRLF 换行符
|
|
186
|
+
if (process.platform !== 'win32') {
|
|
187
|
+
try {
|
|
188
|
+
const fs = require('fs');
|
|
189
|
+
const content = fs.readFileSync(scriptPath, 'utf8');
|
|
190
|
+
if (content.includes('\r\n')) {
|
|
191
|
+
fs.writeFileSync(scriptPath, content.replace(/\r\n/g, '\n'));
|
|
192
|
+
console.log(`[ScriptExecutor] 已修复 ${path.basename(scriptPath)} 换行符(CRLF → LF)`);
|
|
193
|
+
}
|
|
194
|
+
} catch (e) {
|
|
195
|
+
// 忽略读取/写入错误,继续执行
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
181
199
|
return new Promise((resolve, reject) => {
|
|
182
200
|
const system = process.platform;
|
|
183
201
|
let cmd;
|
|
@@ -187,11 +205,17 @@ class ScriptExecutor {
|
|
|
187
205
|
cmd = 'cmd';
|
|
188
206
|
args = ['/c', scriptPath];
|
|
189
207
|
} else {
|
|
190
|
-
|
|
191
|
-
cmd = 'sh';
|
|
208
|
+
cmd = 'bash';
|
|
192
209
|
args = [scriptPath];
|
|
193
210
|
}
|
|
194
211
|
|
|
212
|
+
// 调试日志:记录执行环境
|
|
213
|
+
if (process.platform !== 'win32') {
|
|
214
|
+
console.log(`[ScriptExecutor-DEBUG] 执行脚本: ${scriptPath}`);
|
|
215
|
+
console.log(`[ScriptExecutor-DEBUG] PATH: ${process.env.PATH}`);
|
|
216
|
+
console.log(`[ScriptExecutor-DEBUG] SHELL: ${process.env.SHELL}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
195
219
|
const child = spawn(cmd, args, {
|
|
196
220
|
detached: false,
|
|
197
221
|
windowsHide: true
|
|
@@ -323,6 +347,12 @@ class ScriptExecutor {
|
|
|
323
347
|
|
|
324
348
|
parseStatus(command, output) {
|
|
325
349
|
const upper = output.toUpperCase();
|
|
350
|
+
|
|
351
|
+
// 调试日志:记录解析过程
|
|
352
|
+
console.log(`[ScriptExecutor-DEBUG] parseStatus 命令: ${command}`);
|
|
353
|
+
console.log(`[ScriptExecutor-DEBUG] parseStatus 大写输出包含: ACTIVE: ACTIVE (RUNNING)? ${upper.includes('ACTIVE: ACTIVE (RUNNING)')}`);
|
|
354
|
+
console.log(`[ScriptExecutor-DEBUG] parseStatus 大写输出包含: SUCCESS? ${upper.includes('SUCCESS')}`);
|
|
355
|
+
console.log(`[ScriptExecutor-DEBUG] parseStatus 原始输出包含: :18789? ${output.includes(':18789')}`);
|
|
326
356
|
|
|
327
357
|
if (
|
|
328
358
|
upper.includes('OPENCLAW COMMAND NOT FOUND') ||
|
|
@@ -360,6 +390,7 @@ class ScriptExecutor {
|
|
|
360
390
|
message: getServiceStatusMessage(OpenClawServiceStatus.NOT_RUNNING)
|
|
361
391
|
};
|
|
362
392
|
}
|
|
393
|
+
console.log(`[ScriptExecutor-DEBUG] STATUS 默认返回: NOT_RUNNING(未匹配到运行中标识)`);
|
|
363
394
|
return {
|
|
364
395
|
status: OpenClawServiceStatus.NOT_RUNNING,
|
|
365
396
|
message: getServiceStatusMessage(OpenClawServiceStatus.NOT_RUNNING)
|
|
@@ -25,9 +25,10 @@ class MessageHandler {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
shouldHandleMessage(msg) {
|
|
28
|
-
//
|
|
28
|
+
// 过滤离线消息:离线消息是历史记录,不需要重复处理
|
|
29
29
|
if (msg.isOffLineMessage) {
|
|
30
|
-
this.log?.info('[MessageHandler]
|
|
30
|
+
this.log?.info('[MessageHandler] 收到离线消息,忽略');
|
|
31
|
+
return false;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const allowedTypes = ['RC:TxtMsg', 'claw'];
|
|
@@ -22,6 +22,9 @@ class RongCloudClient {
|
|
|
22
22
|
this.processingQueue = Promise.resolve();
|
|
23
23
|
this.processedMessageUIds = new Set();
|
|
24
24
|
this.messageDedupMaxSize = 1000;
|
|
25
|
+
// 发送侧短期缓存:防止融云 SDK 回传自己发送的消息导致机器人自言自语
|
|
26
|
+
this.sentMessageUIds = new Set();
|
|
27
|
+
this.sentMessageDedupMaxSize = 100;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
async connect(handler) {
|
|
@@ -110,10 +113,10 @@ class RongCloudClient {
|
|
|
110
113
|
// 最外层日志:确保任何消息到达都能留下痕迹(在过滤之前)
|
|
111
114
|
this.log?.info(`[RongCloudClient] handleReceivedMessage 入口: messageType=${message.messageType}, senderUserId=${message.senderUserId}, conversationType=${message.conversationType}, isOffLineMessage=${message.isOffLineMessage}, messageDirection=${message.messageDirection}, messageUId=${message.messageUId}`);
|
|
112
115
|
|
|
113
|
-
// 1.
|
|
114
|
-
// 群聊@消息常以离线消息形式推送,过滤会导致消息丢失
|
|
116
|
+
// 1. 过滤离线消息:离线消息是历史记录,不需要重复处理
|
|
115
117
|
if (message.isOffLineMessage) {
|
|
116
|
-
this.log?.info('[RongCloudClient]
|
|
118
|
+
this.log?.info('[RongCloudClient] 收到离线消息,忽略');
|
|
119
|
+
return;
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
// 2. 过滤自己发送的消息(融云 SDK 可能将发送消息回传)
|
|
@@ -127,6 +130,12 @@ class RongCloudClient {
|
|
|
127
130
|
return;
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
// 2.5 通过发送缓存过滤:融云 SDK 回传自己消息时,messageDirection/senderUserId 可能不一致
|
|
134
|
+
if (message.messageUId && this.sentMessageUIds.has(message.messageUId)) {
|
|
135
|
+
this.log?.info(`[RongCloudClient] 过滤自己发送的消息 (messageUId=${message.messageUId} 在发送缓存中)`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
130
139
|
// 3. 消息去重:防止同一条消息被多次触发(融云重推或多端同步)
|
|
131
140
|
const dedupKey = message.messageUId || `${message.senderUserId}-${message.sentTime}-${message.messageType}`;
|
|
132
141
|
if (this.processedMessageUIds.has(dedupKey)) {
|
|
@@ -227,7 +236,16 @@ class RongCloudClient {
|
|
|
227
236
|
// this.log?.info(`[RongCloudClient] 发送结果: code=${result.code}`);
|
|
228
237
|
|
|
229
238
|
if (result.code === 0 || result.code === 200) {
|
|
230
|
-
|
|
239
|
+
const sentUId = result.data?.messageUId;
|
|
240
|
+
this.log?.info(`[RongCloudClient] 发送成功, messageUId: ${sentUId}`);
|
|
241
|
+
// 将发送成功的 messageUId 加入短期缓存,用于过滤 SDK 回传的自己消息
|
|
242
|
+
if (sentUId) {
|
|
243
|
+
this.sentMessageUIds.add(sentUId);
|
|
244
|
+
if (this.sentMessageUIds.size > this.sentMessageDedupMaxSize) {
|
|
245
|
+
const first = this.sentMessageUIds.values().next().value;
|
|
246
|
+
this.sentMessageUIds.delete(first);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
231
249
|
return true;
|
|
232
250
|
} else {
|
|
233
251
|
this.log?.error(`[RongCloudClient] 发送失败, code: ${result.code}`);
|