agentgui 1.0.512 → 1.0.514

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.
@@ -6,10 +6,10 @@ import path from 'path';
6
6
 
7
7
  const isWindows = os.platform() === 'win32';
8
8
  const TOOLS = [
9
- { id: 'gm-oc', name: 'OpenCode', pkg: 'opencode-ai' },
10
- { id: 'gm-gc', name: 'Gemini CLI', pkg: '@google/gemini-cli' },
11
- { id: 'gm-kilo', name: 'Kilo', pkg: '@kilocode/cli' },
12
- { id: 'gm-cc', name: 'Claude Code', pkg: '@anthropic-ai/claude-code' },
9
+ { id: 'gm-oc', name: 'OpenCode', pkg: 'opencode-ai', pluginId: 'opencode-ai' },
10
+ { id: 'gm-gc', name: 'Gemini CLI', pkg: '@google/gemini-cli', pluginId: 'gm' },
11
+ { id: 'gm-kilo', name: 'Kilo', pkg: '@kilocode/cli', pluginId: '@kilocode/cli' },
12
+ { id: 'gm-cc', name: 'Claude Code', pkg: '@anthropic-ai/claude-code', pluginId: 'gm' },
13
13
  ];
14
14
 
15
15
  const statusCache = new Map();
@@ -23,12 +23,14 @@ const getNodeModulesPath = () => {
23
23
  return path.join(__dirname, '..', 'node_modules');
24
24
  };
25
25
 
