agentgui 1.0.741 → 1.0.743
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/lib/ws-handlers-conv.js +18 -0
- package/package.json +1 -1
- package/server.js +50 -0
- package/static/js/client.js +61 -30
- package/static/js/streaming-renderer.js +11 -0
package/lib/ws-handlers-conv.js
CHANGED
|
@@ -117,6 +117,24 @@ export function register(router, deps) {
|
|
|
117
117
|
return { ok: true, chunks: result.chunks, total: result.total, hasMore: result.hasMore, limit: result.limit };
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
+
router.handle('conv.export', (p) => {
|
|
121
|
+
const conv = queries.getConversation(p.id);
|
|
122
|
+
if (!conv) notFound();
|
|
123
|
+
const msgs = queries.getConversationMessages(p.id);
|
|
124
|
+
const format = p.format || 'markdown';
|
|
125
|
+
if (format === 'json') return { conversation: conv, messages: msgs };
|
|
126
|
+
let md = `# ${conv.title || 'Conversation'}\n\n`;
|
|
127
|
+
md += `Agent: ${conv.agentType || 'unknown'} | Created: ${new Date(conv.created_at).toISOString()}\n\n---\n\n`;
|
|
128
|
+
for (const m of msgs) {
|
|
129
|
+
const role = m.role === 'user' ? 'User' : 'Assistant';
|
|
130
|
+
md += `## ${role}\n\n`;
|
|
131
|
+
let content = m.content;
|
|
132
|
+
try { const parsed = JSON.parse(content); if (Array.isArray(parsed)) { content = parsed.map(b => b.text || b.content || JSON.stringify(b)).join('\n'); } } catch {}
|
|
133
|
+
md += content + '\n\n---\n\n';
|
|
134
|
+
}
|
|
135
|
+
return { markdown: md, title: conv.title };
|
|
136
|
+
});
|
|
137
|
+
|
|
120
138
|
router.handle('conv.cancel', (p) => {
|
|
121
139
|
if (!execMachine.isActive(p.id)) notFound('No active execution to cancel');
|
|
122
140
|
const ctx = execMachine.getContext(p.id);
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -3684,6 +3684,41 @@ const _assetCache = new LRUCache({ max: 200 });
|
|
|
3684
3684
|
let _htmlCache = null;
|
|
3685
3685
|
let _htmlCacheEtag = null;
|
|
3686
3686
|
|
|
3687
|
+
function warmAssetCache() {
|
|
3688
|
+
const dirs = ['js', 'css', 'lib', 'vendor'];
|
|
3689
|
+
let count = 0;
|
|
3690
|
+
for (const dir of dirs) {
|
|
3691
|
+
const full = path.join(staticDir, dir);
|
|
3692
|
+
if (!fs.existsSync(full)) continue;
|
|
3693
|
+
for (const file of fs.readdirSync(full)) {
|
|
3694
|
+
const filePath = path.join(full, file);
|
|
3695
|
+
try {
|
|
3696
|
+
const stats = fs.statSync(filePath);
|
|
3697
|
+
if (!stats.isFile()) continue;
|
|
3698
|
+
const etag = generateETag(stats);
|
|
3699
|
+
if (_assetCache.has(etag)) continue;
|
|
3700
|
+
const raw = fs.readFileSync(filePath);
|
|
3701
|
+
const entry = raw.length < 860 ? { raw, gz: null } : { raw, gz: zlib.gzipSync(raw, { level: 6 }) };
|
|
3702
|
+
_assetCache.set(etag, entry);
|
|
3703
|
+
count++;
|
|
3704
|
+
} catch (_) {}
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
for (const file of ['app.js', 'theme.js']) {
|
|
3708
|
+
const filePath = path.join(staticDir, file);
|
|
3709
|
+
try {
|
|
3710
|
+
const stats = fs.statSync(filePath);
|
|
3711
|
+
const etag = generateETag(stats);
|
|
3712
|
+
if (!_assetCache.has(etag)) {
|
|
3713
|
+
const raw = fs.readFileSync(filePath);
|
|
3714
|
+
_assetCache.set(etag, raw.length < 860 ? { raw, gz: null } : { raw, gz: zlib.gzipSync(raw, { level: 6 }) });
|
|
3715
|
+
count++;
|
|
3716
|
+
}
|
|
3717
|
+
} catch (_) {}
|
|
3718
|
+
}
|
|
3719
|
+
if (count > 0) console.log(`[CACHE] Pre-warmed ${count} static assets`);
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3687
3722
|
function serveFile(filePath, res, req) {
|
|
3688
3723
|
const ext = path.extname(filePath).toLowerCase();
|
|
3689
3724
|
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
@@ -5109,6 +5144,7 @@ function onServerReady() {
|
|
|
5109
5144
|
}
|
|
5110
5145
|
|
|
5111
5146
|
recoverStaleSessions();
|
|
5147
|
+
warmAssetCache();
|
|
5112
5148
|
|
|
5113
5149
|
try {
|
|
5114
5150
|
jsonlWatcher = new JsonlWatcher({ broadcastSync, queries, ownedSessionIds });
|
|
@@ -5120,6 +5156,20 @@ function onServerReady() {
|
|
|
5120
5156
|
|
|
5121
5157
|
resumeInterruptedStreams().catch(err => console.error('[RESUME] Startup error:', err.message));
|
|
5122
5158
|
|
|
5159
|
+
setInterval(() => {
|
|
5160
|
+
try {
|
|
5161
|
+
const streaming = queries.getStreamingConversations();
|
|
5162
|
+
let cleared = 0;
|
|
5163
|
+
for (const c of streaming) {
|
|
5164
|
+
if (!activeExecutions.has(c.id)) {
|
|
5165
|
+
queries.setIsStreaming(c.id, false);
|
|
5166
|
+
cleared++;
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
if (cleared > 0) debugLog(`[HEALTH] Cleared ${cleared} stale streaming flag(s)`);
|
|
5170
|
+
} catch (e) { debugLog(`[HEALTH] Error: ${e.message}`); }
|
|
5171
|
+
}, 5 * 60 * 1000);
|
|
5172
|
+
|
|
5123
5173
|
installGMAgentConfigs().catch(err => console.error('[GM-CONFIG] Startup error:', err.message));
|
|
5124
5174
|
|
|
5125
5175
|
startACPTools().then(() => {
|
package/static/js/client.js
CHANGED
|
@@ -90,14 +90,18 @@ class AgentGUIClient {
|
|
|
90
90
|
currentConversationId: null,
|
|
91
91
|
currentSessionId: null
|
|
92
92
|
};
|
|
93
|
+
|
|
94
|
+
this._debug = typeof localStorage !== 'undefined' && localStorage.getItem('debug') === '1';
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
_dbg(...args) { if (this._debug) this._dbg(...args); }
|
|
98
|
+
|
|
95
99
|
/**
|
|
96
100
|
* Initialize the client
|
|
97
101
|
*/
|
|
98
102
|
async init() {
|
|
99
103
|
try {
|
|
100
|
-
|
|
104
|
+
this._dbg('Initializing AgentGUI client');
|
|
101
105
|
|
|
102
106
|
// Initialize renderer
|
|
103
107
|
this.renderer.init(this.config.outputContainerId, this.config.scrollContainerId);
|
|
@@ -105,7 +109,7 @@ class AgentGUIClient {
|
|
|
105
109
|
// Initialize image loader
|
|
106
110
|
if (typeof ImageLoader !== 'undefined') {
|
|
107
111
|
window.imageLoader = new ImageLoader();
|
|
108
|
-
|
|
112
|
+
this._dbg('Image loader initialized');
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
// Setup event listeners
|
|
@@ -137,7 +141,7 @@ class AgentGUIClient {
|
|
|
137
141
|
this.emit('initialized');
|
|
138
142
|
this._setupDebugHooks();
|
|
139
143
|
|
|
140
|
-
|
|
144
|
+
this._dbg('AgentGUI client initialized');
|
|
141
145
|
return this;
|
|
142
146
|
} catch (error) {
|
|
143
147
|
console.error('Client initialization error:', error);
|
|
@@ -151,7 +155,7 @@ class AgentGUIClient {
|
|
|
151
155
|
*/
|
|
152
156
|
setupWebSocketListeners() {
|
|
153
157
|
this.wsManager.on('connected', () => {
|
|
154
|
-
|
|
158
|
+
this._dbg('WebSocket connected');
|
|
155
159
|
this.updateConnectionStatus('connected');
|
|
156
160
|
this._subscribeToConversationUpdates();
|
|
157
161
|
// On reconnect (not initial connect), invalidate current conversation's DOM
|
|
@@ -170,7 +174,7 @@ class AgentGUIClient {
|
|
|
170
174
|
if (window.__SERVER_VERSION) {
|
|
171
175
|
fetch((window.__BASE_URL || '') + '/api/version').then(r => r.json()).then(d => {
|
|
172
176
|
if (d.version && d.version !== window.__SERVER_VERSION) {
|
|
173
|
-
|
|
177
|
+
this._dbg(`Server updated ${window.__SERVER_VERSION} → ${d.version}, reloading`);
|
|
174
178
|
window.location.reload();
|
|
175
179
|
}
|
|
176
180
|
}).catch(() => {});
|
|
@@ -178,7 +182,7 @@ class AgentGUIClient {
|
|
|
178
182
|
});
|
|
179
183
|
|
|
180
184
|
this.wsManager.on('disconnected', () => {
|
|
181
|
-
|
|
185
|
+
this._dbg('WebSocket disconnected');
|
|
182
186
|
this.updateConnectionStatus('disconnected');
|
|
183
187
|
this.updateSendButtonState();
|
|
184
188
|
this.disablePromptArea();
|
|
@@ -186,7 +190,7 @@ class AgentGUIClient {
|
|
|
186
190
|
});
|
|
187
191
|
|
|
188
192
|
this.wsManager.on('reconnecting', (data) => {
|
|
189
|
-
|
|
193
|
+
this._dbg('WebSocket reconnecting:', data);
|
|
190
194
|
this.updateConnectionStatus('reconnecting');
|
|
191
195
|
});
|
|
192
196
|
|
|
@@ -266,7 +270,7 @@ class AgentGUIClient {
|
|
|
266
270
|
*/
|
|
267
271
|
setupRendererListeners() {
|
|
268
272
|
this.renderer.on('batch:complete', (data) => {
|
|
269
|
-
|
|
273
|
+
this._dbg('Batch rendered:', data);
|
|
270
274
|
this.updateMetrics(data.metrics);
|
|
271
275
|
});
|
|
272
276
|
|
|
@@ -293,7 +297,7 @@ class AgentGUIClient {
|
|
|
293
297
|
if (sessionId && this.isValidId(sessionId)) {
|
|
294
298
|
this.routerState.currentSessionId = sessionId;
|
|
295
299
|
}
|
|
296
|
-
|
|
300
|
+
this._dbg('Restoring conversation from URL:', conversationId);
|
|
297
301
|
this._isLoadingConversation = true;
|
|
298
302
|
if (window.conversationManager) {
|
|
299
303
|
window.conversationManager.select(conversationId);
|
|
@@ -303,7 +307,7 @@ class AgentGUIClient {
|
|
|
303
307
|
// If the URL conversation doesn't exist, try loading the most recent conversation
|
|
304
308
|
if (this.state.conversations && this.state.conversations.length > 0) {
|
|
305
309
|
const latestConv = this.state.conversations[0];
|
|
306
|
-
|
|
310
|
+
this._dbg('Loading latest conversation instead:', latestConv.id);
|
|
307
311
|
return this.loadConversationMessages(latestConv.id);
|
|
308
312
|
} else {
|
|
309
313
|
// No conversations available - show welcome screen
|
|
@@ -510,7 +514,7 @@ class AgentGUIClient {
|
|
|
510
514
|
if (!this.state.currentConversation) return;
|
|
511
515
|
try {
|
|
512
516
|
const data = await window.wsClient.rpc('conv.cancel', { id: this.state.currentConversation.id });
|
|
513
|
-
|
|
517
|
+
this._dbg('Stop response:', data);
|
|
514
518
|
} catch (err) {
|
|
515
519
|
console.error('Failed to stop:', err);
|
|
516
520
|
}
|
|
@@ -561,7 +565,7 @@ class AgentGUIClient {
|
|
|
561
565
|
try {
|
|
562
566
|
// Queue uses msg.send which will enqueue if streaming is active
|
|
563
567
|
const data = await window.wsClient.rpc('msg.send', { id: this.state.currentConversation.id, content: message });
|
|
564
|
-
|
|
568
|
+
this._dbg('Queue response:', data);
|
|
565
569
|
if (this.ui.messageInput) {
|
|
566
570
|
this.ui.messageInput.value = '';
|
|
567
571
|
this.ui.messageInput.style.height = 'auto';
|
|
@@ -587,6 +591,27 @@ class AgentGUIClient {
|
|
|
587
591
|
});
|
|
588
592
|
}
|
|
589
593
|
|
|
594
|
+
document.addEventListener('keydown', (e) => {
|
|
595
|
+
if (e.key === 'n' && (e.ctrlKey || e.metaKey) && !e.shiftKey) {
|
|
596
|
+
e.preventDefault();
|
|
597
|
+
const newBtn = document.querySelector('[data-new-conversation], #newConversationBtn, .new-conversation-btn');
|
|
598
|
+
if (newBtn) newBtn.click();
|
|
599
|
+
}
|
|
600
|
+
if (e.key === 'b' && (e.ctrlKey || e.metaKey) && !e.shiftKey) {
|
|
601
|
+
e.preventDefault();
|
|
602
|
+
const toggleBtn = document.querySelector('[data-sidebar-toggle]');
|
|
603
|
+
if (toggleBtn) toggleBtn.click();
|
|
604
|
+
}
|
|
605
|
+
if (e.key === 'Escape') {
|
|
606
|
+
const activeEl = document.activeElement;
|
|
607
|
+
if (activeEl && activeEl.tagName === 'TEXTAREA') { activeEl.blur(); return; }
|
|
608
|
+
if (this.state.isStreaming) {
|
|
609
|
+
const cancelBtn = document.querySelector('#cancelBtn, [data-cancel-btn]');
|
|
610
|
+
if (cancelBtn && cancelBtn.offsetParent !== null) cancelBtn.click();
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
590
615
|
// Setup theme toggle
|
|
591
616
|
const themeToggle = document.querySelector('[data-theme-toggle]');
|
|
592
617
|
if (themeToggle) {
|
|
@@ -788,7 +813,7 @@ class AgentGUIClient {
|
|
|
788
813
|
}
|
|
789
814
|
|
|
790
815
|
async handleStreamingStart(data) {
|
|
791
|
-
|
|
816
|
+
this._dbg('Streaming started:', data);
|
|
792
817
|
if (window.promptMachineAPI) window.promptMachineAPI.send({ type: 'STREAMING', conversationId: data.conversationId });
|
|
793
818
|
this._clearThinkingCountdown();
|
|
794
819
|
if (this._lastSendTime > 0) {
|
|
@@ -812,9 +837,9 @@ class AgentGUIClient {
|
|
|
812
837
|
// If this streaming event is for a different conversation than what we are viewing,
|
|
813
838
|
// just track the state but do not modify the DOM or start polling
|
|
814
839
|
if (this.state.currentConversation?.id !== data.conversationId) {
|
|
815
|
-
|
|
840
|
+
this._dbg('Streaming started for non-active conversation:', data.conversationId);
|
|
816
841
|
this._setConvStreaming(data.conversationId, true, data.sessionId, data.agentId);
|
|
817
|
-
|
|
842
|
+
this._dbg('[SYNC] streaming_start - non-active conv:', { convId: data.conversationId, sessionId: data.sessionId, streamingCount: this.state.streamingConversations.size });
|
|
818
843
|
this.updateBusyPromptArea(data.conversationId);
|
|
819
844
|
this.emit('streaming:start', data);
|
|
820
845
|
|
|
@@ -900,7 +925,7 @@ class AgentGUIClient {
|
|
|
900
925
|
}
|
|
901
926
|
|
|
902
927
|
async handleStreamingResumed(data) {
|
|
903
|
-
|
|
928
|
+
this._dbg('Streaming resumed:', data);
|
|
904
929
|
const conv = this.state.currentConversation || { id: data.conversationId };
|
|
905
930
|
await this.handleStreamingStart({
|
|
906
931
|
type: 'streaming_start',
|
|
@@ -913,6 +938,9 @@ class AgentGUIClient {
|
|
|
913
938
|
}
|
|
914
939
|
|
|
915
940
|
handleStreamingProgress(data) {
|
|
941
|
+
try { return this._handleStreamingProgressInner(data); } catch (e) { console.error('[render-error] streaming progress:', e); }
|
|
942
|
+
}
|
|
943
|
+
_handleStreamingProgressInner(data) {
|
|
916
944
|
if (!data.block || !data.sessionId) return;
|
|
917
945
|
|
|
918
946
|
// Deduplicate by seq number to guarantee exactly-once rendering
|
|
@@ -1098,7 +1126,7 @@ class AgentGUIClient {
|
|
|
1098
1126
|
|
|
1099
1127
|
// If this event is for a conversation we are NOT currently viewing, just track state
|
|
1100
1128
|
if (conversationId && this.state.currentConversation?.id !== conversationId) {
|
|
1101
|
-
|
|
1129
|
+
this._dbg('Streaming error for non-active conversation:', conversationId);
|
|
1102
1130
|
this._setConvStreaming(conversationId, false);
|
|
1103
1131
|
this.updateBusyPromptArea(conversationId);
|
|
1104
1132
|
this.emit('streaming:error', data);
|
|
@@ -1163,7 +1191,7 @@ class AgentGUIClient {
|
|
|
1163
1191
|
}
|
|
1164
1192
|
|
|
1165
1193
|
handleStreamingComplete(data) {
|
|
1166
|
-
|
|
1194
|
+
this._dbg('Streaming completed:', data);
|
|
1167
1195
|
if (window.promptMachineAPI) window.promptMachineAPI.send({ type: 'READY' });
|
|
1168
1196
|
this._clearThinkingCountdown();
|
|
1169
1197
|
|
|
@@ -1171,16 +1199,16 @@ class AgentGUIClient {
|
|
|
1171
1199
|
if (conversationId) this.invalidateCache(conversationId);
|
|
1172
1200
|
|
|
1173
1201
|
if (conversationId && this.state.currentConversation?.id !== conversationId) {
|
|
1174
|
-
|
|
1202
|
+
this._dbg('Streaming completed for non-active conversation:', conversationId);
|
|
1175
1203
|
this._setConvStreaming(conversationId, false);
|
|
1176
|
-
|
|
1204
|
+
this._dbg('[SYNC] streaming_complete - non-active conv:', { convId: conversationId, streamingCount: this.state.streamingConversations.size });
|
|
1177
1205
|
this.updateBusyPromptArea(conversationId);
|
|
1178
1206
|
this.emit('streaming:complete', data);
|
|
1179
1207
|
return;
|
|
1180
1208
|
}
|
|
1181
1209
|
|
|
1182
1210
|
this._setConvStreaming(conversationId, false);
|
|
1183
|
-
|
|
1211
|
+
this._dbg('[SYNC] streaming_complete - active conv:', { convId: conversationId, streamingCount: this.state.streamingConversations.size, interrupted: data.interrupted });
|
|
1184
1212
|
this.updateBusyPromptArea(conversationId);
|
|
1185
1213
|
|
|
1186
1214
|
const sessionId = data.sessionId || this.state.currentSession?.id;
|
|
@@ -1268,7 +1296,7 @@ class AgentGUIClient {
|
|
|
1268
1296
|
return;
|
|
1269
1297
|
}
|
|
1270
1298
|
|
|
1271
|
-
|
|
1299
|
+
this._dbg('[SYNC] message_created:', { msgId: data.message.id, role: data.message.role, convId: data.conversationId });
|
|
1272
1300
|
|
|
1273
1301
|
// Update messageCount in current conversation state for user messages
|
|
1274
1302
|
if (data.message.role === 'user' && this.state.currentConversation) {
|
|
@@ -2082,7 +2110,7 @@ class AgentGUIClient {
|
|
|
2082
2110
|
}
|
|
2083
2111
|
|
|
2084
2112
|
if (result.queued) {
|
|
2085
|
-
|
|
2113
|
+
this._dbg('Message queued, position:', result.queuePosition);
|
|
2086
2114
|
this.enableControls();
|
|
2087
2115
|
return;
|
|
2088
2116
|
}
|
|
@@ -2182,6 +2210,9 @@ class AgentGUIClient {
|
|
|
2182
2210
|
}
|
|
2183
2211
|
|
|
2184
2212
|
renderChunk(chunk) {
|
|
2213
|
+
try { return this._renderChunkInner(chunk); } catch (e) { console.error('[render-error] chunk:', e); }
|
|
2214
|
+
}
|
|
2215
|
+
_renderChunkInner(chunk) {
|
|
2185
2216
|
if (!chunk || !chunk.block) return;
|
|
2186
2217
|
const seq = chunk.sequence;
|
|
2187
2218
|
if (seq !== undefined) {
|
|
@@ -2260,13 +2291,13 @@ class AgentGUIClient {
|
|
|
2260
2291
|
.map(a => `<option value="${a.id}">${a.name.split(/[\s\-]+/)[0]}</option>`)
|
|
2261
2292
|
.join('');
|
|
2262
2293
|
this.ui.agentSelector.style.display = 'inline-block';
|
|
2263
|
-
|
|
2294
|
+
this._dbg(`[Agent Selector] Loaded ${subAgents.length} sub-agents for ${cliAgentId}`);
|
|
2264
2295
|
// Auto-select first sub-agent and load its models
|
|
2265
2296
|
const firstSubAgentId = subAgents[0].id;
|
|
2266
2297
|
this.ui.agentSelector.value = firstSubAgentId;
|
|
2267
2298
|
this.loadModelsForAgent(cliAgentId); // models keyed to parent agent
|
|
2268
2299
|
} else {
|
|
2269
|
-
|
|
2300
|
+
this._dbg(`[Agent Selector] No sub-agents found for ${cliAgentId}`);
|
|
2270
2301
|
// Load models for the CLI agent itself (fallback for agents without sub-agents)
|
|
2271
2302
|
const cliToAcpMap = {
|
|
2272
2303
|
'cli-opencode': 'opencode',
|
|
@@ -2508,7 +2539,7 @@ class AgentGUIClient {
|
|
|
2508
2539
|
}
|
|
2509
2540
|
if (progress.done || progress.status === 'completed') {
|
|
2510
2541
|
this._modelDownloadInProgress = false;
|
|
2511
|
-
|
|
2542
|
+
this._dbg('[Models] Download complete');
|
|
2512
2543
|
this._updateConnectionIndicator(this.wsManager?.latency?.quality || 'unknown');
|
|
2513
2544
|
return;
|
|
2514
2545
|
}
|
|
@@ -2520,7 +2551,7 @@ class AgentGUIClient {
|
|
|
2520
2551
|
|
|
2521
2552
|
_handleTTSSetupProgress(data) {
|
|
2522
2553
|
if (data.step && data.status) {
|
|
2523
|
-
|
|
2554
|
+
this._dbg('[TTS Setup]', data.step, ':', data.status, data.message || '');
|
|
2524
2555
|
}
|
|
2525
2556
|
}
|
|
2526
2557
|
|
|
@@ -2777,7 +2808,7 @@ class AgentGUIClient {
|
|
|
2777
2808
|
if (window.conversationManager) window.conversationManager.loadConversations();
|
|
2778
2809
|
const fallbackConv = prevConversationId ? prevConversationId : availableFallback?.id;
|
|
2779
2810
|
if (fallbackConv && fallbackConv !== conversationId) {
|
|
2780
|
-
|
|
2811
|
+
this._dbg('Resuming from fallback conversation:', fallbackConv);
|
|
2781
2812
|
this.showError('Conversation not found. Resuming previous conversation.');
|
|
2782
2813
|
await this.loadConversationMessages(fallbackConv);
|
|
2783
2814
|
} else {
|
|
@@ -2958,7 +2989,7 @@ class AgentGUIClient {
|
|
|
2958
2989
|
// Resume from last successful conversation if available, or fall back to any available conversation
|
|
2959
2990
|
const fallbackConv = prevConversationId ? prevConversationId : availableFallback?.id;
|
|
2960
2991
|
if (fallbackConv && fallbackConv !== conversationId) {
|
|
2961
|
-
|
|
2992
|
+
this._dbg('Resuming from fallback conversation due to error:', fallbackConv);
|
|
2962
2993
|
this.showError('Failed to load conversation. Resuming previous conversation.');
|
|
2963
2994
|
try {
|
|
2964
2995
|
await this.loadConversationMessages(fallbackConv);
|
|
@@ -3354,7 +3385,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
3354
3385
|
agentGUIClient = new AgentGUIClient();
|
|
3355
3386
|
window.agentGuiClient = agentGUIClient;
|
|
3356
3387
|
await agentGUIClient.init();
|
|
3357
|
-
|
|
3388
|
+
this._dbg('AgentGUI ready');
|
|
3358
3389
|
} catch (error) {
|
|
3359
3390
|
console.error('Failed to initialize AgentGUI:', error);
|
|
3360
3391
|
}
|
|
@@ -781,6 +781,7 @@ class StreamingRenderer {
|
|
|
781
781
|
const details = document.createElement('details');
|
|
782
782
|
details.className = 'block-tool-use folded-tool';
|
|
783
783
|
if (block.id) details.dataset.toolUseId = block.id;
|
|
784
|
+
details.dataset.startedAt = Date.now();
|
|
784
785
|
details.classList.add(this._getBlockTypeClass('tool_use'));
|
|
785
786
|
details.classList.add(this._getToolColorClass(toolName));
|
|
786
787
|
const normalizedForOpen = toolName.replace(/^mcp__.*?__/, '');
|
|
@@ -2228,6 +2229,16 @@ class StreamingRenderer {
|
|
|
2228
2229
|
statusSpan.className = 'folded-tool-status';
|
|
2229
2230
|
statusSpan.innerHTML = statusSvg;
|
|
2230
2231
|
summary.appendChild(statusSpan);
|
|
2232
|
+
const startedAt = parseInt(toolUseEl.dataset.startedAt);
|
|
2233
|
+
if (startedAt > 0) {
|
|
2234
|
+
const ms = Date.now() - startedAt;
|
|
2235
|
+
const label = ms < 1000 ? ms + 'ms' : (ms / 1000).toFixed(1) + 's';
|
|
2236
|
+
const dur = document.createElement('span');
|
|
2237
|
+
dur.className = 'folded-tool-duration';
|
|
2238
|
+
dur.style.cssText = 'margin-left:0.375rem;font-size:0.6rem;opacity:0.45;font-weight:400';
|
|
2239
|
+
dur.textContent = label;
|
|
2240
|
+
summary.appendChild(dur);
|
|
2241
|
+
}
|
|
2231
2242
|
}
|
|
2232
2243
|
|
|
2233
2244
|
const renderedContent = StreamingRenderer.renderSmartContentHTML(contentStr, this.escapeHtml.bind(this), true);
|