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 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
- if (onProgress) onProgress({ step: 'verifying', status: 'success', message: 'pocket-tts already installed' });
61
- return { success: true };
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
- const mkdirResult = execSync(`python -m venv "${VENV_DIR}"`, { encoding: 'utf-8', stdio: 'pipe' });
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.stderr || 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 a minute)' });
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
- const pipCmd = isWin
79
- ? `"${path.join(VENV_DIR, 'Scripts', 'pip')}" install pocket-tts`
80
- : `"${path.join(VENV_DIR, 'bin', 'pip')}" install pocket-tts`;
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.stderr || 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 exePath = getPocketTtsPath();
93
- const binDir = path.join(VENV_DIR, 'bin');
94
- const binExePath = path.join(binDir, 'pocket-tts');
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
- // On Windows, webtalk looks for pocket-tts in bin/ (Unix path structure)
103
- // Copy the executable there for compatibility with Node.js spawn()
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: 'pocket-tts ready' });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.208",
3
+ "version": "1.0.209",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
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
- while (pocketTtsSetupState.inProgress && waited < 30000) {
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
  }