agentgui 1.0.93 → 1.0.95

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.
@@ -193,66 +193,42 @@ class AgentGUIClient {
193
193
  }
194
194
  }
195
195
 
196
- /**
197
- * Handle incoming WebSocket message
198
- */
199
196
  handleWebSocketMessage(data) {
200
197
  try {
201
- // Route by message type
202
198
  switch (data.type) {
203
199
  case 'streaming_start':
204
200
  this.handleStreamingStart(data);
205
201
  break;
206
-
207
202
  case 'streaming_progress':
208
- this.queueEvent(data);
203
+ this.handleStreamingProgress(data);
209
204
  break;
210
-
211
205
  case 'streaming_complete':
212
206
  this.handleStreamingComplete(data);
213
207
  break;
214
-
215
- case 'file_read':
216
- case 'file_write':
217
- case 'command_execute':
218
- case 'git_status':
219
- case 'error':
220
- case 'text_block':
221
- case 'code_block':
222
- case 'thinking_block':
223
- case 'tool_use':
224
- this.queueEvent(data);
208
+ case 'streaming_error':
209
+ this.handleStreamingError(data);
225
210
  break;
226
-
227
211
  case 'conversation_created':
228
212
  this.handleConversationCreated(data);
229
213
  break;
230
-
231
214
  case 'message_created':
232
215
  this.handleMessageCreated(data);
233
216
  break;
234
-
217
+ case 'queue_status':
218
+ this.handleQueueStatus(data);
219
+ break;
235
220
  default:
236
- console.log('Unhandled message type:', data.type);
221
+ break;
237
222
  }
238
223
  } catch (error) {
239
224
  console.error('Message handling error:', error);
240
225
  }
241
226
  }
242
227
 
