claw-subagent-service 0.0.173 → 0.0.176
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claw-subagent-service",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.176",
|
|
4
4
|
"description": "虾说智能助手",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"publish:patch": "npm version patch && npm publish",
|
|
16
16
|
"publish:minor": "npm version minor && npm publish",
|
|
17
17
|
"publish:major": "npm version major && npm publish",
|
|
18
|
+
"unpublish:last": "node scripts/unpublish.js",
|
|
19
|
+
"unpublish:version": "node scripts/unpublish.js",
|
|
20
|
+
"deprecate:last": "npm deprecate $(node -p \"require('./package.json').name\")@$(node -p \"require('./package.json').version\") \"This version is deprecated. Please upgrade.\"",
|
|
21
|
+
"deprecate:version": "npm deprecate",
|
|
18
22
|
"sync": "node scripts/sync-local.js"
|
|
19
23
|
},
|
|
20
24
|
"files": [
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// scripts/unpublish.js
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const pkg = require('../package.json');
|
|
5
|
+
|
|
6
|
+
const name = pkg.name;
|
|
7
|
+
const version = process.argv[2] || pkg.version;
|
|
8
|
+
const tag = `v${version}`;
|
|
9
|
+
|
|
10
|
+
console.log(`\n🗑️ Unpublishing ${name}@${version}...\n`);
|
|
11
|
+
|
|
12
|
+
// 1. 撤销 npm 包
|
|
13
|
+
try {
|
|
14
|
+
execSync(`npm unpublish ${name}@${version} --force`, { stdio: 'inherit' });
|
|
15
|
+
console.log(`✅ npm 撤销成功\n`);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.log(`⚠️ npm 撤销失败(可能已撤销或超时)\n`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 2. 删除 git tag
|
|
21
|
+
try { execSync(`git tag -d ${tag}`, { stdio: 'pipe' }); console.log(`✅ 本地 tag ${tag} 已删除`); }
|
|
22
|
+
catch (e) { console.log(`⚠️ 本地 tag ${tag} 不存在`); }
|
|
23
|
+
|
|
24
|
+
try { execSync(`git push origin :refs/tags/${tag}`, { stdio: 'pipe' }); console.log(`✅ 远程 tag ${tag} 已删除`); }
|
|
25
|
+
catch (e) { console.log(`⚠️ 远程 tag ${tag} 不存在或无权限`); }
|
|
26
|
+
|
|
27
|
+
// 3. ⭐ 回退 package.json 版本(patch 级别减 1)
|
|
28
|
+
const parts = version.split('.').map(Number);
|
|
29
|
+
parts[2] = Math.max(0, parts[2] - 1); // patch - 1,最低为 0
|
|
30
|
+
const newVersion = parts.join('.');
|
|
31
|
+
|
|
32
|
+
pkg.version = newVersion;
|
|
33
|
+
fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');
|
|
34
|
+
|
|
35
|
+
console.log(`\n📦 package.json 版本已回退: ${version} → ${newVersion}`);
|
|
36
|
+
console.log(`🎉 清理完成!现在可以重新 publish:patch 了\n`);
|
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
* @returns {string} 回复内容
|
|
13
13
|
*/
|
|
14
14
|
const { OpenClawClient } = require('../rongcloud/openclaw-client');
|
|
15
|
+
const axios = require('axios');
|
|
16
|
+
const { loadConfig } = require('./config');
|
|
15
17
|
|
|
16
18
|
const openclawClient = new OpenClawClient(console);
|
|
19
|
+
const config = loadConfig();
|
|
17
20
|
|
|
18
21
|
async function handleNormalMessage(msg) {
|
|
19
22
|
console.log(`[NormalMessageHandler] 收到普通消息:`, {
|
|
@@ -23,11 +26,21 @@ async function handleNormalMessage(msg) {
|
|
|
23
26
|
});
|
|
24
27
|
|
|
25
28
|
try {
|
|
26
|
-
|
|
29
|
+
let content = msg.content;
|
|
27
30
|
if (!content || !content.trim()) {
|
|
28
31
|
return '消息内容为空';
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
// 语音消息:先进行语音识别
|
|
35
|
+
if (msg.messageType === 'RC:HQVCMsg') {
|
|
36
|
+
const voiceText = await recognizeVoice(msg);
|
|
37
|
+
if (voiceText !== null) {
|
|
38
|
+
content = `[语音转文字] ${voiceText}`;
|
|
39
|
+
} else {
|
|
40
|
+
content = `[语音消息,转文字失败] ${content}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
const reply = await openclawClient.chat(content, msg.senderUserId);
|
|
32
45
|
console.log(`[NormalMessageHandler] AI 回复: ${reply.substring(0, 50)}...`);
|
|
33
46
|
return reply;
|
|
@@ -37,6 +50,62 @@ async function handleNormalMessage(msg) {
|
|
|
37
50
|
}
|
|
38
51
|
}
|
|
39
52
|
|
|
53
|
+
/**
|
|
54
|
+
* 语音识别:调用后端百度语音 API
|
|
55
|
+
*/
|
|
56
|
+
async function recognizeVoice(msg) {
|
|
57
|
+
try {
|
|
58
|
+
let content = msg.content;
|
|
59
|
+
if (typeof content === 'string' && content.startsWith('{')) {
|
|
60
|
+
try {
|
|
61
|
+
content = JSON.parse(content);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
// 解析失败,保持原样
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const remoteUrl = content?.remoteUrl || content?.url || msg.remoteUrl || msg.url;
|
|
68
|
+
if (!remoteUrl) {
|
|
69
|
+
console.warn('[recognizeVoice] 语音消息缺少 remoteUrl,跳过识别');
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 从 URL 提取扩展名并映射为百度支持的格式
|
|
74
|
+
const urlPath = remoteUrl.split('?')[0];
|
|
75
|
+
const ext = urlPath.split('.').pop()?.toLowerCase() || '';
|
|
76
|
+
const fmtMap = { aac: 'm4a', ogg: 'mp3', oga: 'mp3', opus: 'mp3' };
|
|
77
|
+
let format = fmtMap[ext] || ext;
|
|
78
|
+
if (!['pcm', 'wav', 'amr', 'm4a', 'mp3'].includes(format)) {
|
|
79
|
+
format = 'mp3';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 采样率修正:amr 强制 8000,其余使用消息自带值兜底 16000
|
|
83
|
+
let sampleRate = content?.sampleRate || msg.sampleRate || 16000;
|
|
84
|
+
if (format === 'amr') sampleRate = 8000;
|
|
85
|
+
|
|
86
|
+
const apiUrl = `${config.apiBaseUrl}/im/api/voice/recognize`;
|
|
87
|
+
console.log(`[recognizeVoice] 调用语音识别 API: ${apiUrl}, format=${format}, sampleRate=${sampleRate}`);
|
|
88
|
+
|
|
89
|
+
const response = await axios.post(apiUrl, {
|
|
90
|
+
audioUrl: remoteUrl,
|
|
91
|
+
format,
|
|
92
|
+
sampleRate,
|
|
93
|
+
}, { timeout: 30000 });
|
|
94
|
+
|
|
95
|
+
if (response.data?.code === 200 && response.data?.data?.text !== undefined) {
|
|
96
|
+
const text = response.data.data.text;
|
|
97
|
+
console.log(`[recognizeVoice] 语音识别成功: "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
|
|
98
|
+
return text;
|
|
99
|
+
} else {
|
|
100
|
+
console.warn(`[recognizeVoice] 语音识别失败: ${JSON.stringify(response.data)}`);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error(`[recognizeVoice] 语音识别异常: ${err.message}`);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
40
109
|
module.exports = {
|
|
41
110
|
handleNormalMessage
|
|
42
111
|
};
|
|
@@ -400,7 +400,18 @@ class MessageHandler {
|
|
|
400
400
|
|
|
401
401
|
try {
|
|
402
402
|
// 确保传入的内容是字符串(claw 类型消息 content 可能是对象)
|
|
403
|
-
|
|
403
|
+
let chatContent = this._extractMessageContent(msg);
|
|
404
|
+
|
|
405
|
+
// 语音消息:先进行语音识别,把识别结果传给 AI
|
|
406
|
+
if (msg.messageType === 'RC:HQVCMsg') {
|
|
407
|
+
const voiceText = await this._recognizeVoice(msg);
|
|
408
|
+
if (voiceText !== null) {
|
|
409
|
+
chatContent = `[语音转文字] ${voiceText}`;
|
|
410
|
+
} else {
|
|
411
|
+
chatContent = `[语音消息,转文字失败] ${chatContent}`;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
404
415
|
this.log?.info(`[MessageHandler] 调用 chatStream, content_type=${typeof msg.content}, chatContent=${chatContent.substring(0, 50)}`);
|
|
405
416
|
await this.openclawClient.chatStream(
|
|
406
417
|
chatContent,
|
|
@@ -720,14 +731,30 @@ class MessageHandler {
|
|
|
720
731
|
let remoteUrl = '';
|
|
721
732
|
let duration = 0;
|
|
722
733
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
734
|
+
// 先尝试解析 content(可能是 JSON 字符串)
|
|
735
|
+
let parsedContent = content;
|
|
736
|
+
if (typeof content === 'string' && content.startsWith('{')) {
|
|
737
|
+
try {
|
|
738
|
+
parsedContent = JSON.parse(content);
|
|
739
|
+
} catch (e) {
|
|
740
|
+
// 解析失败,保持原样
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (typeof parsedContent === 'object' && parsedContent !== null) {
|
|
745
|
+
remoteUrl = parsedContent.remoteUrl || parsedContent.url || '';
|
|
746
|
+
duration = parsedContent.duration || 0;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// 兜底:从 msg 根对象查找
|
|
750
|
+
if (!remoteUrl) {
|
|
727
751
|
remoteUrl = msg.remoteUrl || msg.url || msg.localPath || '';
|
|
728
752
|
}
|
|
753
|
+
if (!duration) {
|
|
754
|
+
duration = msg.duration || 0;
|
|
755
|
+
}
|
|
729
756
|
|
|
730
|
-
this.log?.info(`[_extractMessageContent] 语音消息: remoteUrl=${remoteUrl}`);
|
|
757
|
+
this.log?.info(`[_extractMessageContent] 语音消息: remoteUrl=${remoteUrl}, duration=${duration}`);
|
|
731
758
|
return `[语音] ${remoteUrl} ${duration}秒`;
|
|
732
759
|
}
|
|
733
760
|
|
|
@@ -735,6 +762,62 @@ class MessageHandler {
|
|
|
735
762
|
return typeof content === 'string' ? content : JSON.stringify(content);
|
|
736
763
|
}
|
|
737
764
|
|
|
765
|
+
/**
|
|
766
|
+
* 语音识别:调用后端百度语音 API 将语音转为文字
|
|
767
|
+
*/
|
|
768
|
+
async _recognizeVoice(msg) {
|
|
769
|
+
try {
|
|
770
|
+
let content = msg.content;
|
|
771
|
+
if (typeof content === 'string' && content.startsWith('{')) {
|
|
772
|
+
try {
|
|
773
|
+
content = JSON.parse(content);
|
|
774
|
+
} catch (e) {
|
|
775
|
+
// 解析失败,保持原样
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const remoteUrl = content?.remoteUrl || content?.url || msg.remoteUrl || msg.url;
|
|
780
|
+
if (!remoteUrl) {
|
|
781
|
+
this.log?.warn('[_recognizeVoice] 语音消息缺少 remoteUrl,跳过识别');
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// 从 URL 提取扩展名并映射为百度支持的格式
|
|
786
|
+
const urlPath = remoteUrl.split('?')[0];
|
|
787
|
+
const ext = urlPath.split('.').pop()?.toLowerCase() || '';
|
|
788
|
+
const fmtMap = { aac: 'm4a', ogg: 'mp3', oga: 'mp3', opus: 'mp3' };
|
|
789
|
+
let format = fmtMap[ext] || ext;
|
|
790
|
+
if (!['pcm', 'wav', 'amr', 'm4a', 'mp3'].includes(format)) {
|
|
791
|
+
format = 'mp3';
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// 采样率修正:amr 强制 8000,其余使用消息自带值兜底 16000
|
|
795
|
+
let sampleRate = content?.sampleRate || msg.sampleRate || 16000;
|
|
796
|
+
if (format === 'amr') sampleRate = 8000;
|
|
797
|
+
|
|
798
|
+
const apiUrl = `${this.config.apiBaseUrl}/im/api/voice/recognize`;
|
|
799
|
+
this.log?.info(`[_recognizeVoice] 调用语音识别 API: ${apiUrl}, format=${format}, sampleRate=${sampleRate}`);
|
|
800
|
+
|
|
801
|
+
const response = await axios.post(apiUrl, {
|
|
802
|
+
audioUrl: remoteUrl,
|
|
803
|
+
format,
|
|
804
|
+
sampleRate,
|
|
805
|
+
}, { timeout: 30000 });
|
|
806
|
+
|
|
807
|
+
if (response.data?.code === 200 && response.data?.data?.text !== undefined) {
|
|
808
|
+
const text = response.data.data.text;
|
|
809
|
+
this.log?.info(`[_recognizeVoice] 语音识别成功: "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
|
|
810
|
+
return text;
|
|
811
|
+
} else {
|
|
812
|
+
this.log?.warn(`[_recognizeVoice] 语音识别失败: ${JSON.stringify(response.data)}`);
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
} catch (err) {
|
|
816
|
+
this.log?.error(`[_recognizeVoice] 语音识别异常: ${err.message}`);
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
738
821
|
parseCommand(raw, senderId) {
|
|
739
822
|
const trimmed = raw.slice(1).trim();
|
|
740
823
|
const parts = trimmed.split(/\s+/);
|