claw-subagent-service 0.0.131 → 0.0.134

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.
@@ -252,8 +252,8 @@ start_docker() {
252
252
  log_info "尝试启动: openclaw gateway run --port $PORT"
253
253
  nohup openclaw gateway run --port "$PORT" > "$log_file" 2>&1 &
254
254
 
255
- # 等待 5 秒检查进程是否启动(给更多时间)
256
- sleep 5
255
+ # 等待 10 秒检查进程是否启动(给更多时间)
256
+ sleep 10
257
257
  local started_pid=$!
258
258
  log_info "启动的进程 PID: $started_pid"
259
259
 
@@ -272,7 +272,7 @@ start_docker() {
272
272
 
273
273
  # 检查进程监听的端口
274
274
  log_info "检查进程监听的端口..."
275
- sleep 2
275
+ sleep 5
276
276
  local listening_ports=$(netstat -tlnp 2>/dev/null | grep "$started_pid" || ss -tlnp 2>/dev/null | grep "$started_pid" || echo "")
277
277
  if [ -n "$listening_ports" ]; then
278
278
  log_info "进程 $started_pid 监听的端口:"
@@ -460,6 +460,12 @@ show_help() {
460
460
  main() {
461
461
  local force=""
462
462
 
463
+ # 检查 FORCE 环境变量(来自 script-executor 的强制停止模式)
464
+ if [ "${FORCE:-}" = "1" ]; then
465
+ force="1"
466
+ log_info "检测到 FORCE=1 环境变量,启用强制停止模式"
467
+ fi
468
+
463
469
  # 解析命令行参数
464
470
  while [ $# -gt 0 ]; do
465
471
  case $1 in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.131",
3
+ "version": "0.0.134",
4
4
  "description": "虾说智能助手",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -56,10 +56,31 @@ function getCommandName(command) {
56
56
  return names[command] || '未知命令';
57
57
  }
58
58
 
59
+ /**
60
+ * 检测是否在 Docker 环境(无 systemd)
61
+ */
62
+ function isDockerEnvironment() {
63
+ try {
64
+ const fs = require('fs');
65
+ // 检查 /proc/1/cgroup 是否包含 docker
66
+ const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8');
67
+ return cgroup.includes('docker');
68
+ } catch (e) {
69
+ return false;
70
+ }
71
+ }
72
+
59
73
  /**
60
74
  * 使用 ServiceManager 管理服务(systemd 模式)
75
+ * 注意:在 Docker 环境中不应该使用 ServiceManager,因为 Docker 通常没有 systemd
61
76
  */
62
77
  async function manageWithServiceManager(command) {
78
+ // 如果在 Docker 环境中,直接返回 null,让上层回退到脚本方式
79
+ if (isDockerEnvironment()) {
80
+ console.log('[OpenClawControl] 检测到 Docker 环境,跳过 ServiceManager,使用脚本方式');
81
+ return null;
82
+ }
83
+
63
84
  const serviceMgr = new ServiceManager('openclaw-gateway', 'OpenClaw Gateway');
64
85
 
65
86
  try {
@@ -91,14 +112,16 @@ async function manageWithServiceManager(command) {
91
112
 
92
113
  /**
93
114
  * 使用 ScriptExecutor 执行脚本(Docker 模式)
115
+ * @param {number} command - 命令代码
116
+ * @param {boolean} force - 是否强制停止
94
117
  * @returns {Object} { result, output }
95
118
  */
96
- async function executeWithScript(command) {
119
+ async function executeWithScript(command, force = false) {
97
120
  const executor = getExecutor();
98
121
  const scriptName = getScriptName(command);
99
122
 
100
123
  try {
101
- const result = await executor.executeWithStatus(command, scriptName);
124
+ const result = await executor.executeWithStatus(command, scriptName, force);
102
125
  // ScriptExecutor 现在返回 { status, message, output }
103
126
  return {
104
127
  result: { status: result.status, message: result.message },
@@ -179,10 +202,11 @@ async function verifyCommandResult(command, result, scriptOutput = '') {
179
202
  return result;
180
203
  }
181
204
 
182
- async function executeCommand(command, window, sendResponse) {
205
+ async function executeCommand(command, window, sendResponse, force = false) {
183
206
  const cmdName = getCommandName(command);
184
- console.log(`[OpenClawControl] ====== 开始执行 ${cmdName} 命令 ======`);
185
- console.log(`[OpenClawControl] 命令代码: ${command}`);
207
+ const actionName = force ? '强制' + cmdName : cmdName;
208
+ console.log(`[OpenClawControl] ====== 开始执行 ${actionName} 命令 ======`);
209
+ console.log(`[OpenClawControl] 命令代码: ${command}, force: ${force}`);
186
210
  console.log(`[OpenClawControl] 平台: ${process.platform}`);
187
211
 
188
212
  let result;
@@ -202,7 +226,7 @@ async function executeCommand(command, window, sendResponse) {
202
226
  let scriptOutput = '';
203
227
  if (!result) {
204
228
  console.log(`[OpenClawControl] 使用脚本方式执行...`);
205
- const scriptResult = await executeWithScript(command);
229
+ const scriptResult = await executeWithScript(command, force);
206
230
  result = scriptResult.result;
207
231
  scriptOutput = scriptResult.output;
208
232
  console.log(`[OpenClawControl] 脚本执行结果: ${result.status} - ${result.message}`);
@@ -221,13 +245,13 @@ async function executeCommand(command, window, sendResponse) {
221
245
  result.status === OpenClawServiceStatus.STOP_SUCCESS ||
222
246
  result.status === OpenClawServiceStatus.RESTART_SUCCESS ||
223
247
  result.status === OpenClawServiceStatus.CONFIG_FIX_SUCCESS) {
224
- console.log(`[OpenClawControl] ${cmdName} 成功: ${result.message}`);
248
+ console.log(`[OpenClawControl] ${actionName} 成功: ${result.message}`);
225
249
  } else if (result.status === OpenClawServiceStatus.ERROR) {
226
- console.log(`[OpenClawControl] ${cmdName} 失败: ${result.message}`);
250
+ console.log(`[OpenClawControl] ${actionName} 失败: ${result.message}`);
227
251
  } else if (result.status === OpenClawServiceStatus.NOT_INSTALL) {
228
- console.log(`[OpenClawControl] ${cmdName}: OpenClaw未安装`);
252
+ console.log(`[OpenClawControl] ${actionName}: OpenClaw未安装`);
229
253
  } else {
230
- console.log(`[OpenClawControl] ${cmdName} 状态: ${result.message}`);
254
+ console.log(`[OpenClawControl] ${actionName} 状态: ${result.message}`);
231
255
  }
232
256
 
233
257
  if (sendResponse) {
@@ -243,16 +267,19 @@ async function executeCommand(command, window, sendResponse) {
243
267
  const { getOpenClawStatus } = require('./port-checker');
244
268
  const portStatus = await getOpenClawStatus(18789);
245
269
  console.log(`[OpenClawControl] 端口状态: ${portStatus}`);
246
- // 更新真实状态
270
+ // 更新真实状态 - 始终以端口检测结果为准
247
271
  if (command === OpenClawCommandEnum.STOP) {
248
272
  realStatus = portStatus === 1 ? OpenClawServiceStatus.RUNNING : OpenClawServiceStatus.STOP_SUCCESS;
249
273
  realMessage = portStatus === 1 ? '停止失败: 服务仍在运行' : '服务已停止';
274
+ if (portStatus === 1) httpStatus = 'error';
250
275
  } else if (command === OpenClawCommandEnum.START) {
251
276
  realStatus = portStatus === 1 ? OpenClawServiceStatus.START_SUCCESS : OpenClawServiceStatus.ERROR;
252
277
  realMessage = portStatus === 1 ? '服务已启动' : '启动失败: 服务未运行';
278
+ if (portStatus !== 1) httpStatus = 'error';
253
279
  } else if (command === OpenClawCommandEnum.RESTART) {
254
280
  realStatus = portStatus === 1 ? OpenClawServiceStatus.RESTART_SUCCESS : OpenClawServiceStatus.ERROR;
255
281
  realMessage = portStatus === 1 ? '服务已重启' : '重启失败: 服务未运行';
282
+ if (portStatus !== 1) httpStatus = 'error';
256
283
  }
257
284
  console.log(`[OpenClawControl] 操作后真实状态: portStatus=${portStatus}, realStatus=${realStatus}, message=${realMessage}`);
258
285
  } catch (e) {
@@ -64,27 +64,40 @@ class RongyunMessageHandler {
64
64
  return;
65
65
  }
66
66
 
67
- const msgType = parsed.msg_type;
67
+ // 解析 content 字段(前端将业务数据放在 content 内)
68
+ let data = parsed;
69
+ if (parsed.content && typeof parsed.content === 'string') {
70
+ try {
71
+ const contentData = JSON.parse(parsed.content);
72
+ // 将 content 内的数据合并到顶层,方便处理器直接访问
73
+ // 保留原始 content 字段(用于聊天消息等场景)
74
+ data = { ...parsed, ...contentData, _raw_content: parsed.content };
75
+ } catch (e) {
76
+ this.logWarn(`解析 content 失败: ${e.message}`);
77
+ }
78
+ }
79
+
80
+ const msgType = data.msg_type;
68
81
  this.logInfo(`[RongyunMessageHandler] 处理消息类型: ${msgType}`);
69
82
 
70
83
  switch (msgType) {
71
84
  case RongyunMessageTypeEnum.COMMAND:
72
- await this.handleCommand(parsed);
85
+ await this.handleCommand(data);
73
86
  break;
74
87
  case RongyunMessageTypeEnum.CHAT_MESSAGE:
75
- await this.handleChatMessage(parsed);
88
+ await this.handleChatMessage(data);
76
89
  break;
77
90
  case RongyunMessageTypeEnum.CREATE_OPENCODE_SESSION:
78
- await this.handleCreateSession(parsed);
91
+ await this.handleCreateSession(data);
79
92
  break;
80
93
  case RongyunMessageTypeEnum.DELETE_OPENCODE_SESSION:
81
- await this.handleDeleteSession(parsed);
94
+ await this.handleDeleteSession(data);
82
95
  break;
83
96
  case RongyunMessageTypeEnum.DEVICE_CONTROL:
84
- await this.handleDeviceControl(parsed);
97
+ await this.handleDeviceControl(data);
85
98
  break;
86
99
  case RongyunMessageTypeEnum.DEVICE_STATUS_REQUEST:
87
- await this.handleDeviceStatusRequest(parsed);
100
+ await this.handleDeviceStatusRequest(data);
88
101
  break;
89
102
  default:
90
103
  this.logWarn(`未处理的消息类型: ${msgType}`);
@@ -100,8 +113,9 @@ class RongyunMessageHandler {
100
113
  const commandId = data.command_id;
101
114
  const requestId = data.request_id;
102
115
  const sourceId = data.source_im_id;
116
+ const force = data.force === true; // 是否强制停止
103
117
 
104
- this.logInfo(`[RongyunMessageHandler] 收到命令: command=${command}, command_id=${commandId}, from=${sourceId || 'guardserver'}`);
118
+ this.logInfo(`[RongyunMessageHandler] 收到命令: command=${command}, command_id=${commandId}, force=${force}, from=${sourceId || 'guardserver'}`);
105
119
 
106
120
  // 验证命令是否有效
107
121
  const validCommands = Object.values(OpenClawCommandEnum);
@@ -143,11 +157,12 @@ class RongyunMessageHandler {
143
157
 
144
158
  if (isAsyncCommand) {
145
159
  // 立即响应,告知前端命令已接收
160
+ const actionName = force ? '强制' + getCommandName(command) : getCommandName(command);
146
161
  await this.sendResponse(RongyunMessageTypeEnum.COMMAND_RESULT, {
147
162
  command,
148
163
  command_id: commandId,
149
164
  status: 'success',
150
- message: `${getCommandName(command)}命令已接收,正在执行...`
165
+ message: `${actionName}命令已接收,正在执行...`
151
166
  }, requestId, sourceId);
152
167
  }
153
168
 
@@ -163,8 +178,8 @@ class RongyunMessageHandler {
163
178
  }
164
179
  // 异步命令不在这里响应,因为已经提前响应了
165
180
  // 但我们可以在这里记录执行结果
166
- this.logInfo(`[RongyunMessageHandler] 命令执行完成: command=${command}, status=${response.status}, message=${response.message}`);
167
- }).then(() => {
181
+ this.logInfo(`[RongyunMessageHandler] 命令执行完成: command=${command}, force=${force}, status=${response.status}, message=${response.message}`);
182
+ }, force).then(() => {
168
183
  // 命令执行完成后,立即释放锁
169
184
  this.commandLock = false;
170
185
  if (this.commandLockTimer) {
@@ -206,7 +221,8 @@ class RongyunMessageHandler {
206
221
  async handleChatMessage(data) {
207
222
  const roomId = data.room_id;
208
223
  const sessionId = data.gateway_session_id || data.session_id;
209
- const content = data.content;
224
+ // 使用解析后的 content(聊天内容),如果没有则使用原始 content
225
+ const content = data.content || data._raw_content;
210
226
  const requestId = data.request_id;
211
227
 
212
228
  this.logInfo(`[RongyunMessageHandler] 收到聊天消息, roomId=${roomId}, sessionId=${sessionId}`);
@@ -79,7 +79,7 @@ class ScriptExecutor {
79
79
  }
80
80
  }
81
81
 
82
- async executeWithStatus(command, scriptName) {
82
+ async executeWithStatus(command, scriptName, force = false) {
83
83
  // 配置修复命令特殊处理:直接执行命令,不需要脚本
84
84
  if (command === OpenClawCommandEnum.CONFIG_FIX) {
85
85
  return await this.executeCommandDirect('openclaw doctor --fix', 120);
@@ -88,8 +88,8 @@ class ScriptExecutor {
88
88
  const scriptPath = path.join(this.scriptDir, scriptName);
89
89
 
90
90
  try {
91
- console.log(`[ScriptExecutor] 开始执行脚本: ${scriptPath}`);
92
- const { stdout, stderr, exitCode } = await this.runScript(scriptPath);
91
+ console.log(`[ScriptExecutor] 开始执行脚本: ${scriptPath}, force=${force}`);
92
+ const { stdout, stderr, exitCode } = await this.runScript(scriptPath, force);
93
93
  const fullOutput = stdout + stderr;
94
94
 
95
95
  // 调试日志:记录脚本实际输出
@@ -206,7 +206,7 @@ class ScriptExecutor {
206
206
  });
207
207
  }
208
208
 
209
- runScript(scriptPath) {
209
+ runScript(scriptPath, force = false) {
210
210
  // Linux/Mac 系统:自动修复 shell 脚本的 CRLF 换行符
211
211
  if (process.platform !== 'win32') {
212
212
  try {
@@ -233,6 +233,12 @@ class ScriptExecutor {
233
233
  cmd = 'bash';
234
234
  args = [scriptPath];
235
235
  }
236
+
237
+ // 如果是强制停止,添加 FORCE 环境变量
238
+ if (force) {
239
+ args.unshift('FORCE=1');
240
+ console.log(`[ScriptExecutor] 强制停止模式: FORCE=1`);
241
+ }
236
242
 
237
243
  // 调试日志:记录执行环境
238
244
  if (process.platform !== 'win32') {