claw-subagent-service 0.0.65 → 0.0.67
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/cli.js
CHANGED
|
@@ -30,19 +30,22 @@ function runDaemon() {
|
|
|
30
30
|
console.log(`[CLI] Daemon 路径: ${DAEMON_PATH}`);
|
|
31
31
|
|
|
32
32
|
const daemon = spawn('node', [DAEMON_PATH], {
|
|
33
|
-
stdio: '
|
|
34
|
-
detached:
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
daemon.on('exit', (code) => {
|
|
38
|
-
console.log(`[CLI] Daemon 退出,code=${code}`);
|
|
39
|
-
process.exit(code);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
daemon.on('error', (err) => {
|
|
43
|
-
console.error(`[CLI] Daemon 启动失败: ${err.message}`);
|
|
44
|
-
process.exit(1);
|
|
33
|
+
stdio: 'ignore',
|
|
34
|
+
detached: true
|
|
45
35
|
});
|
|
36
|
+
|
|
37
|
+
daemon.unref();
|
|
38
|
+
|
|
39
|
+
// 不阻塞等待 Daemon 退出:
|
|
40
|
+
// detached: true 使 Daemon 成为独立进程组 leader,避免随 CLI 收到 SIGINT/SIGHUP;
|
|
41
|
+
// stdio: 'ignore' 防止 nohup 场景下 pipe 断开导致异常;
|
|
42
|
+
// unref() 让 CLI 事件循环不等待 Daemon,安装脚本可立即继续。
|
|
43
|
+
console.log(`[CLI] Daemon 已启动,PID=${daemon.pid}`);
|
|
44
|
+
|
|
45
|
+
// 短暂停留确认 Daemon 未立即崩溃,随后 CLI 退出(Daemon 继续后台运行)
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}, 1500);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
function installService() {
|
package/package.json
CHANGED
package/service/daemon.js
CHANGED
|
@@ -49,8 +49,13 @@ function checkSingleton() {
|
|
|
49
49
|
log.error(`[DAEMON] 另一个 Daemon 实例已在运行 (PID: ${pid}, 权限不足),当前实例退出`);
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
52
|
-
//
|
|
52
|
+
// 进程不存在(ESRCH),清理 stale PID 文件
|
|
53
|
+
log.info(`[DAEMON] 发现 stale PID 文件 (PID: ${pid} 已不存在),清理中...`);
|
|
54
|
+
try { fs.unlinkSync(PID_FILE); } catch { /* 忽略 */ }
|
|
53
55
|
}
|
|
56
|
+
} else {
|
|
57
|
+
// PID 文件内容无效,清理
|
|
58
|
+
try { fs.unlinkSync(PID_FILE); } catch { /* 忽略 */ }
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
} catch {
|
|
@@ -129,7 +134,8 @@ function freePortIfNeeded(port) {
|
|
|
129
134
|
];
|
|
130
135
|
for (const cmd of commands) {
|
|
131
136
|
try {
|
|
132
|
-
|
|
137
|
+
// 精简 Docker 镜像中部分命令可能极慢或不存在,缩短超时避免阻塞
|
|
138
|
+
const out = execSync(cmd, { encoding: 'utf8', timeout: 2000 }).trim();
|
|
133
139
|
const firstLine = out.split(/\r?\n/)[0];
|
|
134
140
|
const candidate = parseInt(firstLine, 10);
|
|
135
141
|
if (candidate && candidate > 0 && candidate !== currentWorkerPid && candidate !== process.pid) {
|
|
@@ -148,7 +154,7 @@ function freePortIfNeeded(port) {
|
|
|
148
154
|
// 所有端口查询命令都不可用(常见于精简 Docker 镜像)
|
|
149
155
|
// 兜底:杀掉残留 Worker 进程(Daemon 自身的命令行不含 worker.js,不会自杀)
|
|
150
156
|
try {
|
|
151
|
-
execSync('pkill -9 -f "worker.js" 2>/dev/null || true', { timeout:
|
|
157
|
+
execSync('pkill -9 -f "worker.js" 2>/dev/null || true', { timeout: 2000 });
|
|
152
158
|
log.warn(`[DAEMON] 已尝试杀掉残留 Worker 进程`);
|
|
153
159
|
} catch { /* 忽略 */ }
|
|
154
160
|
}
|
|
@@ -340,8 +346,10 @@ function gracefulShutdown() {
|
|
|
340
346
|
worker = null;
|
|
341
347
|
}
|
|
342
348
|
|
|
343
|
-
//
|
|
344
|
-
freePortIfNeeded
|
|
349
|
+
// Worker 已被 SIGKILL,端口会立即释放,无需再执行可能阻塞的 freePortIfNeeded
|
|
350
|
+
// 旧代码在此处调用 freePortIfNeeded,其内部的 execSync 命令链在精简 Docker 中
|
|
351
|
+
// 可能阻塞 20+ 秒,导致 kill -15 后旧进程迟迟不退出,新实例 checkSingleton 失败。
|
|
352
|
+
cleanupPidFile();
|
|
345
353
|
process.exit(0);
|
|
346
354
|
}
|
|
347
355
|
|
|
@@ -61,7 +61,7 @@ class DashboardReporter {
|
|
|
61
61
|
|
|
62
62
|
start(getMacAddress) {
|
|
63
63
|
const interval = 30000; // 30秒
|
|
64
|
-
this.log?.info(`[DashboardReporter] 启动仪表盘上报定时器,间隔: ${interval}ms`);
|
|
64
|
+
// this.log?.info(`[DashboardReporter] 启动仪表盘上报定时器,间隔: ${interval}ms`);
|
|
65
65
|
|
|
66
66
|
this.timer = setInterval(async () => {
|
|
67
67
|
if (!this.rongcloudClient?.isConnected) return;
|
|
@@ -125,7 +125,7 @@ class DashboardReporter {
|
|
|
125
125
|
try {
|
|
126
126
|
const sent = await this.messageSender.sendProtocolMessage(msgType, data);
|
|
127
127
|
if (sent) {
|
|
128
|
-
this.log?.info(`[DashboardReporter] ${msgType} 发送成功`);
|
|
128
|
+
// this.log?.info(`[DashboardReporter] ${msgType} 发送成功`);
|
|
129
129
|
} else {
|
|
130
130
|
this.log?.warn(`[DashboardReporter] ${msgType} 发送失败`);
|
|
131
131
|
}
|
|
@@ -411,9 +411,11 @@ class OpenClawClient {
|
|
|
411
411
|
{ role: 'user', content: message }
|
|
412
412
|
],
|
|
413
413
|
stream: true,
|
|
414
|
-
|
|
414
|
+
max_tokens: 2048
|
|
415
415
|
};
|
|
416
416
|
|
|
417
|
+
this.log?.info(`[OpenClawClient] SSE 请求 payload: ${JSON.stringify(payload)}`);
|
|
418
|
+
|
|
417
419
|
let fullText = '';
|
|
418
420
|
let buffer = '';
|
|
419
421
|
|
|
@@ -438,8 +440,13 @@ class OpenClawClient {
|
|
|
438
440
|
|
|
439
441
|
try {
|
|
440
442
|
const data = JSON.parse(dataStr);
|
|
441
|
-
|
|
442
|
-
|
|
443
|
+
// 尝试多个可能的内容字段,兼容不同版本 OpenClaw
|
|
444
|
+
const delta = data.choices?.[0]?.delta?.content
|
|
445
|
+
?? data.choices?.[0]?.message?.content
|
|
446
|
+
?? data.choices?.[0]?.text
|
|
447
|
+
?? data.content
|
|
448
|
+
?? data.delta;
|
|
449
|
+
if (typeof delta === 'string' && delta) {
|
|
443
450
|
fullText += delta;
|
|
444
451
|
try {
|
|
445
452
|
await onDelta?.(delta);
|
|
@@ -447,6 +454,9 @@ class OpenClawClient {
|
|
|
447
454
|
reject(err);
|
|
448
455
|
return;
|
|
449
456
|
}
|
|
457
|
+
} else {
|
|
458
|
+
// 调试:打印未识别的 SSE 数据结构
|
|
459
|
+
this.log?.info(`[OpenClawClient] SSE 原始数据(无delta): ${JSON.stringify(data).substring(0, 200)}`);
|
|
450
460
|
}
|
|
451
461
|
} catch {
|
|
452
462
|
// 忽略无法解析的 JSON 行
|
|
@@ -456,6 +466,10 @@ class OpenClawClient {
|
|
|
456
466
|
|
|
457
467
|
response.data.on('end', async () => {
|
|
458
468
|
this.log?.info(`[OpenClawClient] SSE 流结束,总长度: ${fullText.length}`);
|
|
469
|
+
if (fullText.length === 0) {
|
|
470
|
+
reject(new Error('OpenClaw SSE 返回空内容,可能模型未配置或 payload 格式不兼容'));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
459
473
|
try {
|
|
460
474
|
await onDone?.(fullText);
|
|
461
475
|
resolve();
|
|
@@ -226,7 +226,7 @@ class RongCloudClient {
|
|
|
226
226
|
? (RongIMLib.ConversationType?.GROUP || ConversationType.GROUP)
|
|
227
227
|
: (RongIMLib.ConversationType?.PRIVATE || ConversationType.PRIVATE);
|
|
228
228
|
|
|
229
|
-
this.log?.info(`[RongCloudClient] 发送消息 -> ${targetId} (Type: ${convType}): ${content.substring(0, 50)}`);
|
|
229
|
+
// this.log?.info(`[RongCloudClient] 发送消息 -> ${targetId} (Type: ${convType}): ${content.substring(0, 50)}`);
|
|
230
230
|
|
|
231
231
|
const result = await RongIMLib.sendTextMessage(
|
|
232
232
|
{ conversationType: convType, targetId },
|
|
@@ -237,7 +237,7 @@ class RongCloudClient {
|
|
|
237
237
|
|
|
238
238
|
if (result.code === 0 || result.code === 200) {
|
|
239
239
|
const sentUId = result.data?.messageUId;
|
|
240
|
-
this.log?.info(`[RongCloudClient] 发送成功, messageUId: ${sentUId}`);
|
|
240
|
+
// this.log?.info(`[RongCloudClient] 发送成功, messageUId: ${sentUId}`);
|
|
241
241
|
// 将发送成功的 messageUId 加入短期缓存,用于过滤 SDK 回传的自己消息
|
|
242
242
|
if (sentUId) {
|
|
243
243
|
this.sentMessageUIds.add(sentUId);
|