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.
package/command/linux/stop.sh
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
#
|
|
228
|
+
# 连续监控模式:每秒检查并杀死看门狗重启的进程
|
|
229
|
+
# 这样即使看门狗立即重启,也会被再次杀死
|
|
230
|
+
log_info "进入连续监控模式(最多 10 秒),防止看门狗自动重启..."
|
|
218
231
|
local elapsed=0
|
|
219
|
-
|
|
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
|
-
|
|
232
|
-
log_info "
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
|
144
|
-
const
|
|
143
|
+
let stopAttempts = 0;
|
|
144
|
+
const maxStopAttempts = 3; // 最多执行 3 次停止
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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] 停止失败:
|
|
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]
|
|
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 =
|
|
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',
|
|
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');
|