agentgui 1.0.563 → 1.0.565

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.563",
3
+ "version": "1.0.565",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -564,7 +564,6 @@ class AgentGUIClient {
564
564
  if (outputEl) outputEl.innerHTML = '';
565
565
  if (this.ui.messageInput) {
566
566
  this.ui.messageInput.value = '';
567
- this.ui.messageInput.disabled = false;
568
567
  this.ui.messageInput.style.height = 'auto';
569
568
  }
570
569
  this.unlockAgentAndModel();
@@ -863,7 +862,10 @@ class AgentGUIClient {
863
862
  // Start polling for chunks from database
864
863
  this.startChunkPolling(data.conversationId);
865
864
 
866
- this.disableControls();
865
+ // Show queue/steer UI when streaming starts (for busy prompt)
866
+ this.fetchAndRenderQueue(data.conversationId);
867
+
868
+ // IMMUTABLE: Prompt area remains enabled - user can queue/steer messages
867
869
  this.emit('streaming:start', data);
868
870
  }
869
871
 
@@ -982,6 +984,10 @@ class AgentGUIClient {
982
984
  // Stop polling for chunks
983
985
  this.stopChunkPolling();
984
986
 
987
+ // Clear queue indicator on error
988
+ const queueEl = document.querySelector('.queue-indicator');
989
+ if (queueEl) queueEl.remove();
990
+
985
991
  // If this is a premature ACP end, render distinct warning block
986
992
  if (data.isPrematureEnd) {
987
993
  this.renderer.queueEvent({
@@ -1033,6 +1039,10 @@ class AgentGUIClient {
1033
1039
 
1034
1040
  this.stopChunkPolling();
1035
1041
 
1042
+ // Clear queue indicator when streaming completes
1043
+ const queueEl = document.querySelector('.queue-indicator');
1044
+ if (queueEl) queueEl.remove();
1045
+
1036
1046
  const sessionId = data.sessionId || this.state.currentSession?.id;
1037
1047
  const streamingEl = document.getElementById(`streaming-${sessionId}`);
1038
1048
  if (streamingEl) {
@@ -2372,24 +2382,21 @@ class AgentGUIClient {
2372
2382
 
2373
2383
  /**
2374
2384
  * Disable UI controls during streaming
2375
- * NOTE: Only disables stop button visibility. Prompt, input, and inject remain enabled.
2385
+ * NOTE: Prompt area is IMMUTABLE - always enabled while connected.
2386
+ * Streaming state is rendered via queue/steer buttons, not input disabling.
2376
2387
  */
2377
2388
  disableControls() {
2378
- // Prompt area and inject button are NEVER disabled during streaming
2379
- // Only stop button behavior changes
2380
- const stopBtn = document.getElementById('stopBtn');
2381
- if (stopBtn) stopBtn.disabled = false;
2389
+ // IMMUTABLE: Prompt state managed only by syncPromptState() based on WebSocket connection
2390
+ // Never disable input during streaming - use queue/steer visibility instead
2382
2391
  }
2383
2392
 
2384
2393
  /**
2385
2394
  * Enable UI controls
2386
- * NOTE: Restores stop button visibility. Prompt area always stays enabled.
2395
+ * NOTE: Prompt area is always enabled when connected.
2387
2396
  */
2388
2397
  enableControls() {
2389
- // Prompt area and inject button are always enabled unless disconnected
2390
- // Only stop button behavior changes
2391
- const stopBtn = document.getElementById('stopBtn');
2392
- if (stopBtn) stopBtn.disabled = true;
2398
+ // IMMUTABLE: Prompt state managed only by syncPromptState() based on WebSocket connection
2399
+ // Never disable input during streaming - use queue/steer visibility instead
2393
2400
  }
2394
2401
 
2395
2402
  /**
@@ -2659,8 +2666,9 @@ class AgentGUIClient {
2659
2666
  toolResultBlocks.set(chunk.id, chunk);
2660
2667
  return;
2661
2668
  }
2662
- const element = this.renderer.renderBlockHeader(chunk.block, chunk);
2669
+ const element = this.renderer.renderBlock(chunk.block, chunk);
2663
2670
  if (!element) return;
2671
+ element.classList.add('block-loaded');
2664
2672
  blockFrag.appendChild(element);
2665
2673
  });
2666
2674
 
@@ -2674,6 +2682,7 @@ class AgentGUIClient {
2674
2682
  const contextWithParent = { ...chunk, parentIsOpen: lastBlock.hasAttribute('open') };
2675
2683
  const element = this.renderer.renderBlock(chunk.block, contextWithParent);
2676
2684
  if (element && element !== blockFrag.lastElementChild) {
2685
+ element.classList.add('block-loaded');
2677
2686
  lastBlock.appendChild(element);
2678
2687
  }
2679
2688
  }
@@ -2738,7 +2747,7 @@ class AgentGUIClient {
2738
2747
 
2739
2748
  this.chunkPollState.lastFetchTimestamp = lastChunkTime;
2740
2749
  this.startChunkPolling(conversationId);
2741
- this.disableControls();
2750
+ // IMMUTABLE: Prompt remains enabled - syncPromptState will set correct state
2742
2751
  this.syncPromptState(conversationId);
2743
2752
  } else {
2744
2753
  this.syncPromptState(conversationId);
@@ -2759,12 +2768,7 @@ class AgentGUIClient {
2759
2768
  if (!conversation || conversation.id !== conversationId) return;
2760
2769
 
2761
2770
  if (this.ui.messageInput) {
2762
- const isStreaming = this.state.streamingConversations.has(conversationId);
2763
- if (isStreaming) {
2764
- this.ui.messageInput.disabled = true;
2765
- } else {
2766
- this.ui.messageInput.disabled = !this.wsManager.isConnected;
2767
- }
2771
+ this.ui.messageInput.disabled = false;
2768
2772
  }
2769
2773
  }
2770
2774
 
@@ -3039,14 +3043,11 @@ class AgentGUIClient {
3039
3043
  }
3040
3044
 
3041
3045
  /**
3042
- * Disable prompt area (input and inject button) only on disconnect
3046
+ * Disable prompt area - NEVER CALLED. Prompt must always be enabled.
3047
+ * Keeping method for backward compatibility but it does nothing.
3043
3048
  */
3044
3049
  disablePromptArea() {
3045
- if (this.ui.messageInput) {
3046
- this.ui.messageInput.disabled = true;
3047
- }
3048
- const injectBtn = document.getElementById('injectBtn');
3049
- if (injectBtn) injectBtn.disabled = true;
3050
+ // NEVER disable messageInput - prompt must always be writable
3050
3051
  }
3051
3052
 
3052
3053
  /**
@@ -751,6 +751,7 @@ class StreamingRenderer {
751
751
  if (block.id) details.dataset.toolUseId = block.id;
752
752
  details.classList.add(this._getBlockTypeClass('tool_use'));
753
753
  details.classList.add(this._getToolColorClass(toolName));
754
+ details.open = true;
754
755
  const summary = document.createElement('summary');
755
756
  summary.className = 'folded-tool-bar';
756
757
  const displayName = this.getToolUseDisplayName(toolName);
@@ -1226,6 +1227,20 @@ class StreamingRenderer {
1226
1227
  renderBlockToolResult(block, context) {
1227
1228
  const isError = block.is_error || false;
1228
1229
  const content = block.content || '';
1230
+ const toolName = block.tool_name || block.name || '';
1231
+
1232
+ // Special handling for TodoWrite: render directly without success wrapper
1233
+ if (toolName.includes('TodoWrite') && typeof content === 'object' && content.todos && Array.isArray(content.todos)) {
1234
+ const statusIcons = { completed: '✅', in_progress: '⚙', pending: '☐' };
1235
+ const completedCount = content.todos.filter(t => t.status === 'completed').length;
1236
+ const totalCount = content.todos.length;
1237
+ const items = content.todos.map(t => `<div class="todo-item"><span class="todo-status">${statusIcons[t.status] || '&#9744;'}</span><span class="todo-text">${this.escapeHtml(t.content || '')}</span></div>`).join('');
1238
+ const div = document.createElement('div');
1239
+ div.className = 'block-tool-result';
1240
+ div.innerHTML = `<details class="folded-tool" open><summary class="folded-tool-bar" style="cursor:pointer;padding:0.5rem;background:var(--color-bg-secondary);border-radius:0.25rem;user-select:none"><span style="font-weight:600;font-size:0.9rem">📋 Tasks</span><span style="margin-left:0.5rem;font-size:0.8rem;color:var(--color-text-secondary)">${completedCount}/${totalCount} complete</span></summary><div class="folded-tool-body tool-param-todos" style="padding:0.75rem">${items}</div></details>`;
1241
+ return div;
1242
+ }
1243
+
1229
1244
  const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
1230
1245
 
1231
1246
  const details = document.createElement('details');
@@ -2185,9 +2200,11 @@ class StreamingRenderer {
2185
2200
  summary.textContent = summaryText;
2186
2201
 
2187
2202
  const details = document.createElement('details');
2188
- details.className = `block-type-${block.type} ${this._getBlockTypeClass(block.type)}`;
2203
+ const className = `block-${block.type} block-type-${block.type} ${this._getBlockTypeClass(block.type)}`;
2204
+ details.className = className;
2189
2205
  details.setAttribute('data-block-type', block.type);
2190
2206
  details.setAttribute('data-lazy-load', 'pending');
2207
+ details.open = block.type === 'success' || (block.type === 'tool_result' && !block.is_error);
2191
2208
  details.appendChild(summary);
2192
2209
 
2193
2210
  // Attach lazy loader on first open