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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.89",
3
+ "version": "0.0.91",
4
4
  "description": "虾说智能助手",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -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
- return await executor.executeWithStatus(command, scriptName);
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
- status: OpenClawServiceStatus.ERROR,
100
- message: `执行异常: ${msg}`
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 === 1 ? '运行中' : '未运行'}`);
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 === 1 ? '运行中' : '未运行'}`);
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
- result = await executeWithScript(command);
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(3000);
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
- sock.once('error', () => {
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
- sock.connect(port, '127.0.0.1');
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
- const isListening = await checkPortListening(port);
25
- return isListening ? 1 : 0;
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
- // 构建精简状态数据(避免超过融云 128KB 限制)
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
- return result;
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}` };