agentgui 1.0.568 → 1.0.570
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 +1 -1
- package/static/index.html +25 -0
- package/static/js/client.js +83 -38
- package/static/js/streaming-renderer.js +4 -4
package/package.json
CHANGED
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"/>
|
package/static/js/client.js
CHANGED
|
@@ -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
|
|
|
@@ -2473,6 +2488,7 @@ class AgentGUIClient {
|
|
|
2473
2488
|
this._previousConvAbort = new AbortController();
|
|
2474
2489
|
const convSignal = this._previousConvAbort.signal;
|
|
2475
2490
|
|
|
2491
|
+
const prevConversationId = this.state.currentConversation?.id;
|
|
2476
2492
|
this.cacheCurrentConversation();
|
|
2477
2493
|
this.stopChunkPolling();
|
|
2478
2494
|
this.removeScrollUpDetection();
|
|
@@ -2489,6 +2505,7 @@ class AgentGUIClient {
|
|
|
2489
2505
|
|
|
2490
2506
|
if (this.ui.stopButton) this.ui.stopButton.classList.remove('visible');
|
|
2491
2507
|
if (this.ui.injectButton) this.ui.injectButton.classList.remove('visible');
|
|
2508
|
+
if (this.ui.queueButton) this.ui.queueButton.classList.remove('visible');
|
|
2492
2509
|
if (this.ui.steerButton) this.ui.steerButton.classList.remove('visible');
|
|
2493
2510
|
if (this.ui.sendButton) this.ui.sendButton.style.display = '';
|
|
2494
2511
|
|
|
@@ -2542,9 +2559,16 @@ class AgentGUIClient {
|
|
|
2542
2559
|
console.warn('Conversation no longer exists:', conversationId);
|
|
2543
2560
|
this.state.currentConversation = null;
|
|
2544
2561
|
if (window.conversationManager) window.conversationManager.loadConversations();
|
|
2545
|
-
|
|
2546
|
-
if (
|
|
2547
|
-
|
|
2562
|
+
// Resume from last successful conversation if available
|
|
2563
|
+
if (prevConversationId && prevConversationId !== conversationId) {
|
|
2564
|
+
console.log('Resuming from previous conversation:', prevConversationId);
|
|
2565
|
+
this.showError('Conversation not found. Resuming previous conversation.');
|
|
2566
|
+
await this.loadConversationMessages(prevConversationId);
|
|
2567
|
+
} else {
|
|
2568
|
+
const outputEl = document.getElementById('output');
|
|
2569
|
+
if (outputEl) outputEl.innerHTML = '<p class="text-secondary" style="padding:2rem;text-align:center">Conversation not found. It may have been lost during a server restart.</p>';
|
|
2570
|
+
this.enableControls();
|
|
2571
|
+
}
|
|
2548
2572
|
return;
|
|
2549
2573
|
}
|
|
2550
2574
|
throw e;
|
|
@@ -2668,27 +2692,27 @@ class AgentGUIClient {
|
|
|
2668
2692
|
toolResultBlocks.set(chunk.id, chunk);
|
|
2669
2693
|
return;
|
|
2670
2694
|
}
|
|
2671
|
-
const element = this.renderer.renderBlock(chunk.block, chunk);
|
|
2695
|
+
const element = this.renderer.renderBlock(chunk.block, chunk, blockFrag);
|
|
2672
2696
|
if (!element) return;
|
|
2673
|
-
// Ensure CSS classes are always applied for styling consistency
|
|
2674
2697
|
element.classList.add('block-loaded');
|
|
2675
|
-
element.classList.add(`block-type-${chunk.block.type}`);
|
|
2676
2698
|
blockFrag.appendChild(element);
|
|
2677
2699
|
});
|
|
2678
2700
|
|
|
2679
2701
|
blocksEl.appendChild(blockFrag);
|
|
2680
2702
|
|
|
2681
|
-
toolResultBlocks.forEach((chunk
|
|
2682
|
-
const
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2703
|
+
toolResultBlocks.forEach((chunk) => {
|
|
2704
|
+
const toolUseId = chunk.block.tool_use_id;
|
|
2705
|
+
const toolUseEl = toolUseId
|
|
2706
|
+
? blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${toolUseId}"]`)
|
|
2707
|
+
: blocksEl.lastElementChild?.classList?.contains('block-type-tool_use') ? blocksEl.lastElementChild : null;
|
|
2708
|
+
if (!toolUseEl) return;
|
|
2709
|
+
toolUseEl.classList.remove('has-success', 'has-error');
|
|
2710
|
+
toolUseEl.classList.add(chunk.block.is_error ? 'has-error' : 'has-success');
|
|
2711
|
+
const contextWithParent = { ...chunk, parentIsOpen: toolUseEl.hasAttribute('open') };
|
|
2712
|
+
const element = this.renderer.renderBlock(chunk.block, contextWithParent, toolUseEl);
|
|
2713
|
+
if (element) {
|
|
2714
|
+
element.classList.add('block-loaded');
|
|
2715
|
+
toolUseEl.appendChild(element);
|
|
2692
2716
|
}
|
|
2693
2717
|
});
|
|
2694
2718
|
|
|
@@ -2763,7 +2787,19 @@ class AgentGUIClient {
|
|
|
2763
2787
|
} catch (error) {
|
|
2764
2788
|
if (error.name === 'AbortError') return;
|
|
2765
2789
|
console.error('Failed to load conversation messages:', error);
|
|
2766
|
-
|
|
2790
|
+
// Resume from last successful conversation if available
|
|
2791
|
+
if (prevConversationId && prevConversationId !== conversationId) {
|
|
2792
|
+
console.log('Resuming from previous conversation due to error:', prevConversationId);
|
|
2793
|
+
this.showError('Failed to load conversation. Resuming previous conversation.');
|
|
2794
|
+
try {
|
|
2795
|
+
await this.loadConversationMessages(prevConversationId);
|
|
2796
|
+
} catch (fallbackError) {
|
|
2797
|
+
console.error('Failed to resume previous conversation:', fallbackError);
|
|
2798
|
+
this.showError('Failed to load conversation: ' + error.message);
|
|
2799
|
+
}
|
|
2800
|
+
} else {
|
|
2801
|
+
this.showError('Failed to load conversation: ' + error.message);
|
|
2802
|
+
}
|
|
2767
2803
|
}
|
|
2768
2804
|
}
|
|
2769
2805
|
|
|
@@ -2779,20 +2815,22 @@ class AgentGUIClient {
|
|
|
2779
2815
|
}
|
|
2780
2816
|
|
|
2781
2817
|
updateBusyPromptArea(conversationId) {
|
|
2818
|
+
if (this.state.currentConversation?.id !== conversationId) return;
|
|
2782
2819
|
const isStreaming = this.state.streamingConversations.has(conversationId);
|
|
2783
2820
|
const isConnected = this.wsManager?.isConnected;
|
|
2784
2821
|
|
|
2785
2822
|
const injectBtn = document.getElementById('injectBtn');
|
|
2786
2823
|
const steerBtn = document.getElementById('steerBtn');
|
|
2824
|
+
const queueBtn = document.getElementById('queueBtn');
|
|
2787
2825
|
const stopBtn = document.getElementById('stopBtn');
|
|
2788
2826
|
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2827
|
+
[injectBtn, steerBtn, queueBtn, stopBtn].forEach(btn => {
|
|
2828
|
+
if (!btn) return;
|
|
2829
|
+
btn.classList.toggle('visible', isStreaming);
|
|
2830
|
+
btn.disabled = !isConnected;
|
|
2831
|
+
});
|
|
2792
2832
|
|
|
2793
|
-
if (
|
|
2794
|
-
if (steerBtn) steerBtn.disabled = !isConnected;
|
|
2795
|
-
if (stopBtn) stopBtn.disabled = !isConnected;
|
|
2833
|
+
if (this.ui.sendButton) this.ui.sendButton.style.display = isStreaming ? 'none' : '';
|
|
2796
2834
|
}
|
|
2797
2835
|
|
|
2798
2836
|
removeScrollUpDetection() {
|
|
@@ -3070,6 +3108,9 @@ class AgentGUIClient {
|
|
|
3070
3108
|
if (this.ui.steerButton && this.ui.steerButton.classList.contains('visible')) {
|
|
3071
3109
|
this.ui.steerButton.disabled = !this.wsManager.isConnected;
|
|
3072
3110
|
}
|
|
3111
|
+
if (this.ui.queueButton && this.ui.queueButton.classList.contains('visible')) {
|
|
3112
|
+
this.ui.queueButton.disabled = !this.wsManager.isConnected;
|
|
3113
|
+
}
|
|
3073
3114
|
}
|
|
3074
3115
|
|
|
3075
3116
|
/**
|
|
@@ -3103,6 +3144,10 @@ class AgentGUIClient {
|
|
|
3103
3144
|
this.ui.steerButton.classList.add('visible');
|
|
3104
3145
|
this.ui.steerButton.disabled = !this.wsManager.isConnected;
|
|
3105
3146
|
}
|
|
3147
|
+
if (this.ui.queueButton) {
|
|
3148
|
+
this.ui.queueButton.classList.add('visible');
|
|
3149
|
+
this.ui.queueButton.disabled = !this.wsManager.isConnected;
|
|
3150
|
+
}
|
|
3106
3151
|
}
|
|
3107
3152
|
|
|
3108
3153
|
/**
|
|
@@ -751,7 +751,6 @@ 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;
|
|
755
754
|
const summary = document.createElement('summary');
|
|
756
755
|
summary.className = 'folded-tool-bar';
|
|
757
756
|
const displayName = this.getToolUseDisplayName(toolName);
|
|
@@ -1230,7 +1229,8 @@ class StreamingRenderer {
|
|
|
1230
1229
|
const toolName = block.tool_name || block.name || '';
|
|
1231
1230
|
|
|
1232
1231
|
// Special handling for TodoWrite: render directly without success wrapper
|
|
1233
|
-
|
|
1232
|
+
// Detect by tool name OR by content structure (todos array)
|
|
1233
|
+
if ((toolName.includes('TodoWrite') || (typeof content === 'object' && Array.isArray(content?.todos))) && typeof content === 'object' && content.todos && Array.isArray(content.todos)) {
|
|
1234
1234
|
const statusIcons = { completed: '✅', in_progress: '⚙', pending: '☐' };
|
|
1235
1235
|
const completedCount = content.todos.filter(t => t.status === 'completed').length;
|
|
1236
1236
|
const totalCount = content.todos.length;
|
|
@@ -2200,8 +2200,8 @@ class StreamingRenderer {
|
|
|
2200
2200
|
summary.textContent = summaryText;
|
|
2201
2201
|
|
|
2202
2202
|
const details = document.createElement('details');
|
|
2203
|
-
|
|
2204
|
-
details.
|
|
2203
|
+
details.className = `block-${block.type}`;
|
|
2204
|
+
details.classList.add(this._getBlockTypeClass(block.type));
|
|
2205
2205
|
details.setAttribute('data-block-type', block.type);
|
|
2206
2206
|
details.setAttribute('data-lazy-load', 'pending');
|
|
2207
2207
|
details.open = block.type === 'success' || (block.type === 'tool_result' && !block.is_error);
|