agentgui 1.0.568 → 1.0.569

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.568",
3
+ "version": "1.0.569",
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
@@ -948,6 +948,26 @@
948
948
  .inject-btn:disabled { opacity: 0.4; cursor: not-allowed; }
949
949
  .inject-btn.visible { display: flex; }
950
950
 
951
+ .queue-btn {
952
+ display: none;
953
+ align-items: center;
954
+ justify-content: center;
955
+ width: 40px;
956
+ height: 40px;
957
+ background-color: #8b5cf6;
958
+ color: white;
959
+ border: none;
960
+ border-radius: 0.5rem;
961
+ cursor: pointer;
962
+ flex-shrink: 0;
963
+ transition: background-color 0.15s;
964
+ margin-right: 0.25rem;
965
+ }
966
+
967
+ .queue-btn:hover:not(:disabled) { background-color: #7c3aed; }
968
+ .queue-btn:disabled { opacity: 0.4; cursor: not-allowed; }
969
+ .queue-btn.visible { display: flex; }
970
+
951
971
  .steer-btn {
952
972
  display: none;
953
973
  align-items: center;
@@ -3185,6 +3205,11 @@
3185
3205
  <path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/>
3186
3206
  </svg>
3187
3207
  </button>
3208
+ <button class="queue-btn" id="queueBtn" title="Queue message for running agent" aria-label="Queue message">
3209
+ <svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
3210
+ <path d="M15 13H1v-2h14v2zm0-4H1V7h14v2zM1 19h14v-2H1v2z"/>
3211
+ </svg>
3212
+ </button>
3188
3213
  <button class="steer-btn" id="steerBtn" title="Steer running agent with message" aria-label="Steer agent">
3189
3214
  <svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
3190
3215
  <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z"/>
@@ -458,6 +458,7 @@ class AgentGUIClient {
458
458
 
459
459
  this.ui.stopButton = document.getElementById('stopBtn');
460
460
  this.ui.injectButton = document.getElementById('injectBtn');
461
+ this.ui.queueButton = document.getElementById('queueBtn');
461
462
  this.ui.steerButton = document.getElementById('steerBtn');
462
463
 
463
464
  if (this.ui.stopButton) {
@@ -486,6 +487,28 @@ class AgentGUIClient {
486
487
  });
487
488
  }
488
489
 
490
+ if (this.ui.queueButton) {
491
+ this.ui.queueButton.addEventListener('click', async () => {
492
+ if (!this.state.currentConversation) return;
493
+ const message = this.ui.messageInput?.value || '';
494
+ if (!message.trim()) {
495
+ this.showError('Please enter a message to queue');
496
+ return;
497
+ }
498
+ try {
499
+ const data = await window.wsClient.rpc('conv.queue', { id: this.state.currentConversation.id, content: message });
500
+ console.log('Queue response:', data);
501
+ if (this.ui.messageInput) {
502
+ this.ui.messageInput.value = '';
503
+ this.ui.messageInput.style.height = 'auto';
504
+ }
505
+ } catch (err) {
506
+ console.error('Failed to queue:', err);
507
+ this.showError('Failed to queue: ' + err.message);
508
+ }
509
+ });
510
+ }
511
+
489
512
  if (this.ui.steerButton) {
490
513
  this.ui.steerButton.addEventListener('click', async () => {
491
514
  if (!this.state.currentConversation) return;
@@ -726,19 +749,13 @@ class AgentGUIClient {
726
749
  if (this.state.currentConversation?.id !== data.conversationId) {
727
750
  console.log('Streaming started for non-active conversation:', data.conversationId);
728
751
  this.state.streamingConversations.set(data.conversationId, true);
752
+ this.updateBusyPromptArea(data.conversationId);
729
753
  this.emit('streaming:start', data);
730
754
  return;
731
755
  }
732
756
 
733
- // Show stop, steer, queue, and inject buttons when streaming starts for current conversation
734
- if (this.ui.stopButton) this.ui.stopButton.classList.add('visible');
735
- if (this.ui.injectButton) this.ui.injectButton.classList.add('visible');
736
- if (this.ui.steerButton) this.ui.steerButton.classList.add('visible');
737
- if (this.ui.queueButton) this.ui.queueButton.classList.add('visible');
738
- if (this.ui.sendButton) this.ui.sendButton.style.display = 'none';
739
- this.ensurePromptAreaAlwaysEnabled();
740
-
741
757
  this.state.streamingConversations.set(data.conversationId, true);
758
+ this.updateBusyPromptArea(data.conversationId);
742
759
  this.state.currentSession = {
743
760
  id: data.sessionId,
744
761
  conversationId: data.conversationId,
@@ -977,11 +994,13 @@ class AgentGUIClient {
977
994
  if (conversationId && this.state.currentConversation?.id !== conversationId) {
978
995
  console.log('Streaming error for non-active conversation:', conversationId);
979
996
  this.state.streamingConversations.delete(conversationId);
997
+ this.updateBusyPromptArea(conversationId);
980
998
  this.emit('streaming:error', data);
981
999
  return;
982
1000
  }
983
1001
 
984
1002
  this.state.streamingConversations.delete(conversationId);
1003
+ this.updateBusyPromptArea(conversationId);
985
1004
 
986
1005
  // Stop polling for chunks
987
1006
  this.stopChunkPolling();
@@ -1021,23 +1040,19 @@ class AgentGUIClient {
1021
1040
  console.log('Streaming completed:', data);
1022
1041
  this._clearThinkingCountdown();
1023
1042
 
1024
- // Hide stop, steer, and inject buttons when streaming completes
1025
- if (this.ui.stopButton) this.ui.stopButton.classList.remove('visible');
1026
- if (this.ui.injectButton) this.ui.injectButton.classList.remove('visible');
1027
- if (this.ui.steerButton) this.ui.steerButton.classList.remove('visible');
1028
- if (this.ui.sendButton) this.ui.sendButton.style.display = '';
1029
-
1030
1043
  const conversationId = data.conversationId || this.state.currentSession?.conversationId;
1031
1044
  if (conversationId) this.invalidateCache(conversationId);
1032
1045
 
1033
1046
  if (conversationId && this.state.currentConversation?.id !== conversationId) {
1034
1047
  console.log('Streaming completed for non-active conversation:', conversationId);
1035
1048
  this.state.streamingConversations.delete(conversationId);
1049
+ this.updateBusyPromptArea(conversationId);
1036
1050
  this.emit('streaming:complete', data);
1037
1051
  return;
1038
1052
  }
1039
1053
 
1040
1054
  this.state.streamingConversations.delete(conversationId);
1055
+ this.updateBusyPromptArea(conversationId);
1041
1056
 
1042
1057
  this.stopChunkPolling();
1043
1058
 
@@ -2489,6 +2504,7 @@ class AgentGUIClient {
2489
2504
 
2490
2505
  if (this.ui.stopButton) this.ui.stopButton.classList.remove('visible');
2491
2506
  if (this.ui.injectButton) this.ui.injectButton.classList.remove('visible');
2507
+ if (this.ui.queueButton) this.ui.queueButton.classList.remove('visible');
2492
2508
  if (this.ui.steerButton) this.ui.steerButton.classList.remove('visible');
2493
2509
  if (this.ui.sendButton) this.ui.sendButton.style.display = '';
2494
2510
 
@@ -2668,27 +2684,27 @@ class AgentGUIClient {
2668
2684
  toolResultBlocks.set(chunk.id, chunk);
2669
2685
  return;
2670
2686
  }
2671
- const element = this.renderer.renderBlock(chunk.block, chunk);
2687
+ const element = this.renderer.renderBlock(chunk.block, chunk, blockFrag);
2672
2688
  if (!element) return;
2673
- // Ensure CSS classes are always applied for styling consistency
2674
2689
  element.classList.add('block-loaded');
2675
- element.classList.add(`block-type-${chunk.block.type}`);
2676
2690
  blockFrag.appendChild(element);
2677
2691
  });
2678
2692
 
2679
2693
  blocksEl.appendChild(blockFrag);
2680
2694
 
2681
- toolResultBlocks.forEach((chunk, chunkId) => {
2682
- const lastBlock = blocksEl.lastElementChild;
2683
- if (lastBlock?.classList?.contains('block-type-tool_use')) {
2684
- lastBlock.classList.remove('has-success', 'has-error');
2685
- lastBlock.classList.add(chunk.block.is_error ? 'has-error' : 'has-success');
2686
- const contextWithParent = { ...chunk, parentIsOpen: lastBlock.hasAttribute('open') };
2687
- const element = this.renderer.renderBlock(chunk.block, contextWithParent);
2688
- if (element && element !== blockFrag.lastElementChild) {
2689
- element.classList.add('block-loaded');
2690
- lastBlock.appendChild(element);
2691
- }
2695
+ toolResultBlocks.forEach((chunk) => {
2696
+ const toolUseId = chunk.block.tool_use_id;
2697
+ const toolUseEl = toolUseId
2698
+ ? blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${toolUseId}"]`)
2699
+ : blocksEl.lastElementChild?.classList?.contains('block-type-tool_use') ? blocksEl.lastElementChild : null;
2700
+ if (!toolUseEl) return;
2701
+ toolUseEl.classList.remove('has-success', 'has-error');
2702
+ toolUseEl.classList.add(chunk.block.is_error ? 'has-error' : 'has-success');
2703
+ const contextWithParent = { ...chunk, parentIsOpen: toolUseEl.hasAttribute('open') };
2704
+ const element = this.renderer.renderBlock(chunk.block, contextWithParent, toolUseEl);
2705
+ if (element) {
2706
+ element.classList.add('block-loaded');
2707
+ toolUseEl.appendChild(element);
2692
2708
  }
2693
2709
  });
2694
2710
 
@@ -2779,20 +2795,22 @@ class AgentGUIClient {
2779
2795
  }
2780
2796
 
2781
2797
  updateBusyPromptArea(conversationId) {
2798
+ if (this.state.currentConversation?.id !== conversationId) return;
2782
2799
  const isStreaming = this.state.streamingConversations.has(conversationId);
2783
2800
  const isConnected = this.wsManager?.isConnected;
2784
2801
 
2785
2802
  const injectBtn = document.getElementById('injectBtn');
2786
2803
  const steerBtn = document.getElementById('steerBtn');
2804
+ const queueBtn = document.getElementById('queueBtn');
2787
2805
  const stopBtn = document.getElementById('stopBtn');
2788
2806
 
2789
- if (injectBtn) injectBtn.style.display = isStreaming ? 'flex' : 'none';
2790
- if (steerBtn) steerBtn.style.display = isStreaming ? 'flex' : 'none';
2791
- if (stopBtn) stopBtn.style.display = isStreaming ? 'flex' : 'none';
2807
+ [injectBtn, steerBtn, queueBtn, stopBtn].forEach(btn => {
2808
+ if (!btn) return;
2809
+ btn.classList.toggle('visible', isStreaming);
2810
+ btn.disabled = !isConnected;
2811
+ });
2792
2812
 
2793
- if (injectBtn) injectBtn.disabled = !isConnected;
2794
- if (steerBtn) steerBtn.disabled = !isConnected;
2795
- if (stopBtn) stopBtn.disabled = !isConnected;
2813
+ if (this.ui.sendButton) this.ui.sendButton.style.display = isStreaming ? 'none' : '';
2796
2814
  }
2797
2815
 
2798
2816
  removeScrollUpDetection() {
@@ -3070,6 +3088,9 @@ class AgentGUIClient {
3070
3088
  if (this.ui.steerButton && this.ui.steerButton.classList.contains('visible')) {
3071
3089
  this.ui.steerButton.disabled = !this.wsManager.isConnected;
3072
3090
  }
3091
+ if (this.ui.queueButton && this.ui.queueButton.classList.contains('visible')) {
3092
+ this.ui.queueButton.disabled = !this.wsManager.isConnected;
3093
+ }
3073
3094
  }
3074
3095
 
3075
3096
  /**
@@ -3103,6 +3124,10 @@ class AgentGUIClient {
3103
3124
  this.ui.steerButton.classList.add('visible');
3104
3125
  this.ui.steerButton.disabled = !this.wsManager.isConnected;
3105
3126
  }
3127
+ if (this.ui.queueButton) {
3128
+ this.ui.queueButton.classList.add('visible');
3129
+ this.ui.queueButton.disabled = !this.wsManager.isConnected;
3130
+ }
3106
3131
  }
3107
3132
 
3108
3133
  /**
@@ -1230,7 +1230,8 @@ class StreamingRenderer {
1230
1230
  const toolName = block.tool_name || block.name || '';
1231
1231
 
1232
1232
  // Special handling for TodoWrite: render directly without success wrapper
1233
- if (toolName.includes('TodoWrite') && typeof content === 'object' && content.todos && Array.isArray(content.todos)) {
1233
+ // Detect by tool name OR by content structure (todos array)
1234
+ if ((toolName.includes('TodoWrite') || (typeof content === 'object' && Array.isArray(content?.todos))) && typeof content === 'object' && content.todos && Array.isArray(content.todos)) {
1234
1235
  const statusIcons = { completed: '&#9989;', in_progress: '&#9881;', pending: '&#9744;' };
1235
1236
  const completedCount = content.todos.filter(t => t.status === 'completed').length;
1236
1237
  const totalCount = content.todos.length;
@@ -2200,8 +2201,8 @@ class StreamingRenderer {
2200
2201
  summary.textContent = summaryText;
2201
2202
 
2202
2203
  const details = document.createElement('details');
2203
- const className = `block-${block.type} block-type-${block.type} ${this._getBlockTypeClass(block.type)}`;
2204
- details.className = className;
2204
+ details.className = `block-${block.type}`;
2205
+ details.classList.add(this._getBlockTypeClass(block.type));
2205
2206
  details.setAttribute('data-block-type', block.type);
2206
2207
  details.setAttribute('data-lazy-load', 'pending');
2207
2208
  details.open = block.type === 'success' || (block.type === 'tool_result' && !block.is_error);