claw-subagent-service 0.0.88 → 0.0.90
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
|
@@ -208,6 +208,7 @@ stop_docker() {
|
|
|
208
208
|
log_info "OpenClaw 服务已停止。"
|
|
209
209
|
log_info "服务已成功停止。"
|
|
210
210
|
log_info "Success"
|
|
211
|
+
exit 0
|
|
211
212
|
elif check_port "$PORT"; then
|
|
212
213
|
log_error "OpenClaw 服务停止失败!端口 $PORT 仍在监听。"
|
|
213
214
|
exit 1
|
|
@@ -215,6 +216,7 @@ stop_docker() {
|
|
|
215
216
|
log_info "OpenClaw 服务已停止(端口已关闭)。"
|
|
216
217
|
log_info "服务已成功停止。"
|
|
217
218
|
log_info "Success"
|
|
219
|
+
exit 0
|
|
218
220
|
fi
|
|
219
221
|
}
|
|
220
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,183 @@ 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
|
+
* @returns {Object} { result, output }
|
|
89
|
+
*/
|
|
90
|
+
async function executeWithScript(command) {
|
|
61
91
|
const executor = getExecutor();
|
|
62
92
|
const scriptName = getScriptName(command);
|
|
63
|
-
|
|
93
|
+
|
|
64
94
|
try {
|
|
65
95
|
const result = await executor.executeWithStatus(command, scriptName);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
result.status
|
|
69
|
-
result.
|
|
70
|
-
|
|
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;
|
|
96
|
+
// ScriptExecutor 现在返回 { status, message, output }
|
|
97
|
+
return {
|
|
98
|
+
result: { status: result.status, message: result.message },
|
|
99
|
+
output: result.output || ''
|
|
100
|
+
};
|
|
103
101
|
} catch (e) {
|
|
104
102
|
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
|
-
}
|
|
103
|
+
console.log(`[OpenClawControl] 脚本执行异常: ${msg}`);
|
|
115
104
|
return {
|
|
116
|
-
|
|
117
|
-
|
|
105
|
+
result: {
|
|
106
|
+
status: OpenClawServiceStatus.ERROR,
|
|
107
|
+
message: `执行异常: ${msg}`
|
|
108
|
+
},
|
|
109
|
+
output: ''
|
|
118
110
|
};
|
|
119
111
|
}
|
|
120
112
|
}
|
|
121
113
|
|
|
114
|
+
/**
|
|
115
|
+
* 验证命令执行结果
|
|
116
|
+
*
|
|
117
|
+
* 注意:在 Docker 环境中,端口检查可能不可靠(服务可能绑定到特定网络接口)
|
|
118
|
+
* 因此,如果脚本输出明确指示成功,但端口检查失败,会给出警告但保留脚本结果
|
|
119
|
+
*/
|
|
120
|
+
async function verifyCommandResult(command, result, scriptOutput = '') {
|
|
121
|
+
if (result.status === OpenClawServiceStatus.ERROR) {
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const outputUpper = (scriptOutput || '').toUpperCase();
|
|
126
|
+
|
|
127
|
+
if (command === OpenClawCommandEnum.STOP) {
|
|
128
|
+
// 等待 3 秒后验证端口
|
|
129
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
130
|
+
const portStatus = await getOpenClawStatus(18789);
|
|
131
|
+
console.log(`[OpenClawControl] 停止后端口状态: ${portStatus === 1 ? '运行中' : '未运行'}`);
|
|
132
|
+
|
|
133
|
+
if (portStatus === 1) {
|
|
134
|
+
// 端口仍在监听,但检查脚本是否报告已停止
|
|
135
|
+
if (outputUpper.includes('SUCCESS') || outputUpper.includes('STOPPED')) {
|
|
136
|
+
console.warn(`[OpenClawControl] 警告: 端口仍在监听,但脚本报告成功。可能是僵尸进程或服务未正确停止。`);
|
|
137
|
+
// 仍然返回成功,但附带警告信息
|
|
138
|
+
return {
|
|
139
|
+
status: result.status,
|
|
140
|
+
message: result.message + ' (警告: 端口仍在监听)'
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
status: OpenClawServiceStatus.ERROR,
|
|
145
|
+
message: '停止失败: 服务仍在运行'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
} else if (command === OpenClawCommandEnum.START || command === OpenClawCommandEnum.RESTART) {
|
|
149
|
+
// 等待服务启动
|
|
150
|
+
const maxWait = command === OpenClawCommandEnum.START ? 30 : 60;
|
|
151
|
+
let attempts = 0;
|
|
152
|
+
let portStatus = 0;
|
|
153
|
+
|
|
154
|
+
while (attempts < maxWait) {
|
|
155
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
156
|
+
portStatus = await getOpenClawStatus(18789);
|
|
157
|
+
if (portStatus === 1) break;
|
|
158
|
+
attempts++;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`[OpenClawControl] ${getCommandName(command)}后端口状态: ${portStatus === 1 ? '运行中' : '未运行'}`);
|
|
162
|
+
|
|
163
|
+
if (portStatus === 0) {
|
|
164
|
+
// 端口未监听,但检查脚本是否报告成功(可能是服务绑定到其他接口)
|
|
165
|
+
if (outputUpper.includes('SUCCESS') || outputUpper.includes('ALREADY RUNNING')) {
|
|
166
|
+
console.warn(`[OpenClawControl] 警告: 端口检查失败,但脚本报告成功。服务可能绑定到其他网络接口。`);
|
|
167
|
+
// 信任脚本结果,但添加警告
|
|
168
|
+
return {
|
|
169
|
+
status: result.status,
|
|
170
|
+
message: result.message + ' (警告: 端口检查可能不准确)'
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
status: OpenClawServiceStatus.ERROR,
|
|
175
|
+
message: `${getCommandName(command)}失败: 服务未运行`
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function executeCommand(command, window, sendResponse) {
|
|
184
|
+
const cmdName = getCommandName(command);
|
|
185
|
+
console.log(`[OpenClawControl] ${cmdName} OpenClaw...`);
|
|
186
|
+
|
|
187
|
+
let result;
|
|
188
|
+
|
|
189
|
+
// 优先尝试 ServiceManager(systemd 模式)
|
|
190
|
+
if (process.platform === 'linux' || process.platform === 'darwin') {
|
|
191
|
+
result = await manageWithServiceManager(command);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 如果 ServiceManager 失败或不是 Linux/macOS,使用脚本方式
|
|
195
|
+
let scriptOutput = '';
|
|
196
|
+
if (!result) {
|
|
197
|
+
const scriptResult = await executeWithScript(command);
|
|
198
|
+
result = scriptResult.result;
|
|
199
|
+
scriptOutput = scriptResult.output;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 验证结果(传递脚本输出用于后备检查)
|
|
203
|
+
result = await verifyCommandResult(command, result, scriptOutput);
|
|
204
|
+
|
|
205
|
+
// 输出日志
|
|
206
|
+
if (result.status === OpenClawServiceStatus.START_SUCCESS ||
|
|
207
|
+
result.status === OpenClawServiceStatus.STOP_SUCCESS ||
|
|
208
|
+
result.status === OpenClawServiceStatus.RESTART_SUCCESS) {
|
|
209
|
+
console.log(`[OpenClawControl] ${cmdName} 成功: ${result.message}`);
|
|
210
|
+
} else if (result.status === OpenClawServiceStatus.ERROR) {
|
|
211
|
+
console.log(`[OpenClawControl] ${cmdName} 失败: ${result.message}`);
|
|
212
|
+
} else if (result.status === OpenClawServiceStatus.NOT_INSTALL) {
|
|
213
|
+
console.log(`[OpenClawControl] ${cmdName}: OpenClaw未安装`);
|
|
214
|
+
} else {
|
|
215
|
+
console.log(`[OpenClawControl] ${cmdName} 状态: ${result.message}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (sendResponse) {
|
|
219
|
+
let httpStatus = 'success';
|
|
220
|
+
if (result.status === OpenClawServiceStatus.ERROR) httpStatus = 'error';
|
|
221
|
+
sendResponse({
|
|
222
|
+
type: 'command_result',
|
|
223
|
+
command,
|
|
224
|
+
status: httpStatus,
|
|
225
|
+
message: result.message,
|
|
226
|
+
service_status: result.status
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
122
233
|
module.exports = {
|
|
123
234
|
executeCommand,
|
|
124
235
|
getScriptName,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const net = require('net');
|
|
2
|
+
const os = require('os');
|
|
2
3
|
|
|
3
|
-
function checkPortListening(port) {
|
|
4
|
+
function checkPortListening(port, host = '127.0.0.1') {
|
|
4
5
|
return new Promise((resolve) => {
|
|
5
6
|
const sock = new net.Socket();
|
|
6
7
|
sock.setTimeout(3000);
|
|
@@ -16,16 +17,47 @@ function checkPortListening(port) {
|
|
|
16
17
|
sock.destroy();
|
|
17
18
|
resolve(false);
|
|
18
19
|
});
|
|
19
|
-
sock.connect(port,
|
|
20
|
+
sock.connect(port, host);
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* 检查端口是否在所有网络接口上监听
|
|
26
|
+
* Docker 环境中,服务可能绑定到 0.0.0.0 而不是 127.0.0.1
|
|
27
|
+
*/
|
|
28
|
+
async function checkPortOnAllInterfaces(port) {
|
|
29
|
+
// 先检查 localhost
|
|
30
|
+
const localhostListening = await checkPortListening(port, '127.0.0.1');
|
|
31
|
+
if (localhostListening) return true;
|
|
32
|
+
|
|
33
|
+
// 再检查 0.0.0.0(所有接口)
|
|
34
|
+
const allInterfacesListening = await checkPortListening(port, '0.0.0.0');
|
|
35
|
+
if (allInterfacesListening) return true;
|
|
36
|
+
|
|
37
|
+
// 获取所有网络接口并逐一检查
|
|
38
|
+
const interfaces = os.networkInterfaces();
|
|
39
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
40
|
+
for (const addr of addrs) {
|
|
41
|
+
if (addr.family === 'IPv4' && !addr.internal) {
|
|
42
|
+
const listening = await checkPortListening(port, addr.address);
|
|
43
|
+
if (listening) {
|
|
44
|
+
console.log(`[PortChecker] 端口 ${port} 在 ${addr.address} (${name}) 上监听`);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
23
54
|
async function getOpenClawStatus(port = 18789) {
|
|
24
|
-
const isListening = await
|
|
55
|
+
const isListening = await checkPortOnAllInterfaces(port);
|
|
25
56
|
return isListening ? 1 : 0;
|
|
26
57
|
}
|
|
27
58
|
|
|
28
59
|
module.exports = {
|
|
29
60
|
checkPortListening,
|
|
61
|
+
checkPortOnAllInterfaces,
|
|
30
62
|
getOpenClawStatus,
|
|
31
63
|
};
|
|
@@ -88,14 +88,31 @@ 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);
|
|
97
100
|
|
|
98
|
-
|
|
101
|
+
// 对于 STOP 命令,如果脚本退出码非零,强制返回错误
|
|
102
|
+
if (command === OpenClawCommandEnum.STOP && exitCode !== 0) {
|
|
103
|
+
console.log(`[ScriptExecutor] 停止脚本退出码非零(${exitCode}),返回错误`);
|
|
104
|
+
return {
|
|
105
|
+
status: OpenClawServiceStatus.ERROR,
|
|
106
|
+
message: `停止失败: 脚本退出码 ${exitCode}`,
|
|
107
|
+
output: fullOutput
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 返回结果时附带原始输出
|
|
112
|
+
return {
|
|
113
|
+
...result,
|
|
114
|
+
output: fullOutput
|
|
115
|
+
};
|
|
99
116
|
} catch (e) {
|
|
100
117
|
const msg = e instanceof Error ? e.message : String(e);
|
|
101
118
|
return { status: OpenClawServiceStatus.ERROR, message: `执行异常: ${msg}` };
|
|
@@ -239,7 +256,7 @@ class ScriptExecutor {
|
|
|
239
256
|
killed = true;
|
|
240
257
|
clearTimeout(timer);
|
|
241
258
|
this.killProcessTree(child);
|
|
242
|
-
resolve({ stdout, stderr });
|
|
259
|
+
resolve({ stdout, stderr, exitCode: 0 });
|
|
243
260
|
}
|
|
244
261
|
}
|
|
245
262
|
});
|
|
@@ -252,7 +269,7 @@ class ScriptExecutor {
|
|
|
252
269
|
killed = true;
|
|
253
270
|
clearTimeout(timer);
|
|
254
271
|
this.killProcessTree(child);
|
|
255
|
-
resolve({ stdout, stderr });
|
|
272
|
+
resolve({ stdout, stderr, exitCode: 0 });
|
|
256
273
|
}
|
|
257
274
|
}
|
|
258
275
|
});
|
|
@@ -271,7 +288,7 @@ class ScriptExecutor {
|
|
|
271
288
|
killed = true;
|
|
272
289
|
clearTimeout(timer);
|
|
273
290
|
console.log(`[ScriptExecutor] 脚本退出,code=${code}, stdout长度=${stdout.length}`);
|
|
274
|
-
resolve({ stdout, stderr });
|
|
291
|
+
resolve({ stdout, stderr, exitCode: code });
|
|
275
292
|
}
|
|
276
293
|
});
|
|
277
294
|
|
|
@@ -281,7 +298,7 @@ class ScriptExecutor {
|
|
|
281
298
|
killed = true;
|
|
282
299
|
clearTimeout(timer);
|
|
283
300
|
console.log(`[ScriptExecutor] 脚本关闭,code=${code}, stdout长度=${stdout.length}`);
|
|
284
|
-
resolve({ stdout, stderr });
|
|
301
|
+
resolve({ stdout, stderr, exitCode: code });
|
|
285
302
|
}
|
|
286
303
|
});
|
|
287
304
|
|