claw-subagent-service 0.0.0
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/README.md +44 -0
- package/cli.js +254 -0
- package/command/linux/restart.sh +98 -0
- package/command/linux/start.sh +101 -0
- package/command/linux/status.sh +140 -0
- package/command/linux/stop.sh +112 -0
- package/command/win/restart.bat +39 -0
- package/command/win/start.bat +65 -0
- package/command/win/status.bat +52 -0
- package/command/win/stop.bat +55 -0
- package/command/win/windows/345/220/257/345/212/250/350/204/232/346/234/254 +0 -0
- package/package.json +37 -0
- package/scripts/install-silent.js +167 -0
- package/scripts/uninstall.js +61 -0
- package/service/daemon.js +189 -0
- package/service/logger.js +31 -0
- package/service/modules/auth.js +17 -0
- package/service/modules/business-message-handler.js +118 -0
- package/service/modules/command-handler.js +152 -0
- package/service/modules/config.js +44 -0
- package/service/modules/dashboard-collector.js +588 -0
- package/service/modules/heartbeat-dashboard.js +153 -0
- package/service/modules/mac-address.js +15 -0
- package/service/modules/message-processor-example.js +72 -0
- package/service/modules/message-processor.js +62 -0
- package/service/modules/normal-message-handler.js +60 -0
- package/service/modules/openclaw-control.js +128 -0
- package/service/modules/openclaw-enum.js +48 -0
- package/service/modules/opencode-service.js +199 -0
- package/service/modules/opencode-starter.js +194 -0
- package/service/modules/port-checker.js +31 -0
- package/service/modules/rongyun-message-handler.js +250 -0
- package/service/modules/rongyun-message-sender.js +157 -0
- package/service/modules/rongyun-message-types.js +28 -0
- package/service/modules/script-executor.js +550 -0
- package/service/modules/service-manager.js +319 -0
- package/service/modules/structured-message-router.js +118 -0
- package/service/rongcloud/env-polyfill.js +95 -0
- package/service/rongcloud/index.js +19 -0
- package/service/rongcloud/message-handler.js +147 -0
- package/service/rongcloud/message-types.js +22 -0
- package/service/rongcloud/openclaw-client.js +98 -0
- package/service/rongcloud/openclaw-config.js +108 -0
- package/service/rongcloud/rongcloud-client.js +273 -0
- package/service/rongcloud/types.js +9 -0
- package/service/updater.js +348 -0
- package/service/worker.js +376 -0
- package/version.json +4 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const { RongyunMessageTypeEnum } = require('./rongyun-message-types');
|
|
2
|
+
const { collectDashboardData } = require('./dashboard-collector');
|
|
3
|
+
const { RongyunMessageSender } = require('./rongyun-message-sender');
|
|
4
|
+
|
|
5
|
+
class HeartbeatManager {
|
|
6
|
+
constructor(rongcloudClient, config, log) {
|
|
7
|
+
this.rongcloudClient = rongcloudClient;
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.log = log;
|
|
10
|
+
this.timer = null;
|
|
11
|
+
this.messageSender = new RongyunMessageSender(rongcloudClient, config, log);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
start(getMacAddress, getOpenClawStatus) {
|
|
15
|
+
const interval = (this.config.heartbeatInterval || 20) * 1000;
|
|
16
|
+
this.log?.info(`[HeartbeatManager] 启动心跳定时器,间隔: ${interval}ms`);
|
|
17
|
+
|
|
18
|
+
this.timer = setInterval(async () => {
|
|
19
|
+
if (!this.rongcloudClient?.isConnected) return;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const mac = getMacAddress();
|
|
23
|
+
const status = await getOpenClawStatus(this.config.openclawPort || 18789);
|
|
24
|
+
const sent = await this.messageSender.sendProtocolMessage(
|
|
25
|
+
RongyunMessageTypeEnum.HEARTBEAT,
|
|
26
|
+
{
|
|
27
|
+
mac_address: mac,
|
|
28
|
+
nickname: this.config.nodeName,
|
|
29
|
+
open_claw_status: status,
|
|
30
|
+
client_status: 1,
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
if (sent) {
|
|
34
|
+
this.log?.info('[HeartbeatManager] 心跳已发送');
|
|
35
|
+
} else {
|
|
36
|
+
this.log?.warn('[HeartbeatManager] 心跳发送失败');
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
this.log?.error(`[HeartbeatManager] 心跳发送异常: ${err.message}`);
|
|
40
|
+
}
|
|
41
|
+
}, interval);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
stop() {
|
|
45
|
+
if (this.timer) {
|
|
46
|
+
clearInterval(this.timer);
|
|
47
|
+
this.timer = null;
|
|
48
|
+
this.log?.info('[HeartbeatManager] 心跳定时器已停止');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class DashboardReporter {
|
|
54
|
+
constructor(rongcloudClient, config, log) {
|
|
55
|
+
this.rongcloudClient = rongcloudClient;
|
|
56
|
+
this.config = config;
|
|
57
|
+
this.log = log;
|
|
58
|
+
this.timer = null;
|
|
59
|
+
this.messageSender = new RongyunMessageSender(rongcloudClient, config, log);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
start(getMacAddress) {
|
|
63
|
+
const interval = 30000; // 30秒
|
|
64
|
+
this.log?.info(`[DashboardReporter] 启动仪表盘上报定时器,间隔: ${interval}ms`);
|
|
65
|
+
|
|
66
|
+
this.timer = setInterval(async () => {
|
|
67
|
+
if (!this.rongcloudClient?.isConnected) return;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const data = await collectDashboardData();
|
|
71
|
+
const timestamp = data.diagnostics?.generatedAt || new Date().toISOString();
|
|
72
|
+
const mac = getMacAddress();
|
|
73
|
+
|
|
74
|
+
// 拆分为 6 条消息发送
|
|
75
|
+
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_SESSIONS, {
|
|
76
|
+
mac_address: mac,
|
|
77
|
+
sessions: data.sessions,
|
|
78
|
+
sessionStatuses: data.sessionStatuses,
|
|
79
|
+
timestamp,
|
|
80
|
+
}, 1000);
|
|
81
|
+
|
|
82
|
+
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_JOBS, {
|
|
83
|
+
mac_address: mac,
|
|
84
|
+
cronJobs: data.cronJobs,
|
|
85
|
+
approvals: data.approvals,
|
|
86
|
+
timestamp,
|
|
87
|
+
}, 1000);
|
|
88
|
+
|
|
89
|
+
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_PROJECTS, {
|
|
90
|
+
mac_address: mac,
|
|
91
|
+
projects: data.projects,
|
|
92
|
+
tasks: data.tasks,
|
|
93
|
+
timestamp,
|
|
94
|
+
}, 1000);
|
|
95
|
+
|
|
96
|
+
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_SUMMARIES, {
|
|
97
|
+
mac_address: mac,
|
|
98
|
+
projectSummaries: data.projectSummaries,
|
|
99
|
+
tasksSummary: data.tasksSummary,
|
|
100
|
+
budgetSummary: data.budgetSummary,
|
|
101
|
+
diagnostics: data.diagnostics,
|
|
102
|
+
timestamp,
|
|
103
|
+
}, 1000);
|
|
104
|
+
|
|
105
|
+
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_SESSIONS_CONTEXTS, {
|
|
106
|
+
mac_address: mac,
|
|
107
|
+
sessionsContexts: (data.sessionsContexts || []).slice(0, 50),
|
|
108
|
+
timestamp,
|
|
109
|
+
}, 1000);
|
|
110
|
+
|
|
111
|
+
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_USAGE_EVENTS, {
|
|
112
|
+
mac_address: mac,
|
|
113
|
+
usageEvents: (data.usageEvents || []).slice(-100),
|
|
114
|
+
timestamp,
|
|
115
|
+
}, 0);
|
|
116
|
+
|
|
117
|
+
this.log?.info('[DashboardReporter] 所有数据块发送完成');
|
|
118
|
+
} catch (err) {
|
|
119
|
+
this.log?.error(`[DashboardReporter] 上报异常: ${err.message}`);
|
|
120
|
+
}
|
|
121
|
+
}, interval);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async sendChunk(msgType, data, delayMs) {
|
|
125
|
+
try {
|
|
126
|
+
const sent = await this.messageSender.sendProtocolMessage(msgType, data);
|
|
127
|
+
if (sent) {
|
|
128
|
+
this.log?.info(`[DashboardReporter] ${msgType} 发送成功`);
|
|
129
|
+
} else {
|
|
130
|
+
this.log?.warn(`[DashboardReporter] ${msgType} 发送失败`);
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
this.log?.error(`[DashboardReporter] ${msgType} 发送异常: ${err.message}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (delayMs > 0) {
|
|
137
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
stop() {
|
|
142
|
+
if (this.timer) {
|
|
143
|
+
clearInterval(this.timer);
|
|
144
|
+
this.timer = null;
|
|
145
|
+
this.log?.info('[DashboardReporter] 仪表盘上报定时器已停止');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
HeartbeatManager,
|
|
152
|
+
DashboardReporter
|
|
153
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
|
|
3
|
+
function getMacAddress() {
|
|
4
|
+
const interfaces = os.networkInterfaces();
|
|
5
|
+
for (const name of Object.keys(interfaces)) {
|
|
6
|
+
for (const iface of interfaces[name]) {
|
|
7
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
8
|
+
return iface.mac.replace(/:/g, '').toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return 'unknown';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { getMacAddress };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 完整消息处理示例 - 展示如何接收 rongcloud 转发的消息
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { MessageProcessor } = require('./modules/message-processor');
|
|
6
|
+
|
|
7
|
+
// ========== 使用示例 ==========
|
|
8
|
+
|
|
9
|
+
// 1. 创建消息处理器
|
|
10
|
+
const messageProcessor = new MessageProcessor(config, rongcloudClient, log);
|
|
11
|
+
|
|
12
|
+
// 2. 连接融云,传入处理器
|
|
13
|
+
// rongcloud-client.js 第42-43行会保存这个 handler
|
|
14
|
+
// rongcloud-client.js 第161-164行会调用 handler.handleMessage(msg)
|
|
15
|
+
await rongcloudClient.connect(messageProcessor);
|
|
16
|
+
|
|
17
|
+
// ========== 消息接收流程 ==========
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 消息接收流程:
|
|
21
|
+
*
|
|
22
|
+
* 1. 融云SDK收到消息
|
|
23
|
+
* ↓
|
|
24
|
+
* 2. rongcloud-client.js 的 handleReceivedMessage() 被调用
|
|
25
|
+
* 位置:rongcloud-client.js 第115行
|
|
26
|
+
* ↓
|
|
27
|
+
* 3. 检查是否是结构化消息(有 msg_type 字段)
|
|
28
|
+
* 位置:rongcloud-client.js 第131-138行
|
|
29
|
+
* - 如果是结构化消息且 msg_type 在 SYSTEM_MSG_TYPES 中 → 直接 return,不转发
|
|
30
|
+
* - 如果是普通消息 → 继续处理
|
|
31
|
+
* ↓
|
|
32
|
+
* 4. 构建 rongCloudMsg 对象
|
|
33
|
+
* 位置:rongcloud-client.js 第150-159行
|
|
34
|
+
* ↓
|
|
35
|
+
* 5. 调用 handler.handleMessage(rongCloudMsg)
|
|
36
|
+
* 位置:rongcloud-client.js 第163行
|
|
37
|
+
* ↓
|
|
38
|
+
* 6. MessageProcessor.handleMessage(msg) 被调用
|
|
39
|
+
* 位置:modules/message-processor.js
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
// ========== handleMessage 方法签名 ==========
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* handleMessage(msg) 接收的消息格式:
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} msg
|
|
48
|
+
* @param {string} msg.senderUserId - 发送者用户ID
|
|
49
|
+
* @param {string} msg.targetId - 目标用户ID(群聊时为群ID)
|
|
50
|
+
* @param {number} msg.conversationType - 会话类型:1=私聊,3=群聊
|
|
51
|
+
* @param {string} msg.content - 消息文本内容
|
|
52
|
+
* @param {string} msg.messageType - 融云消息类型,如 'RC:TxtMsg'
|
|
53
|
+
* @param {boolean} msg.isOffLineMessage - 是否为离线消息
|
|
54
|
+
* @param {string} msg.messageUId - 消息唯一ID
|
|
55
|
+
* @param {number} msg.sentTime - 消息发送时间戳
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
// ========== 示例:自定义消息处理 ==========
|
|
59
|
+
|
|
60
|
+
class CustomMessageProcessor extends MessageProcessor {
|
|
61
|
+
async handleNormalMessage(msg) {
|
|
62
|
+
// 自定义处理逻辑
|
|
63
|
+
this.log?.info(`[CustomMessageProcessor] 收到消息: ${msg.content}`);
|
|
64
|
+
|
|
65
|
+
// 例如:简单的 echo 回复
|
|
66
|
+
await this.sendReply(msg, `收到: ${msg.content}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 使用自定义处理器
|
|
71
|
+
const customProcessor = new CustomMessageProcessor(config, rongcloudClient, log);
|
|
72
|
+
await rongcloudClient.connect(customProcessor);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 消息处理器 - 接收 rongcloud 转发的消息
|
|
3
|
+
*
|
|
4
|
+
* rongcloud-client.js 会将消息转发到 handler.handleMessage(msg) 方法
|
|
5
|
+
* 位置:rongcloud-client.js 第161-164行
|
|
6
|
+
*
|
|
7
|
+
* 注意:rongcloud-client.js 第131-138行会过滤掉结构化消息(msg_type 在 SYSTEM_MSG_TYPES 中的)
|
|
8
|
+
* 所以此方法只能收到普通消息(AI聊天等)
|
|
9
|
+
*/
|
|
10
|
+
class MessageProcessor {
|
|
11
|
+
constructor(config, rongcloudClient, log) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.rongcloudClient = rongcloudClient;
|
|
14
|
+
this.log = log;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 接收 rongcloud 转发的普通消息
|
|
19
|
+
* 被调用位置:rongcloud-client.js 第163行
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} msg - 消息对象
|
|
22
|
+
* @param {string} msg.senderUserId - 发送者ID
|
|
23
|
+
* @param {string} msg.targetId - 目标ID
|
|
24
|
+
* @param {number} msg.conversationType - 会话类型 (1=私聊, 3=群聊)
|
|
25
|
+
* @param {string} msg.content - 消息内容
|
|
26
|
+
* @param {string} msg.messageType - 消息类型 (如 'RC:TxtMsg')
|
|
27
|
+
* @param {boolean} msg.isOffLineMessage - 是否离线消息
|
|
28
|
+
*/
|
|
29
|
+
async handleMessage(msg) {
|
|
30
|
+
this.log?.info(`[MessageProcessor] 收到普通消息 from=${msg.senderUserId}, content=${msg.content?.substring(0, 50)}`);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// 处理普通消息(AI聊天等)
|
|
34
|
+
await this.handleNormalMessage(msg);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
this.log?.error(`[MessageProcessor] 处理消息异常: ${err.message}`);
|
|
37
|
+
// 发送错误回复
|
|
38
|
+
await this.sendReply(msg, `处理失败: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 处理普通消息
|
|
43
|
+
async handleNormalMessage(msg) {
|
|
44
|
+
// TODO: 实现普通消息处理逻辑(AI聊天等)
|
|
45
|
+
this.log?.info('[MessageProcessor] 处理普通消息');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 发送回复
|
|
49
|
+
async sendReply(msg, content) {
|
|
50
|
+
try {
|
|
51
|
+
await this.rongcloudClient.sendMessage(
|
|
52
|
+
msg.senderUserId,
|
|
53
|
+
content,
|
|
54
|
+
msg.conversationType
|
|
55
|
+
);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
this.log?.error(`[MessageProcessor] 发送回复失败: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { MessageProcessor };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 普通消息处理器 - 接收 rongcloud 转发的普通消息并调用 AI 服务
|
|
3
|
+
*
|
|
4
|
+
* 被调用位置:rongcloud/message-handler.js 第68行
|
|
5
|
+
* 调用方式:await this.handleNormalMessage(msg)
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} msg - 消息对象
|
|
8
|
+
* @param {string} msg.content - 消息内容
|
|
9
|
+
* @param {string} msg.senderUserId - 发送者ID
|
|
10
|
+
* @param {string} msg.targetId - 目标ID
|
|
11
|
+
* @param {number} msg.conversationType - 会话类型 (1=私聊, 3=群聊)
|
|
12
|
+
* @returns {string} 回复内容
|
|
13
|
+
*/
|
|
14
|
+
const { forwardChatMessage } = require('./opencode-service');
|
|
15
|
+
|
|
16
|
+
async function handleNormalMessage(msg) {
|
|
17
|
+
console.log(`[NormalMessageHandler] 收到普通消息:`, {
|
|
18
|
+
from: msg.senderUserId,
|
|
19
|
+
content: msg.content?.substring(0, 50),
|
|
20
|
+
type: msg.conversationType === 1 ? '私聊' : '群聊'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// 使用 senderUserId 作为 session ID,确保每个用户有独立的会话
|
|
25
|
+
const sessionId = msg.senderUserId || 'default';
|
|
26
|
+
const content = msg.content;
|
|
27
|
+
|
|
28
|
+
if (!content || !content.trim()) {
|
|
29
|
+
return '消息内容为空';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 调用 opencode 服务进行 AI 聊天
|
|
33
|
+
let fullResponse = '';
|
|
34
|
+
|
|
35
|
+
await forwardChatMessage(
|
|
36
|
+
sessionId,
|
|
37
|
+
content,
|
|
38
|
+
async (delta) => {
|
|
39
|
+
// 收集响应片段
|
|
40
|
+
fullResponse += delta;
|
|
41
|
+
},
|
|
42
|
+
(level, message) => {
|
|
43
|
+
// 日志回调
|
|
44
|
+
console.log(`[CHAT-${level}] ${message}`);
|
|
45
|
+
},
|
|
46
|
+
600000 // 10分钟超时
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
console.log(`[NormalMessageHandler] AI 回复: ${fullResponse.substring(0, 50)}...`);
|
|
50
|
+
return fullResponse;
|
|
51
|
+
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error(`[NormalMessageHandler] 处理异常:`, err.message);
|
|
54
|
+
return `抱歉,处理消息时出错: ${err.message}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
handleNormalMessage
|
|
60
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw 控制模块 - 与桌面客户端对齐
|
|
3
|
+
*
|
|
4
|
+
* 执行 start/stop/restart/status 命令
|
|
5
|
+
* 基于 nodejs_client/src/main/openclaw-control.ts
|
|
6
|
+
*/
|
|
7
|
+
const { spawn } = require('child_process');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const { OpenClawCommandEnum, OpenClawServiceStatus, getServiceStatusMessage } = require('./openclaw-enum');
|
|
11
|
+
const { ScriptExecutor } = require('./script-executor');
|
|
12
|
+
const { getOpenClawStatus } = require('./port-checker');
|
|
13
|
+
|
|
14
|
+
let globalExecutor = null;
|
|
15
|
+
|
|
16
|
+
function getScriptDir() {
|
|
17
|
+
const system = process.platform;
|
|
18
|
+
const subDir = system === 'win32' ? path.join('command', 'win') : path.join('command', 'linux');
|
|
19
|
+
|
|
20
|
+
// 在 CLI 环境中,脚本在 nodejs_cli_client/command 目录下
|
|
21
|
+
return path.join(__dirname, '..', '..', subDir);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getExecutor() {
|
|
25
|
+
if (!globalExecutor) {
|
|
26
|
+
globalExecutor = new ScriptExecutor(
|
|
27
|
+
getScriptDir(),
|
|
28
|
+
'Success',
|
|
29
|
+
180
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return globalExecutor;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getScriptName(command) {
|
|
36
|
+
const system = process.platform;
|
|
37
|
+
const ext = system === 'win32' ? '.bat' : '.sh';
|
|
38
|
+
const names = {
|
|
39
|
+
[OpenClawCommandEnum.START]: 'start',
|
|
40
|
+
[OpenClawCommandEnum.STOP]: 'stop',
|
|
41
|
+
[OpenClawCommandEnum.RESTART]: 'restart',
|
|
42
|
+
[OpenClawCommandEnum.STATUS]: 'status'
|
|
43
|
+
};
|
|
44
|
+
return (names[command] || 'unknown') + ext;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getCommandName(command) {
|
|
48
|
+
const names = {
|
|
49
|
+
[OpenClawCommandEnum.START]: '启动',
|
|
50
|
+
[OpenClawCommandEnum.STOP]: '停止',
|
|
51
|
+
[OpenClawCommandEnum.RESTART]: '重启',
|
|
52
|
+
[OpenClawCommandEnum.STATUS]: '状态检查'
|
|
53
|
+
};
|
|
54
|
+
return names[command] || '未知命令';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function executeCommand(command, window, sendResponse) {
|
|
58
|
+
const cmdName = getCommandName(command);
|
|
59
|
+
console.log(`[OpenClawControl] ${cmdName} OpenClaw...`);
|
|
60
|
+
|
|
61
|
+
const executor = getExecutor();
|
|
62
|
+
const scriptName = getScriptName(command);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const result = await executor.executeWithStatus(command, scriptName);
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
result.status === OpenClawServiceStatus.START_SUCCESS ||
|
|
69
|
+
result.status === OpenClawServiceStatus.STOP_SUCCESS ||
|
|
70
|
+
result.status === OpenClawServiceStatus.RESTART_SUCCESS
|
|
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;
|
|
103
|
+
} catch (e) {
|
|
104
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
105
|
+
console.log(`[OpenClawControl] ${cmdName} 执行异常: ${msg}`);
|
|
106
|
+
if (sendResponse) {
|
|
107
|
+
sendResponse({
|
|
108
|
+
type: 'command_result',
|
|
109
|
+
command,
|
|
110
|
+
status: 'error',
|
|
111
|
+
message: msg,
|
|
112
|
+
service_status: OpenClawServiceStatus.ERROR
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
status: OpenClawServiceStatus.ERROR,
|
|
117
|
+
message: msg
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
executeCommand,
|
|
124
|
+
getScriptName,
|
|
125
|
+
getCommandName,
|
|
126
|
+
getScriptDir,
|
|
127
|
+
getExecutor
|
|
128
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw 命令枚举和服务状态
|
|
3
|
+
* 与桌面客户端对齐: nodejs_client/src/main/openclaw-enum.ts
|
|
4
|
+
*/
|
|
5
|
+
const OpenClawCommandEnum = {
|
|
6
|
+
START: 1,
|
|
7
|
+
STOP: 2,
|
|
8
|
+
RESTART: 3,
|
|
9
|
+
STATUS: 4,
|
|
10
|
+
CONFIG_FIX: 5
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const OpenClawServiceStatus = {
|
|
14
|
+
RUNNING: 'running',
|
|
15
|
+
NOT_RUNNING: 'not_running',
|
|
16
|
+
START_SUCCESS: 'starting_success',
|
|
17
|
+
STOP_SUCCESS: 'stop_success',
|
|
18
|
+
RESTART_SUCCESS: 'restart_success',
|
|
19
|
+
CONFIG_FIX_SUCCESS: 'config_fix_success',
|
|
20
|
+
NOT_INSTALL: 'not_install',
|
|
21
|
+
ERROR: 'error'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const statusMessages = {
|
|
25
|
+
[OpenClawServiceStatus.RUNNING]: '服务运行中',
|
|
26
|
+
[OpenClawServiceStatus.NOT_RUNNING]: '服务未运行',
|
|
27
|
+
[OpenClawServiceStatus.START_SUCCESS]: '启动成功',
|
|
28
|
+
[OpenClawServiceStatus.STOP_SUCCESS]: '关闭服务成功',
|
|
29
|
+
[OpenClawServiceStatus.RESTART_SUCCESS]: '重启服务成功',
|
|
30
|
+
[OpenClawServiceStatus.CONFIG_FIX_SUCCESS]: '配置修复成功',
|
|
31
|
+
[OpenClawServiceStatus.ERROR]: '未知异常',
|
|
32
|
+
[OpenClawServiceStatus.NOT_INSTALL]: 'openclaw未安装'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function getServiceStatusMessage(status) {
|
|
36
|
+
return statusMessages[status] || '状态未知';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isValidCommand(command) {
|
|
40
|
+
return Object.values(OpenClawCommandEnum).includes(command);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
OpenClawCommandEnum,
|
|
45
|
+
OpenClawServiceStatus,
|
|
46
|
+
getServiceStatusMessage,
|
|
47
|
+
isValidCommand
|
|
48
|
+
};
|