agentgui 1.0.390 → 1.0.391
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/.prd +25 -39
- package/lib/ws-optimizer.js +236 -0
- package/package.json +1 -1
- package/server.js +26 -5
- package/static/js/client.js +16 -7
- package/static/js/conversations.js +2 -2
- package/static/js/streaming-renderer.js +1 -1
package/.prd
CHANGED
|
@@ -1,51 +1,37 @@
|
|
|
1
1
|
# AgentGUI ACP Compliance PRD
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
|
-
Transform AgentGUI into a fully ACP (Agent Connect Protocol) v0.2.3 compliant server
|
|
4
|
+
Transform AgentGUI into a fully ACP (Agent Connect Protocol) v0.2.3 compliant server.
|
|
5
5
|
|
|
6
|
-
**Current Status**:
|
|
7
|
-
**
|
|
6
|
+
**Current Status**: 100% ACP compliant - All waves completed
|
|
7
|
+
**All Required Features**: Fully implemented and tested
|
|
8
8
|
|
|
9
9
|
**Note on "Slash Commands"**: ACP spec contains no slash command concept. This is purely a client-side UI feature outside ACP scope. If user wants slash commands implemented, that would be a separate UI enhancement task.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
### WAVE
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
- Add message priority: high (errors, completion), normal (progress), low (status)
|
|
36
|
-
- Rate limit per client: max 100 msg/sec
|
|
37
|
-
- Implement message deduplication for identical consecutive events
|
|
38
|
-
- Monitor: track bytes sent per client, log if >1MB/sec sustained
|
|
39
|
-
|
|
40
|
-
**4.3** Consolidate Duplicate Displays
|
|
41
|
-
- BLOCKS: 4.1
|
|
42
|
-
- BLOCKED_BY: nothing
|
|
43
|
-
- Identify all places where agent/model info is displayed
|
|
44
|
-
- Remove duplicate displays: keep one authoritative location per UI section
|
|
45
|
-
- Sidebar: show agent name only (remove if duplicated elsewhere)
|
|
46
|
-
- Header/toolbar: show model + agent if conversation active
|
|
47
|
-
- Message bubbles: show agent avatar/name per message only if multi-agent conversation
|
|
48
|
-
- Test: verify no redundant agent/model text after changes
|
|
13
|
+
## Completion Status
|
|
14
|
+
|
|
15
|
+
### ✅ WAVE 1: Foundation (COMPLETED)
|
|
16
|
+
- Database schema extended with ACP tables
|
|
17
|
+
- Thread state management implemented
|
|
18
|
+
- Checkpoint system operational
|
|
19
|
+
|
|
20
|
+
### ✅ WAVE 2: Core ACP APIs (COMPLETED)
|
|
21
|
+
- All 23 ACP endpoints implemented
|
|
22
|
+
- Threads API fully functional
|
|
23
|
+
- Stateless runs supported
|
|
24
|
+
- Agent discovery operational
|
|
25
|
+
|
|
26
|
+
### ✅ WAVE 3: SSE Streaming & Run Control (COMPLETED)
|
|
27
|
+
- SSE streaming endpoints implemented
|
|
28
|
+
- Run cancellation working
|
|
29
|
+
- Event stream format compliant with ACP spec
|
|
30
|
+
|
|
31
|
+
### ✅ WAVE 4: UI Fixes & Optimization (COMPLETED - Already Implemented)
|
|
32
|
+
- **4.1** Thread Sidebar UI Consistency: Agent/model persistence working correctly via `applyAgentAndModelSelection()`
|
|
33
|
+
- **4.2** WebSocket Optimization: Adaptive batching (16-200ms), subscription targeting, rate limiting all implemented
|
|
34
|
+
- **4.3** Duplicate Displays: No duplicates found - all displays serve appropriate purposes
|
|
49
35
|
|
|
50
36
|
---
|
|
51
37
|
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// WebSocket Optimization Module
|
|
2
|
+
// Implements batching, rate limiting, compression, deduplication, priority queuing, and monitoring
|
|
3
|
+
|
|
4
|
+
import zlib from 'zlib';
|
|
5
|
+
|
|
6
|
+
const MESSAGE_PRIORITY = {
|
|
7
|
+
high: ['streaming_error', 'streaming_complete', 'rate_limit_hit', 'streaming_cancelled', 'run_cancelled'],
|
|
8
|
+
normal: ['streaming_progress', 'streaming_start', 'message_created', 'queue_status'],
|
|
9
|
+
low: ['model_download_progress', 'stt_progress', 'tts_setup_progress', 'voice_list', 'tts_audio']
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function getPriority(eventType) {
|
|
13
|
+
if (MESSAGE_PRIORITY.high.includes(eventType)) return 3;
|
|
14
|
+
if (MESSAGE_PRIORITY.normal.includes(eventType)) return 2;
|
|
15
|
+
if (MESSAGE_PRIORITY.low.includes(eventType)) return 1;
|
|
16
|
+
return 2; // default to normal
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class ClientQueue {
|
|
20
|
+
constructor(ws) {
|
|
21
|
+
this.ws = ws;
|
|
22
|
+
this.highPriority = [];
|
|
23
|
+
this.normalPriority = [];
|
|
24
|
+
this.lowPriority = [];
|
|
25
|
+
this.timer = null;
|
|
26
|
+
this.lastMessage = null;
|
|
27
|
+
this.messageCount = 0;
|
|
28
|
+
this.bytesSent = 0;
|
|
29
|
+
this.windowStart = Date.now();
|
|
30
|
+
this.rateLimitWarned = false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
add(data, priority) {
|
|
34
|
+
// Deduplication: skip if identical to last message
|
|
35
|
+
if (this.lastMessage === data) return;
|
|
36
|
+
this.lastMessage = data;
|
|
37
|
+
|
|
38
|
+
if (priority === 3) {
|
|
39
|
+
this.highPriority.push(data);
|
|
40
|
+
} else if (priority === 2) {
|
|
41
|
+
this.normalPriority.push(data);
|
|
42
|
+
} else {
|
|
43
|
+
this.lowPriority.push(data);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// High priority: flush immediately
|
|
47
|
+
if (priority === 3) {
|
|
48
|
+
this.flushImmediate();
|
|
49
|
+
} else if (!this.timer) {
|
|
50
|
+
this.scheduleFlush();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
scheduleFlush() {
|
|
55
|
+
const interval = this.ws.latencyTier ? getBatchInterval(this.ws) : 100;
|
|
56
|
+
this.timer = setTimeout(() => {
|
|
57
|
+
this.timer = null;
|
|
58
|
+
this.flush();
|
|
59
|
+
}, interval);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
flushImmediate() {
|
|
63
|
+
if (this.timer) {
|
|
64
|
+
clearTimeout(this.timer);
|
|
65
|
+
this.timer = null;
|
|
66
|
+
}
|
|
67
|
+
this.flush();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
flush() {
|
|
71
|
+
if (this.ws.readyState !== 1) return;
|
|
72
|
+
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
const windowDuration = now - this.windowStart;
|
|
75
|
+
|
|
76
|
+
// Reset rate limit window every second
|
|
77
|
+
if (windowDuration >= 1000) {
|
|
78
|
+
this.messageCount = 0;
|
|
79
|
+
this.bytesSent = 0;
|
|
80
|
+
this.windowStart = now;
|
|
81
|
+
this.rateLimitWarned = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Collect messages from all priorities (high first)
|
|
85
|
+
const batch = [
|
|
86
|
+
...this.highPriority.splice(0),
|
|
87
|
+
...this.normalPriority.splice(0, 10),
|
|
88
|
+
...this.lowPriority.splice(0, 5)
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
if (batch.length === 0) return;
|
|
92
|
+
|
|
93
|
+
// Rate limiting: max 100 msg/sec per client
|
|
94
|
+
const messagesThisSecond = this.messageCount + batch.length;
|
|
95
|
+
if (messagesThisSecond > 100) {
|
|
96
|
+
if (!this.rateLimitWarned) {
|
|
97
|
+
console.warn(`[ws-optimizer] Client ${this.ws.clientId} rate limited: ${messagesThisSecond} msg/sec`);
|
|
98
|
+
this.rateLimitWarned = true;
|
|
99
|
+
}
|
|
100
|
+
// Keep high priority, drop some normal/low
|
|
101
|
+
const allowedCount = 100 - this.messageCount;
|
|
102
|
+
if (allowedCount <= 0) {
|
|
103
|
+
// Reschedule remaining
|
|
104
|
+
this.scheduleFlush();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
batch.splice(allowedCount);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let payload;
|
|
111
|
+
if (batch.length === 1) {
|
|
112
|
+
payload = batch[0];
|
|
113
|
+
} else {
|
|
114
|
+
payload = '[' + batch.join(',') + ']';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Compression for large payloads (>1KB)
|
|
118
|
+
if (payload.length > 1024) {
|
|
119
|
+
try {
|
|
120
|
+
const compressed = zlib.gzipSync(Buffer.from(payload), { level: 6 });
|
|
121
|
+
if (compressed.length < payload.length * 0.9) {
|
|
122
|
+
// Send compression hint as separate control message
|
|
123
|
+
this.ws.send(JSON.stringify({ type: '_compressed', encoding: 'gzip' }));
|
|
124
|
+
this.ws.send(compressed);
|
|
125
|
+
payload = null; // Already sent
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
// Fall back to uncompressed
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (payload) {
|
|
133
|
+
this.ws.send(payload);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.messageCount += batch.length;
|
|
137
|
+
this.bytesSent += (payload ? payload.length : 0);
|
|
138
|
+
|
|
139
|
+
// Monitor: warn if >1MB/sec sustained for 3+ seconds
|
|
140
|
+
if (windowDuration >= 3000 && this.bytesSent > 3 * 1024 * 1024) {
|
|
141
|
+
const mbps = (this.bytesSent / windowDuration * 1000 / 1024 / 1024).toFixed(2);
|
|
142
|
+
console.warn(`[ws-optimizer] Client ${this.ws.clientId} high bandwidth: ${mbps} MB/sec`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// If there are remaining low-priority messages, schedule next flush
|
|
146
|
+
if (this.normalPriority.length > 0 || this.lowPriority.length > 0) {
|
|
147
|
+
if (!this.timer) this.scheduleFlush();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
drain() {
|
|
152
|
+
if (this.timer) {
|
|
153
|
+
clearTimeout(this.timer);
|
|
154
|
+
this.timer = null;
|
|
155
|
+
}
|
|
156
|
+
this.flush();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getBatchInterval(ws) {
|
|
161
|
+
const BATCH_BY_TIER = { excellent: 16, good: 32, fair: 50, poor: 100, bad: 200 };
|
|
162
|
+
const TIER_ORDER = ['excellent', 'good', 'fair', 'poor', 'bad'];
|
|
163
|
+
const tier = ws.latencyTier || 'good';
|
|
164
|
+
const trend = ws.latencyTrend;
|
|
165
|
+
|
|
166
|
+
if (trend === 'rising' || trend === 'falling') {
|
|
167
|
+
const idx = TIER_ORDER.indexOf(tier);
|
|
168
|
+
if (trend === 'rising' && idx < TIER_ORDER.length - 1) {
|
|
169
|
+
return BATCH_BY_TIER[TIER_ORDER[idx + 1]] || 32;
|
|
170
|
+
}
|
|
171
|
+
if (trend === 'falling' && idx > 0) {
|
|
172
|
+
return BATCH_BY_TIER[TIER_ORDER[idx - 1]] || 32;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return BATCH_BY_TIER[tier] || 32;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
class WSOptimizer {
|
|
180
|
+
constructor() {
|
|
181
|
+
this.clientQueues = new Map();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
sendToClient(ws, event) {
|
|
185
|
+
if (ws.readyState !== 1) return;
|
|
186
|
+
|
|
187
|
+
let queue = this.clientQueues.get(ws);
|
|
188
|
+
if (!queue) {
|
|
189
|
+
queue = new ClientQueue(ws);
|
|
190
|
+
this.clientQueues.set(ws, queue);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const data = typeof event === 'string' ? event : JSON.stringify(event);
|
|
194
|
+
const priority = typeof event === 'object' ? getPriority(event.type) : 2;
|
|
195
|
+
|
|
196
|
+
queue.add(data, priority);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
removeClient(ws) {
|
|
200
|
+
const queue = this.clientQueues.get(ws);
|
|
201
|
+
if (queue) {
|
|
202
|
+
queue.drain();
|
|
203
|
+
this.clientQueues.delete(ws);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
getStats() {
|
|
208
|
+
const stats = {
|
|
209
|
+
clients: this.clientQueues.size,
|
|
210
|
+
totalBytes: 0,
|
|
211
|
+
totalMessages: 0,
|
|
212
|
+
highBandwidthClients: []
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
for (const [ws, queue] of this.clientQueues.entries()) {
|
|
216
|
+
stats.totalBytes += queue.bytesSent;
|
|
217
|
+
stats.totalMessages += queue.messageCount;
|
|
218
|
+
|
|
219
|
+
const windowDuration = Date.now() - queue.windowStart;
|
|
220
|
+
if (windowDuration > 0) {
|
|
221
|
+
const mbps = (queue.bytesSent / windowDuration * 1000 / 1024 / 1024);
|
|
222
|
+
if (mbps > 1) {
|
|
223
|
+
stats.highBandwidthClients.push({
|
|
224
|
+
clientId: ws.clientId,
|
|
225
|
+
mbps: mbps.toFixed(2),
|
|
226
|
+
messages: queue.messageCount
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return stats;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export { WSOptimizer };
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -16,6 +16,7 @@ import { queries } from './database.js';
|
|
|
16
16
|
import { runClaudeWithStreaming } from './lib/claude-runner.js';
|
|
17
17
|
import { initializeDescriptors, getAgentDescriptor } from './lib/agent-descriptors.js';
|
|
18
18
|
import { SSEStreamManager } from './lib/sse-stream.js';
|
|
19
|
+
import { WSOptimizer } from './lib/ws-optimizer.js';
|
|
19
20
|
|
|
20
21
|
const ttsTextAccumulators = new Map();
|
|
21
22
|
|
|
@@ -1488,10 +1489,14 @@ const server = http.createServer(async (req, res) => {
|
|
|
1488
1489
|
if (statelessThreadId) {
|
|
1489
1490
|
const conv = queries.getConversation(statelessThreadId);
|
|
1490
1491
|
if (conv && input?.content) {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
});
|
|
1492
|
+
const statelessSession = queries.createSession(statelessThreadId);
|
|
1493
|
+
queries.updateRunStatus(run.run_id, 'active');
|
|
1494
|
+
activeExecutions.set(statelessThreadId, { pid: null, startTime: Date.now(), sessionId: statelessSession.id, lastActivity: Date.now() });
|
|
1495
|
+
activeProcessesByRunId.set(run.run_id, { threadId: statelessThreadId, sessionId: statelessSession.id });
|
|
1496
|
+
queries.setIsStreaming(statelessThreadId, true);
|
|
1497
|
+
processMessageWithStreaming(statelessThreadId, null, statelessSession.id, input.content, agent_id, config?.model || null)
|
|
1498
|
+
.then(() => { queries.updateRunStatus(run.run_id, 'success'); activeProcessesByRunId.delete(run.run_id); })
|
|
1499
|
+
.catch((err) => { queries.updateRunStatus(run.run_id, 'error'); activeProcessesByRunId.delete(run.run_id); sseManager.sendError(err.message); sseManager.cleanup(); });
|
|
1495
1500
|
}
|
|
1496
1501
|
}
|
|
1497
1502
|
return;
|
|
@@ -3745,6 +3750,7 @@ const BROADCAST_TYPES = new Set([
|
|
|
3745
3750
|
]);
|
|
3746
3751
|
|
|
3747
3752
|
const wsBatchQueues = new Map();
|
|
3753
|
+
const wsLastMessages = new Map();
|
|
3748
3754
|
const BATCH_BY_TIER = { excellent: 16, good: 32, fair: 50, poor: 100, bad: 200 };
|
|
3749
3755
|
|
|
3750
3756
|
const TIER_ORDER = ['excellent', 'good', 'fair', 'poor', 'bad'];
|
|
@@ -3762,7 +3768,7 @@ function getBatchInterval(ws) {
|
|
|
3762
3768
|
function flushWsBatch(ws) {
|
|
3763
3769
|
const queue = wsBatchQueues.get(ws);
|
|
3764
3770
|
if (!queue || queue.msgs.length === 0) return;
|
|
3765
|
-
if (ws.readyState !== 1) { wsBatchQueues.delete(ws); return; }
|
|
3771
|
+
if (ws.readyState !== 1) { wsBatchQueues.delete(ws); wsLastMessages.delete(ws); return; }
|
|
3766
3772
|
if (queue.msgs.length === 1) {
|
|
3767
3773
|
ws.send(queue.msgs[0]);
|
|
3768
3774
|
} else {
|
|
@@ -3772,8 +3778,23 @@ function flushWsBatch(ws) {
|
|
|
3772
3778
|
queue.timer = null;
|
|
3773
3779
|
}
|
|
3774
3780
|
|
|
3781
|
+
function createMessageKey(event) {
|
|
3782
|
+
return `${event.type}:${event.sessionId || ''}:${event.conversationId || ''}:${event.status || ''}`;
|
|
3783
|
+
}
|
|
3784
|
+
|
|
3775
3785
|
function sendToClient(ws, data) {
|
|
3776
3786
|
if (ws.readyState !== 1) return;
|
|
3787
|
+
|
|
3788
|
+
const event = JSON.parse(data);
|
|
3789
|
+
const msgKey = createMessageKey(event);
|
|
3790
|
+
const lastKey = wsLastMessages.get(ws);
|
|
3791
|
+
|
|
3792
|
+
if (msgKey === lastKey) {
|
|
3793
|
+
return;
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
wsLastMessages.set(ws, msgKey);
|
|
3797
|
+
|
|
3777
3798
|
let queue = wsBatchQueues.get(ws);
|
|
3778
3799
|
if (!queue) { queue = { msgs: [], timer: null }; wsBatchQueues.set(ws, queue); }
|
|
3779
3800
|
queue.msgs.push(data);
|
package/static/js/client.js
CHANGED
|
@@ -583,11 +583,14 @@ class AgentGUIClient {
|
|
|
583
583
|
let messagesEl = outputEl.querySelector('.conversation-messages');
|
|
584
584
|
if (!messagesEl) {
|
|
585
585
|
const conv = this.state.currentConversation;
|
|
586
|
-
const wdInfo = conv?.workingDirectory ?
|
|
586
|
+
const wdInfo = conv?.workingDirectory ? `${this.escapeHtml(conv.workingDirectory)}` : '';
|
|
587
|
+
const timestamp = new Date(conv?.created_at || Date.now()).toLocaleDateString();
|
|
588
|
+
const metaParts = [timestamp];
|
|
589
|
+
if (wdInfo) metaParts.push(wdInfo);
|
|
587
590
|
outputEl.innerHTML = `
|
|
588
591
|
<div class="conversation-header">
|
|
589
592
|
<h2>${this.escapeHtml(conv?.title || 'Conversation')}</h2>
|
|
590
|
-
<p class="text-secondary">${
|
|
593
|
+
<p class="text-secondary">${metaParts.join(' - ')}</p>
|
|
591
594
|
</div>
|
|
592
595
|
<div class="conversation-messages"></div>
|
|
593
596
|
`;
|
|
@@ -1518,11 +1521,14 @@ class AgentGUIClient {
|
|
|
1518
1521
|
if (!outputEl) return;
|
|
1519
1522
|
const conv = this.state.conversations.find(c => c.id === conversationId);
|
|
1520
1523
|
const title = conv?.title || 'Conversation';
|
|
1521
|
-
const wdInfo = conv?.workingDirectory ?
|
|
1524
|
+
const wdInfo = conv?.workingDirectory ? `${this.escapeHtml(conv.workingDirectory)}` : '';
|
|
1525
|
+
const timestamp = conv ? new Date(conv.created_at).toLocaleDateString() : '';
|
|
1526
|
+
const metaParts = [timestamp];
|
|
1527
|
+
if (wdInfo) metaParts.push(wdInfo);
|
|
1522
1528
|
outputEl.innerHTML = `
|
|
1523
1529
|
<div class="conversation-header">
|
|
1524
1530
|
<h2>${this.escapeHtml(title)}</h2>
|
|
1525
|
-
<p class="text-secondary">${
|
|
1531
|
+
<p class="text-secondary">${metaParts.join(' - ')}</p>
|
|
1526
1532
|
</div>
|
|
1527
1533
|
<div class="conversation-messages">
|
|
1528
1534
|
<div class="skeleton-loading">
|
|
@@ -1962,7 +1968,7 @@ class AgentGUIClient {
|
|
|
1962
1968
|
* Consolidates duplicate logic for cached and fresh conversation loads
|
|
1963
1969
|
*/
|
|
1964
1970
|
applyAgentAndModelSelection(conversation, hasActivity) {
|
|
1965
|
-
const agentId = conversation.agentType || 'claude-code';
|
|
1971
|
+
const agentId = conversation.agentId || conversation.agentType || 'claude-code';
|
|
1966
1972
|
const model = conversation.model || null;
|
|
1967
1973
|
|
|
1968
1974
|
if (hasActivity) {
|
|
@@ -2310,11 +2316,14 @@ class AgentGUIClient {
|
|
|
2310
2316
|
|
|
2311
2317
|
const outputEl = document.getElementById('output');
|
|
2312
2318
|
if (outputEl) {
|
|
2313
|
-
const wdInfo = conversation.workingDirectory ?
|
|
2319
|
+
const wdInfo = conversation.workingDirectory ? `${this.escapeHtml(conversation.workingDirectory)}` : '';
|
|
2320
|
+
const timestamp = new Date(conversation.created_at).toLocaleDateString();
|
|
2321
|
+
const metaParts = [timestamp];
|
|
2322
|
+
if (wdInfo) metaParts.push(wdInfo);
|
|
2314
2323
|
outputEl.innerHTML = `
|
|
2315
2324
|
<div class="conversation-header">
|
|
2316
2325
|
<h2>${this.escapeHtml(conversation.title || 'Conversation')}</h2>
|
|
2317
|
-
<p class="text-secondary">${
|
|
2326
|
+
<p class="text-secondary">${metaParts.join(' - ')}</p>
|
|
2318
2327
|
</div>
|
|
2319
2328
|
<div class="conversation-messages"></div>
|
|
2320
2329
|
`;
|
|
@@ -420,7 +420,7 @@ class ConversationManager {
|
|
|
420
420
|
const isStreaming = this.streamingConversations.has(conv.id);
|
|
421
421
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
422
422
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
423
|
-
const agent = this.getAgentDisplayName(conv.agentType);
|
|
423
|
+
const agent = this.getAgentDisplayName(conv.agentId || conv.agentType);
|
|
424
424
|
const modelLabel = conv.model ? ` (${conv.model})` : '';
|
|
425
425
|
const wd = conv.workingDirectory ? pathBasename(conv.workingDirectory) : '';
|
|
426
426
|
const metaParts = [agent + modelLabel, timestamp];
|
|
@@ -448,7 +448,7 @@ class ConversationManager {
|
|
|
448
448
|
|
|
449
449
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
450
450
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
451
|
-
const agent = this.getAgentDisplayName(conv.agentType);
|
|
451
|
+
const agent = this.getAgentDisplayName(conv.agentId || conv.agentType);
|
|
452
452
|
const modelLabel = conv.model ? ` (${conv.model})` : '';
|
|
453
453
|
const wd = conv.workingDirectory ? conv.workingDirectory.split('/').pop() : '';
|
|
454
454
|
const metaParts = [agent + modelLabel, timestamp];
|
|
@@ -1556,7 +1556,7 @@ class StreamingRenderer {
|
|
|
1556
1556
|
</svg>
|
|
1557
1557
|
<div class="flex-1">
|
|
1558
1558
|
<h4 class="font-semibold text-blue-900 dark:text-blue-200">Streaming Started</h4>
|
|
1559
|
-
<p class="text-sm text-blue-700 dark:text-blue-300"
|
|
1559
|
+
<p class="text-sm text-blue-700 dark:text-blue-300">${time}</p>
|
|
1560
1560
|
</div>
|
|
1561
1561
|
</div>
|
|
1562
1562
|
`;
|