claw-subagent-service 0.0.166 → 0.0.170
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/cli.js +37 -3
- package/package.json +1 -1
- package/scripts/post-install.js +28 -13
- package/service/modules/rongyun-message-handler.js +58 -2
package/cli.js
CHANGED
|
@@ -24,6 +24,17 @@ const SERVICE_NAME = 'claw-subagent-service';
|
|
|
24
24
|
const args = process.argv.slice(2);
|
|
25
25
|
const command = args[0] || '--run';
|
|
26
26
|
|
|
27
|
+
// --version 支持:直接显示版本并退出,不启动 daemon
|
|
28
|
+
if (command === '--version' || command === '-v') {
|
|
29
|
+
try {
|
|
30
|
+
const version = require('./package.json').version;
|
|
31
|
+
console.log(version);
|
|
32
|
+
} catch {
|
|
33
|
+
console.log('unknown');
|
|
34
|
+
}
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
function runDaemon() {
|
|
28
39
|
console.log('[CLI] 启动 Daemon...');
|
|
29
40
|
console.log(`[CLI] CLI 路径: ${__filename}`);
|
|
@@ -254,15 +265,38 @@ function controlService(action) {
|
|
|
254
265
|
function checkStatus() {
|
|
255
266
|
const platform = process.platform;
|
|
256
267
|
let cmd;
|
|
257
|
-
|
|
268
|
+
|
|
258
269
|
if (platform === 'win32') {
|
|
259
|
-
|
|
270
|
+
// Windows: 先尝试 sc.exe query,若服务未安装则回退到进程检查
|
|
271
|
+
cmd = `sc.exe query "${SERVICE_NAME}"`;
|
|
272
|
+
exec(cmd, (err, stdout, stderr) => {
|
|
273
|
+
if (err) {
|
|
274
|
+
// 1060 = 服务未安装,给出更友好的提示
|
|
275
|
+
if (stderr && stderr.includes('1060')) {
|
|
276
|
+
console.log(`[CLI] 服务 "${SERVICE_NAME}" 未安装`);
|
|
277
|
+
console.log(`[CLI] 请运行: claw-subagent-service --install`);
|
|
278
|
+
} else {
|
|
279
|
+
console.error(`[CLI] 查询状态失败: ${err.message}`);
|
|
280
|
+
}
|
|
281
|
+
// 回退:检查 daemon 进程是否在前台运行
|
|
282
|
+
try {
|
|
283
|
+
const list = execSync('tasklist /FI "IMAGENAME eq node.exe" /FO CSV /NH', { encoding: 'utf8', windowsHide: true });
|
|
284
|
+
const hasDaemon = list.includes('daemon.js');
|
|
285
|
+
if (hasDaemon) {
|
|
286
|
+
console.log('[CLI] 检测到 Daemon 进程正在前台运行');
|
|
287
|
+
}
|
|
288
|
+
} catch { /* 忽略 */ }
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
console.log(stdout);
|
|
292
|
+
});
|
|
293
|
+
return;
|
|
260
294
|
} else if (platform === 'linux') {
|
|
261
295
|
cmd = `systemctl status ${SERVICE_NAME}`;
|
|
262
296
|
} else if (platform === 'darwin') {
|
|
263
297
|
cmd = `launchctl list | grep ${SERVICE_NAME}`;
|
|
264
298
|
}
|
|
265
|
-
|
|
299
|
+
|
|
266
300
|
exec(cmd, (err, stdout) => {
|
|
267
301
|
if (err) {
|
|
268
302
|
console.error(`[CLI] 查询状态失败: ${err.message}`);
|
package/package.json
CHANGED
package/scripts/post-install.js
CHANGED
|
@@ -130,25 +130,40 @@ function installAndStartService() {
|
|
|
130
130
|
svc.on('install', () => {
|
|
131
131
|
console.log('[postinstall] 服务注册成功,正在启动...');
|
|
132
132
|
|
|
133
|
-
// 延迟
|
|
133
|
+
// 延迟 3 秒确保服务注册到 SCM,再设置开机自启 + 崩溃恢复
|
|
134
134
|
setTimeout(() => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (err) console.error(`[postinstall] 设置自动启动失败: ${err.message}`);
|
|
143
|
-
else console.log('[postinstall] 启动类型已设为:自动');
|
|
144
|
-
});
|
|
145
|
-
}, 2000);
|
|
135
|
+
// 使用 execSync 确保配置在启动前生效
|
|
136
|
+
try {
|
|
137
|
+
execSync(`sc.exe failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`, { stdio: 'ignore', timeout: 10000 });
|
|
138
|
+
console.log('[postinstall] 恢复策略已设置:崩溃后自动重启');
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error(`[postinstall] 设置恢复策略失败: ${err.message}`);
|
|
141
|
+
}
|
|
146
142
|
|
|
147
|
-
|
|
143
|
+
try {
|
|
144
|
+
execSync(`sc.exe config "${SERVICE_NAME}" start= auto`, { stdio: 'ignore', timeout: 10000 });
|
|
145
|
+
console.log('[postinstall] 启动类型已设为:自动');
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error(`[postinstall] 设置自动启动失败: ${err.message}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 配置完成后启动服务
|
|
151
|
+
svc.start();
|
|
152
|
+
}, 3000);
|
|
148
153
|
});
|
|
149
154
|
|
|
150
155
|
svc.on('start', () => {
|
|
151
156
|
console.log('[postinstall] 服务已启动');
|
|
157
|
+
// 启动后验证服务状态
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
try {
|
|
160
|
+
const status = execSync(`sc.exe query "${SERVICE_NAME}"`, { encoding: 'utf8', timeout: 5000 });
|
|
161
|
+
console.log('[postinstall] 服务状态验证:');
|
|
162
|
+
console.log(status);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.warn(`[postinstall] 服务状态验证失败: ${err.message}`);
|
|
165
|
+
}
|
|
166
|
+
}, 2000);
|
|
152
167
|
});
|
|
153
168
|
|
|
154
169
|
svc.on('error', (err) => {
|
|
@@ -610,9 +610,29 @@ class RongyunMessageHandler {
|
|
|
610
610
|
const requestId = data.request_id;
|
|
611
611
|
const userId = data.userId || data.source_im_id;
|
|
612
612
|
const sourceId = data.source_im_id;
|
|
613
|
-
|
|
613
|
+
let content = data.content || data._raw_content;
|
|
614
|
+
const voiceUrl = data.voiceUrl;
|
|
615
|
+
const voiceDuration = data.voiceDuration;
|
|
616
|
+
|
|
617
|
+
this.logInfo(`[RongyunMessageHandler] 收到客服消息, userId=${userId}, content=${content?.substring(0, 50)}, voiceUrl=${voiceUrl ? '有' : '无'}`);
|
|
614
618
|
|
|
615
|
-
|
|
619
|
+
// 处理语音消息:如果有语音URL,先进行语音识别
|
|
620
|
+
if (voiceUrl && (!content || content === '[语音]')) {
|
|
621
|
+
try {
|
|
622
|
+
this.logInfo(`[RongyunMessageHandler] 检测到语音消息,开始语音识别: ${voiceUrl}`);
|
|
623
|
+
const recognizedText = await this.recognizeVoice(voiceUrl);
|
|
624
|
+
if (recognizedText) {
|
|
625
|
+
content = recognizedText;
|
|
626
|
+
this.logInfo(`[RongyunMessageHandler] 语音识别成功: ${content.substring(0, 50)}`);
|
|
627
|
+
} else {
|
|
628
|
+
content = '[语音消息识别失败]';
|
|
629
|
+
this.logWarn(`[RongyunMessageHandler] 语音识别失败,使用占位文本`);
|
|
630
|
+
}
|
|
631
|
+
} catch (e) {
|
|
632
|
+
this.logError(`[RongyunMessageHandler] 语音识别异常: ${e.message}`);
|
|
633
|
+
content = '[语音消息识别失败]';
|
|
634
|
+
}
|
|
635
|
+
}
|
|
616
636
|
|
|
617
637
|
if (!content) {
|
|
618
638
|
this.logWarn('客服消息内容为空');
|
|
@@ -691,6 +711,42 @@ class RongyunMessageHandler {
|
|
|
691
711
|
}
|
|
692
712
|
}
|
|
693
713
|
|
|
714
|
+
/**
|
|
715
|
+
* 语音识别 - 调用后端 Python 服务
|
|
716
|
+
* @param {string} voiceUrl - 语音文件 URL
|
|
717
|
+
* @returns {Promise<string>} 识别文本
|
|
718
|
+
*/
|
|
719
|
+
async recognizeVoice(voiceUrl) {
|
|
720
|
+
try {
|
|
721
|
+
const axios = require('axios');
|
|
722
|
+
|
|
723
|
+
// 从配置中获取后端 API 地址
|
|
724
|
+
const apiBaseUrl = this.config.apiBaseUrl || process.env.API_BASE_URL || 'http://localhost:5000';
|
|
725
|
+
const recognizeUrl = `${apiBaseUrl}/im/api/voice/recognize`;
|
|
726
|
+
|
|
727
|
+
this.logInfo(`[RongyunMessageHandler] 调用语音识别 API: ${recognizeUrl}`);
|
|
728
|
+
|
|
729
|
+
const response = await axios.post(recognizeUrl, {
|
|
730
|
+
audioUrl: voiceUrl,
|
|
731
|
+
format: 'mp3',
|
|
732
|
+
sampleRate: 16000
|
|
733
|
+
}, {
|
|
734
|
+
timeout: 30000,
|
|
735
|
+
headers: { 'Content-Type': 'application/json' }
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
if (response.data && response.data.code === 200) {
|
|
739
|
+
return response.data.data.text;
|
|
740
|
+
} else {
|
|
741
|
+
this.logError(`[RongyunMessageHandler] 语音识别 API 返回错误: ${JSON.stringify(response.data)}`);
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
} catch (e) {
|
|
745
|
+
this.logError(`[RongyunMessageHandler] 语音识别请求失败: ${e.message}`);
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
694
750
|
async sendResponse(msgType, content, requestId, targetId) {
|
|
695
751
|
if (!this.messageSender) {
|
|
696
752
|
this.logError('MessageSender 未设置,无法发送响应');
|