agentgui 1.0.616 → 1.0.617

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/static/index.html CHANGED
@@ -1456,7 +1456,6 @@
1456
1456
  align-items: center;
1457
1457
  justify-content: space-between;
1458
1458
  gap: 0.5rem;
1459
- background: #1f2937;
1460
1459
  padding: 0.5rem 1rem;
1461
1460
  }
1462
1461
 
@@ -1532,7 +1531,6 @@
1532
1531
  .block-tool-use {
1533
1532
  margin-bottom: 0;
1534
1533
  border-radius: 0.5rem;
1535
- background: #ecfeff;
1536
1534
  overflow: hidden;
1537
1535
  }
1538
1536
 
@@ -1543,7 +1541,6 @@
1543
1541
  display: flex;
1544
1542
  align-items: center;
1545
1543
  gap: 0.375rem;
1546
- background: #cffafe;
1547
1544
  }
1548
1545
 
1549
1546
  html.dark .block-tool-use .tool-header { }
@@ -2410,10 +2407,10 @@
2410
2407
  overflow: hidden;
2411
2408
  }
2412
2409
 
2413
- .block-tool-result.result-success { background: #f0fdf4; }
2414
- .block-tool-result.result-error { background: #fef2f2; }
2415
- html.dark .block-tool-result.result-success { background: #0a1f0f; }
2416
- html.dark .block-tool-result.result-error { background: #1c0f0f; }
2410
+ .block-tool-result.result-success { }
2411
+ .block-tool-result.result-error { }
2412
+ html.dark .block-tool-result.result-success { }
2413
+ html.dark .block-tool-result.result-error { }
2417
2414
 
2418
2415
  .block-tool-result .result-header {
2419
2416
  padding: 0.3rem 0.75rem;
@@ -2422,10 +2419,10 @@
2422
2419
  justify-content: space-between;
2423
2420
  }
2424
2421
 
2425
- .block-tool-result.result-success .result-header { background: #dcfce7; }
2426
- .block-tool-result.result-error .result-header { background: #fee2e2; }
2427
- html.dark .block-tool-result.result-success .result-header { background: #0f2b1a; }
2428
- html.dark .block-tool-result.result-error .result-header { background: #2c1010; }
2422
+ .block-tool-result.result-success .result-header { }
2423
+ .block-tool-result.result-error .result-header { }
2424
+ html.dark .block-tool-result.result-success .result-header { }
2425
+ html.dark .block-tool-result.result-error .result-header { }
2429
2426
 
2430
2427
  .block-tool-result .result-header .status-label {
2431
2428
  display: flex;
@@ -2497,15 +2494,13 @@
2497
2494
  }
2498
2495
 
2499
2496
  .block-result.result-ok {
2500
- background: linear-gradient(135deg, #ecfdf5, #f0fdf4);
2501
2497
  }
2502
2498
 
2503
2499
  .block-result.result-err {
2504
- background: linear-gradient(135deg, #fef2f2, #fff1f2);
2505
2500
  }
2506
2501
 
2507
- html.dark .block-result.result-ok { background: linear-gradient(135deg, #0a1f0f, #0f2b1a); }
2508
- html.dark .block-result.result-err { background: linear-gradient(135deg, #1c0f0f, #2c1010); }
2502
+ html.dark .block-result.result-ok { }
2503
+ html.dark .block-result.result-err { }
2509
2504
 
2510
2505
  .block-result .result-summary-header {
2511
2506
  padding: 0.375rem 0.75rem;
@@ -2553,21 +2548,19 @@
2553
2548
  .block-system {
2554
2549
  margin-bottom: 0;
2555
2550
  border-radius: 0.5rem;
2556
- background: #eef2ff;
2557
2551
  overflow: hidden;
2558
2552
  }
2559
2553
 
2560
- html.dark .block-system { background: #15103a; }
2554
+ html.dark .block-system { }
2561
2555
 
2562
2556
  .block-system .system-header {
2563
2557
  padding: 0.375rem 0.75rem;
2564
- background: #e0e7ff;
2565
2558
  font-weight: 600;
2566
2559
  font-size: 0.85rem;
2567
2560
  color: #3730a3;
2568
2561
  }
2569
2562
 
2570
- html.dark .block-system .system-header { background: #1e1b4b; color: #a5b4fc; }
2563
+ html.dark .block-system .system-header { color: #a5b4fc; }
2571
2564
 
2572
2565
  .block-system .system-body {
2573
2566
  padding: 0.375rem 0.75rem;
@@ -2611,7 +2604,6 @@
2611
2604
  margin-bottom: 0;
2612
2605
  border-radius: 0.5rem;
2613
2606
  overflow: hidden;
2614
- background: #111827;
2615
2607
  }
2616
2608
 
2617
2609
  .block-bash .bash-command {
@@ -2688,10 +2680,9 @@
2688
2680
  margin-bottom: 0;
2689
2681
  padding: 0.5rem 0.75rem;
2690
2682
  border-radius: 0.5rem;
2691
- background: #fef2f2;
2692
2683
  }
2693
2684
 
2694
- html.dark .block-error { background: #1c0f0f; }
2685
+ html.dark .block-error { }
2695
2686
 
2696
2687
  /* --- Image Block --- */
2697
2688
  .block-image {
@@ -532,7 +532,8 @@ class AgentGUIClient {
532
532
  return;
533
533
  }
534
534
  try {
535
- const data = await window.wsClient.rpc('conv.queue', { id: this.state.currentConversation.id, content: message });
535
+ // Queue uses msg.send which will enqueue if streaming is active
536
+ const data = await window.wsClient.rpc('msg.send', { id: this.state.currentConversation.id, content: message });
536
537
  console.log('Queue response:', data);
537
538
  if (this.ui.messageInput) {
538
539
  this.ui.messageInput.value = '';
@@ -918,6 +919,24 @@ class AgentGUIClient {
918
919
  this.scrollToBottom(true);
919
920
  }
920
921
 
922
+ // Immediately fetch any existing chunks for this session and start polling
923
+ // This ensures real-time feedback appears immediately
924
+ try {
925
+ const initialChunks = await this.fetchChunks(data.conversationId, 0);
926
+ // Filter to only chunks from the current session
927
+ const sessionChunks = initialChunks.filter(c => c.sessionId === data.sessionId && c.block && c.block.type);
928
+ if (sessionChunks.length > 0) {
929
+ this.renderChunkBatch(sessionChunks);
930
+ // Update lastFetchTimestamp so polling doesn't duplicate these chunks
931
+ const lastChunk = sessionChunks[sessionChunks.length - 1];
932
+ if (lastChunk && lastChunk.created_at) {
933
+ this.chunkPollState.lastFetchTimestamp = lastChunk.created_at;
934
+ }
935
+ }
936
+ } catch (e) {
937
+ console.warn('Initial chunk fetch failed:', e.message);
938
+ }
939
+
921
940
  // Start polling for chunks from database
922
941
  this.startChunkPolling(data.conversationId);
923
942
 
@@ -952,6 +971,18 @@ class AgentGUIClient {
952
971
  if (!this.state.streamingBlocks) this.state.streamingBlocks = [];
953
972
  this.state.streamingBlocks.push(block);
954
973
 
974
+ // Thinking blocks are transient and not stored in DB, so render immediately
975
+ if (block.type === 'thinking' && this.state.currentSession?.id === data.sessionId) {
976
+ const streamingEl = document.getElementById(`streaming-${data.sessionId}`);
977
+ if (streamingEl) {
978
+ const blocksEl = streamingEl.querySelector('.streaming-blocks');
979
+ if (blocksEl) {
980
+ const el = this.renderer.renderBlock(block, data, blocksEl);
981
+ if (el) blocksEl.appendChild(el);
982
+ }
983
+ }
984
+ }
985
+
955
986
  // WebSocket is now just a notification trigger, not data source
956
987
  // Actual blocks come from database polling in startChunkPolling()
957
988
  }
@@ -1131,6 +1162,14 @@ class AgentGUIClient {
1131
1162
  this.saveScrollPosition(conversationId);
1132
1163
  }
1133
1164
 
1165
+ // Fetch any final chunks that may have been missed during polling
1166
+ // This ensures all output is visible without requiring a page refresh
1167
+ if (conversationId && sessionId) {
1168
+ this.fetchRemainingChunks(conversationId, sessionId).catch(err => {
1169
+ console.warn('Final chunk fetch failed:', err.message);
1170
+ });
1171
+ }
1172
+
1134
1173
  this.enableControls();
1135
1174
  this.emit('streaming:complete', data);
1136
1175
 
@@ -1935,7 +1974,10 @@ class AgentGUIClient {
1935
1974
  if (pollState.isPolling) return;
1936
1975
 
1937
1976
  pollState.isPolling = true;
1938
- pollState.lastFetchTimestamp = Date.now();
1977
+ // Only reset lastFetchTimestamp if it wasn't already set by initial fetch
1978
+ if (pollState.lastFetchTimestamp === 0) {
1979
+ pollState.lastFetchTimestamp = Date.now();
1980
+ }
1939
1981
  pollState.backoffDelay = this._getAdaptivePollInterval();
1940
1982
  pollState.sessionCheckCounter = 0;
1941
1983
  pollState.emptyPollCount = 0;
@@ -2045,6 +2087,23 @@ class AgentGUIClient {
2045
2087
  if (this._placeholderTimer) { clearTimeout(this._placeholderTimer); this._placeholderTimer = null; }
2046
2088
  }
2047
2089
 
2090
+ /**
2091
+ * Fetch any remaining chunks after streaming completes
2092
+ * Ensures all output is visible without requiring a page refresh
2093
+ */
2094
+ async fetchRemainingChunks(conversationId, sessionId) {
2095
+ try {
2096
+ const lastTimestamp = this.chunkPollState.lastFetchTimestamp || 0;
2097
+ const chunks = await this.fetchChunks(conversationId, lastTimestamp);
2098
+ const sessionChunks = chunks.filter(c => c.sessionId === sessionId && c.block && c.block.type);
2099
+ if (sessionChunks.length > 0) {
2100
+ this.renderChunkBatch(sessionChunks);
2101
+ }
2102
+ } catch (err) {
2103
+ console.error('Failed to fetch remaining chunks:', err);
2104
+ }
2105
+ }
2106
+
2048
2107
  /**
2049
2108
  * Render a single chunk to the output
2050
2109
  */
@@ -334,7 +334,9 @@ class StreamingRenderer {
334
334
  case 'code_block':
335
335
  return this.renderCode(event);
336
336
  case 'thinking_block':
337
- return this.renderThinking(event);
337
+ // Thinking blocks are now rendered immediately via handleStreamingProgress
338
+ // Don't render them here to avoid duplicates
339
+ return null;
338
340
  case 'tool_use':
339
341
  return this.renderToolUse(event);
340
342
  default:
@@ -287,6 +287,17 @@
287
287
  operationInProgress.delete(data.toolId);
288
288
  render();
289
289
  }
290
+ } else if (data.type === 'tool_status_update') {
291
+ var tool = tools.find(t => t.id === data.toolId);
292
+ if (tool && data.data) {
293
+ if (data.data.installed) {
294
+ tool.status = data.data.isUpToDate ? 'installed' : 'needs_update';
295
+ tool.installed = true;
296
+ tool.isUpToDate = data.data.isUpToDate ?? true;
297
+ tool.installedVersion = data.data.installedVersion || tool.installedVersion;
298
+ }
299
+ render();
300
+ }
290
301
  } else if (data.type === 'tools_update_complete') {
291
302
  fetchTools();
292
303
  } else if (data.type === 'tools_refresh_complete') {
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { checkCliInstalled, getCliVersion } from './lib/tool-manager.js';
3
+ import { execSync } from 'child_process';
4
+
5
+ console.log('=== CLI Tool Detection Test ===\n');
6
+
7
+ const tools = [
8
+ { pkg: '@anthropic-ai/claude-code', bin: 'claude' },
9
+ { pkg: 'opencode-ai', bin: 'opencode' },
10
+ { pkg: '@google/gemini-cli', bin: 'gemini' },
11
+ { pkg: '@kilocode/cli', bin: 'kilo' },
12
+ { pkg: '@openai/codex', bin: 'codex' }
13
+ ];
14
+
15
+ tools.forEach(tool => {
16
+ try {
17
+ // Direct which check
18
+ const which = process.platform === 'win32' ? 'where' : 'which';
19
+ execSync(`${which} ${tool.bin}`, { stdio: 'pipe', timeout: 3000 });
20
+ const version = execSync(`${tool.bin} --version`, { stdio: 'pipe', timeout: 2000, encoding: 'utf8' }).trim();
21
+ const match = version.match(/(\d+\.\d+\.\d+)/);
22
+ console.log(`✓ ${tool.pkg}`);
23
+ console.log(` Binary: ${tool.bin} (found)`);
24
+ console.log(` Version output: ${version.substring(0, 50)}`);
25
+ console.log(` Parsed version: ${match ? match[1] : 'NOT FOUND'}`);
26
+ } catch (e) {
27
+ console.log(`✗ ${tool.pkg}`);
28
+ console.log(` Binary: ${tool.bin} (not found in PATH)`);
29
+ }
30
+ console.log();
31
+ });
32
+
33
+ console.log('=== Testing Tool Manager Functions ===\n');
34
+
35
+ // Test with promises
36
+ console.log('Note: Tool manager functions require async context');
37
+ console.log('Use tool-manager directly in server to test full functionality');
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * System Validation Script
5
+ * Verifies that CLI tools are properly detected and agents are available
6
+ * Usage: node test-system-validation.js
7
+ */
8
+
9
+ import { execSync } from 'child_process';
10
+ import os from 'os';
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+
14
+ const homeDir = os.homedir();
15
+ const colors = {
16
+ reset: '\x1b[0m',
17
+ green: '\x1b[32m',
18
+ red: '\x1b[31m',
19
+ yellow: '\x1b[33m',
20
+ blue: '\x1b[34m'
21
+ };
22
+
23
+ function log(color, label, message) {
24
+ console.log(`${colors[color]}[${label}]${colors.reset} ${message}`);
25
+ }
26
+
27
+ function success(label, message) {
28
+ log('green', label, message);
29
+ }
30
+
31
+ function error(label, message) {
32
+ log('red', label, message);
33
+ }
34
+
35
+ function warn(label, message) {
36
+ log('yellow', label, message);
37
+ }
38
+
39
+ function info(label, message) {
40
+ log('blue', label, message);
41
+ }
42
+
43
+ // Test CLI tool detection
44
+ function testCLIDetection() {
45
+ info('TEST', 'CLI Tool Detection');
46
+
47
+ const tools = [
48
+ { pkg: '@anthropic-ai/claude-code', bin: 'claude', name: 'Claude Code' },
49
+ { pkg: 'opencode-ai', bin: 'opencode', name: 'OpenCode' },
50
+ { pkg: '@google/gemini-cli', bin: 'gemini', name: 'Gemini CLI' },
51
+ { pkg: '@kilocode/cli', bin: 'kilo', name: 'Kilo Code' },
52
+ { pkg: '@openai/codex', bin: 'codex', name: 'Codex CLI' }
53
+ ];
54
+
55
+ let foundCount = 0;
56
+
57
+ for (const tool of tools) {
58
+ try {
59
+ const cmd = os.platform() === 'win32' ? 'where' : 'which';
60
+ const result = execSync(`${cmd} ${tool.bin}`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
61
+
62
+ if (result) {
63
+ success(tool.name, `Found at ${result}`);
64
+
65
+ // Try to get version
66
+ try {
67
+ const version = execSync(`${tool.bin} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
68
+ const match = version.match(/(\d+\.\d+\.\d+)/);
69
+ if (match) {
70
+ success(tool.name, `Version ${match[1]}`);
71
+ }
72
+ } catch (_) {
73
+ warn(tool.name, 'Could not detect version');
74
+ }
75
+
76
+ foundCount++;
77
+ }
78
+ } catch (_) {
79
+ error(tool.name, 'NOT FOUND in PATH');
80
+ }
81
+ }
82
+
83
+ console.log(`\n✓ CLI Detection Summary: ${foundCount}/${tools.length} tools found\n`);
84
+ return foundCount > 0;
85
+ }
86
+
87
+ // Test agent discovery paths
88
+ function testAgentPaths() {
89
+ info('TEST', 'Agent Plugin Paths');
90
+
91
+ const paths = [
92
+ { label: 'Claude Code Plugin', path: path.join(homeDir, '.claude', 'plugins', 'gm-cc') },
93
+ { label: 'OpenCode Agent', path: path.join(homeDir, '.config', 'opencode', 'agents', 'gm.md') },
94
+ { label: 'Gemini Extension', path: path.join(homeDir, '.gemini', 'extensions', 'gm') },
95
+ { label: 'Kilo Agent', path: path.join(homeDir, '.config', 'kilo', 'agents', 'gm.md') }
96
+ ];
97
+
98
+ let foundCount = 0;
99
+
100
+ for (const { label, path: p } of paths) {
101
+ if (fs.existsSync(p)) {
102
+ success(label, `Found at ${p}`);
103
+ foundCount++;
104
+ } else {
105
+ warn(label, `Not found at ${p}`);
106
+ }
107
+ }
108
+
109
+ console.log(`\n✓ Agent Paths Summary: ${foundCount}/${paths.length} agent paths exist\n`);
110
+ return foundCount > 0;
111
+ }
112
+
113
+ // Test Node.js binary discovery
114
+ function testNodeBinaries() {
115
+ info('TEST', 'Node Modules Binaries');
116
+
117
+ const agentguiPath = '/config/workspace/agentgui';
118
+ const binaries = ['claude', 'opencode', 'gemini', 'kilo', 'codex'];
119
+
120
+ let foundCount = 0;
121
+
122
+ for (const bin of binaries) {
123
+ const path = `${agentguiPath}/node_modules/.bin/${bin}`;
124
+ if (fs.existsSync(path)) {
125
+ success(`Binary: ${bin}`, `Found at ${path}`);
126
+ foundCount++;
127
+ } else {
128
+ warn(`Binary: ${bin}`, `NOT found at ${path}`);
129
+ }
130
+ }
131
+
132
+ console.log(`\n✓ Node Binaries Summary: ${foundCount}/${binaries.length} binaries found\n`);
133
+ return foundCount > 0;
134
+ }
135
+
136
+ // Test ES module imports
137
+ function testESModuleImports() {
138
+ info('TEST', 'ES Module Imports (tool-manager.js)');
139
+
140
+ try {
141
+ const toolManagerPath = '/config/workspace/agentgui/lib/tool-manager.js';
142
+ const content = fs.readFileSync(toolManagerPath, 'utf8');
143
+
144
+ // Check for proper execSync import
145
+ if (content.includes('import { spawn, execSync } from \'child_process\'')) {
146
+ success('execSync Import', 'Properly imported from child_process');
147
+ } else {
148
+ error('execSync Import', 'NOT found or incorrectly imported');
149
+ return false;
150
+ }
151
+
152
+ // Check that require() is not used
153
+ if (!content.includes('require(\'child_process\')')) {
154
+ success('No require()', 'No CommonJS require() calls found');
155
+ } else {
156
+ error('require() Calls', 'Found require() calls which won\'t work in ES modules');
157
+ return false;
158
+ }
159
+
160
+ console.log();
161
+ return true;
162
+ } catch (err) {
163
+ error('File Read', err.message);
164
+ return false;
165
+ }
166
+ }
167
+
168
+ // Run all tests
169
+ console.log('\n' + colors.blue + '═══════════════════════════════════════════════════════════' + colors.reset);
170
+ console.log(colors.blue + ' AgentGUI System Validation' + colors.reset);
171
+ console.log(colors.blue + '═══════════════════════════════════════════════════════════' + colors.reset + '\n');
172
+
173
+ let allPassed = true;
174
+
175
+ allPassed &= testCLIDetection();
176
+ allPassed &= testAgentPaths();
177
+ allPassed &= testNodeBinaries();
178
+ allPassed &= testESModuleImports();
179
+
180
+ console.log(colors.blue + '═══════════════════════════════════════════════════════════' + colors.reset);
181
+ if (allPassed) {
182
+ success('RESULT', 'All critical checks passed! System should work correctly.');
183
+ } else {
184
+ warn('RESULT', 'Some checks failed. Review errors above.');
185
+ }
186
+ console.log(colors.blue + '═══════════════════════════════════════════════════════════' + colors.reset + '\n');
187
+
188
+ process.exit(allPassed ? 0 : 1);