agentgui 1.0.227 → 1.0.228

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": "agentgui",
3
- "version": "1.0.227",
3
+ "version": "1.0.228",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -24,6 +24,68 @@ async function ensurePocketTtsSetup(onProgress) {
24
24
  return serverTTS.ensureInstalled(onProgress);
25
25
  }
26
26
 
27
+ // Model download manager
28
+ const modelDownloadState = {
29
+ downloading: false,
30
+ progress: null,
31
+ error: null,
32
+ complete: false
33
+ };
34
+
35
+ async function ensureModelsDownloaded() {
36
+ const { createRequire: cr } = await import('module');
37
+ const r = cr(import.meta.url);
38
+ const { checkAllFilesExist, downloadModels } = r('sttttsmodels');
39
+
40
+ if (checkAllFilesExist()) {
41
+ modelDownloadState.complete = true;
42
+ return true;
43
+ }
44
+
45
+ if (modelDownloadState.downloading) {
46
+ // Wait for current download
47
+ while (modelDownloadState.downloading) {
48
+ await new Promise(r => setTimeout(r, 100));
49
+ }
50
+ return modelDownloadState.complete;
51
+ }
52
+
53
+ modelDownloadState.downloading = true;
54
+ modelDownloadState.error = null;
55
+
56
+ try {
57
+ await downloadModels((progress) => {
58
+ modelDownloadState.progress = progress;
59
+ // Broadcast progress to all connected clients
60
+ broadcastSync({
61
+ type: 'model_download_progress',
62
+ progress: {
63
+ started: progress.started,
64
+ done: progress.done,
65
+ error: progress.error,
66
+ downloading: progress.downloading,
67
+ type: progress.type,
68
+ completedFiles: progress.completedFiles,
69
+ totalFiles: progress.totalFiles,
70
+ totalDownloaded: progress.totalDownloaded,
71
+ totalBytes: progress.totalBytes
72
+ }
73
+ });
74
+ });
75
+ modelDownloadState.complete = true;
76
+ return true;
77
+ } catch (err) {
78
+ modelDownloadState.error = err.message;
79
+ broadcastSync({
80
+ type: 'model_download_progress',
81
+ progress: { error: err.message, done: true }
82
+ });
83
+ return false;
84
+ } finally {
85
+ modelDownloadState.downloading = false;
86
+ }
87
+ }
88
+
27
89
  function eagerTTS(text, conversationId, sessionId) {
28
90
  getSpeech().then(speech => {
29
91
  const status = speech.getStatus();
@@ -802,7 +864,14 @@ const server = http.createServer(async (req, res) => {
802
864
  }
803
865
 
804
866
  if (pathOnly === '/api/conversations' && req.method === 'GET') {
805
- sendJSON(req, res, 200, { conversations: queries.getConversationsList() });
867
+ const conversations = queries.getConversationsList();
868
+ // Filter out stale streaming state for conversations not in activeExecutions
869
+ for (const conv of conversations) {
870
+ if (conv.isStreaming && !activeExecutions.has(conv.id)) {
871
+ conv.isStreaming = 0;
872
+ }
873
+ }
874
+ sendJSON(req, res, 200, { conversations });
806
875
  return;
807
876
  }
808
877
 
@@ -1601,11 +1670,18 @@ const server = http.createServer(async (req, res) => {
1601
1670
  pythonDetected: pyInfo.found,
1602
1671
  pythonVersion: pyInfo.version || null,
1603
1672
  setupMessage: baseStatus.ttsReady ? 'pocket-tts ready' : 'Will setup on first TTS request',
1673
+ modelsDownloading: modelDownloadState.downloading,
1674
+ modelsComplete: modelDownloadState.complete,
1675
+ modelsError: modelDownloadState.error,
1676
+ modelsProgress: modelDownloadState.progress,
1604
1677
  });
1605
1678
  } catch (err) {
1606
1679
  sendJSON(req, res, 200, {
1607
1680
  sttReady: false, ttsReady: false, sttLoading: false, ttsLoading: false,
1608
1681
  setupMessage: 'Will setup on first TTS request',
1682
+ modelsDownloading: modelDownloadState.downloading,
1683
+ modelsComplete: modelDownloadState.complete,
1684
+ modelsError: modelDownloadState.error,
1609
1685
  });
1610
1686
  }
1611
1687
  return;
@@ -2532,14 +2608,9 @@ function performAgentHealthCheck() {
2532
2608
  markAgentDead(conversationId, entry, 'Agent process died unexpectedly');
2533
2609
  } else if (now - entry.lastActivity > STUCK_AGENT_THRESHOLD_MS) {
2534
2610
  debugLog(`[HEALTH] Agent PID ${entry.pid} for conv ${conversationId} has no activity for ${Math.round((now - entry.lastActivity) / 1000)}s`);
2535
- broadcastSync({
2536
- type: 'streaming_error',
2537
- sessionId: entry.sessionId,
2538
- conversationId,
2539
- error: 'Agent may be stuck (no activity for 10 minutes)',
2540
- recoverable: true,
2541
- timestamp: now
2542
- });
2611
+ // Kill stuck agent and clear streaming state
2612
+ try { process.kill(entry.pid, 'SIGTERM'); } catch (e) {}
2613
+ markAgentDead(conversationId, entry, 'Agent was stuck (no activity for 10 minutes)');
2543
2614
  }
2544
2615
  } else {
2545
2616
  if (now - entry.startTime > NO_PID_GRACE_PERIOD_MS) {
@@ -2567,6 +2638,16 @@ function onServerReady() {
2567
2638
  // Resume interrupted streams after recovery
2568
2639
  resumeInterruptedStreams().catch(err => console.error('[RESUME] Startup error:', err.message));
2569
2640
 
2641
+ // Start model downloads in background after server is ready
2642
+ setTimeout(() => {
2643
+ ensureModelsDownloaded().then(ok => {
2644
+ if (ok) console.log('[MODELS] Speech models ready');
2645
+ else console.log('[MODELS] Speech model download failed');
2646
+ }).catch(err => {
2647
+ console.error('[MODELS] Download error:', err.message);
2648
+ });
2649
+ }, 2000);
2650
+
2570
2651
  getSpeech().then(s => s.preloadTTS()).catch(e => debugLog('[TTS] Preload failed: ' + e.message));
2571
2652
 
2572
2653
  performAutoImport();
package/static/index.html CHANGED
@@ -2271,6 +2271,7 @@
2271
2271
  .connection-dot.unknown { background: #6b7280; }
2272
2272
  .connection-dot.disconnected { background: #ef4444; animation: pulse 1.5s ease-in-out infinite; }
2273
2273
  .connection-dot.reconnecting { background: #f59e0b; animation: pulse 1s ease-in-out infinite; }
2274
+ .connection-dot.downloading { background: #3b82f6; animation: pulse 1s ease-in-out infinite; }
2274
2275
 
2275
2276
  .connection-tooltip {
2276
2277
  position: absolute;
@@ -432,6 +432,9 @@ class AgentGUIClient {
432
432
  case 'rate_limit_clear':
433
433
  this.handleRateLimitClear(data);
434
434
  break;
435
+ case 'model_download_progress':
436
+ this._handleModelDownloadProgress(data.progress);
437
+ break;
435
438
  default:
436
439
  break;
437
440
  }
@@ -1833,7 +1836,7 @@ class AgentGUIClient {
1833
1836
  }
1834
1837
 
1835
1838
  _updateConnectionIndicator(quality) {
1836
- if (this._indicatorDebounce) return;
1839
+ if (this._indicatorDebounce && !this._modelDownloadInProgress) return;
1837
1840
  this._indicatorDebounce = true;
1838
1841
  setTimeout(() => { this._indicatorDebounce = false; }, 1000);
1839
1842
 
@@ -1855,6 +1858,21 @@ class AgentGUIClient {
1855
1858
  const label = indicator.querySelector('.connection-label');
1856
1859
  if (!dot || !label) return;
1857
1860
 
1861
+ // Check if model download is in progress
1862
+ if (this._modelDownloadInProgress) {
1863
+ dot.className = 'connection-dot downloading';
1864
+ const progress = this._modelDownloadProgress;
1865
+ if (progress && progress.totalBytes > 0) {
1866
+ const pct = Math.round((progress.totalDownloaded / progress.totalBytes) * 100);
1867
+ label.textContent = `Models ${pct}%`;
1868
+ } else if (progress && progress.downloading) {
1869
+ label.textContent = 'Downloading...';
1870
+ } else {
1871
+ label.textContent = 'Loading models...';
1872
+ }
1873
+ return;
1874
+ }
1875
+
1858
1876
  dot.className = 'connection-dot';
1859
1877
  if (quality === 'disconnected' || quality === 'reconnecting') {
1860
1878
  dot.classList.add(quality);
@@ -1866,6 +1884,29 @@ class AgentGUIClient {
1866
1884
  }
1867
1885
  }
1868
1886
 
1887
+ _handleModelDownloadProgress(progress) {
1888
+ this._modelDownloadProgress = progress;
1889
+
1890
+ if (progress.error) {
1891
+ this._modelDownloadInProgress = false;
1892
+ console.error('[Models] Download error:', progress.error);
1893
+ this._updateConnectionIndicator(this.wsManager?.latency?.quality || 'unknown');
1894
+ return;
1895
+ }
1896
+
1897
+ if (progress.done) {
1898
+ this._modelDownloadInProgress = false;
1899
+ console.log('[Models] Download complete');
1900
+ this._updateConnectionIndicator(this.wsManager?.latency?.quality || 'unknown');
1901
+ return;
1902
+ }
1903
+
1904
+ if (progress.started || progress.downloading) {
1905
+ this._modelDownloadInProgress = true;
1906
+ this._updateConnectionIndicator(this.wsManager?.latency?.quality || 'unknown');
1907
+ }
1908
+ }
1909
+
1869
1910
  _toggleConnectionTooltip() {
1870
1911
  let tooltip = document.getElementById('connection-tooltip');
1871
1912
  if (tooltip) { tooltip.remove(); return; }