agentgui 1.0.208 → 1.0.209
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/CLAUDE.md +50 -0
- package/lib/windows-pocket-tts-setup.js +100 -23
- package/package.json +1 -1
- package/server.js +2 -1
package/CLAUDE.md
CHANGED
|
@@ -89,3 +89,53 @@ Server broadcasts:
|
|
|
89
89
|
- `streaming_complete` - Execution finished
|
|
90
90
|
- `streaming_error` - Execution failed
|
|
91
91
|
- `conversation_created`, `conversation_updated`, `conversation_deleted`
|
|
92
|
+
- `tts_setup_progress` - Windows pocket-tts setup progress (step, status, message)
|
|
93
|
+
|
|
94
|
+
## Pocket-TTS Windows Setup (Reliability for Slow/Bad Internet)
|
|
95
|
+
|
|
96
|
+
On Windows, text-to-speech uses pocket-tts which requires Python and pip install. The setup process is now resilient to slow/unreliable connections:
|
|
97
|
+
|
|
98
|
+
### Features
|
|
99
|
+
- **Extended timeouts**: 120s for pip install (accommodates slow connections)
|
|
100
|
+
- **Retry logic**: 3 attempts with exponential backoff (1s, 2s delays)
|
|
101
|
+
- **Progress reporting**: Real-time updates via WebSocket to UI
|
|
102
|
+
- **Partial install cleanup**: Failed venvs are removed to allow retry
|
|
103
|
+
- **Installation verification**: Binary validation via `--version` check
|
|
104
|
+
- **Concurrent waiting**: Multiple simultaneous requests wait for single setup (600s timeout)
|
|
105
|
+
|
|
106
|
+
### Configuration (lib/windows-pocket-tts-setup.js)
|
|
107
|
+
```javascript
|
|
108
|
+
const CONFIG = {
|
|
109
|
+
PIP_TIMEOUT: 120000, // 2 minutes
|
|
110
|
+
VENV_CREATION_TIMEOUT: 30000, // 30 seconds
|
|
111
|
+
MAX_RETRIES: 3, // 3 attempts
|
|
112
|
+
RETRY_DELAY_MS: 1000, // 1 second initial
|
|
113
|
+
RETRY_BACKOFF_MULTIPLIER: 2, // 2x exponential
|
|
114
|
+
};
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Network Requirements
|
|
118
|
+
- **Minimum**: 50 kbps sustained, < 5s latency, < 10% packet loss
|
|
119
|
+
- **Recommended**: 256+ kbps, < 2s latency, < 1% packet loss
|
|
120
|
+
- **Expected time on slow connection**: 2-6 minutes with retries
|
|
121
|
+
|
|
122
|
+
### Progress Messages
|
|
123
|
+
During TTS setup on first use, WebSocket broadcasts:
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"type": "tts_setup_progress",
|
|
127
|
+
"step": "detecting-python|creating-venv|installing|verifying",
|
|
128
|
+
"status": "in-progress|success|error",
|
|
129
|
+
"message": "descriptive status message with retry count if applicable"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Recovery Behavior
|
|
134
|
+
1. Network timeout → auto-retry with backoff
|
|
135
|
+
2. Partial venv → auto-cleanup before retry
|
|
136
|
+
3. Failed verification → auto-cleanup and error
|
|
137
|
+
4. Concurrent requests → first starts setup, others wait up to 600s
|
|
138
|
+
5. Interrupted setup → cleanup allows fresh retry
|
|
139
|
+
|
|
140
|
+
### Testing
|
|
141
|
+
Setup validates by running pocket-tts binary with `--version` flag to confirm functional installation, not just file existence.
|
|
@@ -8,6 +8,14 @@ const VENV_DIR = path.join(os.homedir(), '.gmgui', 'pocket-venv');
|
|
|
8
8
|
const isWin = process.platform === 'win32';
|
|
9
9
|
const EXECUTABLE_NAME = isWin ? 'pocket-tts.exe' : 'pocket-tts';
|
|
10
10
|
|
|
11
|
+
const CONFIG = {
|
|
12
|
+
PIP_TIMEOUT: 120000,
|
|
13
|
+
VENV_CREATION_TIMEOUT: 30000,
|
|
14
|
+
MAX_RETRIES: 3,
|
|
15
|
+
RETRY_DELAY_MS: 1000,
|
|
16
|
+
RETRY_BACKOFF_MULTIPLIER: 2,
|
|
17
|
+
};
|
|
18
|
+
|
|
11
19
|
function getPocketTtsPath() {
|
|
12
20
|
if (isWin) {
|
|
13
21
|
return path.join(VENV_DIR, 'Scripts', EXECUTABLE_NAME);
|
|
@@ -17,7 +25,7 @@ function getPocketTtsPath() {
|
|
|
17
25
|
|
|
18
26
|
function detectPython() {
|
|
19
27
|
try {
|
|
20
|
-
const versionOutput = execSync('python --version', { encoding: 'utf-8' }).trim();
|
|
28
|
+
const versionOutput = execSync('python --version', { encoding: 'utf-8', timeout: 10000 }).trim();
|
|
21
29
|
const match = versionOutput.match(/(\d+)\.(\d+)/);
|
|
22
30
|
if (!match) return { found: false, version: null, error: 'Could not parse version' };
|
|
23
31
|
|
|
@@ -40,6 +48,53 @@ function isSetup() {
|
|
|
40
48
|
return fs.existsSync(exePath);
|
|
41
49
|
}
|
|
42
50
|
|
|
51
|
+
function cleanupPartialInstall() {
|
|
52
|
+
try {
|
|
53
|
+
if (fs.existsSync(VENV_DIR)) {
|
|
54
|
+
fs.rmSync(VENV_DIR, { recursive: true, force: true });
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error(`Failed to cleanup partial install: ${e.message}`);
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function verifyInstallation() {
|
|
64
|
+
const exePath = getPocketTtsPath();
|
|
65
|
+
if (!fs.existsSync(exePath)) {
|
|
66
|
+
return { valid: false, error: `Binary not found at ${exePath}` };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const versionOutput = execSync(`"${exePath}" --version`, { encoding: 'utf-8', timeout: 10000, stdio: 'pipe' });
|
|
71
|
+
return { valid: true, version: versionOutput.trim() };
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return { valid: false, error: `Binary exists but failed verification: ${e.message}` };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function executeWithRetry(fn, stepName, maxRetries = CONFIG.MAX_RETRIES) {
|
|
78
|
+
let lastError = null;
|
|
79
|
+
let delayMs = CONFIG.RETRY_DELAY_MS;
|
|
80
|
+
|
|
81
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
82
|
+
try {
|
|
83
|
+
return await fn(attempt);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
lastError = e;
|
|
86
|
+
if (attempt < maxRetries) {
|
|
87
|
+
console.log(`Attempt ${attempt}/${maxRetries} failed for ${stepName}, retrying in ${delayMs}ms`);
|
|
88
|
+
await new Promise(r => setTimeout(r, delayMs));
|
|
89
|
+
delayMs *= CONFIG.RETRY_BACKOFF_MULTIPLIER;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const msg = `${stepName} failed after ${maxRetries} attempts: ${lastError.message || lastError}`;
|
|
95
|
+
throw new Error(msg);
|
|
96
|
+
}
|
|
97
|
+
|
|
43
98
|
async function install(onProgress) {
|
|
44
99
|
const pythonDetect = detectPython();
|
|
45
100
|
|
|
@@ -57,56 +112,79 @@ async function install(onProgress) {
|
|
|
57
112
|
if (onProgress) onProgress({ step: 'detecting-python', status: 'success', message: `Found Python ${pythonDetect.version}` });
|
|
58
113
|
|
|
59
114
|
if (isSetup()) {
|
|
60
|
-
|
|
61
|
-
|
|
115
|
+
const verify = verifyInstallation();
|
|
116
|
+
if (verify.valid) {
|
|
117
|
+
if (onProgress) onProgress({ step: 'verifying', status: 'success', message: 'pocket-tts already installed' });
|
|
118
|
+
return { success: true };
|
|
119
|
+
}
|
|
62
120
|
}
|
|
63
121
|
|
|
64
122
|
if (onProgress) onProgress({ step: 'creating-venv', status: 'in-progress', message: `Creating virtual environment at ${VENV_DIR}` });
|
|
65
123
|
|
|
66
124
|
try {
|
|
67
|
-
|
|
125
|
+
await executeWithRetry(async (attempt) => {
|
|
126
|
+
return execSync(`python -m venv "${VENV_DIR}"`, {
|
|
127
|
+
encoding: 'utf-8',
|
|
128
|
+
stdio: 'pipe',
|
|
129
|
+
timeout: CONFIG.VENV_CREATION_TIMEOUT,
|
|
130
|
+
});
|
|
131
|
+
}, 'venv creation', 2);
|
|
132
|
+
|
|
68
133
|
if (onProgress) onProgress({ step: 'creating-venv', status: 'success', message: 'Virtual environment created' });
|
|
69
134
|
} catch (e) {
|
|
70
|
-
const msg = `Failed to create venv: ${e.message || e
|
|
135
|
+
const msg = `Failed to create venv: ${e.message || e}`;
|
|
71
136
|
if (onProgress) onProgress({ step: 'creating-venv', status: 'error', message: msg });
|
|
137
|
+
cleanupPartialInstall();
|
|
72
138
|
return { success: false, error: msg };
|
|
73
139
|
}
|
|
74
140
|
|
|
75
|
-
if (onProgress) onProgress({ step: 'installing', status: 'in-progress', message: 'Installing pocket-tts via pip (this may take
|
|
141
|
+
if (onProgress) onProgress({ step: 'installing', status: 'in-progress', message: 'Installing pocket-tts via pip (this may take 2-5 minutes on slow connections)' });
|
|
76
142
|
|
|
77
143
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
144
|
+
await executeWithRetry(async (attempt) => {
|
|
145
|
+
if (attempt > 1 && onProgress) {
|
|
146
|
+
onProgress({ step: 'installing', status: 'in-progress', message: `Installing pocket-tts (attempt ${attempt}/${CONFIG.MAX_RETRIES})` });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const pipCmd = isWin
|
|
150
|
+
? `"${path.join(VENV_DIR, 'Scripts', 'pip')}" install --no-cache-dir pocket-tts`
|
|
151
|
+
: `"${path.join(VENV_DIR, 'bin', 'pip')}" install --no-cache-dir pocket-tts`;
|
|
152
|
+
|
|
153
|
+
return execSync(pipCmd, {
|
|
154
|
+
encoding: 'utf-8',
|
|
155
|
+
stdio: 'pipe',
|
|
156
|
+
timeout: CONFIG.PIP_TIMEOUT,
|
|
157
|
+
env: { ...process.env, PIP_DEFAULT_TIMEOUT: '120' },
|
|
158
|
+
});
|
|
159
|
+
}, 'pip install', CONFIG.MAX_RETRIES);
|
|
81
160
|
|
|
82
|
-
const installResult = execSync(pipCmd, { encoding: 'utf-8', stdio: 'pipe', timeout: 300000 });
|
|
83
161
|
if (onProgress) onProgress({ step: 'installing', status: 'success', message: 'pocket-tts installed successfully' });
|
|
84
162
|
} catch (e) {
|
|
85
|
-
const msg = `Failed to install pocket-tts: ${e.message || e
|
|
163
|
+
const msg = `Failed to install pocket-tts: ${e.message || e}`;
|
|
86
164
|
if (onProgress) onProgress({ step: 'installing', status: 'error', message: msg });
|
|
165
|
+
cleanupPartialInstall();
|
|
87
166
|
return { success: false, error: msg };
|
|
88
167
|
}
|
|
89
168
|
|
|
90
169
|
if (onProgress) onProgress({ step: 'verifying', status: 'in-progress', message: 'Verifying installation' });
|
|
91
170
|
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!fs.existsSync(exePath)) {
|
|
97
|
-
const msg = `pocket-tts binary not found at ${exePath}`;
|
|
171
|
+
const verify = verifyInstallation();
|
|
172
|
+
if (!verify.valid) {
|
|
173
|
+
const msg = verify.error || 'Installation verification failed';
|
|
98
174
|
if (onProgress) onProgress({ step: 'verifying', status: 'error', message: msg });
|
|
175
|
+
cleanupPartialInstall();
|
|
99
176
|
return { success: false, error: msg };
|
|
100
177
|
}
|
|
101
178
|
|
|
102
|
-
|
|
103
|
-
|
|
179
|
+
const exePath = getPocketTtsPath();
|
|
180
|
+
const binDir = path.join(VENV_DIR, 'bin');
|
|
181
|
+
const binExePath = path.join(binDir, 'pocket-tts');
|
|
182
|
+
|
|
104
183
|
if (isWin) {
|
|
105
184
|
try {
|
|
106
185
|
fs.mkdirSync(binDir, { recursive: true });
|
|
107
186
|
} catch (e) {}
|
|
108
187
|
|
|
109
|
-
// Copy pocket-tts.exe to bin folder
|
|
110
188
|
const exeWithExt = path.join(binDir, 'pocket-tts.exe');
|
|
111
189
|
if (fs.existsSync(exePath) && !fs.existsSync(exeWithExt)) {
|
|
112
190
|
try {
|
|
@@ -114,7 +192,6 @@ async function install(onProgress) {
|
|
|
114
192
|
} catch (e) {}
|
|
115
193
|
}
|
|
116
194
|
|
|
117
|
-
// Create a batch file wrapper for Node.js spawn compatibility
|
|
118
195
|
const batchFile = path.join(binDir, 'pocket-tts.bat');
|
|
119
196
|
if (!fs.existsSync(batchFile) && fs.existsSync(exeWithExt)) {
|
|
120
197
|
try {
|
|
@@ -124,9 +201,9 @@ async function install(onProgress) {
|
|
|
124
201
|
}
|
|
125
202
|
}
|
|
126
203
|
|
|
127
|
-
if (onProgress) onProgress({ step: 'verifying', status: 'success', message:
|
|
204
|
+
if (onProgress) onProgress({ step: 'verifying', status: 'success', message: `pocket-tts ready (${verify.version})` });
|
|
128
205
|
|
|
129
206
|
return { success: true };
|
|
130
207
|
}
|
|
131
208
|
|
|
132
|
-
export { detectPython, isSetup, install, getPocketTtsPath, VENV_DIR };
|
|
209
|
+
export { detectPython, isSetup, install, getPocketTtsPath, VENV_DIR, CONFIG, cleanupPartialInstall, verifyInstallation };
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -27,7 +27,8 @@ async function ensurePocketTtsSetup(onProgress) {
|
|
|
27
27
|
|
|
28
28
|
if (pocketTtsSetupState.inProgress) {
|
|
29
29
|
let waited = 0;
|
|
30
|
-
|
|
30
|
+
const MAX_WAIT = 600000;
|
|
31
|
+
while (pocketTtsSetupState.inProgress && waited < MAX_WAIT) {
|
|
31
32
|
await new Promise(r => setTimeout(r, 100));
|
|
32
33
|
waited += 100;
|
|
33
34
|
}
|