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 +10 -1
- package/package.json +1 -1
- package/static/js/client.js +28 -12
- package/static/js/streaming-renderer.js +53 -0
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
|
-
|
|
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
package/static/js/client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
2644
|
-
|
|
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.
|
|
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
|
*/
|