claw-subagent-service 0.0.87 → 0.0.89
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/restart.sh +40 -23
- package/command/linux/start.sh +14 -13
- package/command/linux/status.sh +13 -12
- package/command/linux/stop.sh +68 -30
- package/package.json +1 -1
- package/service/modules/openclaw-control.js +131 -55
- package/service/modules/script-executor.js +18 -6
package/command/linux/restart.sh
CHANGED
|
@@ -34,6 +34,7 @@ get_openclaw_pid() {
|
|
|
34
34
|
local pid=""
|
|
35
35
|
|
|
36
36
|
# 按优先级尝试多种工具(适配精简 Docker 镜像)
|
|
37
|
+
# 方法1: lsof(最可靠)
|
|
37
38
|
if command -v lsof &>/dev/null; then
|
|
38
39
|
pid=$(lsof -i :${port} -t 2>/dev/null | head -1)
|
|
39
40
|
if [ -n "$pid" ]; then
|
|
@@ -42,6 +43,7 @@ get_openclaw_pid() {
|
|
|
42
43
|
fi
|
|
43
44
|
fi
|
|
44
45
|
|
|
46
|
+
# 方法2: fuser
|
|
45
47
|
if command -v fuser &>/dev/null; then
|
|
46
48
|
pid=$(fuser ${port}/tcp 2>/dev/null | tr -d ' ')
|
|
47
49
|
if [ -n "$pid" ]; then
|
|
@@ -50,14 +52,16 @@ get_openclaw_pid() {
|
|
|
50
52
|
fi
|
|
51
53
|
fi
|
|
52
54
|
|
|
55
|
+
# 方法3: ss
|
|
53
56
|
if command -v ss &>/dev/null; then
|
|
54
|
-
pid=$(ss -tlnp 2>/dev/null | grep ":${port} " | head -1 | sed -n 's/.*pid
|
|
57
|
+
pid=$(ss -tlnp 2>/dev/null | grep ":${port} " | head -1 | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
|
|
55
58
|
if [ -n "$pid" ]; then
|
|
56
59
|
echo "$pid"
|
|
57
60
|
return
|
|
58
61
|
fi
|
|
59
62
|
fi
|
|
60
63
|
|
|
64
|
+
# 方法4: netstat
|
|
61
65
|
if command -v netstat &>/dev/null; then
|
|
62
66
|
pid=$(netstat -tnlp 2>/dev/null | grep ":${port} " | head -1 | awk '{print $7}' | cut -d'/' -f1)
|
|
63
67
|
if [ -n "$pid" ]; then
|
|
@@ -66,19 +70,16 @@ get_openclaw_pid() {
|
|
|
66
70
|
fi
|
|
67
71
|
fi
|
|
68
72
|
|
|
69
|
-
#
|
|
73
|
+
# 方法5: 通过 /proc/net/tcp 查找(不需要外部工具,最可靠)
|
|
74
|
+
# 端口 18789 的十六进制 = 0x4965
|
|
75
|
+
local hex_port="4965"
|
|
70
76
|
for proc_dir in /proc/[0-9]*; do
|
|
71
|
-
if [ -
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
basename "$proc_dir"
|
|
78
|
-
return
|
|
79
|
-
fi
|
|
80
|
-
fi
|
|
81
|
-
done
|
|
77
|
+
if [ -f "$proc_dir/net/tcp" ]; then
|
|
78
|
+
# 检查该进程是否监听目标端口
|
|
79
|
+
if grep -q "[^0-9a-fA-F]${hex_port} " "$proc_dir/net/tcp" 2>/dev/null; then
|
|
80
|
+
basename "$proc_dir"
|
|
81
|
+
return
|
|
82
|
+
fi
|
|
82
83
|
fi
|
|
83
84
|
done
|
|
84
85
|
|
|
@@ -146,25 +147,41 @@ restart_docker() {
|
|
|
146
147
|
fi
|
|
147
148
|
|
|
148
149
|
# 如果正在运行,先停止
|
|
149
|
-
if [ -n "$pid" ]; then
|
|
150
|
+
if [ -n "$pid" ] || check_port "$PORT"; then
|
|
150
151
|
log_info "正在停止 OpenClaw 服务..."
|
|
151
|
-
|
|
152
|
+
if [ -n "$pid" ]; then
|
|
153
|
+
kill "$pid" &>/dev/null || true
|
|
154
|
+
fi
|
|
152
155
|
|
|
153
|
-
# 等待停止(最多
|
|
156
|
+
# 等待停止(最多 10 秒),使用端口双重验证
|
|
154
157
|
local elapsed=0
|
|
155
|
-
while [ $elapsed -lt
|
|
156
|
-
|
|
158
|
+
while [ $elapsed -lt 10 ]; do
|
|
159
|
+
local current_pid
|
|
160
|
+
current_pid=$(get_openclaw_pid)
|
|
161
|
+
if [ -z "$current_pid" ] && ! check_port "$PORT"; then
|
|
157
162
|
break
|
|
158
163
|
fi
|
|
159
164
|
sleep 1
|
|
160
165
|
elapsed=$((elapsed + 1))
|
|
161
166
|
done
|
|
162
167
|
|
|
163
|
-
#
|
|
164
|
-
if
|
|
165
|
-
log_warn "服务未在
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
# 如果还在运行,强制停止并使用备选方案
|
|
169
|
+
if check_port "$PORT"; then
|
|
170
|
+
log_warn "服务未在 10 秒内停止,正在强制停止..."
|
|
171
|
+
if [ -n "$pid" ]; then
|
|
172
|
+
kill -9 "$pid" &>/dev/null || true
|
|
173
|
+
fi
|
|
174
|
+
pkill -9 -f "openclaw" &>/dev/null || true
|
|
175
|
+
if command -v fuser &>/dev/null; then
|
|
176
|
+
fuser -k "${PORT}/tcp" &>/dev/null || true
|
|
177
|
+
fi
|
|
178
|
+
sleep 2
|
|
179
|
+
|
|
180
|
+
# 最终验证
|
|
181
|
+
if check_port "$PORT"; then
|
|
182
|
+
log_error "OpenClaw 服务停止失败!端口 $PORT 仍在监听。"
|
|
183
|
+
exit 1
|
|
184
|
+
fi
|
|
168
185
|
fi
|
|
169
186
|
|
|
170
187
|
log_info "服务已停止"
|
package/command/linux/start.sh
CHANGED
|
@@ -34,6 +34,7 @@ get_openclaw_pid() {
|
|
|
34
34
|
local pid=""
|
|
35
35
|
|
|
36
36
|
# 按优先级尝试多种工具(适配精简 Docker 镜像)
|
|
37
|
+
# 方法1: lsof(最可靠)
|
|
37
38
|
if command -v lsof &>/dev/null; then
|
|
38
39
|
pid=$(lsof -i :${port} -t 2>/dev/null | head -1)
|
|
39
40
|
if [ -n "$pid" ]; then
|
|
@@ -42,6 +43,7 @@ get_openclaw_pid() {
|
|
|
42
43
|
fi
|
|
43
44
|
fi
|
|
44
45
|
|
|
46
|
+
# 方法2: fuser
|
|
45
47
|
if command -v fuser &>/dev/null; then
|
|
46
48
|
pid=$(fuser ${port}/tcp 2>/dev/null | tr -d ' ')
|
|
47
49
|
if [ -n "$pid" ]; then
|
|
@@ -50,14 +52,16 @@ get_openclaw_pid() {
|
|
|
50
52
|
fi
|
|
51
53
|
fi
|
|
52
54
|
|
|
55
|
+
# 方法3: ss
|
|
53
56
|
if command -v ss &>/dev/null; then
|
|
54
|
-
pid=$(ss -tlnp 2>/dev/null | grep ":${port} " | head -1 | sed -n 's/.*pid
|
|
57
|
+
pid=$(ss -tlnp 2>/dev/null | grep ":${port} " | head -1 | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
|
|
55
58
|
if [ -n "$pid" ]; then
|
|
56
59
|
echo "$pid"
|
|
57
60
|
return
|
|
58
61
|
fi
|
|
59
62
|
fi
|
|
60
63
|
|
|
64
|
+
# 方法4: netstat
|
|
61
65
|
if command -v netstat &>/dev/null; then
|
|
62
66
|
pid=$(netstat -tnlp 2>/dev/null | grep ":${port} " | head -1 | awk '{print $7}' | cut -d'/' -f1)
|
|
63
67
|
if [ -n "$pid" ]; then
|
|
@@ -66,19 +70,16 @@ get_openclaw_pid() {
|
|
|
66
70
|
fi
|
|
67
71
|
fi
|
|
68
72
|
|
|
69
|
-
#
|
|
73
|
+
# 方法5: 通过 /proc/net/tcp 查找(不需要外部工具,最可靠)
|
|
74
|
+
# 端口 18789 的十六进制 = 0x4965
|
|
75
|
+
local hex_port="4965"
|
|
70
76
|
for proc_dir in /proc/[0-9]*; do
|
|
71
|
-
if [ -
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
basename "$proc_dir"
|
|
78
|
-
return
|
|
79
|
-
fi
|
|
80
|
-
fi
|
|
81
|
-
done
|
|
77
|
+
if [ -f "$proc_dir/net/tcp" ]; then
|
|
78
|
+
# 检查该进程是否监听目标端口
|
|
79
|
+
if grep -q "[^0-9a-fA-F]${hex_port} " "$proc_dir/net/tcp" 2>/dev/null; then
|
|
80
|
+
basename "$proc_dir"
|
|
81
|
+
return
|
|
82
|
+
fi
|
|
82
83
|
fi
|
|
83
84
|
done
|
|
84
85
|
|
package/command/linux/status.sh
CHANGED
|
@@ -48,6 +48,7 @@ get_openclaw_pid() {
|
|
|
48
48
|
local pid=""
|
|
49
49
|
|
|
50
50
|
# 按优先级尝试多种工具(适配精简 Docker 镜像)
|
|
51
|
+
# 方法1: lsof(最可靠)
|
|
51
52
|
if command -v lsof &>/dev/null; then
|
|
52
53
|
pid=$(lsof -i :${port} -t 2>/dev/null | head -1)
|
|
53
54
|
if [ -n "$pid" ]; then
|
|
@@ -56,6 +57,7 @@ get_openclaw_pid() {
|
|
|
56
57
|
fi
|
|
57
58
|
fi
|
|
58
59
|
|
|
60
|
+
# 方法2: fuser
|
|
59
61
|
if command -v fuser &>/dev/null; then
|
|
60
62
|
pid=$(fuser ${port}/tcp 2>/dev/null | tr -d ' ')
|
|
61
63
|
if [ -n "$pid" ]; then
|
|
@@ -64,6 +66,7 @@ get_openclaw_pid() {
|
|
|
64
66
|
fi
|
|
65
67
|
fi
|
|
66
68
|
|
|
69
|
+
# 方法3: ss
|
|
67
70
|
if command -v ss &>/dev/null; then
|
|
68
71
|
pid=$(ss -tlnp 2>/dev/null | grep ":${port} " | head -1 | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
|
|
69
72
|
if [ -n "$pid" ]; then
|
|
@@ -72,6 +75,7 @@ get_openclaw_pid() {
|
|
|
72
75
|
fi
|
|
73
76
|
fi
|
|
74
77
|
|
|
78
|
+
# 方法4: netstat
|
|
75
79
|
if command -v netstat &>/dev/null; then
|
|
76
80
|
pid=$(netstat -tnlp 2>/dev/null | grep ":${port} " | head -1 | awk '{print $7}' | cut -d'/' -f1)
|
|
77
81
|
if [ -n "$pid" ]; then
|
|
@@ -80,19 +84,16 @@ get_openclaw_pid() {
|
|
|
80
84
|
fi
|
|
81
85
|
fi
|
|
82
86
|
|
|
83
|
-
#
|
|
87
|
+
# 方法5: 通过 /proc/net/tcp 查找(不需要外部工具,最可靠)
|
|
88
|
+
# 端口 18789 的十六进制 = 0x4965
|
|
89
|
+
local hex_port="4965"
|
|
84
90
|
for proc_dir in /proc/[0-9]*; do
|
|
85
|
-
if [ -
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
basename "$proc_dir"
|
|
92
|
-
return
|
|
93
|
-
fi
|
|
94
|
-
fi
|
|
95
|
-
done
|
|
91
|
+
if [ -f "$proc_dir/net/tcp" ]; then
|
|
92
|
+
# 检查该进程是否监听目标端口
|
|
93
|
+
if grep -q "[^0-9a-fA-F]${hex_port} " "$proc_dir/net/tcp" 2>/dev/null; then
|
|
94
|
+
basename "$proc_dir"
|
|
95
|
+
return
|
|
96
|
+
fi
|
|
96
97
|
fi
|
|
97
98
|
done
|
|
98
99
|
|
package/command/linux/stop.sh
CHANGED
|
@@ -67,6 +67,7 @@ get_openclaw_pid() {
|
|
|
67
67
|
local pid=""
|
|
68
68
|
|
|
69
69
|
# 按优先级尝试多种工具(适配精简 Docker 镜像)
|
|
70
|
+
# 方法1: lsof(最可靠)
|
|
70
71
|
if command -v lsof &>/dev/null; then
|
|
71
72
|
pid=$(lsof -i :${port} -t 2>/dev/null | head -1)
|
|
72
73
|
if [ -n "$pid" ]; then
|
|
@@ -75,6 +76,7 @@ get_openclaw_pid() {
|
|
|
75
76
|
fi
|
|
76
77
|
fi
|
|
77
78
|
|
|
79
|
+
# 方法2: fuser
|
|
78
80
|
if command -v fuser &>/dev/null; then
|
|
79
81
|
pid=$(fuser ${port}/tcp 2>/dev/null | tr -d ' ')
|
|
80
82
|
if [ -n "$pid" ]; then
|
|
@@ -83,14 +85,16 @@ get_openclaw_pid() {
|
|
|
83
85
|
fi
|
|
84
86
|
fi
|
|
85
87
|
|
|
88
|
+
# 方法3: ss
|
|
86
89
|
if command -v ss &>/dev/null; then
|
|
87
|
-
pid=$(ss -tlnp 2>/dev/null | grep ":${port} " | head -1 | sed -n 's/.*pid
|
|
90
|
+
pid=$(ss -tlnp 2>/dev/null | grep ":${port} " | head -1 | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
|
|
88
91
|
if [ -n "$pid" ]; then
|
|
89
92
|
echo "$pid"
|
|
90
93
|
return
|
|
91
94
|
fi
|
|
92
95
|
fi
|
|
93
96
|
|
|
97
|
+
# 方法4: netstat
|
|
94
98
|
if command -v netstat &>/dev/null; then
|
|
95
99
|
pid=$(netstat -tnlp 2>/dev/null | grep ":${port} " | head -1 | awk '{print $7}' | cut -d'/' -f1)
|
|
96
100
|
if [ -n "$pid" ]; then
|
|
@@ -99,19 +103,16 @@ get_openclaw_pid() {
|
|
|
99
103
|
fi
|
|
100
104
|
fi
|
|
101
105
|
|
|
102
|
-
#
|
|
106
|
+
# 方法5: 通过 /proc/net/tcp 查找(不需要外部工具,最可靠)
|
|
107
|
+
# 端口 18789 的十六进制 = 0x4965
|
|
108
|
+
local hex_port="4965"
|
|
103
109
|
for proc_dir in /proc/[0-9]*; do
|
|
104
|
-
if [ -
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
basename "$proc_dir"
|
|
111
|
-
return
|
|
112
|
-
fi
|
|
113
|
-
fi
|
|
114
|
-
done
|
|
110
|
+
if [ -f "$proc_dir/net/tcp" ]; then
|
|
111
|
+
# 检查该进程是否监听目标端口
|
|
112
|
+
if grep -q "[^0-9a-fA-F]${hex_port} " "$proc_dir/net/tcp" 2>/dev/null; then
|
|
113
|
+
basename "$proc_dir"
|
|
114
|
+
return
|
|
115
|
+
fi
|
|
115
116
|
fi
|
|
116
117
|
done
|
|
117
118
|
|
|
@@ -126,6 +127,32 @@ stop_docker() {
|
|
|
126
127
|
# 检查服务状态
|
|
127
128
|
log_info "检查 OpenClaw 服务状态..."
|
|
128
129
|
if [ -z "$pid" ]; then
|
|
130
|
+
# 即使找不到 PID,也检查端口是否还在监听
|
|
131
|
+
if check_port "$PORT"; then
|
|
132
|
+
log_warn "端口 $PORT 仍在监听,但无法获取 PID,尝试备选停止方案..."
|
|
133
|
+
# 尝试通过 fuser 直接通过端口杀进程
|
|
134
|
+
if command -v fuser &>/dev/null; then
|
|
135
|
+
log_info "使用 fuser 通过端口停止服务..."
|
|
136
|
+
fuser -k "${PORT}/tcp" &>/dev/null || true
|
|
137
|
+
sleep 2
|
|
138
|
+
fi
|
|
139
|
+
# 尝试通过 pkill 停止 openclaw 相关进程
|
|
140
|
+
if check_port "$PORT"; then
|
|
141
|
+
log_info "使用 pkill 停止 openclaw 进程..."
|
|
142
|
+
pkill -f "openclaw" &>/dev/null || true
|
|
143
|
+
sleep 2
|
|
144
|
+
fi
|
|
145
|
+
# 最终验证
|
|
146
|
+
if ! check_port "$PORT"; then
|
|
147
|
+
log_info "OpenClaw 服务已停止(通过备选方案)。"
|
|
148
|
+
log_info "服务已成功停止。"
|
|
149
|
+
log_info "Success"
|
|
150
|
+
exit 0
|
|
151
|
+
else
|
|
152
|
+
log_error "OpenClaw 服务停止失败!所有停止方案均无效。"
|
|
153
|
+
exit 1
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
129
156
|
log_warn "OpenClaw 服务未在运行。"
|
|
130
157
|
exit 0
|
|
131
158
|
fi
|
|
@@ -133,14 +160,17 @@ stop_docker() {
|
|
|
133
160
|
log_info "OpenClaw 服务正在运行(PID: $pid),准备停止..."
|
|
134
161
|
|
|
135
162
|
# 停止服务:先发送 SIGTERM(优雅停止)
|
|
136
|
-
log_info "正在停止 OpenClaw
|
|
163
|
+
log_info "正在停止 OpenClaw 服务(发送 SIGTERM)..."
|
|
137
164
|
kill "$pid" &>/dev/null || true
|
|
138
165
|
|
|
139
|
-
# 等待服务完全停止(最多 10
|
|
166
|
+
# 等待服务完全停止(最多 10 秒),使用端口双重验证
|
|
140
167
|
local elapsed=0
|
|
141
168
|
while [ $elapsed -lt 10 ]; do
|
|
142
|
-
|
|
143
|
-
|
|
169
|
+
# 双重验证:检查 PID 和端口
|
|
170
|
+
local current_pid
|
|
171
|
+
current_pid=$(get_openclaw_pid)
|
|
172
|
+
if [ -z "$current_pid" ] && ! check_port "$PORT"; then
|
|
173
|
+
log_info "OpenClaw 服务停止成功!(PID 和端口均已关闭)"
|
|
144
174
|
log_info "服务已成功停止。"
|
|
145
175
|
log_info "Success"
|
|
146
176
|
exit 0
|
|
@@ -153,32 +183,40 @@ stop_docker() {
|
|
|
153
183
|
log_warn "服务未在 10 秒内停止,正在强制停止..."
|
|
154
184
|
kill -9 "$pid" &>/dev/null || true
|
|
155
185
|
|
|
156
|
-
#
|
|
186
|
+
# 额外:尝试 pkill 确保所有相关进程都被停止
|
|
187
|
+
pkill -9 -f "openclaw" &>/dev/null || true
|
|
188
|
+
|
|
189
|
+
# 等待进程消失(最多 5 秒)
|
|
157
190
|
elapsed=0
|
|
158
|
-
while [ $elapsed -lt
|
|
159
|
-
|
|
160
|
-
|
|
191
|
+
while [ $elapsed -lt 5 ]; do
|
|
192
|
+
local current_pid
|
|
193
|
+
current_pid=$(get_openclaw_pid)
|
|
194
|
+
if [ -z "$current_pid" ] && ! check_port "$PORT"; then
|
|
195
|
+
log_info "OpenClaw 服务已强制停止。"
|
|
196
|
+
log_info "服务已成功停止。"
|
|
197
|
+
log_info "Success"
|
|
198
|
+
exit 0
|
|
161
199
|
fi
|
|
162
200
|
sleep 1
|
|
163
201
|
elapsed=$((elapsed + 1))
|
|
164
202
|
done
|
|
165
203
|
|
|
166
|
-
#
|
|
167
|
-
# 使用端口检测作为辅助,避免僵尸进程导致误判
|
|
204
|
+
# 最终验证
|
|
168
205
|
local current_pid
|
|
169
206
|
current_pid=$(get_openclaw_pid)
|
|
170
|
-
if [ -z "$current_pid" ]; then
|
|
171
|
-
log_info "OpenClaw
|
|
207
|
+
if [ -z "$current_pid" ] && ! check_port "$PORT"; then
|
|
208
|
+
log_info "OpenClaw 服务已停止。"
|
|
172
209
|
log_info "服务已成功停止。"
|
|
173
210
|
log_info "Success"
|
|
174
|
-
|
|
175
|
-
|
|
211
|
+
exit 0
|
|
212
|
+
elif check_port "$PORT"; then
|
|
213
|
+
log_error "OpenClaw 服务停止失败!端口 $PORT 仍在监听。"
|
|
214
|
+
exit 1
|
|
215
|
+
else
|
|
176
216
|
log_info "OpenClaw 服务已停止(端口已关闭)。"
|
|
177
217
|
log_info "服务已成功停止。"
|
|
178
218
|
log_info "Success"
|
|
179
|
-
|
|
180
|
-
log_error "OpenClaw 服务停止失败!进程仍然存在且端口仍在监听。"
|
|
181
|
-
exit 1
|
|
219
|
+
exit 0
|
|
182
220
|
fi
|
|
183
221
|
}
|
|
184
222
|
|
package/package.json
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
* 执行 start/stop/restart/status 命令
|
|
5
5
|
* 基于 nodejs_client/src/main/openclaw-control.ts
|
|
6
6
|
*/
|
|
7
|
-
const { spawn } = require('child_process');
|
|
8
7
|
const path = require('path');
|
|
9
|
-
const os = require('os');
|
|
10
8
|
const { OpenClawCommandEnum, OpenClawServiceStatus, getServiceStatusMessage } = require('./openclaw-enum');
|
|
11
9
|
const { ScriptExecutor } = require('./script-executor');
|
|
10
|
+
const { ServiceManager } = require('./service-manager');
|
|
12
11
|
const { getOpenClawStatus } = require('./port-checker');
|
|
13
12
|
|
|
14
13
|
let globalExecutor = null;
|
|
@@ -54,71 +53,148 @@ function getCommandName(command) {
|
|
|
54
53
|
return names[command] || '未知命令';
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
/**
|
|
57
|
+
* 使用 ServiceManager 管理服务(systemd 模式)
|
|
58
|
+
*/
|
|
59
|
+
async function manageWithServiceManager(command) {
|
|
60
|
+
const serviceMgr = new ServiceManager('openclaw-gateway', 'OpenClaw Gateway');
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
switch (command) {
|
|
64
|
+
case OpenClawCommandEnum.STOP:
|
|
65
|
+
await serviceMgr.stop();
|
|
66
|
+
return { status: OpenClawServiceStatus.STOP_SUCCESS, message: '服务已停止' };
|
|
67
|
+
case OpenClawCommandEnum.START:
|
|
68
|
+
await serviceMgr.start();
|
|
69
|
+
return { status: OpenClawServiceStatus.START_SUCCESS, message: '服务已启动' };
|
|
70
|
+
case OpenClawCommandEnum.RESTART:
|
|
71
|
+
await serviceMgr.restart();
|
|
72
|
+
return { status: OpenClawServiceStatus.RESTART_SUCCESS, message: '服务已重启' };
|
|
73
|
+
case OpenClawCommandEnum.STATUS:
|
|
74
|
+
const status = await serviceMgr.status();
|
|
75
|
+
return { status: OpenClawServiceStatus.RUNNING, message: status };
|
|
76
|
+
default:
|
|
77
|
+
return { status: OpenClawServiceStatus.ERROR, message: '未知命令' };
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
// ServiceManager 失败,返回 null 让上层回退到脚本方式
|
|
81
|
+
console.log(`[OpenClawControl] ServiceManager 失败: ${err.message}`);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
60
85
|
|
|
86
|
+
/**
|
|
87
|
+
* 使用 ScriptExecutor 执行脚本(Docker 模式)
|
|
88
|
+
*/
|
|
89
|
+
async function executeWithScript(command) {
|
|
61
90
|
const executor = getExecutor();
|
|
62
91
|
const scriptName = getScriptName(command);
|
|
63
|
-
|
|
92
|
+
|
|
64
93
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
result.status === OpenClawServiceStatus.START_SUCCESS ||
|
|
69
|
-
result.status === OpenClawServiceStatus.STOP_SUCCESS ||
|
|
70
|
-
result.status === OpenClawServiceStatus.RESTART_SUCCESS
|
|
71
|
-
) {
|
|
72
|
-
console.log(`[OpenClawControl] ${cmdName} 成功: ${result.message}`);
|
|
73
|
-
// 延迟更新状态
|
|
74
|
-
setTimeout(async () => {
|
|
75
|
-
const portStatus = await getOpenClawStatus(18789);
|
|
76
|
-
console.log(`[OpenClawControl] 状态已更新: ${portStatus === 1 ? '运行中' : '未运行'}`);
|
|
77
|
-
}, 2000);
|
|
78
|
-
} else if (result.status === OpenClawServiceStatus.ERROR) {
|
|
79
|
-
console.log(`[OpenClawControl] ${cmdName} 失败: ${result.message}`);
|
|
80
|
-
} else if (result.status === OpenClawServiceStatus.NOT_INSTALL) {
|
|
81
|
-
console.log(`[OpenClawControl] ${cmdName}: OpenClaw未安装`);
|
|
82
|
-
} else {
|
|
83
|
-
console.log(`[OpenClawControl] ${cmdName} 状态: ${result.message}`);
|
|
84
|
-
setTimeout(async () => {
|
|
85
|
-
const portStatus = await getOpenClawStatus(18789);
|
|
86
|
-
console.log(`[OpenClawControl] 状态: ${portStatus}`);
|
|
87
|
-
}, 2000);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (sendResponse) {
|
|
91
|
-
let httpStatus = 'success';
|
|
92
|
-
if (result.status === OpenClawServiceStatus.ERROR) httpStatus = 'error';
|
|
93
|
-
sendResponse({
|
|
94
|
-
type: 'command_result',
|
|
95
|
-
command,
|
|
96
|
-
status: httpStatus,
|
|
97
|
-
message: result.message,
|
|
98
|
-
service_status: result.status
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return result;
|
|
94
|
+
return await executor.executeWithStatus(command, scriptName);
|
|
103
95
|
} catch (e) {
|
|
104
96
|
const msg = e instanceof Error ? e.message : String(e);
|
|
105
|
-
console.log(`[OpenClawControl]
|
|
106
|
-
if (sendResponse) {
|
|
107
|
-
sendResponse({
|
|
108
|
-
type: 'command_result',
|
|
109
|
-
command,
|
|
110
|
-
status: 'error',
|
|
111
|
-
message: msg,
|
|
112
|
-
service_status: OpenClawServiceStatus.ERROR
|
|
113
|
-
});
|
|
114
|
-
}
|
|
97
|
+
console.log(`[OpenClawControl] 脚本执行异常: ${msg}`);
|
|
115
98
|
return {
|
|
116
99
|
status: OpenClawServiceStatus.ERROR,
|
|
117
|
-
message: msg
|
|
100
|
+
message: `执行异常: ${msg}`
|
|
118
101
|
};
|
|
119
102
|
}
|
|
120
103
|
}
|
|
121
104
|
|
|
105
|
+
/**
|
|
106
|
+
* 验证命令执行结果
|
|
107
|
+
*/
|
|
108
|
+
async function verifyCommandResult(command, result) {
|
|
109
|
+
if (result.status === OpenClawServiceStatus.ERROR) {
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (command === OpenClawCommandEnum.STOP) {
|
|
114
|
+
// 等待 3 秒后验证端口
|
|
115
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
116
|
+
const portStatus = await getOpenClawStatus(18789);
|
|
117
|
+
console.log(`[OpenClawControl] 停止后端口状态: ${portStatus === 1 ? '运行中' : '未运行'}`);
|
|
118
|
+
|
|
119
|
+
if (portStatus === 1) {
|
|
120
|
+
return {
|
|
121
|
+
status: OpenClawServiceStatus.ERROR,
|
|
122
|
+
message: '停止失败: 服务仍在运行'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
} else if (command === OpenClawCommandEnum.START || command === OpenClawCommandEnum.RESTART) {
|
|
126
|
+
// 等待服务启动
|
|
127
|
+
const maxWait = command === OpenClawCommandEnum.START ? 30 : 60;
|
|
128
|
+
let attempts = 0;
|
|
129
|
+
let portStatus = 0;
|
|
130
|
+
|
|
131
|
+
while (attempts < maxWait) {
|
|
132
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
133
|
+
portStatus = await getOpenClawStatus(18789);
|
|
134
|
+
if (portStatus === 1) break;
|
|
135
|
+
attempts++;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(`[OpenClawControl] ${getCommandName(command)}后端口状态: ${portStatus === 1 ? '运行中' : '未运行'}`);
|
|
139
|
+
|
|
140
|
+
if (portStatus === 0) {
|
|
141
|
+
return {
|
|
142
|
+
status: OpenClawServiceStatus.ERROR,
|
|
143
|
+
message: `${getCommandName(command)}失败: 服务未运行`
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function executeCommand(command, window, sendResponse) {
|
|
152
|
+
const cmdName = getCommandName(command);
|
|
153
|
+
console.log(`[OpenClawControl] ${cmdName} OpenClaw...`);
|
|
154
|
+
|
|
155
|
+
let result;
|
|
156
|
+
|
|
157
|
+
// 优先尝试 ServiceManager(systemd 模式)
|
|
158
|
+
if (process.platform === 'linux' || process.platform === 'darwin') {
|
|
159
|
+
result = await manageWithServiceManager(command);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 如果 ServiceManager 失败或不是 Linux/macOS,使用脚本方式
|
|
163
|
+
if (!result) {
|
|
164
|
+
result = await executeWithScript(command);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 验证结果
|
|
168
|
+
result = await verifyCommandResult(command, result);
|
|
169
|
+
|
|
170
|
+
// 输出日志
|
|
171
|
+
if (result.status === OpenClawServiceStatus.START_SUCCESS ||
|
|
172
|
+
result.status === OpenClawServiceStatus.STOP_SUCCESS ||
|
|
173
|
+
result.status === OpenClawServiceStatus.RESTART_SUCCESS) {
|
|
174
|
+
console.log(`[OpenClawControl] ${cmdName} 成功: ${result.message}`);
|
|
175
|
+
} else if (result.status === OpenClawServiceStatus.ERROR) {
|
|
176
|
+
console.log(`[OpenClawControl] ${cmdName} 失败: ${result.message}`);
|
|
177
|
+
} else if (result.status === OpenClawServiceStatus.NOT_INSTALL) {
|
|
178
|
+
console.log(`[OpenClawControl] ${cmdName}: OpenClaw未安装`);
|
|
179
|
+
} else {
|
|
180
|
+
console.log(`[OpenClawControl] ${cmdName} 状态: ${result.message}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (sendResponse) {
|
|
184
|
+
let httpStatus = 'success';
|
|
185
|
+
if (result.status === OpenClawServiceStatus.ERROR) httpStatus = 'error';
|
|
186
|
+
sendResponse({
|
|
187
|
+
type: 'command_result',
|
|
188
|
+
command,
|
|
189
|
+
status: httpStatus,
|
|
190
|
+
message: result.message,
|
|
191
|
+
service_status: result.status
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
122
198
|
module.exports = {
|
|
123
199
|
executeCommand,
|
|
124
200
|
getScriptName,
|
|
@@ -88,14 +88,26 @@ class ScriptExecutor {
|
|
|
88
88
|
const scriptPath = path.join(this.scriptDir, scriptName);
|
|
89
89
|
|
|
90
90
|
try {
|
|
91
|
-
const { stdout, stderr } = await this.runScript(scriptPath);
|
|
91
|
+
const { stdout, stderr, exitCode } = await this.runScript(scriptPath);
|
|
92
92
|
const fullOutput = stdout + stderr;
|
|
93
93
|
|
|
94
94
|
// 调试日志:记录脚本实际输出
|
|
95
95
|
console.log(`[ScriptExecutor] 执行脚本: ${scriptPath}`);
|
|
96
96
|
console.log(`[ScriptExecutor] ${scriptName} 输出:\n${fullOutput}`);
|
|
97
|
+
console.log(`[ScriptExecutor] ${scriptName} 退出码: ${exitCode}`);
|
|
98
|
+
|
|
99
|
+
const result = this.parseStatus(command, fullOutput);
|
|
100
|
+
|
|
101
|
+
// 对于 STOP 命令,如果脚本退出码非零,强制返回错误
|
|
102
|
+
if (command === OpenClawCommandEnum.STOP && exitCode !== 0) {
|
|
103
|
+
console.log(`[ScriptExecutor] 停止脚本退出码非零(${exitCode}),返回错误`);
|
|
104
|
+
return {
|
|
105
|
+
status: OpenClawServiceStatus.ERROR,
|
|
106
|
+
message: `停止失败: 脚本退出码 ${exitCode}`
|
|
107
|
+
};
|
|
108
|
+
}
|
|
97
109
|
|
|
98
|
-
return
|
|
110
|
+
return result;
|
|
99
111
|
} catch (e) {
|
|
100
112
|
const msg = e instanceof Error ? e.message : String(e);
|
|
101
113
|
return { status: OpenClawServiceStatus.ERROR, message: `执行异常: ${msg}` };
|
|
@@ -239,7 +251,7 @@ class ScriptExecutor {
|
|
|
239
251
|
killed = true;
|
|
240
252
|
clearTimeout(timer);
|
|
241
253
|
this.killProcessTree(child);
|
|
242
|
-
resolve({ stdout, stderr });
|
|
254
|
+
resolve({ stdout, stderr, exitCode: 0 });
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
257
|
});
|
|
@@ -252,7 +264,7 @@ class ScriptExecutor {
|
|
|
252
264
|
killed = true;
|
|
253
265
|
clearTimeout(timer);
|
|
254
266
|
this.killProcessTree(child);
|
|
255
|
-
resolve({ stdout, stderr });
|
|
267
|
+
resolve({ stdout, stderr, exitCode: 0 });
|
|
256
268
|
}
|
|
257
269
|
}
|
|
258
270
|
});
|
|
@@ -271,7 +283,7 @@ class ScriptExecutor {
|
|
|
271
283
|
killed = true;
|
|
272
284
|
clearTimeout(timer);
|
|
273
285
|
console.log(`[ScriptExecutor] 脚本退出,code=${code}, stdout长度=${stdout.length}`);
|
|
274
|
-
resolve({ stdout, stderr });
|
|
286
|
+
resolve({ stdout, stderr, exitCode: code });
|
|
275
287
|
}
|
|
276
288
|
});
|
|
277
289
|
|
|
@@ -281,7 +293,7 @@ class ScriptExecutor {
|
|
|
281
293
|
killed = true;
|
|
282
294
|
clearTimeout(timer);
|
|
283
295
|
console.log(`[ScriptExecutor] 脚本关闭,code=${code}, stdout长度=${stdout.length}`);
|
|
284
|
-
resolve({ stdout, stderr });
|
|
296
|
+
resolve({ stdout, stderr, exitCode: code });
|
|
285
297
|
}
|
|
286
298
|
});
|
|
287
299
|
|