agentgui 1.0.555 → 1.0.557

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
@@ -676,8 +676,17 @@ export const queries = {
676
676
  },
677
677
 
678
678
  getResumableConversations() {
679
+ // Get conversations with active/pending sessions that can be resumed
680
+ // Include conversations regardless of isStreaming flag - check database for actual active sessions
679
681
  const stmt = prep(
680
- "SELECT id, title, claudeSessionId, agentId, agentType, workingDirectory, model, subAgent FROM conversations WHERE isStreaming = 1 AND claudeSessionId IS NOT NULL AND claudeSessionId != ''"
682
+ `SELECT DISTINCT c.id, c.title, c.claudeSessionId, c.agentId, c.agentType, c.workingDirectory, c.model, c.subAgent
683
+ FROM conversations c
684
+ WHERE c.claudeSessionId IS NOT NULL AND c.claudeSessionId != ''
685
+ AND EXISTS (
686
+ SELECT 1 FROM sessions s
687
+ WHERE s.conversationId = c.id
688
+ AND s.status IN ('active', 'pending', 'interrupted')
689
+ )`
681
690
  );
682
691
  return stmt.all();
683
692
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.555",
3
+ "version": "1.0.557",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -2474,7 +2474,8 @@ class AgentGUIClient {
2474
2474
  if (this.ui.messageInput) {
2475
2475
  this.ui.messageInput.value = '';
2476
2476
  this.ui.messageInput.style.height = 'auto';
2477
- this.ui.messageInput.disabled = false;
2477
+ // Note: prompt disabled state will be set immutably based on shouldResumeStreaming
2478
+ // after conversation data loads, don't set here
2478
2479
  }
2479
2480
 
2480
2481
  if (this.ui.stopButton) this.ui.stopButton.classList.remove('visible');
@@ -2637,26 +2638,34 @@ class AgentGUIClient {
2637
2638
 
2638
2639
  const blocksEl = messageDiv.querySelector('.message-blocks');
2639
2640
  const blockFrag = document.createDocumentFragment();
2641
+ const toolResultBlocks = new Map();
2642
+
2640
2643
  sessionChunkList.forEach(chunk => {
2641
2644
  if (!chunk.block?.type) return;
2642
2645
  if (chunk.block.type === 'tool_result') {
2643
- const lastInFrag = blockFrag.lastElementChild;
2644
- if (lastInFrag?.classList?.contains('block-tool-use')) {
2645
- lastInFrag.classList.remove('has-success', 'has-error');
2646
- lastInFrag.classList.add(chunk.block.is_error ? 'has-error' : 'has-success');
2647
- const parentIsOpen = lastInFrag.hasAttribute('open');
2648
- const contextWithParent = { ...chunk, parentIsOpen };
2649
- const element = this.renderer.renderBlock(chunk.block, contextWithParent, blockFrag);
2650
- if (element) { lastInFrag.appendChild(element); }
2651
- return;
2652
- }
2646
+ toolResultBlocks.set(chunk.id, chunk);
2647
+ return;
2653
2648
  }
2654
- const element = this.renderer.renderBlock(chunk.block, chunk, blockFrag);
2649
+ const element = this.renderer.renderBlockHeader(chunk.block, chunk);
2655
2650
  if (!element) return;
2656
2651
  blockFrag.appendChild(element);
2657
2652
  });
2653
+
2658
2654
  blocksEl.appendChild(blockFrag);
2659
2655
 
2656
+ toolResultBlocks.forEach((chunk, chunkId) => {
2657
+ const lastBlock = blocksEl.lastElementChild;
2658
+ if (lastBlock?.classList?.contains('block-type-tool_use')) {
2659
+ lastBlock.classList.remove('has-success', 'has-error');
2660
+ lastBlock.classList.add(chunk.block.is_error ? 'has-error' : 'has-success');
2661
+ const contextWithParent = { ...chunk, parentIsOpen: lastBlock.hasAttribute('open') };
2662
+ const element = this.renderer.renderBlock(chunk.block, contextWithParent);
2663
+ if (element && element !== blockFrag.lastElementChild) {
2664
+ lastBlock.appendChild(element);
2665
+ }
2666
+ }
2667
+ });
2668
+
2660
2669
  if (isCurrentActiveSession) {
2661
2670
  const indicatorDiv = document.createElement('div');
2662
2671
  indicatorDiv.className = 'streaming-indicator';
@@ -2717,6 +2726,13 @@ class AgentGUIClient {
2717
2726
  this.chunkPollState.lastFetchTimestamp = lastChunkTime;
2718
2727
  this.startChunkPolling(conversationId);
2719
2728
  this.disableControls();
2729
+ // IMMUTABLE STATE: Prompt is disabled during active streaming, do NOT enable
2730
+ if (this.ui.messageInput) this.ui.messageInput.disabled = true;
2731
+ } else {
2732
+ // IMMUTABLE STATE: Prompt is enabled when NOT streaming (only disabled on WebSocket disconnect)
2733
+ if (this.ui.messageInput && this.wsManager.isConnected) {
2734
+ this.ui.messageInput.disabled = false;
2735
+ }
2720
2736
  }
2721
2737
 
2722
2738
  this.restoreScrollPosition(conversationId);
@@ -2154,6 +2154,59 @@ class StreamingRenderer {
2154
2154
  }
2155
2155
  }
2156
2156
 
2157
+ /**
2158
+ * Render block header with lazy-loading placeholder for body
2159
+ * Returns a <details> element with just the summary, body content deferred
2160
+ */
2161
+ renderBlockHeader(block, context = {}) {
2162
+ if (!block || !block.type) return null;
2163
+
2164
+ const typeLabel = block.type.charAt(0).toUpperCase() + block.type.slice(1).replace(/_/g, ' ');
2165
+ const summary = document.createElement('summary');
2166
+ summary.style.cursor = 'pointer';
2167
+ summary.style.userSelect = 'none';
2168
+ summary.className = 'block-header-summary';
2169
+
2170
+ let summaryText = typeLabel;
2171
+ if (block.type === 'code' && block.language) {
2172
+ summaryText += ` (${block.language})`;
2173
+ } else if (block.type === 'bash' && block.source) {
2174
+ summaryText += ` - ${block.source}`;
2175
+ } else if (block.type === 'tool_use' && block.name) {
2176
+ summaryText += ` - ${block.name}`;
2177
+ } else if (block.type === 'text' && block.text) {
2178
+ const preview = block.text.substring(0, 60).replace(/\n/g, ' ');
2179
+ summaryText = preview + (block.text.length > 60 ? '...' : '');
2180
+ }
2181
+
2182
+ summary.textContent = summaryText;
2183
+
2184
+ const details = document.createElement('details');
2185
+ details.className = `block-type-${block.type}`;
2186
+ details.setAttribute('data-block-type', block.type);
2187
+ details.setAttribute('data-lazy-load', 'pending');
2188
+ details.appendChild(summary);
2189
+
2190
+ // Attach lazy loader on first open
2191
+ details.addEventListener('toggle', async (e) => {
2192
+ if (details.open && details.getAttribute('data-lazy-load') === 'pending') {
2193
+ details.setAttribute('data-lazy-load', 'loading');
2194
+ try {
2195
+ const body = this.renderBlock(block, context);
2196
+ if (body && body !== summary) {
2197
+ details.appendChild(body);
2198
+ }
2199
+ details.setAttribute('data-lazy-load', 'loaded');
2200
+ } catch (err) {
2201
+ console.error('Failed to lazy-load block:', err);
2202
+ details.setAttribute('data-lazy-load', 'failed');
2203
+ }
2204
+ }
2205
+ }, { once: false });
2206
+
2207
+ return details;
2208
+ }
2209
+
2157
2210
  /**
2158
2211
  * Cleanup resources
2159
2212
  */