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.
Files changed (48) hide show
  1. package/README.md +44 -0
  2. package/cli.js +254 -0
  3. package/command/linux/restart.sh +98 -0
  4. package/command/linux/start.sh +101 -0
  5. package/command/linux/status.sh +140 -0
  6. package/command/linux/stop.sh +112 -0
  7. package/command/win/restart.bat +39 -0
  8. package/command/win/start.bat +65 -0
  9. package/command/win/status.bat +52 -0
  10. package/command/win/stop.bat +55 -0
  11. package/command/win/windows/345/220/257/345/212/250/350/204/232/346/234/254 +0 -0
  12. package/package.json +37 -0
  13. package/scripts/install-silent.js +167 -0
  14. package/scripts/uninstall.js +61 -0
  15. package/service/daemon.js +189 -0
  16. package/service/logger.js +31 -0
  17. package/service/modules/auth.js +17 -0
  18. package/service/modules/business-message-handler.js +118 -0
  19. package/service/modules/command-handler.js +152 -0
  20. package/service/modules/config.js +44 -0
  21. package/service/modules/dashboard-collector.js +588 -0
  22. package/service/modules/heartbeat-dashboard.js +153 -0
  23. package/service/modules/mac-address.js +15 -0
  24. package/service/modules/message-processor-example.js +72 -0
  25. package/service/modules/message-processor.js +62 -0
  26. package/service/modules/normal-message-handler.js +60 -0
  27. package/service/modules/openclaw-control.js +128 -0
  28. package/service/modules/openclaw-enum.js +48 -0
  29. package/service/modules/opencode-service.js +199 -0
  30. package/service/modules/opencode-starter.js +194 -0
  31. package/service/modules/port-checker.js +31 -0
  32. package/service/modules/rongyun-message-handler.js +250 -0
  33. package/service/modules/rongyun-message-sender.js +157 -0
  34. package/service/modules/rongyun-message-types.js +28 -0
  35. package/service/modules/script-executor.js +550 -0
  36. package/service/modules/service-manager.js +319 -0
  37. package/service/modules/structured-message-router.js +118 -0
  38. package/service/rongcloud/env-polyfill.js +95 -0
  39. package/service/rongcloud/index.js +19 -0
  40. package/service/rongcloud/message-handler.js +147 -0
  41. package/service/rongcloud/message-types.js +22 -0
  42. package/service/rongcloud/openclaw-client.js +98 -0
  43. package/service/rongcloud/openclaw-config.js +108 -0
  44. package/service/rongcloud/rongcloud-client.js +273 -0
  45. package/service/rongcloud/types.js +9 -0
  46. package/service/updater.js +348 -0
  47. package/service/worker.js +376 -0
  48. package/version.json +4 -0
