claw-subagent-service 0.0.123 → 0.0.125

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.
@@ -163,7 +163,9 @@ get_openclaw_pid() {
163
163
  }
164
164
 
165
165
  # Docker 模式:停止服务
166
+ # 参数: $1 = force (1=强制停止, 空=优雅停止)
166
167
  stop_docker() {
168
+ local force="$1"
167
169
  log_info "=== OpenClaw Docker 停止模式 ==="
168
170
  log_info "检查 OpenClaw 服务状态..."
169
171
 
@@ -213,9 +215,48 @@ stop_docker() {
213
215
 
214
216
  log_info "发现 OpenClaw 进程: $all_pids"
215
217
 
216
- # 直接发送 SIGKILL(强制停止),避免 SIGTERM 被忽略
218
+ # 如果不是强制模式,先尝试优雅停止
219
+ if [ -z "$force" ]; then
220
+ log_info "正在优雅停止 OpenClaw 服务..."
221
+ # 尝试发送 SIGTERM
222
+ for p in $all_pids; do
223
+ log_info "执行: kill -15 $p"
224
+ kill -15 "$p" 2>/dev/null || log_warn "kill -15 $p 失败"
225
+ done
226
+
227
+ # 等待 5 秒让服务优雅退出
228
+ log_info "等待 5 秒让服务优雅退出..."
229
+ sleep 5
230
+
231
+ # 检查是否已停止
232
+ local pid_after_graceful
233
+ pid_after_graceful=$(get_openclaw_pid)
234
+ if [ -z "$pid_after_graceful" ] && ! check_port "$PORT"; then
235
+ log_info "OpenClaw 服务已通过优雅停止退出。"
236
+ log_info "服务已成功停止。"
237
+ log_info "Success"
238
+ exit 0
239
+ fi
240
+
241
+ log_warn "优雅停止失败,服务仍在运行,切换到强制停止..."
242
+ fi
243
+
244
+ # 强制停止模式(直接发送 SIGKILL)
217
245
  log_info "正在强制停止 OpenClaw 服务(SIGKILL)..."
218
- for p in $all_pids; do
246
+
247
+ # 获取最新的进程列表(因为优雅停止后可能有新进程)
248
+ local current_pids=""
249
+ if command -v pgrep &>/dev/null; then
250
+ current_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
251
+ elif command -v pidof &>/dev/null; then
252
+ current_pids=$(pidof openclaw)
253
+ else
254
+ current_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
255
+ fi
256
+
257
+ log_info "当前 openclaw 进程: $current_pids"
258
+
259
+ for p in $current_pids; do
219
260
  log_info "执行: kill -9 $p"
220
261
  kill -9 "$p" 2>/dev/null || log_warn "kill -9 $p 失败"
221
262
  done
@@ -225,12 +266,15 @@ stop_docker() {
225
266
  log_info "执行: killall -9 openclaw"
226
267
  killall -9 openclaw 2>/dev/null || log_warn "killall 失败"
227
268
 
269
+ # 等待 2 秒让进程退出
270
+ sleep 2
271
+
228
272
  # 连续监控模式:每秒检查并杀死看门狗重启的进程
229
273
  # 这样即使看门狗立即重启,也会被再次杀死
230
- log_info "进入连续监控模式(最多 10 秒),防止看门狗自动重启..."
274
+ log_info "进入连续监控模式(最多 20 秒),防止看门狗自动重启..."
231
275
  local elapsed=0
232
276
  local consecutive_empty=0
233
- while [ $elapsed -lt 10 ]; do
277
+ while [ $elapsed -lt 20 ]; do
234
278
  sleep 1
235
279
 
236
280
  # 检查是否还有 openclaw 进程
@@ -243,11 +287,17 @@ stop_docker() {
243
287
  remaining_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
244
288
  fi
245
289
 
246
- if [ -z "$remaining_pids" ]; then
290
+ # 同时检查端口是否仍在监听
291
+ local port_listening=false
292
+ if check_port "$PORT"; then
293
+ port_listening=true
294
+ fi
295
+
296
+ if [ -z "$remaining_pids" ] && [ "$port_listening" = false ]; then
247
297
  consecutive_empty=$((consecutive_empty + 1))
248
- log_info "第 $elapsed 秒: 无 openclaw 进程(连续 $consecutive_empty 次)"
249
- # 连续 2 秒没有进程,认为已停止
250
- if [ $consecutive_empty -ge 2 ]; then
298
+ log_info "第 $elapsed 秒: 无 openclaw 进程且端口未监听(连续 $consecutive_empty 次)"
299
+ # 连续 3 秒没有进程且端口未监听,认为已停止
300
+ if [ $consecutive_empty -ge 3 ]; then
251
301
  log_info "OpenClaw 服务已停止。(看门狗已放弃重启)"
252
302
  log_info "服务已成功停止。"
253
303
  log_info "Success"
@@ -255,7 +305,18 @@ stop_docker() {
255
305
  fi
256
306
  else
257
307
  consecutive_empty=0
258
- log_info "第 $elapsed 秒: 发现新进程 $remaining_pids,再次 kill..."
308
+ if [ -n "$remaining_pids" ]; then
309
+ log_info "第 $elapsed 秒: 发现新进程 $remaining_pids,再次 kill..."
310
+ for rp in $remaining_pids; do
311
+ kill -9 "$rp" 2>/dev/null || true
312
+ done
313
+ fi
314
+ if [ "$port_listening" = true ]; then
315
+ log_info "第 $elapsed 秒: 端口 $PORT 仍在监听,尝试 fuser 终止..."
316
+ if command -v fuser &>/dev/null; then
317
+ fuser -k "${PORT}/tcp" 2>/dev/null || true
318
+ fi
319
+ fi
259
320
  pkill -9 -f "openclaw" 2>/dev/null || true
260
321
  killall -9 openclaw 2>/dev/null || true
261
322
  fi
@@ -263,6 +324,14 @@ stop_docker() {
263
324
  elapsed=$((elapsed + 1))
264
325
  done
265
326
 
327
+ # 最终验证前,再执行一次全面清理
328
+ log_info "执行最终清理..."
329
+ pkill -9 -f "openclaw" 2>/dev/null || true
330
+ killall -9 openclaw 2>/dev/null || true
331
+
332
+ # 等待 2 秒
333
+ sleep 2
334
+
266
335
  # 最终验证
267
336
  local remaining_pids=""
268
337
  if command -v pgrep &>/dev/null; then
@@ -282,19 +351,33 @@ stop_docker() {
282
351
  ss -tlnp 2>/dev/null | grep ":${PORT} " || log_info "端口 ${PORT} 未监听"
283
352
  fi
284
353
 
285
- if [ -z "$remaining_pids" ]; then
354
+ # 最终验证:同时检查进程和端口
355
+ local port_listening_final=false
356
+ if check_port "$PORT"; then
357
+ port_listening_final=true
358
+ fi
359
+
360
+ if [ -z "$remaining_pids" ] && [ "$port_listening_final" = false ]; then
286
361
  log_info "OpenClaw 服务已停止。"
287
362
  log_info "服务已成功停止。"
288
363
  log_info "Success"
289
364
  exit 0
290
365
  else
291
- log_error "OpenClaw 服务停止失败!进程仍然存在: $remaining_pids"
366
+ if [ -n "$remaining_pids" ]; then
367
+ log_error "OpenClaw 服务停止失败!进程仍然存在: $remaining_pids"
368
+ fi
369
+ if [ "$port_listening_final" = true ]; then
370
+ log_error "OpenClaw 服务停止失败!端口 $PORT 仍在监听。"
371
+ fi
292
372
  exit 1
293
373
  fi
294
374
  }
295
375
 
296
376
  # Systemd 模式:停止服务
377
+ # 参数: $1 = force (1=强制停止, 空=优雅停止)
297
378
  stop_systemd() {
379
+ local force="$1"
380
+
298
381
  # 检查服务是否存在
299
382
  if ! systemctl --user list-unit-files "$SERVICE_NAME" &>/dev/null; then
300
383
  log_error "服务 $SERVICE_NAME 不存在。"
@@ -321,8 +404,33 @@ stop_systemd() {
321
404
 
322
405
  # 验证服务状态
323
406
  if systemctl --user is-active --quiet "$SERVICE_NAME"; then
324
- log_warn "服务可能仍在运行,请检查进程。"
325
- systemctl --user status "$SERVICE_NAME" --no-pager
407
+ if [ -n "$force" ]; then
408
+ log_warn "服务仍在运行,执行强制停止..."
409
+ # 获取进程 PID 并强制终止
410
+ local main_pid
411
+ main_pid=$(systemctl --user show "$SERVICE_NAME" --property=MainPID --value)
412
+ if [ -n "$main_pid" ] && [ "$main_pid" != "0" ]; then
413
+ log_info "强制终止进程 PID: $main_pid"
414
+ kill -9 "$main_pid" 2>/dev/null || true
415
+ fi
416
+ # 同时清理所有 openclaw 相关进程
417
+ pkill -9 -f "openclaw" 2>/dev/null || true
418
+ killall -9 openclaw 2>/dev/null || true
419
+
420
+ sleep 2
421
+
422
+ if ! systemctl --user is-active --quiet "$SERVICE_NAME"; then
423
+ log_info "服务已成功强制停止。"
424
+ log_info "Success"
425
+ exit 0
426
+ else
427
+ log_error "强制停止失败!服务仍在运行。"
428
+ exit 1
429
+ fi
430
+ else
431
+ log_warn "服务可能仍在运行,请检查进程。"
432
+ systemctl --user status "$SERVICE_NAME" --no-pager
433
+ fi
326
434
  else
327
435
  log_info "服务已成功停止。"
328
436
  log_info "Success"
@@ -372,9 +480,9 @@ main() {
372
480
  done
373
481
 
374
482
  if is_docker; then
375
- stop_docker
483
+ stop_docker "$force"
376
484
  else
377
- stop_systemd
485
+ stop_systemd "$force"
378
486
  fi
379
487
  }
380
488
 
@@ -43,13 +43,33 @@ echo.
43
43
  REM Wait and verify stopped
44
44
  timeout /t 3 /nobreak >nul
45
45
 
46
+ REM Check if port is still listening
46
47
  netstat -an | findstr ":18789 " | findstr "LISTENING" >nul
47
48
  if errorlevel 1 (
48
49
  echo [OK] Service stopped successfully
49
50
  echo.
50
51
  echo Success
51
52
  exit /b 0
52
- ) else (
53
- echo [WARN] Service may still be running
53
+ )
54
+
55
+ REM Port still listening, try graceful stop again with force flag
56
+ echo [WARN] Service may still be running, attempting force stop...
57
+ echo.
58
+
59
+ REM Kill openclaw processes
60
+ taskkill /f /im openclaw.exe >nul 2>&1
61
+
62
+ REM Wait for processes to terminate
63
+ timeout /t 2 /nobreak >nul
64
+
65
+ REM Final check
66
+ netstat -an | findstr ":18789 " | findstr "LISTENING" >nul
67
+ if errorlevel 1 (
68
+ echo [OK] Service stopped successfully after force stop
69
+ echo.
70
+ echo Success
54
71
  exit /b 0
72
+ ) else (
73
+ echo [ERROR] Service stop failed: port 18789 still listening after force stop
74
+ exit /b 1
55
75
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.123",
3
+ "version": "0.0.125",
4
4
  "description": "虾说智能助手",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -20,13 +20,14 @@ class HeartbeatManager {
20
20
 
21
21
  try {
22
22
  const mac = getMacAddress();
23
+ // 真实检测 openclaw 状态(通过端口检查)
23
24
  const status = await getOpenClawStatus(this.config.openclawPort || 18789);
24
25
  const sent = await this.messageSender.sendProtocolMessage(
25
26
  RongyunMessageTypeEnum.HEARTBEAT,
26
27
  {
27
28
  mac_address: mac,
28
29
  nickname: this.config.nodeName,
29
- open_claw_status: status,
30
+ open_claw_status: status, // 1=运行中, 0=未运行(真实检测)
30
31
  client_status: 1,
31
32
  }
32
33
  );
@@ -138,25 +138,82 @@ async function verifyCommandResult(command, result, scriptOutput = '') {
138
138
  if (command === OpenClawCommandEnum.STOP) {
139
139
  // 停止命令验证:多次检查端口并重复执行停止,处理看门狗自动重启
140
140
  console.log(`[OpenClawControl] 开始验证停止结果...`);
141
-
141
+
142
142
  let portStatus = 1;
143
143
  let stopAttempts = 0;
144
144
  const maxStopAttempts = 3; // 最多执行 3 次停止
145
-
145
+
146
146
  // 等待 3 秒,给看门狗一次重启机会,然后验证
147
147
  await new Promise(resolve => setTimeout(resolve, 3000));
148
-
149
- portStatus = await getOpenClawStatus(18789);
150
- console.log(`[OpenClawControl] 停止后端口状态: ${portStatus}`);
151
-
148
+
149
+ while (stopAttempts < maxStopAttempts) {
150
+ portStatus = await getOpenClawStatus(18789);
151
+ console.log(`[OpenClawControl] 停止后端口状态 (尝试 ${stopAttempts + 1}/${maxStopAttempts}): ${portStatus}`);
152
+
153
+ if (portStatus !== 1) {
154
+ console.log(`[OpenClawControl] 停止验证通过: 端口已关闭`);
155
+ break;
156
+ }
157
+
158
+ stopAttempts++;
159
+ if (stopAttempts < maxStopAttempts) {
160
+ console.log(`[OpenClawControl] 端口仍在监听,尝试第 ${stopAttempts + 1} 次停止...`);
161
+ const scriptResult = await executeWithScript(command);
162
+ const retryResult = scriptResult.result;
163
+ console.log(`[OpenClawControl] 第 ${stopAttempts} 次重试停止结果: ${retryResult.status} - ${retryResult.message}`);
164
+
165
+ // 等待看门狗重启后再检查
166
+ await new Promise(resolve => setTimeout(resolve, 3000));
167
+ }
168
+ }
169
+
152
170
  if (portStatus === 1) {
153
- console.error(`[OpenClawControl] 停止失败: 端口 18789 仍在监听。stop.sh 可能未能成功停止服务。`);
171
+ console.error(`[OpenClawControl] 停止失败: 端口 18789 仍在监听。尝试直接强制终止进程...`);
172
+
173
+ // 直接执行强制终止命令(不通过脚本)
174
+ try {
175
+ const { execSync } = require('child_process');
176
+
177
+ // 先尝试 pkill
178
+ try {
179
+ execSync('pkill -9 -f "openclaw"', { timeout: 5000 });
180
+ console.log('[OpenClawControl] pkill -9 -f openclaw 执行成功');
181
+ } catch (e) {
182
+ console.log('[OpenClawControl] pkill 执行失败或没有匹配进程');
183
+ }
184
+
185
+ // 再尝试 killall
186
+ try {
187
+ execSync('killall -9 openclaw', { timeout: 5000 });
188
+ console.log('[OpenClawControl] killall -9 openclaw 执行成功');
189
+ } catch (e) {
190
+ console.log('[OpenClawControl] killall 执行失败或没有匹配进程');
191
+ }
192
+
193
+ // 等待进程退出
194
+ await new Promise(resolve => setTimeout(resolve, 5000));
195
+
196
+ // 再次验证端口
197
+ const finalPortStatus = await getOpenClawStatus(18789);
198
+
199
+ if (finalPortStatus !== 1) {
200
+ console.log(`[OpenClawControl] 强制停止成功: 端口已关闭`);
201
+ return {
202
+ status: OpenClawServiceStatus.STOP_SUCCESS,
203
+ message: '服务已停止(强制停止)'
204
+ };
205
+ }
206
+ } catch (err) {
207
+ console.error(`[OpenClawControl] 强制停止异常: ${err.message}`);
208
+ }
209
+
210
+ console.error(`[OpenClawControl] 停止失败: 端口 18789 仍在监听。所有停止方法均已尝试。`);
154
211
  return {
155
212
  status: OpenClawServiceStatus.ERROR,
156
213
  message: '停止失败: 服务仍在运行'
157
214
  };
158
215
  }
159
-
216
+
160
217
  console.log(`[OpenClawControl] 停止验证通过: 端口已关闭`);
161
218
  } else if (command === OpenClawCommandEnum.START || command === OpenClawCommandEnum.RESTART) {
162
219
  // 等待服务启动
@@ -239,12 +296,33 @@ async function executeCommand(command, window, sendResponse) {
239
296
  if (sendResponse) {
240
297
  let httpStatus = 'success';
241
298
  if (result.status === OpenClawServiceStatus.ERROR) httpStatus = 'error';
299
+
300
+ // 获取操作后的真实状态
301
+ let realStatus = result.status;
302
+ if (command === OpenClawCommandEnum.STOP || command === OpenClawCommandEnum.START || command === OpenClawCommandEnum.RESTART) {
303
+ try {
304
+ const { getOpenClawStatus } = require('./port-checker');
305
+ const portStatus = await getOpenClawStatus(18789);
306
+ // 更新真实状态
307
+ if (command === OpenClawCommandEnum.STOP) {
308
+ realStatus = portStatus === 1 ? OpenClawServiceStatus.RUNNING : OpenClawServiceStatus.STOP_SUCCESS;
309
+ } else if (command === OpenClawCommandEnum.START) {
310
+ realStatus = portStatus === 1 ? OpenClawServiceStatus.START_SUCCESS : OpenClawServiceStatus.ERROR;
311
+ } else if (command === OpenClawCommandEnum.RESTART) {
312
+ realStatus = portStatus === 1 ? OpenClawServiceStatus.RESTART_SUCCESS : OpenClawServiceStatus.ERROR;
313
+ }
314
+ console.log(`[OpenClawControl] 操作后真实状态检测: portStatus=${portStatus}, realStatus=${realStatus}`);
315
+ } catch (e) {
316
+ console.error(`[OpenClawControl] 真实状态检测失败: ${e.message}`);
317
+ }
318
+ }
319
+
242
320
  sendResponse({
243
321
  type: 'command_result',
244
322
  command,
245
323
  status: httpStatus,
246
324
  message: result.message,
247
- service_status: result.status
325
+ service_status: realStatus
248
326
  });
249
327
  }
250
328
 
@@ -337,10 +337,23 @@ class RongyunMessageHandler {
337
337
  this.logInfo(`[RongyunMessageHandler] 收到设备状态请求, from=${targetId}, requestId=${requestId}`);
338
338
 
339
339
  try {
340
- // 获取 OpenClaw 运行状态(检查端口 18789)
340
+ // 获取 OpenClaw 真实运行状态(检查端口 18789)
341
341
  const openClawStatus = await getOpenClawStatus();
342
342
 
343
- // 构建状态数据
343
+ // 获取真实的版本信息(如果可能)
344
+ let version = 'unknown';
345
+ try {
346
+ const { execSync } = require('child_process');
347
+ const versionOutput = execSync('openclaw --version', { encoding: 'utf8', timeout: 5000 }).trim();
348
+ const match = versionOutput.match(/(\d+\.\d+\.\d+)/);
349
+ if (match) {
350
+ version = match[1];
351
+ }
352
+ } catch (e) {
353
+ // 忽略版本获取失败
354
+ }
355
+
356
+ // 构建真实状态数据
344
357
  // openClawStatus: 1=端口监听正常(服务可用), 0=未运行(端口未监听)
345
358
  const statusMessage = openClawStatus === 1 ? '运行中' : '未运行';
346
359
 
@@ -348,11 +361,11 @@ class RongyunMessageHandler {
348
361
  open_claw_status: openClawStatus, // 1=运行中, 0=未运行
349
362
  status_message: statusMessage,
350
363
  mac_address: getMacAddress(),
351
- version: '0.0.20',
364
+ version: version,
352
365
  timestamp: Date.now(),
353
366
  };
354
367
 
355
- this.logInfo(`[RongyunMessageHandler] 设备状态: openClawStatus=${openClawStatus}`);
368
+ this.logInfo(`[RongyunMessageHandler] 设备真实状态: openClawStatus=${openClawStatus}, version=${version}`);
356
369
  await this.sendDeviceStatusReport(targetId, requestId, statusData);
357
370
  } catch (e) {
358
371
  const msg = e instanceof Error ? e.message : String(e);
@@ -446,6 +446,7 @@ class ScriptExecutor {
446
446
  upper.includes('服务停止成功') ||
447
447
  upper.includes('服务已成功停止') ||
448
448
  upper.includes('GATEWAY STOP SIGNAL SENT') ||
449
+ upper.includes('STOPPED SUCCESSFULLY AFTER FORCE STOP') ||
449
450
  (upper.includes('[INFO] STOPPING SERVICE') && upper.includes('STOP SIGNAL'))
450
451
  ) {
451
452
  if (