26
- const getInstalledVersion = (pkg) => {
26
+ const getInstalledVersion = (pkg, pluginId = null) => {
27
27
  try {
28
28
  const homeDir = os.homedir();
29
+ const tool = pluginId ? TOOLS.find(t => t.pkg === pkg) : null;
30
+ const actualPluginId = pluginId || (tool?.pluginId) || pkg;
29
31
 
30
- // Check Claude Code plugins
31
- const claudePath = path.join(homeDir, '.claude', 'plugins', pkg, 'plugin.json');
32
+ // Check Claude Code plugins using correct pluginId
33
+ const claudePath = path.join(homeDir, '.claude', 'plugins', actualPluginId, 'plugin.json');
32
34
  if (fs.existsSync(claudePath)) {
33
35
  try {
34
36
  const pluginJson = JSON.parse(fs.readFileSync(claudePath, 'utf-8'));
@@ -38,8 +40,8 @@ const getInstalledVersion = (pkg) => {
38
40
  }
39
41
  }
40
42
 
41
- // Check OpenCode agents
42
- const opencodePath = path.join(homeDir, '.config', 'opencode', 'agents', pkg, 'plugin.json');
43
+ // Check OpenCode agents using correct pluginId
44
+ const opencodePath = path.join(homeDir, '.config', 'opencode', 'agents', actualPluginId, 'plugin.json');
43
45
  if (fs.existsSync(opencodePath)) {
44
46
  try {
45
47
  const pluginJson = JSON.parse(fs.readFileSync(opencodePath, 'utf-8'));
@@ -50,7 +52,7 @@ const getInstalledVersion = (pkg) => {
50
52
  }
51
53
 
52
54
  // Check Gemini CLI agents (stored as 'gm' directory)
53
- const geminiPath = path.join(homeDir, '.gemini', 'extensions', 'gm', 'plugin.json');
55
+ const geminiPath = path.join(homeDir, '.gemini', 'extensions', actualPluginId, 'plugin.json');
54
56
  if (fs.existsSync(geminiPath)) {
55
57
  try {
56
58
  const pluginJson = JSON.parse(fs.readFileSync(geminiPath, 'utf-8'));
@@ -60,7 +62,7 @@ const getInstalledVersion = (pkg) => {
60
62
  }
61
63
  }
62
64
  // Try gemini-extension.json as fallback
63
- const geminiExtPath = path.join(homeDir, '.gemini', 'extensions', 'gm', 'gemini-extension.json');
65
+ const geminiExtPath = path.join(homeDir, '.gemini', 'extensions', actualPluginId, 'gemini-extension.json');
64
66
  if (fs.existsSync(geminiExtPath)) {
65
67
  try {
66
68
  const extJson = JSON.parse(fs.readFileSync(geminiExtPath, 'utf-8'));
@@ -70,8 +72,8 @@ const getInstalledVersion = (pkg) => {
70
72
  }
71
73
  }
72
74
 
73
- // Check Kilo agents
74
- const kiloPath = path.join(homeDir, '.config', 'kilo', 'agents', pkg, 'plugin.json');
75
+ // Check Kilo agents using correct pluginId
76
+ const kiloPath = path.join(homeDir, '.config', 'kilo', 'agents', actualPluginId, 'plugin.json');
75
77
  if (fs.existsSync(kiloPath)) {
76
78
  try {
77
79
  const pluginJson = JSON.parse(fs.readFileSync(kiloPath, 'utf-8'));
@@ -379,7 +381,7 @@ export async function install(toolId, onProgress) {
379
381
  statusCache.delete(toolId);
380
382
  versionCache.clear();
381
383
 
382
- const version = getInstalledVersion(tool.pkg);
384
+ const version = getInstalledVersion(tool.pkg, tool.pluginId);
383
385
  if (!version) {
384
386
  console.warn(`[tool-manager] Install succeeded but version detection failed for ${toolId}. Attempting CLI check...`);
385
387
  const cliVersion = await getCliToolVersion(tool.pkg);
@@ -413,7 +415,7 @@ export async function update(toolId, onProgress) {
413
415
  statusCache.delete(toolId);
414
416
  versionCache.clear();
415
417
 
416
- const version = getInstalledVersion(tool.pkg);
418
+ const version = getInstalledVersion(tool.pkg, tool.pluginId);
417
419
  if (!version) {
418
420
  console.warn(`[tool-manager] Update succeeded but version detection failed for ${toolId}. Attempting CLI check...`);
419
421
  const cliVersion = await getCliToolVersion(tool.pkg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.512",
3
+ "version": "1.0.514",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -1863,17 +1863,13 @@ const server = http.createServer(async (req, res) => {
1863
1863
  if (!installCompleted) {
1864
1864
  installCompleted = true;
1865
1865
  queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Install timeout after 6 minutes' });
1866
- if (wsOptimizer && wsOptimizer.broadcast) {
1867
- wsOptimizer.broadcast({ type: 'tool_install_failed', toolId, data: { success: false, error: 'Install timeout after 6 minutes' } });
1868
- }
1866
+ broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error: 'Install timeout after 6 minutes' } });
1869
1867
  queries.addToolInstallHistory(toolId, 'install', 'failed', 'Install timeout after 6 minutes');
1870
1868
  }
1871
1869
  }, 360000);
1872
1870
 
1873
1871
  toolManager.install(toolId, (msg) => {
1874
- if (wsOptimizer && wsOptimizer.broadcast) {
1875
- wsOptimizer.broadcast({ type: 'tool_install_progress', toolId, data: msg });
1876
- }
1872
+ broadcastSync({ type: 'tool_install_progress', toolId, data: msg });
1877
1873
  }).then(async (result) => {
1878
1874
  clearTimeout(installTimeout);
1879
1875
  if (installCompleted) return;
@@ -1884,16 +1880,12 @@ const server = http.createServer(async (req, res) => {
1884
1880
  queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
1885
1881
  const freshStatus = await toolManager.checkToolStatusAsync(toolId);
1886
1882
  console.log(`[TOOLS-API] Fresh status after install for ${toolId}:`, JSON.stringify(freshStatus));
1887
- if (wsOptimizer && wsOptimizer.broadcast) {
1888
- wsOptimizer.broadcast({ type: 'tool_install_complete', toolId, data: { success: true, ...freshStatus } });
1889
- }
1883
+ broadcastSync({ type: 'tool_install_complete', toolId, data: { success: true, ...freshStatus } });
1890
1884
  queries.addToolInstallHistory(toolId, 'install', 'success', null);
1891
1885
  } else {
1892
1886
  console.error(`[TOOLS-API] Install failed for ${toolId}:`, result.error);
1893
1887
  queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
1894
- if (wsOptimizer && wsOptimizer.broadcast) {
1895
- wsOptimizer.broadcast({ type: 'tool_install_failed', toolId, data: result });
1896
- }
1888
+ broadcastSync({ type: 'tool_install_failed', toolId, data: result });
1897
1889
  queries.addToolInstallHistory(toolId, 'install', 'failed', result.error);
1898
1890
  }
1899
1891
  }).catch((err) => {
@@ -1903,9 +1895,7 @@ const server = http.createServer(async (req, res) => {
1903
1895
  const error = err?.message || 'Unknown error';
1904
1896
  console.error(`[TOOLS-API] Install error for ${toolId}:`, error);
1905
1897
  queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
1906
- if (wsOptimizer && wsOptimizer.broadcast) {
1907
- wsOptimizer.broadcast({ type: 'tool_install_failed', toolId, data: { success: false, error } });
1908
- }
1898
+ broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error } });
1909
1899
  queries.addToolInstallHistory(toolId, 'install', 'failed', error);
1910
1900
  });
1911
1901
  return;
@@ -1932,17 +1922,13 @@ const server = http.createServer(async (req, res) => {
1932
1922
  if (!updateCompleted) {
1933
1923
  updateCompleted = true;
1934
1924
  queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Update timeout after 6 minutes' });
1935
- if (wsOptimizer && wsOptimizer.broadcast) {
1936
- wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: { success: false, error: 'Update timeout after 6 minutes' } });
1937
- }
1925
+ broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: 'Update timeout after 6 minutes' } });
1938
1926
  queries.addToolInstallHistory(toolId, 'update', 'failed', 'Update timeout after 6 minutes');
1939
1927
  }
1940
1928
  }, 360000);
1941
1929
 
1942
1930
  toolManager.update(toolId, (msg) => {
1943
- if (wsOptimizer && wsOptimizer.broadcast) {
1944
- wsOptimizer.broadcast({ type: 'tool_update_progress', toolId, data: msg });
1945
- }
1931
+ broadcastSync({ type: 'tool_update_progress', toolId, data: msg });
1946
1932
  }).then(async (result) => {
1947
1933
  clearTimeout(updateTimeout);
1948
1934
  if (updateCompleted) return;
@@ -1953,16 +1939,12 @@ const server = http.createServer(async (req, res) => {
1953
1939
  queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
1954
1940
  const freshStatus = await toolManager.checkToolStatusAsync(toolId);
1955
1941
  console.log(`[TOOLS-API] Fresh status after update for ${toolId}:`, JSON.stringify(freshStatus));
1956
- if (wsOptimizer && wsOptimizer.broadcast) {
1957
- wsOptimizer.broadcast({ type: 'tool_update_complete', toolId, data: { success: true, ...freshStatus } });
1958
- }
1942
+ broadcastSync({ type: 'tool_update_complete', toolId, data: { success: true, ...freshStatus } });
1959
1943
  queries.addToolInstallHistory(toolId, 'update', 'success', null);
1960
1944
  } else {
1961
1945
  console.error(`[TOOLS-API] Update failed for ${toolId}:`, result.error);
1962
1946
  queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
1963
- if (wsOptimizer && wsOptimizer.broadcast) {
1964
- wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: result });
1965
- }
1947
+ broadcastSync({ type: 'tool_update_failed', toolId, data: result });
1966
1948
  queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
1967
1949
  }
1968
1950
  }).catch((err) => {
@@ -1972,9 +1954,7 @@ const server = http.createServer(async (req, res) => {
1972
1954
  const error = err?.message || 'Unknown error';
1973
1955
  console.error(`[TOOLS-API] Update error for ${toolId}:`, error);
1974
1956
  queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
1975
- if (wsOptimizer && wsOptimizer.broadcast) {
1976
- wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: { success: false, error } });
1977
- }
1957
+ broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error } });
1978
1958
  queries.addToolInstallHistory(toolId, 'update', 'failed', error);
1979
1959
  });
1980
1960
  return;
@@ -453,6 +453,43 @@
453
453
  });
454
454
  }
