claw-subagent-service 0.0.89 → 0.0.91
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/package.json
CHANGED
|
@@ -85,42 +85,72 @@ async function manageWithServiceManager(command) {
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* 使用 ScriptExecutor 执行脚本(Docker 模式)
|
|
88
|
+
* @returns {Object} { result, output }
|
|
88
89
|
*/
|
|
89
90
|
async function executeWithScript(command) {
|
|
90
91
|
const executor = getExecutor();
|
|
91
92
|
const scriptName = getScriptName(command);
|
|
92
93
|
|
|
93
94
|
try {
|
|
94
|
-
|
|
95
|
+
const result = await executor.executeWithStatus(command, scriptName);
|
|
96
|
+
// ScriptExecutor 现在返回 { status, message, output }
|
|
97
|
+
return {
|
|
98
|
+
result: { status: result.status, message: result.message },
|
|
99
|
+
output: result.output || ''
|
|
100
|
+
};
|
|
95
101
|
} catch (e) {
|
|
96
102
|
const msg = e instanceof Error ? e.message : String(e);
|
|
97
103
|
console.log(`[OpenClawControl] 脚本执行异常: ${msg}`);
|
|
98
104
|
return {
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
result: {
|
|
106
|
+
status: OpenClawServiceStatus.ERROR,
|
|
107
|
+
message: `执行异常: ${msg}`
|
|
108
|
+
},
|
|
109
|
+
output: ''
|
|
101
110
|
};
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
/**
|
|
106
115
|
* 验证命令执行结果
|
|
116
|
+
*
|
|
117
|
+
* 注意:在 Docker 环境中,端口检查可能不可靠(服务可能绑定到特定网络接口)
|
|
118
|
+
* 因此,如果脚本输出明确指示成功,但端口检查失败,会给出警告但保留脚本结果
|
|
107
119
|
*/
|
|
108
|
-
async function verifyCommandResult(command, result) {
|
|
120
|
+
async function verifyCommandResult(command, result, scriptOutput = '') {
|
|
109
121
|
if (result.status === OpenClawServiceStatus.ERROR) {
|
|
110
122
|
return result;
|
|
111
123
|
}
|
|
112
124
|
|
|
125
|
+
const outputUpper = (scriptOutput || '').toUpperCase();
|
|
126
|
+
|
|
113
127
|
if (command === OpenClawCommandEnum.STOP) {
|
|
114
128
|
// 等待 3 秒后验证端口
|
|
115
129
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
116
130
|
const portStatus = await getOpenClawStatus(18789);
|
|
117
|
-
console.log(`[OpenClawControl] 停止后端口状态: ${portStatus
|
|
131
|
+
console.log(`[OpenClawControl] 停止后端口状态: ${portStatus}`);
|
|
118
132
|
|
|
119
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
|
+
}
|
|
120
143
|
return {
|
|
121
144
|
status: OpenClawServiceStatus.ERROR,
|
|
122
145
|
message: '停止失败: 服务仍在运行'
|
|
123
146
|
};
|
|
147
|
+
} else if (portStatus === 2) {
|
|
148
|
+
// 进程存在但端口未监听,可能服务正在停止中
|
|
149
|
+
console.warn(`[OpenClawControl] 警告: openclaw 进程仍存在但端口已关闭。`);
|
|
150
|
+
return {
|
|
151
|
+
status: result.status,
|
|
152
|
+
message: result.message + ' (警告: 进程仍存在)'
|
|
153
|
+
};
|
|
124
154
|
}
|
|
125
155
|
} else if (command === OpenClawCommandEnum.START || command === OpenClawCommandEnum.RESTART) {
|
|
126
156
|
// 等待服务启动
|
|
@@ -131,17 +161,33 @@ async function verifyCommandResult(command, result) {
|
|
|
131
161
|
while (attempts < maxWait) {
|
|
132
162
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
133
163
|
portStatus = await getOpenClawStatus(18789);
|
|
134
|
-
if (portStatus === 1) break;
|
|
164
|
+
if (portStatus === 1 || portStatus === 2) break; // 端口监听或进程存在都算成功
|
|
135
165
|
attempts++;
|
|
136
166
|
}
|
|
137
167
|
|
|
138
|
-
console.log(`[OpenClawControl] ${getCommandName(command)}后端口状态: ${portStatus
|
|
168
|
+
console.log(`[OpenClawControl] ${getCommandName(command)}后端口状态: ${portStatus}`);
|
|
139
169
|
|
|
140
170
|
if (portStatus === 0) {
|
|
171
|
+
// 端口未监听且进程不存在,但检查脚本是否报告成功
|
|
172
|
+
if (outputUpper.includes('SUCCESS') || outputUpper.includes('ALREADY RUNNING')) {
|
|
173
|
+
console.warn(`[OpenClawControl] 警告: 端口检查失败,但脚本报告成功。服务可能绑定到其他网络接口。`);
|
|
174
|
+
// 信任脚本结果,但添加警告
|
|
175
|
+
return {
|
|
176
|
+
status: result.status,
|
|
177
|
+
message: result.message + ' (警告: 端口检查可能不准确)'
|
|
178
|
+
};
|
|
179
|
+
}
|
|
141
180
|
return {
|
|
142
181
|
status: OpenClawServiceStatus.ERROR,
|
|
143
182
|
message: `${getCommandName(command)}失败: 服务未运行`
|
|
144
183
|
};
|
|
184
|
+
} else if (portStatus === 2) {
|
|
185
|
+
// 进程存在但端口未监听,服务可能在启动中或绑定到其他地址
|
|
186
|
+
console.warn(`[OpenClawControl] 警告: openclaw 进程存在但端口 ${portStatus} 未监听。`);
|
|
187
|
+
return {
|
|
188
|
+
status: result.status === OpenClawServiceStatus.START_SUCCESS ? result.status : OpenClawServiceStatus.START_SUCCESS,
|
|
189
|
+
message: result.message + ' (进程已启动,端口检查可能不准确)'
|
|
190
|
+
};
|
|
145
191
|
}
|
|
146
192
|
}
|
|
147
193
|
|
|
@@ -160,12 +206,15 @@ async function executeCommand(command, window, sendResponse) {
|
|
|
160
206
|
}
|
|
161
207
|
|
|
162
208
|
// 如果 ServiceManager 失败或不是 Linux/macOS,使用脚本方式
|
|
209
|
+
let scriptOutput = '';
|
|
163
210
|
if (!result) {
|
|
164
|
-
|
|
211
|
+
const scriptResult = await executeWithScript(command);
|
|
212
|
+
result = scriptResult.result;
|
|
213
|
+
scriptOutput = scriptResult.output;
|
|
165
214
|
}
|
|
166
215
|
|
|
167
|
-
//
|
|
168
|
-
result = await verifyCommandResult(command, result);
|
|
216
|
+
// 验证结果(传递脚本输出用于后备检查)
|
|
217
|
+
result = await verifyCommandResult(command, result, scriptOutput);
|
|
169
218
|
|
|
170
219
|
// 输出日志
|
|
171
220
|
if (result.status === OpenClawServiceStatus.START_SUCCESS ||
|
|
@@ -1,31 +1,162 @@
|
|
|
1
1
|
const net = require('net');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
2
4
|
|
|
3
|
-
function checkPortListening(port) {
|
|
5
|
+
function checkPortListening(port, host = '127.0.0.1') {
|
|
4
6
|
return new Promise((resolve) => {
|
|
5
7
|
const sock = new net.Socket();
|
|
6
|
-
sock.setTimeout(
|
|
8
|
+
sock.setTimeout(5000);
|
|
9
|
+
|
|
10
|
+
console.log(`[PortChecker] 尝试连接 ${host}:${port}`);
|
|
11
|
+
|
|
7
12
|
sock.once('connect', () => {
|
|
13
|
+
console.log(`[PortChecker] 成功连接到 ${host}:${port}`);
|
|
8
14
|
sock.destroy();
|
|
9
15
|
resolve(true);
|
|
10
16
|
});
|
|
11
|
-
|
|
17
|
+
|
|
18
|
+
sock.once('error', (err) => {
|
|
19
|
+
console.log(`[PortChecker] 连接 ${host}:${port} 失败: ${err.code} - ${err.message}`);
|
|
12
20
|
sock.destroy();
|
|
13
21
|
resolve(false);
|
|
14
22
|
});
|
|
23
|
+
|
|
15
24
|
sock.once('timeout', () => {
|
|
25
|
+
console.log(`[PortChecker] 连接 ${host}:${port} 超时`);
|
|
16
26
|
sock.destroy();
|
|
17
27
|
resolve(false);
|
|
18
28
|
});
|
|
19
|
-
|
|
29
|
+
|
|
30
|
+
sock.connect(port, host);
|
|
20
31
|
});
|
|
21
32
|
}
|
|
22
33
|
|
|
34
|
+
/**
|
|
35
|
+
* 检查端口是否在所有网络接口上监听
|
|
36
|
+
* Docker 环境中,服务可能绑定到 0.0.0.0 而不是 127.0.0.1
|
|
37
|
+
*/
|
|
38
|
+
async function checkPortOnAllInterfaces(port) {
|
|
39
|
+
// 先检查 localhost
|
|
40
|
+
const localhostListening = await checkPortListening(port, '127.0.0.1');
|
|
41
|
+
if (localhostListening) return true;
|
|
42
|
+
|
|
43
|
+
// 再检查 0.0.0.0(所有接口)
|
|
44
|
+
const allInterfacesListening = await checkPortListening(port, '0.0.0.0');
|
|
45
|
+
if (allInterfacesListening) return true;
|
|
46
|
+
|
|
47
|
+
// 获取所有网络接口并逐一检查
|
|
48
|
+
const interfaces = os.networkInterfaces();
|
|
49
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
50
|
+
for (const addr of addrs) {
|
|
51
|
+
if (addr.family === 'IPv4' && !addr.internal) {
|
|
52
|
+
const listening = await checkPortListening(port, addr.address);
|
|
53
|
+
if (listening) {
|
|
54
|
+
console.log(`[PortChecker] 端口 ${port} 在 ${addr.address} (${name}) 上监听`);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 通过系统命令检查端口是否监听(备选方法)
|
|
66
|
+
* 当 net.Socket 连接失败时使用
|
|
67
|
+
*/
|
|
68
|
+
function checkPortViaSystemCommand(port) {
|
|
69
|
+
try {
|
|
70
|
+
// 按优先级尝试多种工具
|
|
71
|
+
const commands = [
|
|
72
|
+
`ss -tlnp 2>/dev/null | grep -q ":${port} " && echo "found"`,
|
|
73
|
+
`netstat -tlnp 2>/dev/null | grep -q ":${port} " && echo "found"`,
|
|
74
|
+
`lsof -i :${port} 2>/dev/null | grep -q LISTEN && echo "found"`,
|
|
75
|
+
`fuser ${port}/tcp 2>/dev/null | grep -q '[0-9]' && echo "found"`
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
for (const cmd of commands) {
|
|
79
|
+
try {
|
|
80
|
+
const result = execSync(cmd, { encoding: 'utf8', timeout: 5000 }).trim();
|
|
81
|
+
if (result === 'found') {
|
|
82
|
+
console.log(`[PortChecker] 系统命令检测到端口 ${port} 正在监听`);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// 命令失败,尝试下一个
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error(`[PortChecker] 系统命令检查端口失败: ${e.message}`);
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 检查 openclaw 进程是否存在
|
|
97
|
+
*/
|
|
98
|
+
function checkProcessExists() {
|
|
99
|
+
try {
|
|
100
|
+
// 尝试多种方法检查进程
|
|
101
|
+
const commands = [
|
|
102
|
+
'ps aux | grep -v grep | grep openclaw',
|
|
103
|
+
'pgrep -f openclaw',
|
|
104
|
+
'pidof openclaw'
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
for (const cmd of commands) {
|
|
108
|
+
try {
|
|
109
|
+
const output = execSync(cmd, {
|
|
110
|
+
encoding: 'utf8',
|
|
111
|
+
timeout: 5000
|
|
112
|
+
}).trim();
|
|
113
|
+
|
|
114
|
+
if (output) {
|
|
115
|
+
console.log(`[PortChecker] 检测到 openclaw 进程存在 (通过: ${cmd.split(' ')[0]})`);
|
|
116
|
+
console.log(`[PortChecker] 进程信息: ${output.split('\n')[0]}`);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
} catch (e) {
|
|
120
|
+
// 命令失败,尝试下一个
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(`[PortChecker] 未检测到 openclaw 进程`);
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.error(`[PortChecker] 进程检查失败: ${e.message}`);
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
23
131
|
async function getOpenClawStatus(port = 18789) {
|
|
24
|
-
|
|
25
|
-
|
|
132
|
+
// 方法1: 通过 net.Socket 检查端口
|
|
133
|
+
const isListening = await checkPortOnAllInterfaces(port);
|
|
134
|
+
if (isListening) {
|
|
135
|
+
console.log(`[PortChecker] 端口 ${port} 检测为运行中(net.Socket)`);
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 方法2: 通过系统命令检查端口
|
|
140
|
+
const isListeningViaCmd = checkPortViaSystemCommand(port);
|
|
141
|
+
if (isListeningViaCmd) {
|
|
142
|
+
console.log(`[PortChecker] 端口 ${port} 检测为运行中(系统命令)`);
|
|
143
|
+
return 1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 方法3: 检查进程是否存在(备选)
|
|
147
|
+
const processExists = checkProcessExists();
|
|
148
|
+
if (processExists) {
|
|
149
|
+
console.warn(`[PortChecker] 警告: openclaw 进程存在,但端口 ${port} 未监听。可能绑定到其他端口或地址。`);
|
|
150
|
+
// 进程存在但端口未监听,返回特殊状态 2
|
|
151
|
+
return 2;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(`[PortChecker] 端口 ${port} 检测为未运行`);
|
|
155
|
+
return 0;
|
|
26
156
|
}
|
|
27
157
|
|
|
28
158
|
module.exports = {
|
|
29
159
|
checkPortListening,
|
|
160
|
+
checkPortOnAllInterfaces,
|
|
30
161
|
getOpenClawStatus,
|
|
31
162
|
};
|
|
@@ -326,9 +326,20 @@ class RongyunMessageHandler {
|
|
|
326
326
|
// 获取 OpenClaw 运行状态(检查端口 18789)
|
|
327
327
|
const openClawStatus = await getOpenClawStatus();
|
|
328
328
|
|
|
329
|
-
//
|
|
329
|
+
// 构建状态数据
|
|
330
|
+
// openClawStatus: 1=端口监听正常, 2=进程存在但端口未监听, 0=未运行
|
|
331
|
+
let statusMessage;
|
|
332
|
+
if (openClawStatus === 1) {
|
|
333
|
+
statusMessage = '运行中';
|
|
334
|
+
} else if (openClawStatus === 2) {
|
|
335
|
+
statusMessage = '运行中(端口异常)';
|
|
336
|
+
} else {
|
|
337
|
+
statusMessage = '未运行';
|
|
338
|
+
}
|
|
339
|
+
|
|
330
340
|
const statusData = {
|
|
331
|
-
open_claw_status: openClawStatus, // 1=运行中, 0=未运行
|
|
341
|
+
open_claw_status: openClawStatus, // 1=运行中, 2=进程存在但端口未监听, 0=未运行
|
|
342
|
+
status_message: statusMessage,
|
|
332
343
|
mac_address: getMacAddress(),
|
|
333
344
|
version: '0.0.20',
|
|
334
345
|
timestamp: Date.now(),
|
|
@@ -103,11 +103,16 @@ class ScriptExecutor {
|
|
|
103
103
|
console.log(`[ScriptExecutor] 停止脚本退出码非零(${exitCode}),返回错误`);
|
|
104
104
|
return {
|
|
105
105
|
status: OpenClawServiceStatus.ERROR,
|
|
106
|
-
message: `停止失败: 脚本退出码 ${exitCode}
|
|
106
|
+
message: `停止失败: 脚本退出码 ${exitCode}`,
|
|
107
|
+
output: fullOutput
|
|
107
108
|
};
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
// 返回结果时附带原始输出
|
|
112
|
+
return {
|
|
113
|
+
...result,
|
|
114
|
+
output: fullOutput
|
|
115
|
+
};
|
|
111
116
|
} catch (e) {
|
|
112
117
|
const msg = e instanceof Error ? e.message : String(e);
|
|
113
118
|
return { status: OpenClawServiceStatus.ERROR, message: `执行异常: ${msg}` };
|