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.
@@ -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=\\([0-9]*\\).*/\\1/p')
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
- # 最后尝试 /proc 文件系统(最可靠,无需外部工具)
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 [ -d "$proc_dir/fd" ]; then
72
- for fd in $proc_dir/fd/*; do
73
- if [ -L "$fd" ]; then
74
- local target
75
- target=$(readlink "$fd" 2>/dev/null)
76
- if [ -n "$target" ] && echo "$target" | grep -q ":${port}"; then
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
- kill "$pid" &>/dev/null || true
152
+ if [ -n "$pid" ]; then
153
+ kill "$pid" &>/dev/null || true
154
+ fi
152
155
 
153
- # 等待停止(最多 5 秒)
156
+ # 等待停止(最多 10 秒),使用端口双重验证
154
157
  local elapsed=0
155
- while [ $elapsed -lt 5 ]; do
156
- if [ -z "$(get_openclaw_pid)" ]; then
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 [ -n "$(get_openclaw_pid)" ]; then
165
- log_warn "服务未在 5 秒内停止,正在强制停止..."
166
- kill -9 "$pid" &>/dev/null || true
167
- sleep 1
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 "服务已停止"
@@ -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=\\([0-9]*\\).*/\\1/p')
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
- # 最后尝试 /proc 文件系统(最可靠,无需外部工具)
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 [ -d "$proc_dir/fd" ]; then
72
- for fd in $proc_dir/fd/*; do
73
- if [ -L "$fd" ]; then
74
- local target
75
- target=$(readlink "$fd" 2>/dev/null)
76
- if [ -n "$target" ] && echo "$target" | grep -q ":${port}"; then
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
 
@@ -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
- # 最后尝试 /proc 文件系统(最可靠,无需外部工具)
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 [ -d "$proc_dir/fd" ]; then
86
- for fd in $proc_dir/fd/*; do
87
- if [ -L "$fd" ]; then
88
- local target
89
- target=$(readlink "$fd" 2>/dev/null)
90
- if [ -n "$target" ] && echo "$target" | grep -q ":${port}"; then
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
 
@@ -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=\\([0-9]*\\).*/\\1/p')
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
- # 最后尝试 /proc 文件系统(最可靠,无需外部工具)
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 [ -d "$proc_dir/fd" ]; then
105
- for fd in $proc_dir/fd/*; do
106
- if [ -L "$fd" ]; then
107
- local target
108
- target=$(readlink "$fd" 2>/dev/null)
109
- if [ -n "$target" ] && echo "$target" | grep -q ":${port}"; then
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
- if [ -z "$(get_openclaw_pid)" ]; then
143
- log_info "OpenClaw 服务停止成功!"
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
- # 等待进程消失(最多 3 秒)
186
+ # 额外:尝试 pkill 确保所有相关进程都被停止
187
+ pkill -9 -f "openclaw" &>/dev/null || true
188
+
189
+ # 等待进程消失(最多 5 秒)
157
190
  elapsed=0
158
- while [ $elapsed -lt 3 ]; do
159
- if [ -z "$(get_openclaw_pid)" ]; then
160
- break
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
- elif ! check_port "$PORT"; then
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
- else
180
- log_error "OpenClaw 服务停止失败!进程仍然存在且端口仍在监听。"
181
- exit 1
219
+ exit 0
182
220
  fi
183
221
  }
184
222
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.87",
3
+ "version": "0.0.89",
4
4
  "description": "虾说智能助手",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -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
- async function executeCommand(command, window, sendResponse) {
58
- const cmdName = getCommandName(command);
59
- console.log(`[OpenClawControl] ${cmdName} OpenClaw...`);
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
- const result = await executor.executeWithStatus(command, scriptName);
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] ${cmdName} 执行异常: ${msg}`);
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 this.parseStatus(command, fullOutput);
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