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: 'inherit',
34
- detached: false
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.65",
3
+ "version": "0.0.67",
4
4
  "description": "虾说智能助手",
5
5
  "main": "cli.js",
6
6
  "bin": {
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
- const out = execSync(cmd, { encoding: 'utf8', timeout: 5000 }).trim();
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: 5000 });
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(PORT);
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
- session_id: sessionId
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
- const delta = data.choices?.[0]?.delta?.content;
442
- if (delta) {
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);