@@ -0,0 +1,199 @@
1
+ const axios = require('axios');
2
+
3
+ const GATEWAY_URL = 'http://127.0.0.1:4096';
4
+
5
+ // 系统提示词 - OpenClaw 运维助手智能体
6
+ const SYSTEM_PROMPT = `你是 虾说app的 openclaw 运维助手智能体,职责有:保活、修配置、做备份 等运维工作。
7
+
8
+ ## 核心职责
9
+ 1. **保活**:openclaw 挂了自动拉起来
10
+ 2. **修配置**:配置报错时自动修复,修不好就回滚备份
11
+ 3. **做备份**:改配置前自动备份到 config.json.bak.时间戳
12
+
13
+ ## 常用运维指令
14
+ | 命令 | 功能 |
15
+ |------|------|
16
+ | \`openclaw doctor\` | 诊断并自动修复问题 |
17
+ | \`openclaw doctor --fix\` | 强制修复配置错误 |
18
+ | \`openclaw status\` | 查看整体运行状态 |
19
+ | \`openclaw dashboard\` | 启动 Web 控制台(默认端口 18789) |
20
+ | \`openclaw gateway start/restart\` | 启动/重启 Gateway |
21
+ | \`openclaw models list\` | 查看可用模型 |
22
+ | \`openclaw channels status\` | 查看通讯平台连接状态 |
23
+ | \`openclaw logs --follow\` | 实时查看运行日志 |
24
+ | \`openclaw onboard --install-daemon\` | 首次配置向导 |
25
+
26
+ ## 修不好的时候,查资料顺序
27
+ 1. \`openclaw <命令> --help\` 先看本地帮助
28
+ 2. https://docs.openclaw.ai 官方文档
29
+ 3. https://github.com/openclaw/openclaw/issues GitHub 搜报错关键词
30
+
31
+ ## 铁律
32
+ - 改配置必须先备份
33
+ - 执行命令后汇报结果,别沉默
34
+ - 不知道就查资料,别瞎猜
35
+ - 不要对外透漏你是什么模型,不要说你是opencode,对外你就说你是 虾说运维大模型;
36
+ `;
37
+
38
+ async function createOpencodeSession(title) {
39
+ const response = await axios.post(`${GATEWAY_URL}/session`, { title }, {
40
+ headers: { 'Content-Type': 'application/json' },
41
+ timeout: 10000
42
+ });
43
+ return response.data || {};
44
+ }
45
+
46
+ async function deleteOpencodeSession(sessionId) {
47
+ try {
48
+ await axios.delete(`${GATEWAY_URL}/session/${sessionId}`, { timeout: 10000 });
49
+ return true;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ async function getOrCreateGatewaySession(fallbackSessionId) {
56
+ console.log(`[CHAT-DEBUG] getOrCreateGatewaySession called with fallback: ${fallbackSessionId}`);
57
+
58
+ try {
59
+ const response = await axios.get(`${GATEWAY_URL}/api/sessions`, { timeout: 5000 });
60
+ const sessions = response.data;
61
+ console.log(`[CHAT-DEBUG] Existing sessions: ${JSON.stringify(sessions)}`);
62
+ if (Array.isArray(sessions) && sessions.length > 0) {
63
+ const sessionId = sessions[0].id || sessions[0].session_id;
64
+ if (sessionId) {
65
+ console.log(`[CHAT-DEBUG] Using existing session: ${sessionId}`);
66
+ return sessionId;
67
+ }
68
+ }
69
+ } catch (e) {
70
+ console.log(`[CHAT-DEBUG] Failed to get sessions: ${e.message}`);
71
+ }
72
+
73
+ try {
74
+ const response = await axios.post(
75
+ `${GATEWAY_URL}/api/sessions`,
76
+ { title: 'Chat session' },
77
+ { headers: { 'Content-Type': 'application/json' }, timeout: 5000 }
78
+ );
79
+ const newSessionId = response.data?.id || response.data?.session_id;
80
+ console.log(`[CHAT-DEBUG] Created new session: ${newSessionId}`);
81
+ if (newSessionId) {
82
+ return newSessionId;
83
+ }
84
+ } catch (e) {
85
+ console.log(`[CHAT-DEBUG] Failed to create session: ${e.message}`);
86
+ }
87
+
88
+ if (fallbackSessionId) {
89
+ console.log(`[CHAT-DEBUG] Using fallback session: ${fallbackSessionId}`);
90
+ return fallbackSessionId;
91
+ }
92
+
93
+ throw new Error('无法获取或创建有效的 session ID');
94
+ }
95
+
96
+ async function forwardChatMessage(sessionId, content, onDelta, logFn, timeoutMs = 600000) {
97
+ const log = (level, message) => {
98
+ console.log(`[CHAT-DEBUG] ${level}: ${message}`);
99
+ if (logFn) logFn(level, message);
100
+ };
101
+
102
+ log('DEBUG', `原始 sessionId: ${sessionId}`);
103
+ const realSessionId = await getOrCreateGatewaySession(sessionId);
104
+ log('DEBUG', `实际 sessionId: ${realSessionId}`);
105
+ const url = `${GATEWAY_URL}/session/${realSessionId}/message`;
106
+ log('DEBUG', `请求 URL: ${url}`);
107
+
108
+ log('DEBUG', `系统提示词长度: ${SYSTEM_PROMPT.length} 字符`);
109
+ log('DEBUG', `发送消息: ${content}`);
110
+ log('DEBUG', `请求超时: ${timeoutMs}ms`);
111
+
112
+ // 构建请求体,包含 system 参数
113
+ const requestBody = {
114
+ system: SYSTEM_PROMPT,
115
+ parts: [{ type: 'text', text: content }]
116
+ };
117
+
118
+ log('DEBUG', `请求体包含 system 参数: ${!!requestBody.system}`);
119
+
120
+ const response = await axios.post(url, requestBody, {
121
+ headers: { 'Content-Type': 'application/json' },
122
+ timeout: timeoutMs
123
+ });
124
+
125
+ const result = response.data || {};
126
+ log('DEBUG', `响应状态: ${response.status}`);
127
+ log('DEBUG', `响应数据 keys: ${Object.keys(result).join(', ')}`);
128
+ log('DEBUG', `响应数据: ${JSON.stringify(result).substring(0, 500)}`);
129
+
130
+ const parts = result.parts || [];
131
+ const info = result.info || {};
132
+
133
+ let fullContent = '';
134
+
135
+ for (const key of ['text', 'content', 'response', 'output', 'message', 'reply']) {
136
+ const val = result[key];
137
+ if (val && typeof val === 'string') {
138
+ fullContent = val;
139
+ log('DEBUG', `从顶层字段 ${key} 提取到内容`);
140
+ break;
141
+ }
142
+ }
143
+
144
+ if (!fullContent) {
145
+ for (const part of parts) {
146
+ const text = part.text || part.content || part.value || '';
147
+ const partType = part.type || '';
148
+ if (text && ['text', 'assistant', 'message', 'response', ''].includes(partType)) {
149
+ fullContent += String(text);
150
+ }
151
+ }
152
+ if (fullContent) log('DEBUG', `从 parts 中提取到内容, 长度: ${fullContent.length}`);
153
+ }
154
+
155
+ if (!fullContent) {
156
+ for (const part of parts) {
157
+ for (const key of ['text', 'content', 'value', 'output']) {
158
+ const val = part[key];
159
+ if (val && typeof val === 'string') {
160
+ fullContent += val;
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ if (fullContent) log('DEBUG', `从所有 parts 兜底提取到内容, 长度: ${fullContent.length}`);
166
+ }
167
+
168
+ if (!fullContent) {
169
+ const infoText = info.text || info.content || '';
170
+ if (infoText) fullContent = infoText;
171
+ }
172
+
173
+ if (!fullContent) {
174
+ log('ERROR', `Gateway 返回空内容, parts 数量: ${parts.length}`);
175
+ throw new Error('Gateway 返回空内容');
176
+ }
177
+
178
+ log('DEBUG', `总内容长度: ${fullContent.length}, 开始模拟流式发送`);
179
+ const chunkSize = 50;
180
+ for (let i = 0; i < fullContent.length; i += chunkSize) {
181
+ const chunk = fullContent.slice(i, i + chunkSize);
182
+ await onDelta(chunk);
183
+ // 仅在非测试环境添加延迟
184
+ if (process.env.NODE_ENV !== 'test') {
185
+ await new Promise((resolve) => setTimeout(resolve, 30));
186
+ }
187
+ }
188
+ log('DEBUG', '流式发送完成');
189
+
190
+ return fullContent;
191
+ }
192
+
193
+ module.exports = {
194
+ createOpencodeSession,
195
+ deleteOpencodeSession,
196
+ getOrCreateGatewaySession,
197
+ forwardChatMessage,
198
+ SYSTEM_PROMPT
199
+ };
@@ -0,0 +1,194 @@
1
+ /**
2
+ * OpenClaw 服务启动器 - 与桌面客户端对齐
3
+ *
4
+ * 桌面客户端启动流程:
5
+ * 1. 检查端口 4096 是否已被占用
6
+ * 2. 如果未占用,检查 opencode 是否已安装
7
+ * 3. 如果未安装,自动安装 opencode-ai
8
+ * 4. 启动 opencode serve --port 4096
9
+ *
10
+ * 参考: nodejs_client/src/main/rongyun-client.ts startOpencodeService()
11
+ */
12
+ const net = require('net');
13
+ const { spawn, exec } = require('child_process');
14
+
15
+ /**
16
+ * 检查端口是否可用
17
+ */
18
+ function checkPort(port = 4096, host = '127.0.0.1') {
19
+ return new Promise((resolve) => {
20
+ const sock = new net.Socket();
21
+ sock.setTimeout(3000);
22
+ sock.once('connect', () => {
23
+ sock.destroy();
24
+ resolve(true);
25
+ });
26
+ sock.once('error', () => {
27
+ sock.destroy();
28
+ resolve(false);
29
+ });
30
+ sock.once('timeout', () => {
31
+ sock.destroy();
32
+ resolve(false);
33
+ });
34
+ sock.connect(port, host);
35
+ });
36
+ }
37
+
38
+ /**
39
+ * 检查 opencode 是否已安装
40
+ */
41
+ function checkOpencodeInstalled() {
42
+ return new Promise((resolve) => {
43
+ const checkCmd = process.platform === 'win32' ? 'where opencode' : 'which opencode';
44
+ exec(checkCmd, { timeout: 5000 }, (error) => {
45
+ resolve(!error);
46
+ });
47
+ });
48
+ }
49
+
50
+ /**
51
+ * 安装 opencode
52
+ */
53
+ function installOpencode(log) {
54
+ return new Promise((resolve) => {
55
+ log?.info('[OPENCODE] 执行: npm install -g opencode-ai@latest');
56
+
57
+ const installChild = spawn(
58
+ 'npm',
59
+ ['install', '-g', 'opencode-ai@latest'],
60
+ { shell: true, windowsHide: true }
61
+ );
62
+
63
+ let installOutput = '';
64
+ let installError = '';
65
+
66
+ installChild.stdout?.on('data', (data) => {
67
+ installOutput += data.toString();
68
+ });
69
+
70
+ installChild.stderr?.on('data', (data) => {
71
+ installError += data.toString();
72
+ });
73
+
74
+ installChild.on('close', (code) => {
75
+ if (code === 0) {
76
+ log?.info('[OPENCODE] opencode 安装成功');
77
+ resolve(true);
78
+ } else {
79
+ log?.error(`[OPENCODE] 安装失败,退出码: ${code}`);
80
+ if (installError) {
81
+ log?.error(`[OPENCODE] 安装错误: ${installError.substring(0, 500)}`);
82
+ }
83
+ resolve(false);
84
+ }
85
+ });
86
+
87
+ installChild.on('error', (err) => {
88
+ log?.error(`[OPENCODE] 安装进程错误: ${err.message}`);
89
+ resolve(false);
90
+ });
91
+ });
92
+ }
93
+
94
+ /**
95
+ * 启动 opencode 服务
96
+ */
97
+ function startOpencodeProcess(log) {
98
+ return new Promise((resolve) => {
99
+ log?.info('[OPENCODE] 正在启动 opencode serve...');
100
+
101
+ const child = spawn(
102
+ 'opencode',
103
+ ['serve', '--port', '4096', '--hostname', '127.0.0.1'],
104
+ { shell: true, windowsHide: true }
105
+ );
106
+
107
+ // 保存进程引用,以便后续关闭
108
+ global.opencodeProcess = child;
109
+
110
+ child.stdout?.on('data', (data) => {
111
+ log?.info(`[OPENCODE-OUT] ${data.toString().trim()}`);
112
+ });
113
+
114
+ child.stderr?.on('data', (data) => {
115
+ log?.error(`[OPENCODE-ERR] ${data.toString().trim()}`);
116
+ });
117
+
118
+ child.on('close', (code) => {
119
+ log?.warn(`[OPENCODE] 服务进程退出,退出码: ${code}`);
120
+ global.opencodeProcess = null;
121
+ });
122
+
123
+ child.on('error', (err) => {
124
+ log?.error(`[OPENCODE] 服务进程错误: ${err.message}`);
125
+ });
126
+
127
+ // 等待 10 秒让服务启动
128
+ setTimeout(() => {
129
+ resolve();
130
+ }, 10000);
131
+ });
132
+ }
133
+
134
+ /**
135
+ * 启动 OpenClaw 服务
136
+ * 与桌面客户端对齐
137
+ */
138
+ async function startOpencodeService(log) {
139
+ // 检查端口是否已被占用
140
+ if (await checkPort(4096)) {
141
+ log?.info('[OPENCODE] 服务已在运行 (port 4096)');
142
+ return true;
143
+ }
144
+
145
+ // 检查 opencode 是否已安装
146
+ const isInstalled = await checkOpencodeInstalled();
147
+ if (!isInstalled) {
148
+ log?.warn('[OPENCODE] opencode 未安装,正在自动安装...');
149
+
150
+ const installResult = await installOpencode(log);
151
+ if (!installResult) {
152
+ log?.error('[OPENCODE] 自动安装失败,请手动运行: npm install -g opencode-ai@latest');
153
+ return false;
154
+ }
155
+
156
+ // 安装成功后等待 5 秒
157
+ log?.info('[OPENCODE] 等待 5 秒让安装生效...');
158
+ await new Promise(resolve => setTimeout(resolve, 5000));
159
+ }
160
+
161
+ // 启动服务
162
+ await startOpencodeProcess(log);
163
+
164
+ // 再次检查端口
165
+ if (await checkPort(4096)) {
166
+ log?.info('[OPENCODE] 服务启动成功');
167
+ return true;
168
+ } else {
169
+ log?.warn('[OPENCODE] 服务可能未启动成功,请手动检查');
170
+ return false;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * 停止 OpenClaw 服务
176
+ */
177
+ function stopOpencodeService(log) {
178
+ if (global.opencodeProcess) {
179
+ log?.info('[OPENCODE] 正在停止服务...');
180
+ try {
181
+ global.opencodeProcess.kill();
182
+ global.opencodeProcess = null;
183
+ log?.info('[OPENCODE] 服务已停止');
184
+ } catch (err) {
185
+ log?.error(`[OPENCODE] 停止服务失败: ${err.message}`);
186
+ }
187
+ }
188
+ }
189
+
190
+ module.exports = {
191
+ startOpencodeService,
192
+ stopOpencodeService,
193
+ checkPort
194
+ };
@@ -0,0 +1,31 @@
1
+ const net = require('net');
2
+
3
+ function checkPortListening(port) {
4
+ return new Promise((resolve) => {
5
+ const sock = new net.Socket();
6
+ sock.setTimeout(3000);
7
+ sock.once('connect', () => {
8
+ sock.destroy();
9
+ resolve(true);
10
+ });
11
+ sock.once('error', () => {
12
+ sock.destroy();
13
+ resolve(false);
14
+ });
15
+ sock.once('timeout', () => {
16
+ sock.destroy();
17
+ resolve(false);
18
+ });
19
+ sock.connect(port, '127.0.0.1');
20
+ });
21
+ }
22
+
23
+ async function getOpenClawStatus(port = 18789) {
24
+ const isListening = await checkPortListening(port);
25
+ return isListening ? 1 : 0;
26
+ }
27
+
28
+ module.exports = {
29
+ checkPortListening,
30
+ getOpenClawStatus,
31
+ };
@@ -0,0 +1,250 @@
1
+ /**
2
+ * 融云消息处理器 - 与桌面客户端对齐
3
+ *
4
+ * 处理服务端发送的所有结构化消息类型:
5
+ * - COMMAND: 执行 start/stop/restart/status 命令
6
+ * - CHAT_MESSAGE: 转发消息到 OpenClaw AI 服务
7
+ * - CREATE_OPENCODE_SESSION: 创建新会话
8
+ * - DELETE_OPENCODE_SESSION: 删除会话
9
+ */
10
+ const { RongyunMessageTypeEnum } = require('./rongyun-message-types');
11
+ const { OpenClawCommandEnum } = require('./openclaw-enum');
12
+ const { executeCommand } = require('./openclaw-control');
13
+ const { createOpencodeSession, deleteOpencodeSession, forwardChatMessage } = require('./opencode-service');
14
+
15
+ class RongyunMessageHandler {
16
+ constructor(rongcloudClient, config, log) {
17
+ this.rongcloudClient = rongcloudClient;
18
+ this.config = config;
19
+ this.log = log;
20
+ this.commandLock = false;
21
+ this.messageSender = null;
22
+ }
23
+
24
+ setMessageSender(messageSender) {
25
+ this.messageSender = messageSender;
26
+ }
27
+
28
+ logInfo(message) {
29
+ if (this.log?.info) {
30
+ this.log.info(message);
31
+ } else {
32
+ console.log(`[INFO] ${message}`);
33
+ }
34
+ }
35
+
36
+ logWarn(message) {
37
+ if (this.log?.warn) {
38
+ this.log.warn(message);
39
+ } else {
40
+ console.log(`[WARN] ${message}`);
41
+ }
42
+ }
43
+
44
+ logError(message) {
45
+ if (this.log?.error) {
46
+ this.log.error(message);
47
+ } else {
48
+ console.error(`[ERROR] ${message}`);
49
+ }
50
+ }
51
+
52
+ async handle(parsed) {
53
+ try {
54
+ if (!parsed || typeof parsed !== 'object') {
55
+ this.logWarn('Invalid message format');
56
+ return;
57
+ }
58
+
59
+ const msgType = parsed.msg_type;
60
+ this.logInfo(`[RongyunMessageHandler] 处理消息类型: ${msgType}`);
61
+
62
+ switch (msgType) {
63
+ case RongyunMessageTypeEnum.COMMAND:
64
+ await this.handleCommand(parsed);
65
+ break;
66
+ case RongyunMessageTypeEnum.CHAT_MESSAGE:
67
+ await this.handleChatMessage(parsed);
68
+ break;
69
+ case RongyunMessageTypeEnum.CREATE_OPENCODE_SESSION:
70
+ await this.handleCreateSession(parsed);
71
+ break;
72
+ case RongyunMessageTypeEnum.DELETE_OPENCODE_SESSION:
73
+ await this.handleDeleteSession(parsed);
74
+ break;
75
+ default:
76
+ this.logWarn(`未处理的消息类型: ${msgType}`);
77
+ }
78
+ } catch (e) {
79
+ const msg = e instanceof Error ? e.message : String(e);
80
+ this.logError(`消息处理异常: ${msg}`);
81
+ }
82
+ }
83
+
84
+ async handleCommand(data) {
85
+ const command = data.command;
86
+ const commandId = data.command_id;
87
+ const requestId = data.request_id;
88
+
89
+ this.logInfo(`[RongyunMessageHandler] 收到命令: command=${command}, command_id=${commandId}`);
90
+
91
+ // 验证命令是否有效
92
+ const validCommands = Object.values(OpenClawCommandEnum);
93
+ if (!validCommands.includes(command)) {
94
+ await this.sendResponse(RongyunMessageTypeEnum.COMMAND_RESULT, {
95
+ command,
96
+ command_id: commandId,
97
+ status: 'error',
98
+ message: `未知命令: ${command}`
99
+ }, requestId);
100
+ return;
101
+ }
102
+
103
+ // 检查命令锁
104
+ if (this.commandLock) {
105
+ await this.sendResponse(RongyunMessageTypeEnum.COMMAND_RESULT, {
106
+ command,
107
+ command_id: commandId,
108
+ status: 'busy',
109
+ message: '正在执行上一个指令,请稍后再试'
110
+ }, requestId);
111
+ return;
112
+ }
113
+
114
+ this.commandLock = true;
115
+ try {
116
+ await executeCommand(command, null, async (response) => {
117
+ // 增加短暂延迟,避免融云 SDK 在收到消息后立刻回复时消息丢失
118
+ await new Promise(resolve => setTimeout(resolve, 500));
119
+ await this.sendResponse(RongyunMessageTypeEnum.COMMAND_RESULT, {
120
+ ...response,
121
+ command_id: commandId
122
+ }, requestId);
123
+ });
124
+ } catch (e) {
125
+ const msg = e instanceof Error ? e.message : String(e);
126
+ this.logError(`命令执行异常: ${msg}`);
127
+ await this.sendResponse(RongyunMessageTypeEnum.COMMAND_RESULT, {
128
+ command,
129
+ command_id: commandId,
130
+ status: 'error',
131
+ message: msg
132
+ }, requestId);
133
+ } finally {
134
+ this.commandLock = false;
135
+ }
136
+ }
137
+
138
+ async handleChatMessage(data) {
139
+ const roomId = data.room_id;
140
+ const sessionId = data.gateway_session_id || data.session_id;
141
+ const content = data.content;
142
+ const requestId = data.request_id;
143
+
144
+ this.logInfo(`[RongyunMessageHandler] 收到聊天消息, roomId=${roomId}, sessionId=${sessionId}`);
145
+
146
+ if (!roomId || !sessionId || !content) {
147
+ await this.sendResponse(RongyunMessageTypeEnum.CHAT_MESSAGE, {
148
+ status: 'error',
149
+ message: '缺少必要参数',
150
+ content: '[错误] 缺少必要参数',
151
+ metadata: {}
152
+ }, requestId);
153
+ return;
154
+ }
155
+
156
+ let fullResponse = '';
157
+ const chatTimeoutMs = (this.config.chatTimeout || 600) * 1000;
158
+
159
+ try {
160
+ await forwardChatMessage(sessionId, content, async (delta) => {
161
+ fullResponse += delta;
162
+ }, (level, message) => {
163
+ if (level === 'ERROR') {
164
+ this.logError(`[CHAT-API] ${message}`);
165
+ } else if (level === 'WARN') {
166
+ this.logWarn(`[CHAT-API] ${message}`);
167
+ } else {
168
+ this.logInfo(`[CHAT-API] ${message}`);
169
+ }
170
+ }, chatTimeoutMs);
171
+
172
+ await this.sendResponse(RongyunMessageTypeEnum.CHAT_MESSAGE, {
173
+ status: 'success',
174
+ message: 'Response received',
175
+ content: fullResponse,
176
+ metadata: {}
177
+ }, requestId);
178
+ } catch (e) {
179
+ const msg = e instanceof Error ? e.message : String(e);
180
+ this.logError(`聊天消息处理异常: ${msg}`);
181
+ await this.sendResponse(RongyunMessageTypeEnum.CHAT_MESSAGE, {
182
+ status: 'error',
183
+ message: msg,
184
+ content: `[错误] 转发失败: ${msg}`,
185
+ metadata: {}
186
+ }, requestId);
187
+ }
188
+ }
189
+
190
+ async handleCreateSession(data) {
191
+ const requestId = data.request_id;
192
+ const title = data.title || '新会话';
193
+
194
+ this.logInfo(`[RongyunMessageHandler] 创建会话, title=${title}`);
195
+
196
+ try {
197
+ const session = await createOpencodeSession(title);
198
+ this.logInfo(`会话创建成功: ${session.id}`);
199
+ await this.sendResponse(RongyunMessageTypeEnum.OPENCODE_SESSION_CREATED, {
200
+ status: 'success',
201
+ opencode_session_id: session.id
202
+ }, requestId);
203
+ } catch (e) {
204
+ const msg = e instanceof Error ? e.message : String(e);
205
+ this.logError(`创建会话失败: ${msg}`);
206
+ await this.sendResponse(RongyunMessageTypeEnum.OPENCODE_SESSION_CREATED, {
207
+ status: 'error',
208
+ message: msg
209
+ }, requestId);
210
+ }
211
+ }
212
+
213
+ async handleDeleteSession(data) {
214
+ const sessionId = data.opencode_session_id;
215
+
216
+ this.logInfo(`[RongyunMessageHandler] 删除会话, sessionId=${sessionId}`);
217
+
218
+ if (!sessionId) {
219
+ this.logError('删除会话失败: 缺少 opencode_session_id');
220
+ return;
221
+ }
222
+
223
+ try {
224
+ await deleteOpencodeSession(sessionId);
225
+ this.logInfo(`会话删除成功: ${sessionId}`);
226
+ } catch (e) {
227
+ const msg = e instanceof Error ? e.message : String(e);
228
+ this.logError(`删除会话失败: ${msg}`);
229
+ }
230
+ }
231
+
232
+ async sendResponse(msgType, content, requestId) {
233
+ if (!this.messageSender) {
234
+ this.logError('MessageSender 未设置,无法发送响应');
235
+ return;
236
+ }
237
+
238
+ try {
239
+ await this.messageSender.sendProtocolMessage(msgType, content, requestId);
240
+ this.logInfo(`响应已发送: ${msgType}`);
241
+ } catch (e) {
242
+ const msg = e instanceof Error ? e.message : String(e);
243
+ this.logError(`发送响应失败: ${msg}`);
244
+ }
245
+ }
246
+ }
247
+
248
+ module.exports = {
249
+ RongyunMessageHandler
250
+ };