claw-subagent-service 0.0.124 → 0.0.126

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.
@@ -224,9 +224,9 @@ stop_docker() {
224
224
  kill -15 "$p" 2>/dev/null || log_warn "kill -15 $p 失败"
225
225
  done
226
226
 
227
- # 等待 3 秒让服务优雅退出
228
- log_info "等待 3 秒让服务优雅退出..."
229
- sleep 3
227
+ # 等待 5 秒让服务优雅退出
228
+ log_info "等待 5 秒让服务优雅退出..."
229
+ sleep 5
230
230
 
231
231
  # 检查是否已停止
232
232
  local pid_after_graceful
@@ -243,7 +243,20 @@ stop_docker() {
243
243
 
244
244
  # 强制停止模式(直接发送 SIGKILL)
245
245
  log_info "正在强制停止 OpenClaw 服务(SIGKILL)..."
246
- 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
247
260
  log_info "执行: kill -9 $p"
248
261
  kill -9 "$p" 2>/dev/null || log_warn "kill -9 $p 失败"
249
262
  done
@@ -253,12 +266,15 @@ stop_docker() {
253
266
  log_info "执行: killall -9 openclaw"
254
267
  killall -9 openclaw 2>/dev/null || log_warn "killall 失败"
255
268
 
269
+ # 等待 2 秒让进程退出
270
+ sleep 2
271
+
256
272
  # 连续监控模式:每秒检查并杀死看门狗重启的进程
257
273
  # 这样即使看门狗立即重启,也会被再次杀死
258
- log_info "进入连续监控模式(最多 15 秒),防止看门狗自动重启..."
274
+ log_info "进入连续监控模式(最多 20 秒),防止看门狗自动重启..."
259
275
  local elapsed=0
260
276
  local consecutive_empty=0
261
- while [ $elapsed -lt 15 ]; do
277
+ while [ $elapsed -lt 20 ]; do
262
278
  sleep 1
263
279
 
264
280
  # 检查是否还有 openclaw 进程
@@ -291,6 +307,9 @@ stop_docker() {
291
307
  consecutive_empty=0
292
308
  if [ -n "$remaining_pids" ]; then
293
309
  log_info "第 $elapsed 秒: 发现新进程 $remaining_pids,再次 kill..."
310
+ for rp in $remaining_pids; do
311
+ kill -9 "$rp" 2>/dev/null || true
312
+ done
294
313
  fi
295
314
  if [ "$port_listening" = true ]; then
296
315
  log_info "第 $elapsed 秒: 端口 $PORT 仍在监听,尝试 fuser 终止..."
@@ -305,6 +324,14 @@ stop_docker() {
305
324
  elapsed=$((elapsed + 1))
306
325
  done
307
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
+
308
335
  # 最终验证
309
336
  local remaining_pids=""
310
337
  if command -v pgrep &>/dev/null; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.124",
3
+ "version": "0.0.126",
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
  );
@@ -136,101 +136,43 @@ 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
- let portStatus = 1;
143
- let stopAttempts = 0;
144
- const maxStopAttempts = 3; // 最多执行 3 次停止
142
+ // 等待 2 秒后检查
143
+ await new Promise(resolve => setTimeout(resolve, 2000));
144
+ const portStatus = await getOpenClawStatus(18789);
145
+ console.log(`[OpenClawControl] 停止后端口状态: ${portStatus}`);
145
146
 
146
- // 等待 3 秒,给看门狗一次重启机会,然后验证
147
- await new Promise(resolve => setTimeout(resolve, 3000));
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
-
170
- if (portStatus === 1) {
171
- console.error(`[OpenClawControl] 停止失败: 端口 18789 仍在监听。尝试强制停止...`);
172
-
173
- // 尝试强制停止
174
- const forceScriptResult = await executeWithScript(command);
175
- const forceResult = forceScriptResult.result;
176
- console.log(`[OpenClawControl] 强制停止结果: ${forceResult.status} - ${forceResult.message}`);
177
-
178
- // 再次验证端口
179
- await new Promise(resolve => setTimeout(resolve, 3000));
180
- const finalPortStatus = await getOpenClawStatus(18789);
181
-
182
- if (finalPortStatus !== 1) {
183
- console.log(`[OpenClawControl] 强制停止成功: 端口已关闭`);
184
- return {
185
- status: OpenClawServiceStatus.STOP_SUCCESS,
186
- message: '服务已停止(强制停止)'
187
- };
188
- }
189
-
190
- console.error(`[OpenClawControl] 停止失败: 端口 18789 仍在监听。stop.sh 可能未能成功停止服务。`);
147
+ if (portStatus !== 1) {
148
+ console.log(`[OpenClawControl] 停止验证通过: 端口已关闭`);
149
+ } else {
150
+ console.error(`[OpenClawControl] 停止失败: 端口 18789 仍在监听。`);
191
151
  return {
192
152
  status: OpenClawServiceStatus.ERROR,
193
153
  message: '停止失败: 服务仍在运行'
194
154
  };
195
155
  }
196
-
197
- console.log(`[OpenClawControl] 停止验证通过: 端口已关闭`);
198
156
  } else if (command === OpenClawCommandEnum.START || command === OpenClawCommandEnum.RESTART) {
199
- // 等待服务启动
200
- const maxWait = command === OpenClawCommandEnum.START ? 30 : 60;
157
+ // 等待服务启动(最多等待 30 秒)
158
+ const maxWait = 15; // 最多 15 次检查
201
159
  let attempts = 0;
202
160
  let portStatus = 0;
203
161
 
204
162
  while (attempts < maxWait) {
205
163
  await new Promise(resolve => setTimeout(resolve, 2000));
206
164
  portStatus = await getOpenClawStatus(18789);
207
- if (portStatus === 1 || portStatus === 2) break; // 端口监听或进程存在都算成功
165
+ if (portStatus === 1) break; // 端口监听算成功
208
166
  attempts++;
209
167
  }
210
168
 
211
169
  console.log(`[OpenClawControl] ${getCommandName(command)}后端口状态: ${portStatus}`);
212
170
 
213
171
  if (portStatus === 0) {
214
- // 端口未监听且进程不存在,但检查脚本是否报告成功
215
- if (outputUpper.includes('SUCCESS') || outputUpper.includes('ALREADY RUNNING')) {
216
- console.warn(`[OpenClawControl] 警告: 端口检查失败,但脚本报告成功。服务可能绑定到其他网络接口。`);
217
- // 信任脚本结果,但添加警告
218
- return {
219
- status: result.status,
220
- message: result.message + ' (警告: 端口检查可能不准确)'
221
- };
222
- }
223
172
  return {
224
173
  status: OpenClawServiceStatus.ERROR,
225
174
  message: `${getCommandName(command)}失败: 服务未运行`
226
175
  };
227
- } else if (portStatus === 2) {
228
- // 进程存在但端口未监听,服务可能在启动中或绑定到其他地址
229
- console.warn(`[OpenClawControl] 警告: openclaw 进程存在但端口 ${portStatus} 未监听。`);
230
- return {
231
- status: result.status === OpenClawServiceStatus.START_SUCCESS ? result.status : OpenClawServiceStatus.START_SUCCESS,
232
- message: result.message + ' (进程已启动,端口检查可能不准确)'
233
- };
234
176
  }
235
177
  }
236
178
 
@@ -276,12 +218,33 @@ async function executeCommand(command, window, sendResponse) {
276
218
  if (sendResponse) {
277
219
  let httpStatus = 'success';
278
220
  if (result.status === OpenClawServiceStatus.ERROR) httpStatus = 'error';
221
+
222
+ // 获取操作后的真实状态
223
+ let realStatus = result.status;
224
+ if (command === OpenClawCommandEnum.STOP || command === OpenClawCommandEnum.START || command === OpenClawCommandEnum.RESTART) {
225
+ try {
226
+ const { getOpenClawStatus } = require('./port-checker');
227
+ const portStatus = await getOpenClawStatus(18789);
228
+ // 更新真实状态
229
+ if (command === OpenClawCommandEnum.STOP) {
230
+ realStatus = portStatus === 1 ? OpenClawServiceStatus.RUNNING : OpenClawServiceStatus.STOP_SUCCESS;
231
+ } else if (command === OpenClawCommandEnum.START) {
232
+ realStatus = portStatus === 1 ? OpenClawServiceStatus.START_SUCCESS : OpenClawServiceStatus.ERROR;
233
+ } else if (command === OpenClawCommandEnum.RESTART) {
234
+ realStatus = portStatus === 1 ? OpenClawServiceStatus.RESTART_SUCCESS : OpenClawServiceStatus.ERROR;
235
+ }
236
+ console.log(`[OpenClawControl] 操作后真实状态检测: portStatus=${portStatus}, realStatus=${realStatus}`);
237
+ } catch (e) {
238
+ console.error(`[OpenClawControl] 真实状态检测失败: ${e.message}`);
239
+ }
240
+ }
241
+
279
242
  sendResponse({
280
243
  type: 'command_result',
281
244
  command,
282
245
  status: httpStatus,
283
246
  message: result.message,
284
- service_status: result.status
247
+ service_status: realStatus
285
248
  });
286
249
  }
287
250
 
@@ -40,9 +40,21 @@ function isValidCommand(command) {
40
40
  return Object.values(OpenClawCommandEnum).includes(command);
41
41
  }
42
42
 
43
+ function getCommandName(command) {
44
+ const names = {
45
+ [OpenClawCommandEnum.START]: '启动',
46
+ [OpenClawCommandEnum.STOP]: '停止',
47
+ [OpenClawCommandEnum.RESTART]: '重启',
48
+ [OpenClawCommandEnum.STATUS]: '状态检查',
49
+ [OpenClawCommandEnum.CONFIG_FIX]: '配置修复'
50
+ };
51
+ return names[command] || '未知命令';
52
+ }
53
+
43
54
  module.exports = {
44
55
  OpenClawCommandEnum,
45
56
  OpenClawServiceStatus,
46
57
  getServiceStatusMessage,
47
- isValidCommand
58
+ isValidCommand,
59
+ getCommandName
48
60
  };
@@ -8,7 +8,7 @@
8
8
  * - DELETE_OPENCODE_SESSION: 删除会话
9
9
  */
10
10
  const { RongyunMessageTypeEnum } = require('./rongyun-message-types');
11
- const { OpenClawCommandEnum } = require('./openclaw-enum');
11
+ const { OpenClawCommandEnum, getCommandName } = require('./openclaw-enum');
12
12
  const { executeCommand } = require('./openclaw-control');
13
13
  const { createOpencodeSession, deleteOpencodeSession, forwardChatMessage } = require('./opencode-service');
14
14
  const { ServiceManager } = require('./service-manager');
@@ -128,21 +128,39 @@ class RongyunMessageHandler {
128
128
  }
129
129
 
130
130
  this.commandLock = true;
131
- // 设置 60 秒超时保护,防止命令永久卡住导致锁无法释放
131
+ // 设置 120 秒超时保护,防止命令永久卡住导致锁无法释放
132
+ // 启动命令可能需要较长时间(等待服务启动)
132
133
  this.commandLockTimer = setTimeout(() => {
133
- this.logWarn('[RongyunMessageHandler] 命令锁超时(60秒),自动释放');
134
+ this.logWarn('[RongyunMessageHandler] 命令锁超时(120秒),自动释放');
134
135
  this.commandLock = false;
135
136
  this.commandLockTimer = null;
136
- }, 60000);
137
+ }, 120000);
137
138
 
138
139
  try {
139
- await executeCommand(command, null, async (response) => {
140
- // 增加短暂延迟,避免融云 SDK 在收到消息后立刻回复时消息丢失
141
- await new Promise(resolve => setTimeout(resolve, 500));
140
+ // 先发送响应,再执行命令(避免前端超时)
141
+ // 启动/停止/重启命令是异步的,前端只需要知道命令已接收
142
+ const isAsyncCommand = [OpenClawCommandEnum.START, OpenClawCommandEnum.STOP, OpenClawCommandEnum.RESTART].includes(command);
143
+
144
+ if (isAsyncCommand) {
145
+ // 立即响应,告知前端命令已接收
142
146
  await this.sendResponse(RongyunMessageTypeEnum.COMMAND_RESULT, {
143
- ...response,
144
- command_id: commandId
147
+ command,
148
+ command_id: commandId,
149
+ status: 'success',
150
+ message: `${getCommandName(command)}命令已接收,正在执行...`
145
151
  }, requestId, sourceId);
152
+ }
153
+
154
+ // 执行命令
155
+ await executeCommand(command, null, async (response) => {
156
+ if (!isAsyncCommand) {
157
+ // 同步命令立即响应
158
+ await this.sendResponse(RongyunMessageTypeEnum.COMMAND_RESULT, {
159
+ ...response,
160
+ command_id: commandId
161
+ }, requestId, sourceId);
162
+ }
163
+ // 异步命令不在这里响应,因为已经提前响应了
146
164
  });
147
165
  } catch (e) {
148
166
  const msg = e instanceof Error ? e.message : String(e);
@@ -337,10 +355,23 @@ class RongyunMessageHandler {
337
355
  this.logInfo(`[RongyunMessageHandler] 收到设备状态请求, from=${targetId}, requestId=${requestId}`);
338
356
 
339
357
  try {
340
- // 获取 OpenClaw 运行状态(检查端口 18789)
358
+ // 获取 OpenClaw 真实运行状态(检查端口 18789)
341
359
  const openClawStatus = await getOpenClawStatus();
342
360
 
343
- // 构建状态数据
361
+ // 获取真实的版本信息(如果可能)
362
+ let version = 'unknown';
363
+ try {
364
+ const { execSync } = require('child_process');
365
+ const versionOutput = execSync('openclaw --version', { encoding: 'utf8', timeout: 5000 }).trim();
366
+ const match = versionOutput.match(/(\d+\.\d+\.\d+)/);
367
+ if (match) {
368
+ version = match[1];
369
+ }
370
+ } catch (e) {
371
+ // 忽略版本获取失败
372
+ }
373
+
374
+ // 构建真实状态数据
344
375
  // openClawStatus: 1=端口监听正常(服务可用), 0=未运行(端口未监听)
345
376
  const statusMessage = openClawStatus === 1 ? '运行中' : '未运行';
346
377
 
@@ -348,11 +379,11 @@ class RongyunMessageHandler {
348
379
  open_claw_status: openClawStatus, // 1=运行中, 0=未运行
349
380
  status_message: statusMessage,
350
381
  mac_address: getMacAddress(),
351
- version: '0.0.20',
382
+ version: version,
352
383
  timestamp: Date.now(),
353
384
  };
354
385
 
355
- this.logInfo(`[RongyunMessageHandler] 设备状态: openClawStatus=${openClawStatus}`);
386
+ this.logInfo(`[RongyunMessageHandler] 设备真实状态: openClawStatus=${openClawStatus}, version=${version}`);
356
387
  await this.sendDeviceStatusReport(targetId, requestId, statusData);
357
388
  } catch (e) {
358
389
  const msg = e instanceof Error ? e.message : String(e);