243
- /**
244
- * Queue event for rendering
245
- */
246
228
  queueEvent(data) {
247
229
  try {
248
- // Process event
249
230
  const processed = this.eventProcessor.processEvent(data);
250
231
  if (!processed) return;
251
-
252
- // Queue for rendering
253
- this.renderer.queueEvent(processed);
254
-
255
- // Track session events
256
232
  if (data.sessionId && this.state.currentSession?.id === data.sessionId) {
257
233
  this.state.sessionEvents.push(processed);
258
234
  }
@@ -261,9 +237,6 @@ class AgentGUIClient {
261
237
  }
262
238
  }
263
239
 
264
- /**
265
- * Handle streaming start
266
- */
267
240
  handleStreamingStart(data) {
268
241
  console.log('Streaming started:', data);
269
242
  this.state.isStreaming = true;
@@ -273,60 +246,146 @@ class AgentGUIClient {
273
246
  agentId: data.agentId,
274
247
  startTime: Date.now()
275
248
  };
276
- this.state.currentConversation = { id: data.conversationId };
277
249
  this.state.sessionEvents = [];
250
+ this.state.streamingBlocks = [];
278
251
 
279
- // Auto-select the streaming conversation in the sidebar
280
- if (window.conversationManager) {
281
- window.conversationManager.select(data.conversationId);
252
+ if (this.wsManager.isConnected) {
253
+ this.wsManager.subscribeToSession(data.sessionId);
282
254
  }
283
255
 
284
- // Load the conversation to display it in real-time
285
- this.loadConversationMessages(data.conversationId).then(() => {
286
- // Clear output and prepare for streaming
287
- const outputEl = document.getElementById('output');
288
- if (outputEl) {
289
- outputEl.innerHTML = '';
256
+ const outputEl = document.getElementById('output');
257
+ if (outputEl) {
258
+ let messagesEl = outputEl.querySelector('.conversation-messages');
259
+ if (!messagesEl) {
260
+ outputEl.innerHTML = '<div class="conversation-messages"></div>';
261
+ messagesEl = outputEl.querySelector('.conversation-messages');
290
262
  }
291
- }).catch(err => {
292
- console.error('Failed to load conversation during streaming:', err);
293
- this.renderer.clear();
294
- });
295
-
296
- this.renderer.queueEvent({
297
- type: 'streaming_start',
298
- sessionId: data.sessionId,
299
- conversationId: data.conversationId,
300
- agentId: data.agentId,
301
- timestamp: data.timestamp || Date.now()
302
- });
263
+ const streamingDiv = document.createElement('div');
264
+ streamingDiv.className = 'message message-assistant streaming-message';
265
+ streamingDiv.id = `streaming-${data.sessionId}`;
266
+ streamingDiv.innerHTML = `
267
+ <div class="message-role">Assistant</div>
268
+ <div class="message-blocks streaming-blocks"></div>
269
+ <div class="streaming-indicator" style="display:flex;align-items:center;gap:0.5rem;padding:0.5rem 0;color:var(--color-text-secondary);font-size:0.875rem;">
270
+ <span class="animate-spin" style="display:inline-block;width:1rem;height:1rem;border:2px solid var(--color-border);border-top-color:var(--color-primary);border-radius:50%;"></span>
271
+ Thinking...
272
+ </div>
273
+ `;
274
+ messagesEl.appendChild(streamingDiv);
275
+ this.scrollToBottom();
276
+ }
303
277
 
304
278
  this.disableControls();
305
279
  this.emit('streaming:start', data);
306
280
  }
307
281
 
308
- /**
309
- * Handle streaming complete
310
- */
282
+ handleStreamingProgress(data) {
283
+ if (!data.block) return;
284
+
285
+ const block = data.block;
286
+ if (!this.state.streamingBlocks) this.state.streamingBlocks = [];
287
+ this.state.streamingBlocks.push(block);
288
+
289
+ const sessionId = data.sessionId || this.state.currentSession?.id;
290
+ const streamingEl = document.getElementById(`streaming-${sessionId}`);
291
+ if (!streamingEl) return;
292
+
293
+ const blocksEl = streamingEl.querySelector('.streaming-blocks');
294
+ if (!blocksEl) return;
295
+
296
+ const indicator = streamingEl.querySelector('.streaming-indicator');
297
+
298
+ if (block.type === 'text' && block.text) {
299
+ const existingTextEl = blocksEl.querySelector('.streaming-text-current');
300
+ if (existingTextEl && !data.isResult) {
301
+ existingTextEl.innerHTML = this.renderBlockContent(block);
302
+ } else {
303
+ const div = document.createElement('div');
304
+ div.className = 'message-text streaming-text-current';
305
+ div.innerHTML = this.renderBlockContent(block);
306
+ blocksEl.appendChild(div);
307
+ }
308
+ } else if (block.type === 'tool_use') {
309
+ const prevTextEl = blocksEl.querySelector('.streaming-text-current');
310
+ if (prevTextEl) prevTextEl.classList.remove('streaming-text-current');
311
+
312
+ const div = document.createElement('div');
313
+ div.className = 'message-tool';
314
+ div.textContent = `[Tool: ${block.name || 'unknown'}]`;
315
+ blocksEl.appendChild(div);
316
+ } else if (block.type === 'tool_result') {
317
+ const div = document.createElement('div');
318
+ div.className = 'message-text';
319
+ div.innerHTML = `<em style="color:var(--color-text-secondary)">${this.escapeHtml(String(block.result || '').substring(0, 500))}</em>`;
320
+ blocksEl.appendChild(div);
321
+ }
322
+
323
+ if (indicator) indicator.querySelector('span:last-child')?.remove();
324
+ if (indicator) {
325
+ const label = document.createElement('span');
326
+ label.textContent = block.type === 'tool_use' ? `Using ${block.name}...` : 'Responding...';
327
+ indicator.appendChild(label);
328
+ }
329
+
330
+ this.scrollToBottom();
331
+ }
332
+
333
+ renderBlockContent(block) {
334
+ if (block.type === 'text' && block.text) {
335
+ const text = block.text;
336
+ if (text.includes('<') && (text.includes('</') || text.includes('/>'))) {
337
+ return text;
338
+ }
339
+ return this.escapeHtml(text);
340
+ }
341
+ return this.escapeHtml(JSON.stringify(block));
342
+ }
343
+
344
+ scrollToBottom() {
345
+ const scrollContainer = document.getElementById('output-scroll');
346
+ if (scrollContainer) {
347
+ scrollContainer.scrollTop = scrollContainer.scrollHeight;
348
+ }
349
+ }
350
+
351
+ handleStreamingError(data) {
352
+ console.error('Streaming error:', data);
353
+ this.state.isStreaming = false;
354
+
355
+ const sessionId = data.sessionId || this.state.currentSession?.id;
356
+ const streamingEl = document.getElementById(`streaming-${sessionId}`);
357
+ if (streamingEl) {
358
+ const indicator = streamingEl.querySelector('.streaming-indicator');
359
+ if (indicator) {
360
+ indicator.innerHTML = `<span style="color:var(--color-error);">Error: ${this.escapeHtml(data.error || 'Unknown error')}</span>`;
361
+ }
362
+ }
363
+
364
+ this.enableControls();
365
+ this.emit('streaming:error', data);
366
+ }
367
+
311
368
  handleStreamingComplete(data) {
312
369
  console.log('Streaming completed:', data);
313
370
  this.state.isStreaming = false;
314
371
 
315
- const duration = data.duration || (Date.now() - (this.state.currentSession?.startTime || Date.now()));
316
-
317
- this.renderer.queueEvent({
318
- type: 'streaming_complete',
319
- sessionId: data.sessionId,
320
- duration,
321
- timestamp: data.timestamp || Date.now()
322
- });
372
+ const sessionId = data.sessionId || this.state.currentSession?.id;
373
+ const streamingEl = document.getElementById(`streaming-${sessionId}`);
374
+ if (streamingEl) {
375
+ const indicator = streamingEl.querySelector('.streaming-indicator');
376
+ if (indicator) indicator.remove();
377
+ streamingEl.classList.remove('streaming-message');
378
+ const prevTextEl = streamingEl.querySelector('.streaming-text-current');
379
+ if (prevTextEl) prevTextEl.classList.remove('streaming-text-current');
380
+
381
+ const ts = document.createElement('div');
382
+ ts.className = 'message-timestamp';
383
+ ts.textContent = new Date().toLocaleString();
384
+ streamingEl.appendChild(ts);
385
+ }
323
386
 
324
387
  this.enableControls();
325
- this.emit('streaming:complete', {
326
- ...data,
327
- duration,
328
- eventCount: this.state.sessionEvents.length
329
- });
388
+ this.emit('streaming:complete', data);
330
389
  }
331
390
 
332
391
  /**
@@ -339,32 +398,55 @@ class AgentGUIClient {
339
398
  }
340
399
  }
341
400
 
342
- /**
343
- * Handle message created
344
- */
345
401
  handleMessageCreated(data) {
346
- // If the message is for the currently displayed conversation, append it to the output
347
- if (data.conversationId === this.state.currentConversation?.id && data.message) {
348
- const outputEl = document.querySelector('.conversation-messages');
349
- if (outputEl) {
350
- const messageHtml = `
351
- <div class="message message-${data.message.role}">
352
- <div class="message-role">${data.message.role.charAt(0).toUpperCase() + data.message.role.slice(1)}</div>
353
- ${this.renderMessageContent(data.message.content)}
354
- <div class="message-timestamp">${new Date(data.message.created_at).toLocaleString()}</div>
355
- </div>
356
- `;
357
- outputEl.insertAdjacentHTML('beforeend', messageHtml);
358
- // Scroll to bottom
359
- const scrollContainer = document.getElementById('output-scroll');
360
- if (scrollContainer) {
361
- scrollContainer.scrollTop = scrollContainer.scrollHeight;
362
- }
363
- }
402
+ if (data.conversationId !== this.state.currentConversation?.id || !data.message) {
403
+ this.emit('message:created', data);
404
+ return;
364
405
  }
406
+
407
+ if (data.message.role === 'assistant' && this.state.isStreaming) {
408
+ this.emit('message:created', data);
409
+ return;
410
+ }
411
+
412
+ const outputEl = document.querySelector('.conversation-messages');
413
+ if (!outputEl) {
414
+ this.emit('message:created', data);
415
+ return;
416
+ }
417
+
418
+ const messageHtml = `
419
+ <div class="message message-${data.message.role}" data-msg-id="${data.message.id}">
420
+ <div class="message-role">${data.message.role.charAt(0).toUpperCase() + data.message.role.slice(1)}</div>
421
+ ${this.renderMessageContent(data.message.content)}
422
+ <div class="message-timestamp">${new Date(data.message.created_at).toLocaleString()}</div>
423
+ </div>
424
+ `;
425
+ outputEl.insertAdjacentHTML('beforeend', messageHtml);
426
+ this.scrollToBottom();
365
427
  this.emit('message:created', data);
366
428
  }
367
429
 
430
+ handleQueueStatus(data) {
431
+ if (data.conversationId !== this.state.currentConversation?.id) return;
432
+
433
+ const outputEl = document.querySelector('.conversation-messages');
434
+ if (!outputEl) return;
435
+
436
+ let queueEl = outputEl.querySelector('.queue-indicator');
437
+ if (data.queueLength > 0) {
438
+ if (!queueEl) {
439
+ queueEl = document.createElement('div');
440
+ queueEl.className = 'queue-indicator';
441
+ queueEl.style.cssText = 'padding:0.5rem 1rem;margin:0.5rem 0;border-radius:0.375rem;background:var(--color-warning);color:#000;font-size:0.875rem;text-align:center;';
442
+ outputEl.appendChild(queueEl);
443
+ }
444
+ queueEl.textContent = `${data.queueLength} message${data.queueLength > 1 ? 's' : ''} queued`;
445
+ } else if (queueEl) {
446
+ queueEl.remove();
447
+ }
448
+ }
449
+
368
450
  /**
369
451
  * Parse markdown code blocks from text
370
452
  * Returns array of parts with type ('text' or 'code') and content/language/code
@@ -476,15 +558,7 @@ class AgentGUIClient {
476
558
  }
477
559
  }
478
560
 
479
- /**
480
- * Start execution
481
- */
482
561
  async startExecution() {
483
- if (this.state.isStreaming) {
484
- this.showError('Streaming already in progress');
485
- return;
486
- }
487
-
488
562
  const prompt = this.ui.messageInput?.value || '';
489
563
  const agentId = this.ui.agentSelector?.value || 'claude-code';
490
564
 
@@ -493,23 +567,28 @@ class AgentGUIClient {
493
567
  return;
494
568
  }
495
569
 
496
- try {
497
- this.disableControls();
570
+ if (this.ui.messageInput) this.ui.messageInput.value = '';
498
571
 
499
- const response = await fetch(window.__BASE_URL + '/api/conversations', {
500
- method: 'POST',
501
- headers: { 'Content-Type': 'application/json' },
502
- body: JSON.stringify({
503
- agentId,
504
- title: prompt.substring(0, 50)
505
- })
506
- });
572
+ try {
573
+ if (this.state.currentConversation?.id) {
574
+ await this.streamToConversation(this.state.currentConversation.id, prompt, agentId);
575
+ } else {
576
+ this.disableControls();
577
+ const response = await fetch(window.__BASE_URL + '/api/conversations', {
578
+ method: 'POST',
579
+ headers: { 'Content-Type': 'application/json' },
580
+ body: JSON.stringify({ agentId, title: prompt.substring(0, 50) })
581
+ });
582
+ const { conversation } = await response.json();
583
+ this.state.currentConversation = conversation;
507
584
 
508
- const { conversation } = await response.json();
509
- this.state.currentConversation = conversation;
585
+ if (window.conversationManager) {
586
+ window.conversationManager.loadConversations();
587
+ window.conversationManager.select(conversation.id);
588
+ }
510
589
 
511
- // Start streaming
512
- await this.streamToConversation(conversation.id, prompt, agentId);
590
+ await this.streamToConversation(conversation.id, prompt, agentId);
591
+ }
513
592
  } catch (error) {
514
593
  console.error('Execution error:', error);
515
594
  this.showError('Failed to start execution: ' + error.message);
@@ -517,33 +596,32 @@ class AgentGUIClient {
517
596
  }
518
597
  }
519
598
 
520
- /**
521
- * Stream execution to conversation
522
- */
523
599
  async streamToConversation(conversationId, prompt, agentId) {
524
600
  try {
601
+ if (this.wsManager.isConnected) {
602
+ this.wsManager.sendMessage({ type: 'subscribe', conversationId });
603
+ }
604
+
525
605
  const response = await fetch(`${window.__BASE_URL}/api/conversations/${conversationId}/stream`, {
526
606
  method: 'POST',
527
607
  headers: { 'Content-Type': 'application/json' },
528
- body: JSON.stringify({
529
- content: prompt,
530
- agentId,
531
- skipPermissions: false
532
- })
608
+ body: JSON.stringify({ content: prompt, agentId, skipPermissions: false })
533
609
  });
534
610
 
535
- if (!response.ok) {
536
- throw new Error(`HTTP ${response.status}`);
537
- }
611
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
538
612
 
539
- const { session, streamId } = await response.json();
613
+ const result = await response.json();
540
614
 
541
- // Subscribe to session events via WebSocket
542
- if (this.wsManager.isConnected) {
543
- this.wsManager.subscribeToSession(session.id);
615
+ if (result.queued) {
616
+ console.log('Message queued, position:', result.queuePosition);
617
+ return;
544
618
  }
545
619
 
546
- this.emit('execution:started', { session, streamId });
620
+ if (result.session && this.wsManager.isConnected) {
621
+ this.wsManager.subscribeToSession(result.session.id);
622
+ }
623
+
624
+ this.emit('execution:started', result);
547
625
  } catch (error) {
548
626
  console.error('Stream execution error:', error);
549
627
  this.showError('Failed to stream execution: ' + error.message);
@@ -675,37 +753,33 @@ class AgentGUIClient {
675
753
  }
676
754
  }
677
755
 
678
- /**
679
- * Load and display conversation messages
680
- */
681
756
  async loadConversationMessages(conversationId) {
682
757
  try {
683
- this.state.currentConversation = { id: conversationId };
684
-
685
- // Fetch conversation details
686
758
  const convResponse = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}`);
687
759
  const { conversation } = await convResponse.json();
760
+ this.state.currentConversation = conversation;
688
761
 
689
- // Fetch messages
690
- const messagesResponse = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/messages`);
691
- if (!messagesResponse.ok) {
692
- throw new Error(`Failed to fetch messages: ${messagesResponse.status}`);
762
+ if (this.wsManager.isConnected) {
763
+ this.wsManager.sendMessage({ type: 'subscribe', conversationId });
693
764
  }
765
+
766
+ const messagesResponse = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/messages`);
767
+ if (!messagesResponse.ok) throw new Error(`Failed to fetch messages: ${messagesResponse.status}`);
694
768
  const messagesData = await messagesResponse.json();
695
769
 
696
- // Clear output and display conversation header
697
770
  const outputEl = document.getElementById('output');
698
771
  if (outputEl) {
699
- const wdInfo = conversation.workingDirectory ? ` ${this.escapeHtml(conversation.workingDirectory)}` : '';
772
+ const wdInfo = conversation.workingDirectory ? ` - ${this.escapeHtml(conversation.workingDirectory)}` : '';
700
773
  outputEl.innerHTML = `
701
774
  <div class="conversation-header">
702
775
  <h2>${this.escapeHtml(conversation.title || 'Conversation')}</h2>
703
- <p class="text-secondary">${conversation.agentType || 'unknown'} ${new Date(conversation.created_at).toLocaleDateString()}${wdInfo}</p>
776
+ <p class="text-secondary">${conversation.agentType || 'unknown'} - ${new Date(conversation.created_at).toLocaleDateString()}${wdInfo}</p>
704
777
  </div>
705
778
  <div class="conversation-messages">
706
779
  ${this.renderMessages(messagesData.messages || [])}
707
780
  </div>
708
781
  `;
782
+ this.scrollToBottom();
709
783
  }
710
784
  } catch (error) {
711
785
  console.error('Failed to load conversation messages:', error);