agentgui 1.0.637 → 1.0.638

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.
@@ -3,7 +3,7 @@ import { spawn, spawnSync } from 'child_process';
3
3
  const isWindows = process.platform === 'win32';
4
4
 
5
5
  function getSpawnOptions(cwd, additionalOptions = {}) {
6
- const options = { cwd, ...additionalOptions };
6
+ const options = { cwd, windowsHide: true, ...additionalOptions };
7
7
  if (isWindows) {
8
8
  options.shell = true;
9
9
  }
@@ -15,11 +15,12 @@ function log(msg) { console.log('[GM-CONFIG] ' + msg); }
15
15
 
16
16
  function runInstaller(pkg) {
17
17
  return new Promise((resolve) => {
18
- const npxCmd = isWindows ? 'npx.cmd' : 'npx';
19
- const proc = spawn(npxCmd, ['--yes', pkg], {
18
+ const bunCmd = isWindows ? 'bun.cmd' : 'bun';
19
+ const proc = spawn(bunCmd, ['x', pkg], {
20
20
  stdio: ['ignore', 'pipe', 'pipe'],
21
21
  timeout: 60000,
22
22
  shell: isWindows,
23
+ windowsHide: true,
23
24
  });
24
25
 
25
26
  let stdout = '';
@@ -158,7 +158,7 @@ const checkCliInstalled = (pkg) => {
158
158
  const binMap = { '@anthropic-ai/claude-code': 'claude', 'opencode-ai': 'opencode', '@google/gemini-cli': 'gemini', '@kilocode/cli': 'kilo', '@openai/codex': 'codex' };
159
159
  const bin = binMap[pkg];
160
160
  if (bin) {
161
- execSync(`${cmd} ${bin}`, { stdio: 'pipe', timeout: 3000 });
161
+ execSync(`${cmd} ${bin}`, { stdio: 'pipe', timeout: 3000, windowsHide: true });
162
162
  return true;
163
163
  }
164
164
  } catch (_) {}
@@ -172,7 +172,7 @@ const getCliVersion = (pkg) => {
172
172
  if (bin) {
173
173
  try {
174
174
  // Use short timeout - we already know the binary exists from checkCliInstalled
175
- const out = execSync(`${bin} --version`, { stdio: 'pipe', timeout: 1000, encoding: 'utf8' });
175
+ const out = execSync(`${bin} --version`, { stdio: 'pipe', timeout: 1000, encoding: 'utf8', windowsHide: true });
176
176
  const match = out.match(/(\d+\.\d+\.\d+)/);
177
177
  if (match) {
178
178
  console.log(`[tool-manager] CLI ${pkg} (${bin}) version: ${match[1]}`);
@@ -315,7 +315,7 @@ const spawnNpmInstall = (pkg, onProgress) => new Promise((resolve) => {
315
315
  let completed = false, stderr = '', stdout = '';
316
316
  let proc;
317
317
  try {
318
- proc = spawn(cmd, ['install', '-g', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows });
318
+ proc = spawn(cmd, ['install', '-g', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows, windowsHide: true });
319
319
  } catch (err) {
320
320
  return resolve({ success: false, error: `Failed to spawn npm install: ${err.message}` });
321
321
  }
@@ -340,7 +340,7 @@ const spawnBunxProc = (pkg, onProgress) => new Promise((resolve) => {
340
340
  let proc;
341
341
 
342
342
  try {
343
- proc = spawn(cmd, ['x', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows });
343
+ proc = spawn(cmd, ['x', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows, windowsHide: true });
344
344
  } catch (err) {
345
345
  return resolve({ success: false, error: `Failed to spawn bun x: ${err.message}` });
346
346
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.637",
3
+ "version": "1.0.638",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -66,6 +66,12 @@ class AgentGUIClient {
66
66
  this._inflightRequests = new Map();
67
67
  this._previousConvAbort = null;
68
68
 
69
+ // Background conversation cache: keeps last 50 conversations' streaming blocks in memory
70
+ // Blocks are stored as packed msgpackr Uint8Arrays for memory efficiency
71
+ // Map<conversationId, { packed: Uint8Array[], seqSet: Set<number>, sessionId: string }>
72
+ this._bgCache = new Map();
73
+ this.BG_CACHE_MAX = 50;
74
+
69
75
  // PHASE 2: Request Lifetime Tracking
70
76
  this._loadInProgress = {}; // { [conversationId]: { requestId, abortController, timestamp, prevConversationId } }
71
77
  this._currentRequestId = 0; // Auto-incrementing request counter
@@ -955,6 +961,29 @@ class AgentGUIClient {
955
961
  if (!this.state.streamingBlocks) this.state.streamingBlocks = [];
956
962
  this.state.streamingBlocks.push(block);
957
963
 
964
+ // Cache block for background conversations (all 50 cached convs, not just active)
965
+ const convId = data.conversationId;
966
+ if (convId) {
967
+ let entry = this._bgCache.get(convId);
968
+ if (!entry) {
969
+ // Evict oldest if at capacity
970
+ if (this._bgCache.size >= this.BG_CACHE_MAX) {
971
+ const oldestKey = this._bgCache.keys().next().value;
972
+ this._bgCache.delete(oldestKey);
973
+ }
974
+ entry = { packed: [], seqSet: new Set(), sessionId: data.sessionId };
975
+ this._bgCache.set(convId, entry);
976
+ }
977
+ if (data.seq === undefined || !entry.seqSet.has(data.seq)) {
978
+ if (data.seq !== undefined) entry.seqSet.add(data.seq);
979
+ entry.sessionId = data.sessionId;
980
+ // Pack with msgpackr if available, else store raw
981
+ try {
982
+ entry.packed.push(typeof msgpackr !== 'undefined' ? msgpackr.pack(block) : block);
983
+ } catch (_) { entry.packed.push(block); }
984
+ }
985
+ }
986
+
958
987
  // Only render for the currently-visible session
959
988
  if (this.state.currentSession?.id !== data.sessionId) return;
960
989
 
@@ -1671,6 +1700,30 @@ class AgentGUIClient {
1671
1700
  }
1672
1701
  }
1673
1702
 
1703
+ // Flush background-cached blocks into the active streaming container
1704
+ _flushBgCache(conversationId, sessionId) {
1705
+ const entry = this._bgCache.get(conversationId);
1706
+ if (!entry || entry.packed.length === 0) return;
1707
+ if (entry.sessionId !== sessionId) { this._bgCache.delete(conversationId); return; }
1708
+
1709
+ const streamingEl = document.getElementById(`streaming-${sessionId}`);
1710
+ if (!streamingEl) return;
1711
+ const blocksEl = streamingEl.querySelector('.streaming-blocks');
1712
+ if (!blocksEl) return;
1713
+
1714
+ const seenSeqs = (this._renderedSeqs || {})[sessionId] || new Set();
1715
+ for (const packed of entry.packed) {
1716
+ try {
1717
+ const block = (typeof msgpackr !== 'undefined' && packed instanceof Uint8Array)
1718
+ ? msgpackr.unpack(packed) : packed;
1719
+ const el = this.renderer.renderBlock(block, { sessionId }, blocksEl);
1720
+ if (el) blocksEl.appendChild(el);
1721
+ } catch (_) {}
1722
+ }
1723
+ this._bgCache.delete(conversationId);
1724
+ this.scrollToBottom();
1725
+ }
1726
+
1674
1727
  async _recoverMissedChunks() {
1675
1728
  if (!this.state.currentSession?.id) return;
1676
1729
  if (!this.state.streamingConversations.has(this.state.currentConversation?.id)) return;
@@ -1813,12 +1866,25 @@ class AgentGUIClient {
1813
1866
  _showWelcomeScreen() {
1814
1867
  const outputEl = document.getElementById('output');
1815
1868
  if (!outputEl) return;
1869
+ // Build agent options from loaded agents list
1870
+ const agents = this.state.agents || [];
1871
+ const agentOptions = agents.map(a =>
1872
+ `<option value="${this.escapeHtml(a.id)}">${this.escapeHtml(a.name.split(/[\s\-]+/)[0])}</option>`
1873
+ ).join('');
1816
1874
  outputEl.innerHTML = `
1817
1875
  <div style="display:flex;align-items:center;justify-content:center;height:100%;flex-direction:column;gap:2rem;padding:2rem;">
1818
1876
  <div style="text-align:center;">
1819
1877
  <h1 style="margin:0;font-size:2.5rem;color:var(--color-text-primary);">Welcome to AgentGUI</h1>
1820
1878
  <p style="margin:1rem 0 0 0;font-size:1.1rem;color:var(--color-text-secondary);">Start a new conversation or select one from the sidebar</p>
1821
1879
  </div>
1880
+ ${agents.length > 0 ? `
1881
+ <div style="display:flex;flex-direction:column;align-items:center;gap:0.75rem;">
1882
+ <label style="font-size:0.85rem;color:var(--color-text-secondary);font-weight:500;">Select Agent</label>
1883
+ <select id="welcomeAgentSelect" style="padding:0.5rem 1rem;border-radius:0.375rem;border:1px solid var(--color-border);background:var(--color-bg-secondary);color:var(--color-text-primary);font-size:1rem;cursor:pointer;">
1884
+ ${agentOptions}
1885
+ </select>
1886
+ </div>
1887
+ ` : ''}
1822
1888
  <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:1rem;max-width:600px;">
1823
1889
  <div style="padding:1.5rem;border-radius:0.5rem;background:var(--color-bg-secondary);border:1px solid var(--color-border);">
1824
1890
  <h3 style="margin:0 0 0.5rem 0;color:var(--color-primary);">New Conversation</h3>
@@ -1826,11 +1892,22 @@ class AgentGUIClient {
1826
1892
  </div>
1827
1893
  <div style="padding:1.5rem;border-radius:0.5rem;background:var(--color-bg-secondary);border:1px solid var(--color-border);">
1828
1894
  <h3 style="margin:0 0 0.5rem 0;color:var(--color-primary);">Available Agents</h3>
1829
- <p style="margin:0;font-size:0.9rem;color:var(--color-text-secondary);">Claude Code, Gemini, OpenCode, and more</p>
1895
+ <p style="margin:0;font-size:0.9rem;color:var(--color-text-secondary);">${agents.length > 0 ? agents.map(a => a.name.split(/[\s\-]+/)[0]).join(', ') : 'Claude Code, Gemini, OpenCode, and more'}</p>
1830
1896
  </div>
1831
1897
  </div>
1832
1898
  </div>
1833
1899
  `;
1900
+ // Sync welcome agent select with the bottom bar cli selector
1901
+ const welcomeSel = document.getElementById('welcomeAgentSelect');
1902
+ if (welcomeSel) {
1903
+ if (this.ui.cliSelector) welcomeSel.value = this.ui.cliSelector.value;
1904
+ welcomeSel.addEventListener('change', () => {
1905
+ if (this.ui.cliSelector) {
1906
+ this.ui.cliSelector.value = welcomeSel.value;
1907
+ this.ui.cliSelector.dispatchEvent(new Event('change'));
1908
+ }
1909
+ });
1910
+ }
1834
1911
  }
1835
1912
 
1836
1913
  _showSkeletonLoading(conversationId) {
@@ -2463,6 +2540,7 @@ class AgentGUIClient {
2463
2540
  }
2464
2541
  window.ConversationState?.selectConversation(conversationId, 'dom_cache_load', 1);
2465
2542
  this.state.currentConversation = cached.conversation;
2543
+ window.dispatchEvent(new CustomEvent('conversation-changed', { detail: { conversationId, conversation: cached.conversation } }));
2466
2544
  const cachedHasActivity = cached.conversation.messageCount > 0 || this.state.streamingConversations.has(conversationId);
2467
2545
  this.applyAgentAndModelSelection(cached.conversation, cachedHasActivity);
2468
2546
  this.conversationCache.delete(conversationId);
@@ -2510,6 +2588,7 @@ class AgentGUIClient {
2510
2588
 
2511
2589
  window.ConversationState?.selectConversation(conversationId, 'server_load', 1);
2512
2590
  this.state.currentConversation = conversation;
2591
+ window.dispatchEvent(new CustomEvent('conversation-changed', { detail: { conversationId, conversation } }));
2513
2592
  const hasActivity = (allMessages && allMessages.length > 0) || isActivelyStreaming || latestSession || this.state.streamingConversations.has(conversationId);
2514
2593
  this.applyAgentAndModelSelection(conversation, hasActivity);
2515
2594
 
@@ -2703,6 +2782,9 @@ class AgentGUIClient {
2703
2782
 
2704
2783
  this.updateUrlForConversation(conversationId, latestSession.id);
2705
2784
 
2785
+ // Flush any blocks accumulated in the background cache while this conv wasn't active
2786
+ this._flushBgCache(conversationId, latestSession.id);
2787
+
2706
2788
  // IMMUTABLE: Prompt remains enabled - syncPromptState will set correct state
2707
2789
  this.syncPromptState(conversationId);
2708
2790
  } else {
@@ -180,6 +180,17 @@
180
180
  }
181
181
  });
182
182
 
183
+ // Restart terminal in the new conversation's cwd when conversation switches while terminal is active
184
+ window.addEventListener('conversation-changed', function(e) {
185
+ if (!termActive) return;
186
+ var cwd = getCwd();
187
+ if (ws && ws.readyState === WebSocket.OPEN) {
188
+ var dims = term ? { cols: term.cols, rows: term.rows } : { cols: 80, rows: 24 };
189
+ ws.send(JSON.stringify({ type: 'terminal_start', cwd: cwd, cols: dims.cols, rows: dims.rows }));
190
+ if (term) term.write('\r\n\x1b[33m[Switched to: ' + (cwd || '/') + ']\x1b[0m\r\n');
191
+ }
192
+ });
193
+
183
194
  window.terminalModule = {
184
195
  start: startTerminal,
185
196
  stop: stopTerminal,