agentgui 1.0.129 → 1.0.131

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/database.js CHANGED
@@ -253,9 +253,9 @@ export const queries = {
253
253
  const id = generateId('conv');
254
254
  const now = Date.now();
255
255
  const stmt = db.prepare(
256
- `INSERT INTO conversations (id, agentType, title, created_at, updated_at, status, workingDirectory) VALUES (?, ?, ?, ?, ?, ?, ?)`
256
+ `INSERT INTO conversations (id, agentId, agentType, title, created_at, updated_at, status, workingDirectory) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
257
257
  );
258
- stmt.run(id, agentType, title, now, now, 'active', workingDirectory);
258
+ stmt.run(id, agentType, agentType, title, now, now, 'active', workingDirectory);
259
259
 
260
260
  return {
261
261
  id,
@@ -280,7 +280,7 @@ export const queries = {
280
280
 
281
281
  getConversationsList() {
282
282
  const stmt = db.prepare(
283
- 'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory FROM conversations WHERE status != ? ORDER BY updated_at DESC'
283
+ 'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory, isStreaming FROM conversations WHERE status != ? ORDER BY updated_at DESC'
284
284
  );
285
285
  return stmt.all('deleted');
286
286
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.129",
3
+ "version": "1.0.131",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -550,6 +550,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
550
550
  const startTime = Date.now();
551
551
  activeExecutions.set(conversationId, true);
552
552
  queries.setIsStreaming(conversationId, true);
553
+ queries.updateSession(sessionId, { status: 'active' });
553
554
 
554
555
  try {
555
556
  debugLog(`[stream] Starting: conversationId=${conversationId}, sessionId=${sessionId}`);
@@ -886,7 +887,7 @@ function broadcastSync(event) {
886
887
  shouldSend = true;
887
888
  } else if (event.conversationId && ws.subscriptions?.has(`conv-${event.conversationId}`)) {
888
889
  shouldSend = true;
889
- } else if (event.type === 'message_created' || event.type === 'conversation_created' || event.type === 'conversations_updated' || event.type === 'conversation_deleted' || event.type === 'queue_status') {
890
+ } else if (event.type === 'message_created' || event.type === 'conversation_created' || event.type === 'conversations_updated' || event.type === 'conversation_deleted' || event.type === 'queue_status' || event.type === 'streaming_start' || event.type === 'streaming_complete' || event.type === 'streaming_error') {
890
891
  shouldSend = true;
891
892
  }
892
893
 
package/static/index.html CHANGED
@@ -161,6 +161,26 @@
161
161
 
162
162
  .conversation-item.active .conversation-item-meta { color: rgba(255,255,255,0.7); }
163
163
 
164
+ .conversation-streaming-badge {
165
+ display: inline-flex;
166
+ align-items: center;
167
+ margin-right: 0.375rem;
168
+ vertical-align: middle;
169
+ }
170
+
171
+ .streaming-dot {
172
+ display: inline-block;
173
+ width: 0.5rem;
174
+ height: 0.5rem;
175
+ border-radius: 50%;
176
+ background-color: var(--color-success);
177
+ animation: pulse 1.5s ease-in-out infinite;
178
+ }
179
+
180
+ .conversation-item.active .streaming-dot {
181
+ background-color: #fff;
182
+ }
183
+
164
184
  .conversation-item {
165
185
  display: flex;
166
186
  align-items: center;
@@ -344,6 +344,9 @@ class AgentGUIClient {
344
344
 
345
345
  handleWebSocketMessage(data) {
346
346
  try {
347
+ // Dispatch to window so other modules (conversations.js) can listen
348
+ window.dispatchEvent(new CustomEvent('ws-message', { detail: data }));
349
+
347
350
  switch (data.type) {
348
351
  case 'streaming_start':
349
352
  this.handleStreamingStart(data);
@@ -388,6 +391,15 @@ class AgentGUIClient {
388
391
 
389
392
  handleStreamingStart(data) {
390
393
  console.log('Streaming started:', data);
394
+
395
+ // If this streaming event is for a different conversation than what we are viewing,
396
+ // just track the state but do not modify the DOM or start polling
397
+ if (this.state.currentConversation?.id !== data.conversationId) {
398
+ console.log('Streaming started for non-active conversation:', data.conversationId);
399
+ this.emit('streaming:start', data);
400
+ return;
401
+ }
402
+
391
403
  this.state.isStreaming = true;
392
404
  this.state.currentSession = {
393
405
  id: data.sessionId,
@@ -490,8 +502,21 @@ class AgentGUIClient {
490
502
 
491
503
  handleStreamingError(data) {
492
504
  console.error('Streaming error:', data);
505
+
506
+ const conversationId = data.conversationId || this.state.currentSession?.conversationId;
507
+
508
+ // If this event is for a conversation we are NOT currently viewing, just track state
509
+ if (conversationId && this.state.currentConversation?.id !== conversationId) {
510
+ console.log('Streaming error for non-active conversation:', conversationId);
511
+ this.emit('streaming:error', data);
512
+ return;
513
+ }
514
+
493
515
  this.state.isStreaming = false;
494
516
 
517
+ // Stop polling for chunks
518
+ this.stopChunkPolling();
519
+
495
520
  const sessionId = data.sessionId || this.state.currentSession?.id;
496
521
  const streamingEl = document.getElementById(`streaming-${sessionId}`);
497
522
  if (streamingEl) {
@@ -507,6 +532,16 @@ class AgentGUIClient {
507
532
 
508
533
  handleStreamingComplete(data) {
509
534
  console.log('Streaming completed:', data);
535
+
536
+ const conversationId = data.conversationId || this.state.currentSession?.conversationId;
537
+
538
+ // If this event is for a conversation we are NOT currently viewing, just track state
539
+ if (conversationId && this.state.currentConversation?.id !== conversationId) {
540
+ console.log('Streaming completed for non-active conversation:', conversationId);
541
+ this.emit('streaming:complete', data);
542
+ return;
543
+ }
544
+
510
545
  this.state.isStreaming = false;
511
546
 
512
547
  // Stop polling for chunks
@@ -528,7 +563,6 @@ class AgentGUIClient {
528
563
  }
529
564
 
530
565
  // Save scroll position after streaming completes
531
- const conversationId = data.conversationId || this.state.currentSession?.conversationId;
532
566
  if (conversationId) {
533
567
  this.saveScrollPosition(conversationId);
534
568
  }
@@ -1099,9 +1133,21 @@ class AgentGUIClient {
1099
1133
 
1100
1134
  async loadConversationMessages(conversationId) {
1101
1135
  try {
1136
+ // Save scroll position of current conversation before switching
1137
+ if (this.state.currentConversation?.id) {
1138
+ this.saveScrollPosition(this.state.currentConversation.id);
1139
+ }
1140
+
1102
1141
  // Stop any existing polling when switching conversations
1103
1142
  this.stopChunkPolling();
1104
1143
 
1144
+ // Clear streaming state from previous conversation view
1145
+ // (the actual streaming continues on the server, we just stop tracking it on the UI side)
1146
+ if (this.state.isStreaming && this.state.currentConversation?.id !== conversationId) {
1147
+ this.state.isStreaming = false;
1148
+ this.state.currentSession = null;
1149
+ }
1150
+
1105
1151
  const convResponse = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}`);
1106
1152
  const { conversation, isActivelyStreaming, latestSession } = await convResponse.json();
1107
1153
  this.state.currentConversation = conversation;
@@ -1114,7 +1160,10 @@ class AgentGUIClient {
1114
1160
  }
1115
1161
 
1116
1162
  // Check if there's an active streaming session that needs to be resumed
1117
- const shouldResumeStreaming = isActivelyStreaming && latestSession && latestSession.status === 'active';
1163
+ // isActivelyStreaming comes from the server checking both in-memory activeExecutions map
1164
+ // AND database session status. Use it as primary signal, with session status as confirmation.
1165
+ const shouldResumeStreaming = isActivelyStreaming && latestSession &&
1166
+ (latestSession.status === 'active' || latestSession.status === 'pending');
1118
1167
 
1119
1168
  // Try to fetch chunks first (Wave 3 architecture)
1120
1169
  try {
@@ -1202,17 +1251,22 @@ class AgentGUIClient {
1202
1251
  startTime: latestSession.created_at
1203
1252
  };
1204
1253
 
1205
- // Subscribe to WebSocket updates
1254
+ // Subscribe to WebSocket updates for BOTH conversation and session
1206
1255
  if (this.wsManager.isConnected) {
1207
1256
  this.wsManager.subscribeToSession(latestSession.id);
1257
+ this.wsManager.sendMessage({ type: 'subscribe', conversationId });
1208
1258
  }
1209
1259
 
1260
+ // Update URL with session ID
1261
+ this.updateUrlForConversation(conversationId, latestSession.id);
1262
+
1210
1263
  // Get the timestamp of the last chunk to start polling from
1264
+ // Use the last chunk's created_at to avoid re-fetching already-rendered chunks
1211
1265
  const lastChunkTime = chunks.length > 0
1212
1266
  ? chunks[chunks.length - 1].created_at
1213
- : Date.now();
1267
+ : 0;
1214
1268
 
1215
- // Start polling for new chunks
1269
+ // Start polling for new chunks from where we left off
1216
1270
  this.chunkPollState.lastFetchTimestamp = lastChunkTime;
1217
1271
  this.startChunkPolling(conversationId);
1218
1272
 
@@ -12,6 +12,7 @@ class ConversationManager {
12
12
  this.emptyEl = document.querySelector('[data-conversation-empty]');
13
13
  this.newBtn = document.querySelector('[data-new-conversation]');
14
14
  this.sidebarEl = document.querySelector('[data-sidebar]');
15
+ this.streamingConversations = new Set();
15
16
 
16
17
  this.folderBrowser = {
17
18
  modal: null,
@@ -191,6 +192,14 @@ class ConversationManager {
191
192
 
192
193
  const data = await res.json();
193
194
  this.conversations = data.conversations || [];
195
+
196
+ // Seed streaming state from database isStreaming flag
197
+ for (const conv of this.conversations) {
198
+ if (conv.isStreaming === 1 || conv.isStreaming === true) {
199
+ this.streamingConversations.add(conv.id);
200
+ }
201
+ }
202
+
194
203
  this.render();
195
204
  } catch (err) {
196
205
  console.error('Failed to load conversations:', err);
@@ -225,6 +234,8 @@ class ConversationManager {
225
234
  li.dataset.convId = conv.id;
226
235
  if (conv.id === this.activeId) li.classList.add('active');
227
236
 
237
+ const isStreaming = conv.isStreaming === 1 || conv.isStreaming === true || this.streamingConversations?.has(conv.id);
238
+
228
239
  const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
229
240
  const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
230
241
  const agent = conv.agentType || 'unknown';
@@ -232,9 +243,13 @@ class ConversationManager {
232
243
  const metaParts = [agent, timestamp];
233
244
  if (wd) metaParts.push(wd);
234
245
 
246
+ const streamingBadge = isStreaming
247
+ ? '<span class="conversation-streaming-badge" title="Streaming in progress"><span class="streaming-dot"></span></span>'
248
+ : '';
249
+
235
250
  li.innerHTML = `
236
251
  <div class="conversation-item-content">
237
- <div class="conversation-item-title">${this.escapeHtml(title)}</div>
252
+ <div class="conversation-item-title">${streamingBadge}${this.escapeHtml(title)}</div>
238
253
  <div class="conversation-item-meta">${metaParts.join(' • ')}</div>
239
254
  </div>
240
255
  <button class="conversation-item-delete" title="Delete conversation" data-delete-conv="${conv.id}">
@@ -335,6 +350,12 @@ class ConversationManager {
335
350
  this.updateConversation(msg.conversation.id, msg.conversation);
336
351
  } else if (msg.type === 'conversation_deleted') {
337
352
  this.deleteConversation(msg.conversationId);
353
+ } else if (msg.type === 'streaming_start' && msg.conversationId) {
354
+ this.streamingConversations.add(msg.conversationId);
355
+ this.render();
356
+ } else if ((msg.type === 'streaming_complete' || msg.type === 'streaming_error') && msg.conversationId) {
357
+ this.streamingConversations.delete(msg.conversationId);
358
+ this.render();
338
359
  }
339
360
  });
340
361
  }