agentgui 1.0.139 → 1.0.141

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.139",
3
+ "version": "1.0.141",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/static/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
6
6
  <meta name="description" content="AgentGUI - Real-time Claude Code Execution Visualization">
7
7
  <title>AgentGUI</title>
8
- <link rel="icon" type="image/svg+xml" href="/favicon.ico">
8
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='20' fill='%233b82f6'/%3E%3Ctext x='50' y='68' font-size='50' font-family='sans-serif' font-weight='bold' fill='white' text-anchor='middle'%3EG%3C/text%3E%3C/svg%3E">
9
9
 
10
10
  <link href="https://cdn.jsdelivr.net/npm/rippleui@1.12.1/dist/css/styles.css" rel="stylesheet">
11
11
  <link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-dark.css" rel="stylesheet">
@@ -1511,6 +1511,13 @@
1511
1511
  html.dark .block-tool-result.result-success .result-body { color: #bbf7d0; }
1512
1512
  html.dark .block-tool-result.result-error .result-body { color: #fecaca; }
1513
1513
 
1514
+ .result-collapsible { margin: 0; }
1515
+ .result-collapsible > summary { list-style: none; }
1516
+ .result-collapsible > summary::-webkit-details-marker { display: none; }
1517
+ .result-collapsible > summary::marker { display: none; content: ''; }
1518
+ .result-collapsible > summary .status-label::before { content: '\25b6'; font-size: 0.6rem; margin-right: 0.375rem; display: inline-block; transition: transform 0.15s; }
1519
+ .result-collapsible[open] > summary .status-label::before { transform: rotate(90deg); }
1520
+
1514
1521
  .block-tool-result .result-body.collapsed { max-height: 150px; position: relative; }
1515
1522
  .block-tool-result .result-body.collapsed::after {
1516
1523
  content: '';
@@ -348,7 +348,7 @@ class AgentGUIClient {
348
348
 
349
349
  switch (data.type) {
350
350
  case 'streaming_start':
351
- this.handleStreamingStart(data);
351
+ this.handleStreamingStart(data).catch(e => console.error('handleStreamingStart error:', e));
352
352
  break;
353
353
  case 'streaming_progress':
354
354
  this.handleStreamingProgress(data);
@@ -388,7 +388,7 @@ class AgentGUIClient {
388
388
  }
389
389
  }
390
390
 
391
- handleStreamingStart(data) {
391
+ async handleStreamingStart(data) {
392
392
  console.log('Streaming started:', data);
393
393
 
394
394
  // If this streaming event is for a different conversation than what we are viewing,
@@ -420,8 +420,60 @@ class AgentGUIClient {
420
420
  if (outputEl) {
421
421
  let messagesEl = outputEl.querySelector('.conversation-messages');
422
422
  if (!messagesEl) {
423
- outputEl.innerHTML = '<div class="conversation-messages"></div>';
423
+ // Load existing conversation history before starting the stream
424
+ const conv = this.state.currentConversation;
425
+ const wdInfo = conv?.workingDirectory ? ` - ${this.escapeHtml(conv.workingDirectory)}` : '';
426
+ outputEl.innerHTML = `
427
+ <div class="conversation-header">
428
+ <h2>${this.escapeHtml(conv?.title || 'Conversation')}</h2>
429
+ <p class="text-secondary">${conv?.agentType || 'unknown'} - ${new Date(conv?.created_at || Date.now()).toLocaleDateString()}${wdInfo}</p>
430
+ </div>
431
+ <div class="conversation-messages"></div>
432
+ `;
424
433
  messagesEl = outputEl.querySelector('.conversation-messages');
434
+ // Load prior messages into the container
435
+ try {
436
+ const msgResp = await fetch(window.__BASE_URL + `/api/conversations/${data.conversationId}/messages`);
437
+ if (msgResp.ok) {
438
+ const msgData = await msgResp.json();
439
+ const priorChunks = await this.fetchChunks(data.conversationId, 0);
440
+ if (priorChunks.length > 0) {
441
+ const userMsgs = (msgData.messages || []).filter(m => m.role === 'user');
442
+ const sessionOrder = [];
443
+ const sessionGroups = {};
444
+ priorChunks.forEach(c => {
445
+ if (!sessionGroups[c.sessionId]) { sessionGroups[c.sessionId] = []; sessionOrder.push(c.sessionId); }
446
+ sessionGroups[c.sessionId].push(c);
447
+ });
448
+ let ui = 0;
449
+ sessionOrder.forEach(sid => {
450
+ const sList = sessionGroups[sid];
451
+ const sStart = sList[0].created_at;
452
+ while (ui < userMsgs.length && userMsgs[ui].created_at <= sStart) {
453
+ const m = userMsgs[ui++];
454
+ messagesEl.insertAdjacentHTML('beforeend', `<div class="message message-user" data-msg-id="${m.id}"><div class="message-role">User</div>${this.renderMessageContent(m.content)}<div class="message-timestamp">${new Date(m.created_at).toLocaleString()}</div></div>`);
455
+ }
456
+ const mDiv = document.createElement('div');
457
+ mDiv.className = 'message message-assistant';
458
+ mDiv.id = `message-${sid}`;
459
+ mDiv.innerHTML = '<div class="message-role">Assistant</div><div class="message-blocks streaming-blocks"></div>';
460
+ const bEl = mDiv.querySelector('.message-blocks');
461
+ sList.forEach(chunk => { if (chunk.block?.type) { const el = this.renderer.renderBlock(chunk.block, chunk); if (el) bEl.appendChild(el); } });
462
+ const ts = document.createElement('div'); ts.className = 'message-timestamp'; ts.textContent = new Date(sList[sList.length - 1].created_at).toLocaleString();
463
+ mDiv.appendChild(ts);
464
+ messagesEl.appendChild(mDiv);
465
+ });
466
+ while (ui < userMsgs.length) {
467
+ const m = userMsgs[ui++];
468
+ messagesEl.insertAdjacentHTML('beforeend', `<div class="message message-user" data-msg-id="${m.id}"><div class="message-role">User</div>${this.renderMessageContent(m.content)}<div class="message-timestamp">${new Date(m.created_at).toLocaleString()}</div></div>`);
469
+ }
470
+ } else {
471
+ messagesEl.innerHTML = this.renderMessages(msgData.messages || []);
472
+ }
473
+ }
474
+ } catch (e) {
475
+ console.warn('Failed to load prior messages for streaming view:', e);
476
+ }
425
477
  }
426
478
  const streamingDiv = document.createElement('div');
427
479
  streamingDiv.className = 'message message-assistant streaming-message';
@@ -1181,17 +1233,48 @@ class AgentGUIClient {
1181
1233
  // Render all chunks
1182
1234
  const messagesEl = outputEl.querySelector('.conversation-messages');
1183
1235
  if (chunks.length > 0) {
1184
- // Group chunks by session
1236
+ // Fetch user messages to interleave with session chunks
1237
+ let userMessages = [];
1238
+ try {
1239
+ const msgResp = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/messages`);
1240
+ if (msgResp.ok) {
1241
+ const msgData = await msgResp.json();
1242
+ userMessages = (msgData.messages || []).filter(m => m.role === 'user');
1243
+ }
1244
+ } catch (_) {}
1245
+
1246
+ // Group chunks by session, preserving order
1247
+ const sessionOrder = [];
1185
1248
  const sessionChunks = {};
1186
1249
  chunks.forEach(chunk => {
1187
1250
  if (!sessionChunks[chunk.sessionId]) {
1188
1251
  sessionChunks[chunk.sessionId] = [];
1252
+ sessionOrder.push(chunk.sessionId);
1189
1253
  }
1190
1254
  sessionChunks[chunk.sessionId].push(chunk);
1191
1255
  });
1192
1256
 
1193
- // Render each session's chunks
1194
- Object.entries(sessionChunks).forEach(([sessionId, sessionChunkList]) => {
1257
+ // Build a timeline: match user messages to sessions by timestamp
1258
+ let userMsgIdx = 0;
1259
+ sessionOrder.forEach((sessionId) => {
1260
+ const sessionChunkList = sessionChunks[sessionId];
1261
+ const sessionStart = sessionChunkList[0].created_at;
1262
+
1263
+ // Render user messages that came before this session
1264
+ while (userMsgIdx < userMessages.length && userMessages[userMsgIdx].created_at <= sessionStart) {
1265
+ const msg = userMessages[userMsgIdx];
1266
+ const userDiv = document.createElement('div');
1267
+ userDiv.className = 'message message-user';
1268
+ userDiv.setAttribute('data-msg-id', msg.id);
1269
+ userDiv.innerHTML = `
1270
+ <div class="message-role">User</div>
1271
+ ${this.renderMessageContent(msg.content)}
1272
+ <div class="message-timestamp">${new Date(msg.created_at).toLocaleString()}</div>
1273
+ `;
1274
+ messagesEl.appendChild(userDiv);
1275
+ userMsgIdx++;
1276
+ }
1277
+
1195
1278
  const isCurrentActiveSession = shouldResumeStreaming && latestSession && latestSession.id === sessionId;
1196
1279
  const messageDiv = document.createElement('div');
1197
1280
  messageDiv.className = `message message-assistant${isCurrentActiveSession ? ' streaming-message' : ''}`;
@@ -1208,7 +1291,6 @@ class AgentGUIClient {
1208
1291
  }
1209
1292
  });
1210
1293
 
1211
- // Add streaming indicator for active session
1212
1294
  if (isCurrentActiveSession) {
1213
1295
  const indicatorDiv = document.createElement('div');
1214
1296
  indicatorDiv.className = 'streaming-indicator';
@@ -1227,6 +1309,21 @@ class AgentGUIClient {
1227
1309
 
1228
1310
  messagesEl.appendChild(messageDiv);
1229
1311
  });
1312
+
1313
+ // Render any remaining user messages after the last session
1314
+ while (userMsgIdx < userMessages.length) {
1315
+ const msg = userMessages[userMsgIdx];
1316
+ const userDiv = document.createElement('div');
1317
+ userDiv.className = 'message message-user';
1318
+ userDiv.setAttribute('data-msg-id', msg.id);
1319
+ userDiv.innerHTML = `
1320
+ <div class="message-role">User</div>
1321
+ ${this.renderMessageContent(msg.content)}
1322
+ <div class="message-timestamp">${new Date(msg.created_at).toLocaleString()}</div>
1323
+ `;
1324
+ messagesEl.appendChild(userDiv);
1325
+ userMsgIdx++;
1326
+ }
1230
1327
  } else {
1231
1328
  // Fall back to messages if no chunks
1232
1329
  const messagesResponse = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/messages`);
@@ -76,48 +76,18 @@ class StreamingRenderer {
76
76
  * Setup DOM mutation observer for external changes
77
77
  */
78
78
  setupDOMObserver() {
79
- try {
80
- this.observer = new MutationObserver(() => {
81
- this.updateDOMNodeCount();
82
- });
83
-
84
- this.observer.observe(this.outputContainer, {
85
- childList: true,
86
- subtree: true,
87
- characterData: false,
88
- attributes: false
89
- });
90
- } catch (e) {
91
- console.warn('DOM observer setup failed:', e.message);
92
- }
93
79
  }
94
80
 
95
81
  /**
96
82
  * Setup resize observer for viewport changes
97
83
  */
98
84
  setupResizeObserver() {
99
- try {
100
- this.resizeObserver = new ResizeObserver(() => {
101
- this.updateVirtualScroll();
102
- });
103
-
104
- if (this.scrollContainer) {
105
- this.resizeObserver.observe(this.scrollContainer);
106
- }
107
- } catch (e) {
108
- console.warn('Resize observer setup failed:', e.message);
109
- }
110
85
  }
111
86
 
112
87
  /**
113
88
  * Setup scroll optimization and auto-scroll
114
89
  */
115
90
  setupScrollOptimization() {
116
- if (this.scrollContainer) {
117
- this.scrollContainer.addEventListener('scroll', () => {
118
- this.updateVirtualScroll();
119
- }, { passive: true });
120
- }
121
91
  }
122
92
 
123
93
  /**
@@ -1076,13 +1046,27 @@ class StreamingRenderer {
1076
1046
  ? '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>'
1077
1047
  : '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>';
1078
1048
 
1079
- div.innerHTML = `
1080
- <div class="result-header">
1081
- <span class="status-label">${iconSvg} ${isError ? 'Error' : 'Success'}</span>
1082
- ${toolUseId ? `<span class="result-id">${this.escapeHtml(toolUseId)}</span>` : ''}
1083
- </div>
1084
- ${StreamingRenderer.renderSmartContentHTML(contentStr, this.escapeHtml.bind(this))}
1085
- `;
1049
+ const renderedContent = StreamingRenderer.renderSmartContentHTML(contentStr, this.escapeHtml.bind(this));
1050
+
1051
+ if (isError) {
1052
+ div.innerHTML = `
1053
+ <div class="result-header">
1054
+ <span class="status-label">${iconSvg} Error</span>
1055
+ ${toolUseId ? `<span class="result-id">${this.escapeHtml(toolUseId)}</span>` : ''}
1056
+ </div>
1057
+ ${renderedContent}
1058
+ `;
1059
+ } else {
1060
+ div.innerHTML = `
1061
+ <details class="result-collapsible">
1062
+ <summary class="result-header" style="cursor:pointer;list-style:none;user-select:none">
1063
+ <span class="status-label">${iconSvg} Success</span>
1064
+ ${toolUseId ? `<span class="result-id">${this.escapeHtml(toolUseId)}</span>` : ''}
1065
+ </summary>
1066
+ <div class="result-collapsible-body">${renderedContent}</div>
1067
+ </details>
1068
+ `;
1069
+ }
1086
1070
 
1087
1071
  return div;
1088
1072
  }
@@ -1697,28 +1681,7 @@ class StreamingRenderer {
1697
1681
  }
1698
1682
  }
1699
1683
 
1700
- /**
1701
- * Update virtual scroll based on viewport
1702
- */
1703
1684
  updateVirtualScroll() {
1704
- if (!this.scrollContainer) return;
1705
-
1706
- // Calculate visible items
1707
- const scrollTop = this.scrollContainer.scrollTop;
1708
- const viewportHeight = this.scrollContainer.clientHeight;
1709
- const itemHeight = 80; // Approximate item height
1710
-
1711
- const firstVisible = Math.floor(scrollTop / itemHeight);
1712
- const lastVisible = Math.ceil((scrollTop + viewportHeight) / itemHeight);
1713
-
1714
- // Update visibility of DOM nodes
1715
- const items = this.outputContainer?.querySelectorAll('[data-event-id]');
1716
- if (!items) return;
1717
-
1718
- items.forEach((item, index) => {
1719
- const isVisible = index >= firstVisible && index <= lastVisible;
1720
- item.style.display = isVisible ? '' : 'none';
1721
- });
1722
1685
  }
1723
1686
 
1724
1687
  /**
@@ -1,7 +1,19 @@
1
- import { STT, TTS } from 'webtalk-sdk';
2
-
3
1
  (function() {
4
2
  const BASE = window.__BASE_URL || '';
3
+ let STT = null;
4
+ let TTS = null;
5
+
6
+ async function loadSDK() {
7
+ try {
8
+ const mod = await import(BASE + '/webtalk/sdk.js');
9
+ STT = mod.STT;
10
+ TTS = mod.TTS;
11
+ return true;
12
+ } catch (e) {
13
+ console.warn('Webtalk SDK load failed:', e.message);
14
+ return false;
15
+ }
16
+ }
5
17
  let stt = null;
6
18
  let tts = null;
7
19
  let isRecording = false;
@@ -19,8 +31,14 @@ import { STT, TTS } from 'webtalk-sdk';
19
31
  setupUI();
20
32
  setupStreamingListener();
21
33
  setupAgentSelector();
22
- initSTT();
23
- initTTS();
34
+ var sdkLoaded = await loadSDK();
35
+ if (sdkLoaded) {
36
+ initSTT();
37
+ initTTS();
38
+ } else {
39
+ sttLoadPhase = 'failed';
40
+ updateMicState();
41
+ }
24
42
  }
25
43
 
26
44
  var sttLoadPhase = 'starting';