455
455
 
456
+ function preGenerateTTS(text) {
457
+ if (!ttsEnabled) return;
458
+ var clean = text.replace(/<[^>]*>/g, '').trim();
459
+ if (!clean) return;
460
+ var parts = [];
461
+ if (typeof agentGUIClient !== 'undefined' && agentGUIClient && typeof agentGUIClient.parseMarkdownCodeBlocks === 'function') {
462
+ parts = agentGUIClient.parseMarkdownCodeBlocks(clean);
463
+ } else {
464
+ parts = [{ type: 'text', content: clean }];
465
+ }
466
+ parts.forEach(function(part) {
467
+ if (part.type === 'code') return;
468
+ var segment = part.content.trim();
469
+ if (!segment) return;
470
+ var cacheKey = selectedVoiceId + ':' + segment;
471
+ if (ttsAudioCache.has(cacheKey)) return;
472
+ var optimizedText = optimizePromptForSpeech(segment);
473
+ fetch(BASE + '/api/tts', {
474
+ method: 'POST',
475
+ headers: { 'Content-Type': 'application/json' },
476
+ body: JSON.stringify({ text: optimizedText, voiceId: selectedVoiceId })
477
+ }).then(function(resp) {
478
+ if (!resp.ok) throw new Error('TTS pre-generation failed: ' + resp.status);
479
+ return resp.arrayBuffer();
480
+ }).then(function(buf) {
481
+ var blob = new Blob([buf], { type: 'audio/wav' });
482
+ if (ttsAudioCache.size >= TTS_CLIENT_CACHE_MAX) {
483
+ var oldest = ttsAudioCache.keys().next().value;
484
+ ttsAudioCache.delete(oldest);
485
+ }
486
+ ttsAudioCache.set(cacheKey, blob);
487
+ }).catch(function(err) {
488
+ console.warn('[Voice] TTS pre-generation failed:', err);
489
+ });
490
+ });
491
+ }
492
+
456
493
  function processQueue() {
457
494
  if (isSpeaking || speechQueue.length === 0) return;
458
495
  if (ttsDisabledUntilReset) {
@@ -783,6 +820,7 @@
783
820
  var div = addVoiceBlock(block.text, isUser);
784
821
  if (div && isNew && ttsEnabled && blockRole === 'assistant') {
785
822
  div.classList.add('speaking');
823
+ preGenerateTTS(block.text);
786
824
  speak(block.text);
787
825
  setTimeout(function() { div.classList.remove('speaking'); }, 2000);
788
826
  }
package/test-fixes.mjs ADDED
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Test script for validating the two fixes:
5
+ * 1. Agent selector visibility in chat view
6
+ * 2. TTS streaming pre-generation
7
+ */
8
+
9
+ import fs from 'fs';
10
+ import { execSync } from 'child_process';
11
+
12
+ console.log('=== AgentGUI Fix Validation ===\n');
13
+
14
+ // Test 1: Verify agent selectors are NOT hidden on desktop
15
+ console.log('Test 1: Agent Selector Visibility');
16
+ console.log('-----------------------------------');
17
+ const html = fs.readFileSync('./static/index.html', 'utf-8');
18
+ const beforeMobileMedia = html.substring(0, html.indexOf('@media (max-width: 480px)'));
19
+ const cliHiddenOnDesktop = beforeMobileMedia.includes('.cli-selector { display: none; }');
20
+ const modelHiddenOnDesktop = beforeMobileMedia.includes('.model-selector { display: none; }');
21
+
22
+ if (!cliHiddenOnDesktop && !modelHiddenOnDesktop) {
23
+ console.log('✓ PASS: Agent selectors are NOT hidden by CSS on desktop');
24
+ console.log(' - CLI selector will be visible when populated');
25
+ console.log(' - Model selector will be visible when populated');
26
+ } else {
27
+ console.log('❌ FAIL: Agent selectors are still hidden on desktop');
28
+ if (cliHiddenOnDesktop) console.log(' - .cli-selector has display:none');
29
+ if (modelHiddenOnDesktop) console.log(' - .model-selector has display:none');
30
+ }
31
+
32
+ // Verify selectors are in HTML
33
+ const hasSelectors = html.includes('data-cli-selector') &&
34
+ html.includes('data-agent-selector') &&
35
+ html.includes('data-model-selector');
36
+ if (hasSelectors) {
37
+ console.log('✓ PASS: All selector elements are present in HTML');
38
+ } else {
39
+ console.log('❌ FAIL: Some selector elements are missing');
40
+ }
41
+
42
+ // Test 2: Verify TTS pre-generation is implemented
43
+ console.log('\nTest 2: TTS Streaming Pre-generation');
44
+ console.log('--------------------------------------');
45
+ const voiceJs = fs.readFileSync('./static/js/voice.js', 'utf-8');
46
+ const hasPreGenerateFunction = voiceJs.includes('function preGenerateTTS(text)');
47
+ const callsPreGenerate = voiceJs.includes('preGenerateTTS(block.text)');
48
+ const usesCache = voiceJs.includes('if (ttsAudioCache.has(cacheKey)) return;');
49
+ const fetchesTTS = voiceJs.match(/fetch\(BASE \+ '\/api\/tts'/g)?.length >= 2; // Should be in both preGenerate and processQueue
50
+
51
+ if (hasPreGenerateFunction && callsPreGenerate && usesCache && fetchesTTS) {
52
+ console.log('✓ PASS: TTS pre-generation is fully implemented');
53
+ console.log(' - preGenerateTTS function exists');
54
+ console.log(' - Called when assistant text arrives');
55
+ console.log(' - Uses cache to avoid duplicate generation');
56
+ console.log(' - Audio generation happens in background');
57
+ } else {
58
+ console.log('❌ FAIL: TTS pre-generation incomplete');
59
+ if (!hasPreGenerateFunction) console.log(' - Missing preGenerateTTS function');
60
+ if (!callsPreGenerate) console.log(' - Not called in handleVoiceBlock');
61
+ if (!usesCache) console.log(' - Missing cache check');
62
+ }
63
+
64
+ // Test 3: Check that client.js populates selectors correctly
65
+ console.log('\nTest 3: Selector Population Logic');
66
+ console.log('-----------------------------------');
67
+ const clientJs = fs.readFileSync('./static/js/client.js', 'utf-8');
68
+ const setsDisplay = clientJs.includes("this.ui.cliSelector.style.display = 'inline-block'");
69
+ const populatesOptions = clientJs.includes('.innerHTML = displayAgents');
70
+
71
+ if (setsDisplay && populatesOptions) {
72
+ console.log('✓ PASS: Client.js correctly populates and displays selectors');
73
+ console.log(' - Sets display to inline-block when agents loaded');
74
+ console.log(' - Populates options from agent list');
75
+ } else {
76
+ console.log('❌ FAIL: Selector population logic may be broken');
77
+ }
78
+
79
+ // Summary
80
+ console.log('\n=== SUMMARY ===');
81
+ const allTestsPass = !cliHiddenOnDesktop && !modelHiddenOnDesktop &&
82
+ hasSelectors && hasPreGenerateFunction &&
83
+ callsPreGenerate && usesCache &&
84
+ setsDisplay && populatesOptions;
85
+
86
+ if (allTestsPass) {
87
+ console.log('✓ ALL TESTS PASSED\n');
88
+ console.log('Manual Testing Instructions:');
89
+ console.log('1. Start server: npm run dev');
90
+ console.log('2. Open http://localhost:3000/gm/');
91
+ console.log('3. Check chat input area - you should see:');
92
+ console.log(' - CLI selector dropdown (e.g., "Claude")');
93
+ console.log(' - Model selector dropdown (e.g., "Sonnet 4.5")');
94
+ console.log(' - Microphone button for voice input');
95
+ console.log('4. Open Voice tab and enable "Auto-speak responses"');
96
+ console.log('5. Send a message and observe:');
97
+ console.log(' - Text streams in as agent responds');
98
+ console.log(' - Voice playback starts immediately after completion (no delay)');
99
+ console.log(' - Audio was pre-generated during streaming');
100
+ } else {
101
+ console.log('❌ SOME TESTS FAILED - Review output above\n');
102
+ process.exit(1);
103
+ }