@foxden-app/foxclaw 0.5.48 → 0.5.50
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/.env.example +14 -6
- package/CHANGELOG.md +24 -0
- package/dist/config.d.ts +4 -2
- package/dist/config.js +6 -4
- package/dist/controller/controller.js +1 -1
- package/dist/voice/tts.js +13 -4
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -56,17 +56,25 @@ CODEX_CLI_BIN=/absolute/path/to/codex
|
|
|
56
56
|
# Optional Telegram voice summaries.
|
|
57
57
|
# When enabled, completed final answers get a "listen" button and /voice can
|
|
58
58
|
# generate Telegram voice messages. SSH mode runs TTS and ffmpeg conversion on
|
|
59
|
-
#
|
|
59
|
+
# your own speech host; HTTP mode calls your own VOICE_TTS_URL and converts
|
|
60
|
+
# locally. FoxClaw does not ship with a public TTS backend.
|
|
60
61
|
# VOICE_TTS_ENABLED=false
|
|
62
|
+
# HTTP backend example: compatible service exposing /v1/tts/custom or
|
|
63
|
+
# /v1/tts/design and returning WAV audio. Local ffmpeg is required.
|
|
64
|
+
# VOICE_TTS_MODE=http
|
|
65
|
+
# VOICE_TTS_URL=http://127.0.0.1:18081
|
|
66
|
+
# VOICE_TTS_TOKEN=
|
|
67
|
+
# VOICE_FFMPEG_BIN=ffmpeg
|
|
68
|
+
# SSH backend example: remote host has the compatible service, token in its
|
|
69
|
+
# service .env, and ffmpeg available on the remote host.
|
|
61
70
|
# VOICE_TTS_MODE=ssh
|
|
62
|
-
# VOICE_TTS_SSH_HOST
|
|
63
|
-
# VOICE_TTS_SSH_DIR=/
|
|
71
|
+
# VOICE_TTS_SSH_HOST=<ssh-host>
|
|
72
|
+
# VOICE_TTS_SSH_DIR=/path/to/qwen-speech-server
|
|
64
73
|
# VOICE_TTS_DESIGN_INSTRUCT=用自然清晰的中文女声朗读,语速适中。
|
|
65
74
|
# VOICE_SUMMARY_BUTTON_ENABLED=true
|
|
75
|
+
# VOICE_SUMMARY_TEXT_LIMIT=180
|
|
66
76
|
# VOICE_TEXT_LIMIT=2800
|
|
67
|
-
#
|
|
68
|
-
# VOICE_TTS_TOKEN=
|
|
69
|
-
# VOICE_FFMPEG_BIN=ffmpeg
|
|
77
|
+
# VOICE_TTS_TIMEOUT_MS=120000
|
|
70
78
|
|
|
71
79
|
# Optional: standard HTTP(S) proxy for Telegram and ChatGPT/Codex backend requests.
|
|
72
80
|
# Put these in the same env file that `foxclaw start` installs into systemd/launchd.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable FoxClaw changes are listed here. Each release note is bilingual so GitHub Releases and the npm package are useful to both Chinese and English readers.
|
|
4
4
|
|
|
5
|
+
## 0.5.50 - 2026-06-20
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- 给 Telegram “听总结”按钮增加独立的 `VOICE_SUMMARY_TEXT_LIMIT`,默认只朗读短摘要,避免把完整最终回复交给慢速 TTS 后端导致长时间排队或超时。
|
|
9
|
+
- 新增 `VOICE_TTS_TIMEOUT_MS`,HTTP/SSH 语音后端共用该超时配置;SSH 超时会终止远端调用,HTTP 超时会中止请求。
|
|
10
|
+
- `/voice <文本>` 仍使用 `VOICE_TEXT_LIMIT`,方便手动朗读较长文本;最终总结按钮走更短的摘要上限,优先保证可听、及时返回。
|
|
11
|
+
|
|
12
|
+
### English
|
|
13
|
+
- Added a separate `VOICE_SUMMARY_TEXT_LIMIT` for Telegram "Listen" buttons, so final-answer voice summaries read a short digest instead of sending the entire final response to slow TTS backends.
|
|
14
|
+
- Added `VOICE_TTS_TIMEOUT_MS` for both HTTP and SSH voice backends; SSH calls are terminated on timeout and HTTP requests are aborted.
|
|
15
|
+
- `/voice <text>` still uses `VOICE_TEXT_LIMIT` for manual longer reads, while final-answer buttons use the shorter summary limit for timely playback.
|
|
16
|
+
|
|
17
|
+
## 0.5.49 - 2026-06-20
|
|
18
|
+
|
|
19
|
+
### 中文
|
|
20
|
+
- 将 Telegram 总结语音后端彻底改为用户自配:默认不再内置 `thinkbook16p`、`tts.foxden.app` 或任何私有 TTS 服务地址。
|
|
21
|
+
- 启用语音后如果缺少 `VOICE_TTS_URL` 或 `VOICE_TTS_SSH_HOST`/`VOICE_TTS_SSH_DIR`,会明确提示配置缺失,而不是尝试连接开发者私有服务。
|
|
22
|
+
- 更新 `.env.example`,把 HTTP/SSH 语音后端写成通用占位示例,明确 FoxClaw 不随包提供公共 TTS 后端。
|
|
23
|
+
|
|
24
|
+
### English
|
|
25
|
+
- Made the Telegram voice-summary backend fully user-configured: FoxClaw no longer embeds `thinkbook16p`, `tts.foxden.app`, or any private TTS service endpoint as a product default.
|
|
26
|
+
- When voice is enabled without `VOICE_TTS_URL` or `VOICE_TTS_SSH_HOST`/`VOICE_TTS_SSH_DIR`, FoxClaw now reports the missing configuration instead of trying a developer-private service.
|
|
27
|
+
- Updated `.env.example` to show generic HTTP/SSH backend placeholders and state that FoxClaw does not ship with a public TTS backend.
|
|
28
|
+
|
|
5
29
|
## 0.5.48 - 2026-06-20
|
|
6
30
|
|
|
7
31
|
### 中文
|
package/dist/config.d.ts
CHANGED
|
@@ -67,12 +67,14 @@ export interface AppConfig {
|
|
|
67
67
|
voiceTtsMode: 'http' | 'ssh';
|
|
68
68
|
voiceTtsUrl: string | null;
|
|
69
69
|
voiceTtsToken: string | null;
|
|
70
|
-
voiceTtsSshHost: string;
|
|
71
|
-
voiceTtsSshDir: string;
|
|
70
|
+
voiceTtsSshHost: string | null;
|
|
71
|
+
voiceTtsSshDir: string | null;
|
|
72
72
|
voiceTtsDesignInstruct: string;
|
|
73
73
|
voiceFfmpegBin: string;
|
|
74
74
|
voiceSummaryButtonEnabled: boolean;
|
|
75
|
+
voiceSummaryTextLimit: number;
|
|
75
76
|
voiceTextLimit: number;
|
|
77
|
+
voiceTtsTimeoutMs: number;
|
|
76
78
|
}
|
|
77
79
|
export declare function loadConfig(): AppConfig;
|
|
78
80
|
export declare function selectDefaultRuntimeBotToken(configuredTokens: string[], legacyToken: string | null): string | null;
|
package/dist/config.js
CHANGED
|
@@ -98,15 +98,17 @@ export function loadConfig() {
|
|
|
98
98
|
authSyncTempDir: process.env.AUTH_SYNC_TEMP_DIR || DEFAULT_AUTH_SYNC_TEMP_DIR,
|
|
99
99
|
authAutoDeleteNeedsRepair: boolEnv('AUTH_AUTO_DELETE_NEEDS_REPAIR', false),
|
|
100
100
|
voiceTtsEnabled: boolEnv('VOICE_TTS_ENABLED', false),
|
|
101
|
-
voiceTtsMode: parseVoiceTtsMode(process.env.VOICE_TTS_MODE || (process.env.
|
|
102
|
-
voiceTtsUrl: optional('VOICE_TTS_URL')
|
|
101
|
+
voiceTtsMode: parseVoiceTtsMode(process.env.VOICE_TTS_MODE || (process.env.VOICE_TTS_SSH_HOST?.trim() ? 'ssh' : 'http')),
|
|
102
|
+
voiceTtsUrl: optional('VOICE_TTS_URL'),
|
|
103
103
|
voiceTtsToken: optional('VOICE_TTS_TOKEN'),
|
|
104
|
-
voiceTtsSshHost:
|
|
105
|
-
voiceTtsSshDir:
|
|
104
|
+
voiceTtsSshHost: optional('VOICE_TTS_SSH_HOST'),
|
|
105
|
+
voiceTtsSshDir: optional('VOICE_TTS_SSH_DIR'),
|
|
106
106
|
voiceTtsDesignInstruct: process.env.VOICE_TTS_DESIGN_INSTRUCT?.trim() || '用自然清晰的中文女声朗读,语速适中。',
|
|
107
107
|
voiceFfmpegBin: process.env.VOICE_FFMPEG_BIN?.trim() || 'ffmpeg',
|
|
108
108
|
voiceSummaryButtonEnabled: boolEnv('VOICE_SUMMARY_BUTTON_ENABLED', true),
|
|
109
|
+
voiceSummaryTextLimit: intEnv('VOICE_SUMMARY_TEXT_LIMIT', 180),
|
|
109
110
|
voiceTextLimit: intEnv('VOICE_TEXT_LIMIT', 2800),
|
|
111
|
+
voiceTtsTimeoutMs: intEnv('VOICE_TTS_TIMEOUT_MS', 120_000),
|
|
110
112
|
};
|
|
111
113
|
ensureAppDirs(config);
|
|
112
114
|
return config;
|
|
@@ -8394,7 +8394,7 @@ export class BridgeSessionCore {
|
|
|
8394
8394
|
}
|
|
8395
8395
|
registerVoiceSnippet(scopeId, text) {
|
|
8396
8396
|
this.pruneVoiceSnippets();
|
|
8397
|
-
const normalized = normalizeVoiceText(text, this.config.
|
|
8397
|
+
const normalized = normalizeVoiceText(text, this.config.voiceSummaryTextLimit);
|
|
8398
8398
|
const id = crypto.randomBytes(6).toString('hex');
|
|
8399
8399
|
this.voiceSnippets.set(id, { scopeId, text: normalized, createdAt: Date.now() });
|
|
8400
8400
|
this.latestVoiceSnippetByScope.set(scopeId, id);
|
package/dist/voice/tts.js
CHANGED
|
@@ -61,14 +61,17 @@ async function requestTtsWav(config, pathname, body) {
|
|
|
61
61
|
throw new Error('VOICE_TTS_URL is not configured');
|
|
62
62
|
}
|
|
63
63
|
const endpoint = new URL(pathname, config.voiceTtsUrl.endsWith('/') ? config.voiceTtsUrl : `${config.voiceTtsUrl}/`);
|
|
64
|
+
const abortController = new AbortController();
|
|
65
|
+
const timer = setTimeout(() => abortController.abort(), config.voiceTtsTimeoutMs);
|
|
64
66
|
const response = await fetch(endpoint, {
|
|
65
67
|
method: 'POST',
|
|
68
|
+
signal: abortController.signal,
|
|
66
69
|
headers: {
|
|
67
70
|
'content-type': 'application/json',
|
|
68
71
|
...(config.voiceTtsToken ? { authorization: `Bearer ${config.voiceTtsToken}` } : {}),
|
|
69
72
|
},
|
|
70
73
|
body: JSON.stringify(body),
|
|
71
|
-
});
|
|
74
|
+
}).finally(() => clearTimeout(timer));
|
|
72
75
|
if (!response.ok) {
|
|
73
76
|
throw new Error(`TTS request failed: ${response.status} ${response.statusText}: ${await response.text().catch(() => '')}`);
|
|
74
77
|
}
|
|
@@ -105,6 +108,12 @@ async function convertToOggOpus(input, ffmpegBin) {
|
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
async function synthesizeViaSsh(text, config) {
|
|
111
|
+
if (!config.voiceTtsSshHost) {
|
|
112
|
+
throw new Error('VOICE_TTS_SSH_HOST is not configured');
|
|
113
|
+
}
|
|
114
|
+
if (!config.voiceTtsSshDir) {
|
|
115
|
+
throw new Error('VOICE_TTS_SSH_DIR is not configured');
|
|
116
|
+
}
|
|
108
117
|
const encodedText = Buffer.from(text, 'utf8').toString('base64');
|
|
109
118
|
const script = String.raw `set -euo pipefail
|
|
110
119
|
TEXT="$(printf '%s' "$1" | base64 -d)"
|
|
@@ -166,9 +175,9 @@ PY
|
|
|
166
175
|
encodedText,
|
|
167
176
|
config.voiceTtsSshDir,
|
|
168
177
|
config.voiceTtsDesignInstruct,
|
|
169
|
-
], script);
|
|
178
|
+
], script, config.voiceTtsTimeoutMs);
|
|
170
179
|
}
|
|
171
|
-
async function runSshBinary(args, stdin) {
|
|
180
|
+
async function runSshBinary(args, stdin, timeoutMs) {
|
|
172
181
|
return new Promise((resolve, reject) => {
|
|
173
182
|
const child = spawn('ssh', args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
174
183
|
const stdout = [];
|
|
@@ -176,7 +185,7 @@ async function runSshBinary(args, stdin) {
|
|
|
176
185
|
const timer = setTimeout(() => {
|
|
177
186
|
child.kill('SIGTERM');
|
|
178
187
|
reject(new Error('remote TTS timed out'));
|
|
179
|
-
},
|
|
188
|
+
}, timeoutMs);
|
|
180
189
|
child.stdout.on('data', (chunk) => {
|
|
181
190
|
stdout.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
182
191
|
});
|