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.
package/command/linux/start.sh
CHANGED
|
@@ -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
|
-
# 等待
|
|
256
|
-
sleep
|
|
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
|
|
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 监听的端口:"
|
package/command/linux/stop.sh
CHANGED
|
@@ -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
|
@@ -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
|
-
|
|
185
|
-
console.log(`[OpenClawControl]
|
|
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] ${
|
|
248
|
+
console.log(`[OpenClawControl] ${actionName} 成功: ${result.message}`);
|
|
225
249
|
} else if (result.status === OpenClawServiceStatus.ERROR) {
|
|
226
|
-
console.log(`[OpenClawControl] ${
|
|
250
|
+
console.log(`[OpenClawControl] ${actionName} 失败: ${result.message}`);
|
|
227
251
|
} else if (result.status === OpenClawServiceStatus.NOT_INSTALL) {
|
|
228
|
-
console.log(`[OpenClawControl] ${
|
|
252
|
+
console.log(`[OpenClawControl] ${actionName}: OpenClaw未安装`);
|
|
229
253
|
} else {
|
|
230
|
-
console.log(`[OpenClawControl] ${
|
|
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
|
-
|
|
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(
|
|
85
|
+
await this.handleCommand(data);
|
|
73
86
|
break;
|
|
74
87
|
case RongyunMessageTypeEnum.CHAT_MESSAGE:
|
|
75
|
-
await this.handleChatMessage(
|
|
88
|
+
await this.handleChatMessage(data);
|
|
76
89
|
break;
|
|
77
90
|
case RongyunMessageTypeEnum.CREATE_OPENCODE_SESSION:
|
|
78
|
-
await this.handleCreateSession(
|
|
91
|
+
await this.handleCreateSession(data);
|
|
79
92
|
break;
|
|
80
93
|
case RongyunMessageTypeEnum.DELETE_OPENCODE_SESSION:
|
|
81
|
-
await this.handleDeleteSession(
|
|
94
|
+
await this.handleDeleteSession(data);
|
|
82
95
|
break;
|
|
83
96
|
case RongyunMessageTypeEnum.DEVICE_CONTROL:
|
|
84
|
-
await this.handleDeviceControl(
|
|
97
|
+
await this.handleDeviceControl(data);
|
|
85
98
|
break;
|
|
86
99
|
case RongyunMessageTypeEnum.DEVICE_STATUS_REQUEST:
|
|
87
|
-
await this.handleDeviceStatusRequest(
|
|
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: `${
|
|
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
|
-
|
|
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') {
|