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.
package/command/linux/stop.sh
CHANGED
|
@@ -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
|
-
# 等待
|
|
228
|
-
log_info "等待
|
|
229
|
-
sleep
|
|
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
|
-
|
|
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 "进入连续监控模式(最多
|
|
274
|
+
log_info "进入连续监控模式(最多 20 秒),防止看门狗自动重启..."
|
|
259
275
|
local elapsed=0
|
|
260
276
|
local consecutive_empty=0
|
|
261
|
-
while [ $elapsed -lt
|
|
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
|
@@ -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
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
142
|
+
// 等待 2 秒后检查
|
|
143
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
144
|
+
const portStatus = await getOpenClawStatus(18789);
|
|
145
|
+
console.log(`[OpenClawControl] 停止后端口状态: ${portStatus}`);
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
-
// 设置
|
|
131
|
+
// 设置 120 秒超时保护,防止命令永久卡住导致锁无法释放
|
|
132
|
+
// 启动命令可能需要较长时间(等待服务启动)
|
|
132
133
|
this.commandLockTimer = setTimeout(() => {
|
|
133
|
-
this.logWarn('[RongyunMessageHandler] 命令锁超时(
|
|
134
|
+
this.logWarn('[RongyunMessageHandler] 命令锁超时(120秒),自动释放');
|
|
134
135
|
this.commandLock = false;
|
|
135
136
|
this.commandLockTimer = null;
|
|
136
|
-
},
|
|
137
|
+
}, 120000);
|
|
137
138
|
|
|
138
139
|
try {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
382
|
+
version: version,
|
|
352
383
|
timestamp: Date.now(),
|
|
353
384
|
};
|
|
354
385
|
|
|
355
|
-
this.logInfo(`[RongyunMessageHandler]
|
|
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);
|