claw-subagent-service 0.0.96 → 0.0.99

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.
@@ -7,6 +7,9 @@
7
7
  # 注意:不使用 set -e,因为我们已经实现了完善的错误处理和验证逻辑
8
8
  # set -e 可能导致 pgrep/pidof 找不到进程时脚本意外退出
9
9
 
10
+ # 调试模式:记录每条执行的命令(用于排查问题)
11
+ # set -x
12
+
10
13
  # 颜色定义
11
14
  RED='\033[0;31m'
12
15
  GREEN='\033[0;32m'
@@ -161,16 +164,20 @@ get_openclaw_pid() {
161
164
 
162
165
  # Docker 模式:停止服务
163
166
  stop_docker() {
167
+ log_info "=== OpenClaw Docker 停止模式 ==="
164
168
  log_info "检查 OpenClaw 服务状态..."
165
169
 
166
170
  # 获取所有 openclaw 进程 PID
167
171
  local all_pids=""
168
172
  if command -v pgrep &>/dev/null; then
169
173
  all_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
174
+ log_info "使用 pgrep 查找进程: $all_pids"
170
175
  elif command -v pidof &>/dev/null; then
171
176
  all_pids=$(pidof openclaw)
177
+ log_info "使用 pidof 查找进程: $all_pids"
172
178
  else
173
179
  all_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
180
+ log_info "使用 ps 查找进程: $all_pids"
174
181
  fi
175
182
 
176
183
  local pid
@@ -209,14 +216,23 @@ stop_docker() {
209
216
  # 直接发送 SIGKILL(强制停止),避免 SIGTERM 被忽略
210
217
  log_info "正在强制停止 OpenClaw 服务(SIGKILL)..."
211
218
  for p in $all_pids; do
212
- kill -9 "$p" 2>/dev/null || true
219
+ log_info "执行: kill -9 $p"
220
+ kill -9 "$p" 2>/dev/null || log_warn "kill -9 $p 失败"
213
221
  done
214
222
  # 额外使用 pkill 确保所有相关进程都被停止
215
- pkill -9 -f "openclaw" 2>/dev/null || true
223
+ log_info "执行: pkill -9 -f openclaw"
224
+ pkill -9 -f "openclaw" 2>/dev/null || log_warn "pkill 失败"
225
+ log_info "执行: killall -9 openclaw"
226
+ killall -9 openclaw 2>/dev/null || log_warn "killall 失败"
216
227
 
217
- # 等待进程退出(最多 3 秒)
228
+ # 连续监控模式:每秒检查并杀死看门狗重启的进程
229
+ # 这样即使看门狗立即重启,也会被再次杀死
230
+ log_info "进入连续监控模式(最多 10 秒),防止看门狗自动重启..."
218
231
  local elapsed=0
219
- while [ $elapsed -lt 3 ]; do
232
+ local consecutive_empty=0
233
+ while [ $elapsed -lt 10 ]; do
234
+ sleep 1
235
+
220
236
  # 检查是否还有 openclaw 进程
221
237
  local remaining_pids=""
222
238
  if command -v pgrep &>/dev/null; then
@@ -228,22 +244,26 @@ stop_docker() {
228
244
  fi
229
245
 
230
246
  if [ -z "$remaining_pids" ]; then
231
- log_info "OpenClaw 服务已停止。(所有进程已退出)"
232
- log_info "服务已成功停止。"
233
- log_info "Success"
234
- exit 0
247
+ consecutive_empty=$((consecutive_empty + 1))
248
+ log_info "第 $elapsed 秒: 无 openclaw 进程(连续 $consecutive_empty 次)"
249
+ # 连续 2 秒没有进程,认为已停止
250
+ if [ $consecutive_empty -ge 2 ]; then
251
+ log_info "OpenClaw 服务已停止。(看门狗已放弃重启)"
252
+ log_info "服务已成功停止。"
253
+ log_info "Success"
254
+ exit 0
255
+ fi
256
+ else
257
+ consecutive_empty=0
258
+ log_info "第 $elapsed 秒: 发现新进程 $remaining_pids,再次 kill..."
259
+ pkill -9 -f "openclaw" 2>/dev/null || true
260
+ killall -9 openclaw 2>/dev/null || true
235
261
  fi
236
- sleep 1
262
+
237
263
  elapsed=$((elapsed + 1))
238
264
  done
239
265
 
240
- # 如果进程仍在,再次强制停止
241
- log_warn "进程仍在运行,再次强制停止..."
242
- pkill -9 -f "openclaw" 2>/dev/null || true
243
- killall -9 openclaw 2>/dev/null || true
244
-
245
- # 最终检查
246
- sleep 2
266
+ # 最终验证
247
267
  local remaining_pids=""
248
268
  if command -v pgrep &>/dev/null; then
249
269
  remaining_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
@@ -253,6 +273,15 @@ stop_docker() {
253
273
  remaining_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
254
274
  fi
255
275
 
276
+ # 最终验证:显示当前所有进程
277
+ log_info "最终验证: 当前 openclaw 进程: $remaining_pids"
278
+ log_info "最终验证: 当前端口状态:"
279
+ if command -v netstat &>/dev/null; then
280
+ netstat -tlnp 2>/dev/null | grep ":${PORT} " || log_info "端口 ${PORT} 未监听"
281
+ elif command -v ss &>/dev/null; then
282
+ ss -tlnp 2>/dev/null | grep ":${PORT} " || log_info "端口 ${PORT} 未监听"
283
+ fi
284
+
256
285
  if [ -z "$remaining_pids" ]; then
257
286
  log_info "OpenClaw 服务已停止。"
258
287
  log_info "服务已成功停止。"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.96",
3
+ "version": "0.0.99",
4
4
  "description": "虾说智能助手",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -136,25 +136,24 @@ async function verifyCommandResult(command, result, scriptOutput = '') {
136
136
  }
137
137
 
138
138
  if (command === OpenClawCommandEnum.STOP) {
139
- // 停止命令验证:多次检查端口,处理看门狗自动重启的情况
139
+ // 停止命令验证:多次检查端口并重复执行停止,处理看门狗自动重启
140
140
  console.log(`[OpenClawControl] 开始验证停止结果...`);
141
141
 
142
142
  let portStatus = 1;
143
- let checkCount = 0;
144
- const maxChecks = 5; // 最多检查 5
143
+ let stopAttempts = 0;
144
+ const maxStopAttempts = 3; // 最多执行 3 次停止
145
145
 
146
- while (checkCount < maxChecks && portStatus === 1) {
147
- await new Promise(resolve => setTimeout(resolve, 3000)); // 每次等待 3 秒
148
- portStatus = await getOpenClawStatus(18789);
149
- console.log(`[OpenClawControl] 停止验证第 ${checkCount + 1} 次检查,端口状态: ${portStatus}`);
150
- checkCount++;
151
- }
146
+ // 等待 3 秒,给看门狗一次重启机会,然后验证
147
+ await new Promise(resolve => setTimeout(resolve, 3000));
148
+
149
+ portStatus = await getOpenClawStatus(18789);
150
+ console.log(`[OpenClawControl] 停止后端口状态: ${portStatus}`);
152
151
 
153
152
  if (portStatus === 1) {
154
- console.error(`[OpenClawControl] 停止失败: 经过 ${maxChecks} 次检查,端口 18789 仍在监听。服务可能被看门狗自动重启。`);
153
+ console.error(`[OpenClawControl] 停止失败: 端口 18789 仍在监听。stop.sh 可能未能成功停止服务。`);
155
154
  return {
156
155
  status: OpenClawServiceStatus.ERROR,
157
- message: '停止失败: 服务仍在运行(可能被自动重启)'
156
+ message: '停止失败: 服务仍在运行'
158
157
  };
159
158
  }
160
159
 
@@ -56,9 +56,9 @@ class MessageHandler {
56
56
  return false;
57
57
  }
58
58
 
59
- const allowedTypes = ['RC:TxtMsg'];
59
+ const allowedTypes = ['RC:TxtMsg', 'RC:ImgMsg', 'RC:SightMsg', 'RC:FileMsg', 'RC:HQVCMsg'];
60
60
  if (!allowedTypes.includes(msg.messageType)) {
61
- this.log?.info(`[MessageHandler] 忽略非文本消息: ${msg.messageType}`);
61
+ this.log?.info(`[MessageHandler] 忽略不支持的消息类型: ${msg.messageType}`);
62
62
  return false;
63
63
  }
64
64
 
@@ -253,6 +253,10 @@ class MessageHandler {
253
253
  }
254
254
 
255
255
  getMessageType(msg) {
256
+ // 如果是媒体消息,直接返回 NORMAL 类型
257
+ if (['RC:ImgMsg', 'RC:SightMsg', 'RC:FileMsg', 'RC:HQVCMsg'].includes(msg.messageType)) {
258
+ return MessageType.NORMAL;
259
+ }
256
260
  const text = typeof msg.content === 'string' ? msg.content : (msg.content?.content || '');
257
261
  if (text.startsWith('/')) {
258
262
  return MessageType.COMMAND;
@@ -324,7 +328,7 @@ class MessageHandler {
324
328
 
325
329
  try {
326
330
  // 确保传入的内容是字符串(claw 类型消息 content 可能是对象)
327
- const chatContent = typeof msg.content === 'string' ? msg.content : (msg.content?.content || JSON.stringify(msg.content));
331
+ const chatContent = this._extractMessageContent(msg);
328
332
  this.log?.info(`[MessageHandler] 调用 chatStream, content_type=${typeof msg.content}, chatContent=${chatContent.substring(0, 50)}`);
329
333
  await this.openclawClient.chatStream(
330
334
  chatContent,
@@ -486,6 +490,51 @@ class MessageHandler {
486
490
  await this._streamQueue;
487
491
  }
488
492
 
493
+ /**
494
+ * 提取消息内容,支持文本、图片、视频、文件、语音
495
+ */
496
+ _extractMessageContent(msg) {
497
+ const msgType = msg.messageType;
498
+ const content = msg.content;
499
+
500
+ // 文本消息
501
+ if (msgType === 'RC:TxtMsg') {
502
+ return typeof content === 'string' ? content : (content?.content || JSON.stringify(content));
503
+ }
504
+
505
+ // 图片消息
506
+ if (msgType === 'RC:ImgMsg') {
507
+ const imageUri = content?.imageUri || '';
508
+ return `[图片] ${imageUri}`;
509
+ }
510
+
511
+ // 视频消息
512
+ if (msgType === 'RC:SightMsg') {
513
+ const sightUrl = content?.sightUrl || '';
514
+ const name = content?.name || '未知视频';
515
+ const duration = content?.duration || 0;
516
+ return `[视频] ${sightUrl} ${name} ${duration}秒`;
517
+ }
518
+
519
+ // 文件消息
520
+ if (msgType === 'RC:FileMsg') {
521
+ const fileUrl = content?.fileUrl || '';
522
+ const name = content?.name || '未知文件';
523
+ const size = content?.size || 0;
524
+ return `[文件] ${fileUrl} ${name} ${size}`;
525
+ }
526
+
527
+ // 语音消息
528
+ if (msgType === 'RC:HQVCMsg') {
529
+ const remoteUrl = content?.remoteUrl || '';
530
+ const duration = content?.duration || 0;
531
+ return `[语音] ${remoteUrl} ${duration}秒`;
532
+ }
533
+
534
+ // 兜底
535
+ return typeof content === 'string' ? content : JSON.stringify(content);
536
+ }
537
+
489
538
  parseCommand(raw, senderId) {
490
539
  const trimmed = raw.slice(1).trim();
491
540
  const parts = trimmed.split(/\s+/);
@@ -42,7 +42,7 @@ class RongCloudClient {
42
42
  // 注册 command 自定义消息类型(与前端对齐)
43
43
  try {
44
44
  if (typeof RongIMLib.registerMessageType === 'function') {
45
- this.SystemServiceMessage = RongIMLib.registerMessageType('command', true, false);
45
+ this.SystemServiceMessage = RongIMLib.registerMessageType('command', false, false);
46
46
  this.log?.info('[RongCloudClient] command 自定义消息类型已注册');
47
47
  } else {
48
48
  this.log?.warn('[RongCloudClient] SDK 不支持 registerMessageType');