agentgui 1.0.85 → 1.0.87
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/server.js +23 -12
- package/static/index.html +101 -0
- package/static/js/client.js +156 -1
- package/static/js/conversations.js +1 -0
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -7,6 +7,9 @@ import { execSync } from 'child_process';
|
|
|
7
7
|
import { queries } from './database.js';
|
|
8
8
|
import { runClaudeWithStreaming } from './lib/claude-runner.js';
|
|
9
9
|
|
|
10
|
+
// System prompt for Claude to format responses as HTML
|
|
11
|
+
const SYSTEM_PROMPT = `Always write your responses in ripple-ui enhanced HTML. Avoid overriding light/dark mode CSS variables. Use all the benefits of HTML to express technical details with proper semantic markup, tables, code blocks, headings, and lists. Write clean, well-structured HTML that respects the existing design system.`;
|
|
12
|
+
|
|
10
13
|
// Debug logging to file
|
|
11
14
|
const debugLog = (msg) => {
|
|
12
15
|
const timestamp = new Date().toISOString();
|
|
@@ -65,13 +68,16 @@ const server = http.createServer(async (req, res) => {
|
|
|
65
68
|
const routePath = req.url.slice(BASE_URL.length) || '/';
|
|
66
69
|
|
|
67
70
|
try {
|
|
68
|
-
|
|
71
|
+
// Remove query parameters from routePath for matching
|
|
72
|
+
const pathOnly = routePath.split('?')[0];
|
|
73
|
+
|
|
74
|
+
if (pathOnly === '/api/conversations' && req.method === 'GET') {
|
|
69
75
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
70
76
|
res.end(JSON.stringify({ conversations: queries.getConversationsList() }));
|
|
71
77
|
return;
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
if (
|
|
80
|
+
if (pathOnly === '/api/conversations' && req.method === 'POST') {
|
|
75
81
|
const body = await parseBody(req);
|
|
76
82
|
const conversation = queries.createConversation(body.agentId, body.title);
|
|
77
83
|
queries.createEvent('conversation.created', { agentId: body.agentId }, conversation.id);
|
|
@@ -81,7 +87,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
81
87
|
return;
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
const convMatch =
|
|
90
|
+
const convMatch = pathOnly.match(/^\/api\/conversations\/([^/]+)$/);
|
|
85
91
|
if (convMatch) {
|
|
86
92
|
if (req.method === 'GET') {
|
|
87
93
|
const conv = queries.getConversation(convMatch[1]);
|
|
@@ -111,7 +117,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
const messagesMatch =
|
|
120
|
+
const messagesMatch = pathOnly.match(/^\/api\/conversations\/([^/]+)\/messages$/);
|
|
115
121
|
if (messagesMatch) {
|
|
116
122
|
if (req.method === 'GET') {
|
|
117
123
|
const url = new URL(req.url, 'http://localhost');
|
|
@@ -141,7 +147,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
141
147
|
}
|
|
142
148
|
}
|
|
143
149
|
|
|
144
|
-
const streamMatch =
|
|
150
|
+
const streamMatch = pathOnly.match(/^\/api\/conversations\/([^/]+)\/stream$/);
|
|
145
151
|
if (streamMatch && req.method === 'POST') {
|
|
146
152
|
const conversationId = streamMatch[1];
|
|
147
153
|
const body = await parseBody(req);
|
|
@@ -180,7 +186,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
180
186
|
return;
|
|
181
187
|
}
|
|
182
188
|
|
|
183
|
-
const messageMatch =
|
|
189
|
+
const messageMatch = pathOnly.match(/^\/api\/conversations\/([^/]+)\/messages\/([^/]+)$/);
|
|
184
190
|
if (messageMatch && req.method === 'GET') {
|
|
185
191
|
const msg = queries.getMessage(messageMatch[2]);
|
|
186
192
|
if (!msg || msg.conversationId !== messageMatch[1]) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Not found' })); return; }
|
|
@@ -189,7 +195,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
189
195
|
return;
|
|
190
196
|
}
|
|
191
197
|
|
|
192
|
-
const sessionMatch =
|
|
198
|
+
const sessionMatch = pathOnly.match(/^\/api\/sessions\/([^/]+)$/);
|
|
193
199
|
if (sessionMatch && req.method === 'GET') {
|
|
194
200
|
const sess = queries.getSession(sessionMatch[1]);
|
|
195
201
|
if (!sess) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Not found' })); return; }
|
|
@@ -199,8 +205,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
199
205
|
return;
|
|
200
206
|
}
|
|
201
207
|
|
|
202
|
-
if (
|
|
203
|
-
const convId =
|
|
208
|
+
if (pathOnly.match(/^\/api\/conversations\/([^/]+)\/sessions\/latest$/) && req.method === 'GET') {
|
|
209
|
+
const convId = pathOnly.match(/^\/api\/conversations\/([^/]+)\/sessions\/latest$/)[1];
|
|
204
210
|
const latestSession = queries.getLatestSession(convId);
|
|
205
211
|
if (!latestSession) {
|
|
206
212
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -213,7 +219,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
213
219
|
return;
|
|
214
220
|
}
|
|
215
221
|
|
|
216
|
-
const executionMatch =
|
|
222
|
+
const executionMatch = pathOnly.match(/^\/api\/sessions\/([^/]+)\/execution$/);
|
|
217
223
|
if (executionMatch && req.method === 'GET') {
|
|
218
224
|
const sessionId = executionMatch[1];
|
|
219
225
|
const url = new URL(req.url, 'http://localhost');
|
|
@@ -383,7 +389,10 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
383
389
|
print: true
|
|
384
390
|
};
|
|
385
391
|
|
|
386
|
-
|
|
392
|
+
// Prepend system prompt to user content
|
|
393
|
+
const promptWithSystem = `${SYSTEM_PROMPT}\n\n${content}`;
|
|
394
|
+
|
|
395
|
+
const outputs = await runClaudeWithStreaming(promptWithSystem, cwd, actualAgentId, config);
|
|
387
396
|
debugLog(`[stream] Claude returned ${outputs.length} streaming outputs`);
|
|
388
397
|
|
|
389
398
|
// Process streaming outputs similar to processMessage
|
|
@@ -500,7 +509,9 @@ async function processMessage(conversationId, messageId, content, agentId) {
|
|
|
500
509
|
const actualAgentId = agentId || 'claude-code';
|
|
501
510
|
|
|
502
511
|
debugLog(`[processMessage] Calling runClaudeWithStreaming with prompt: "${content.substring(0, 50)}..."`);
|
|
503
|
-
|
|
512
|
+
// Prepend system prompt to user content
|
|
513
|
+
const promptWithSystem = `${SYSTEM_PROMPT}\n\n${content}`;
|
|
514
|
+
const outputs = await runClaudeWithStreaming(promptWithSystem, cwd, actualAgentId);
|
|
504
515
|
debugLog(`[processMessage] Claude returned ${outputs.length} outputs`);
|
|
505
516
|
|
|
506
517
|
// Collect all message blocks to preserve full execution details
|
package/static/index.html
CHANGED
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
background-color: var(--color-bg-secondary);
|
|
75
75
|
border-right: 1px solid var(--color-border);
|
|
76
76
|
overflow: hidden;
|
|
77
|
+
min-height: 0;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
.sidebar-header {
|
|
@@ -359,6 +360,106 @@
|
|
|
359
360
|
font-size: 0.7rem;
|
|
360
361
|
}
|
|
361
362
|
|
|
363
|
+
/* Conversation display */
|
|
364
|
+
.conversation-header {
|
|
365
|
+
padding: 1rem;
|
|
366
|
+
border-bottom: 1px solid var(--color-border);
|
|
367
|
+
margin-bottom: 1rem;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.conversation-header h2 {
|
|
371
|
+
margin: 0 0 0.5rem 0;
|
|
372
|
+
font-size: 1.5rem;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.conversation-header p {
|
|
376
|
+
margin: 0;
|
|
377
|
+
font-size: 0.875rem;
|
|
378
|
+
color: var(--color-text-secondary);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.conversation-messages {
|
|
382
|
+
padding: 1rem;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.message {
|
|
386
|
+
margin-bottom: 1rem;
|
|
387
|
+
padding: 1rem;
|
|
388
|
+
border-radius: 0.375rem;
|
|
389
|
+
background-color: var(--color-bg-secondary);
|
|
390
|
+
border-left: 3px solid var(--color-primary);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.message-user {
|
|
394
|
+
border-left-color: var(--color-primary);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.message-assistant {
|
|
398
|
+
border-left-color: var(--color-success);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.message-role {
|
|
402
|
+
font-weight: 600;
|
|
403
|
+
font-size: 0.75rem;
|
|
404
|
+
text-transform: uppercase;
|
|
405
|
+
color: var(--color-text-secondary);
|
|
406
|
+
margin-bottom: 0.5rem;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.message-content {
|
|
410
|
+
font-size: 0.9rem;
|
|
411
|
+
line-height: 1.5;
|
|
412
|
+
white-space: pre-wrap;
|
|
413
|
+
word-break: break-word;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.message-timestamp {
|
|
417
|
+
font-size: 0.75rem;
|
|
418
|
+
color: var(--color-text-secondary);
|
|
419
|
+
margin-top: 0.5rem;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.message-blocks {
|
|
423
|
+
display: flex;
|
|
424
|
+
flex-direction: column;
|
|
425
|
+
gap: 0.75rem;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.message-text {
|
|
429
|
+
line-height: 1.6;
|
|
430
|
+
word-break: break-word;
|
|
431
|
+
white-space: pre-wrap;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.message-code {
|
|
435
|
+
background-color: var(--color-bg-code);
|
|
436
|
+
border-radius: 0.375rem;
|
|
437
|
+
padding: 0.75rem;
|
|
438
|
+
overflow-x: auto;
|
|
439
|
+
margin: 0.5rem 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.message-code pre {
|
|
443
|
+
margin: 0;
|
|
444
|
+
font-family: 'Courier New', monospace;
|
|
445
|
+
font-size: 0.85rem;
|
|
446
|
+
color: #e0e0e0;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.message-tool {
|
|
450
|
+
background-color: var(--color-primary);
|
|
451
|
+
color: white;
|
|
452
|
+
padding: 0.25rem 0.5rem;
|
|
453
|
+
border-radius: 0.25rem;
|
|
454
|
+
font-size: 0.75rem;
|
|
455
|
+
display: inline-block;
|
|
456
|
+
margin: 0.25rem 0;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.text-secondary {
|
|
460
|
+
color: var(--color-text-secondary);
|
|
461
|
+
}
|
|
462
|
+
|
|
362
463
|
/* Responsive */
|
|
363
464
|
@media (max-width: 768px) {
|
|
364
465
|
.layout-with-sidebar {
|
package/static/js/client.js
CHANGED
|
@@ -170,6 +170,11 @@ class AgentGUIClient {
|
|
|
170
170
|
|
|
171
171
|
// Listen for new conversation creation
|
|
172
172
|
window.addEventListener('create-new-conversation', () => this.createNewConversation());
|
|
173
|
+
|
|
174
|
+
// Listen for conversation selection
|
|
175
|
+
window.addEventListener('conversation-selected', (event) => {
|
|
176
|
+
this.loadConversationMessages(event.detail.conversationId);
|
|
177
|
+
});
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
/**
|
|
@@ -266,8 +271,25 @@ class AgentGUIClient {
|
|
|
266
271
|
agentId: data.agentId,
|
|
267
272
|
startTime: Date.now()
|
|
268
273
|
};
|
|
274
|
+
this.state.currentConversation = { id: data.conversationId };
|
|
269
275
|
this.state.sessionEvents = [];
|
|
270
|
-
|
|
276
|
+
|
|
277
|
+
// Auto-select the streaming conversation in the sidebar
|
|
278
|
+
if (window.conversationManager) {
|
|
279
|
+
window.conversationManager.select(data.conversationId);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Load the conversation to display it in real-time
|
|
283
|
+
this.loadConversationMessages(data.conversationId).then(() => {
|
|
284
|
+
// Clear output and prepare for streaming
|
|
285
|
+
const outputEl = document.getElementById('output');
|
|
286
|
+
if (outputEl) {
|
|
287
|
+
outputEl.innerHTML = '';
|
|
288
|
+
}
|
|
289
|
+
}).catch(err => {
|
|
290
|
+
console.error('Failed to load conversation during streaming:', err);
|
|
291
|
+
this.renderer.clear();
|
|
292
|
+
});
|
|
271
293
|
|
|
272
294
|
this.renderer.queueEvent({
|
|
273
295
|
type: 'streaming_start',
|
|
@@ -319,9 +341,54 @@ class AgentGUIClient {
|
|
|
319
341
|
* Handle message created
|
|
320
342
|
*/
|
|
321
343
|
handleMessageCreated(data) {
|
|
344
|
+
// If the message is for the currently displayed conversation, append it to the output
|
|
345
|
+
if (data.conversationId === this.state.currentConversation?.id && data.message) {
|
|
346
|
+
const outputEl = document.querySelector('.conversation-messages');
|
|
347
|
+
if (outputEl) {
|
|
348
|
+
const messageHtml = `
|
|
349
|
+
<div class="message message-${data.message.role}">
|
|
350
|
+
<div class="message-role">${data.message.role.charAt(0).toUpperCase() + data.message.role.slice(1)}</div>
|
|
351
|
+
${this.renderMessageContent(data.message.content)}
|
|
352
|
+
<div class="message-timestamp">${new Date(data.message.created_at).toLocaleString()}</div>
|
|
353
|
+
</div>
|
|
354
|
+
`;
|
|
355
|
+
outputEl.insertAdjacentHTML('beforeend', messageHtml);
|
|
356
|
+
// Scroll to bottom
|
|
357
|
+
const scrollContainer = document.getElementById('output-scroll');
|
|
358
|
+
if (scrollContainer) {
|
|
359
|
+
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
322
363
|
this.emit('message:created', data);
|
|
323
364
|
}
|
|
324
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Render message content based on type
|
|
368
|
+
*/
|
|
369
|
+
renderMessageContent(content) {
|
|
370
|
+
if (typeof content === 'string') {
|
|
371
|
+
return `<div class="message-text">${this.escapeHtml(content)}</div>`;
|
|
372
|
+
} else if (content && typeof content === 'object' && content.type === 'claude_execution') {
|
|
373
|
+
let html = '<div class="message-blocks">';
|
|
374
|
+
if (content.blocks && Array.isArray(content.blocks)) {
|
|
375
|
+
content.blocks.forEach(block => {
|
|
376
|
+
if (block.type === 'text') {
|
|
377
|
+
html += `<div class="message-text">${this.escapeHtml(block.text)}</div>`;
|
|
378
|
+
} else if (block.type === 'code_block') {
|
|
379
|
+
html += `<div class="message-code"><pre>${this.escapeHtml(block.code)}</pre></div>`;
|
|
380
|
+
} else if (block.type === 'tool_use') {
|
|
381
|
+
html += `<div class="message-tool">[Tool: ${this.escapeHtml(block.name)}]</div>`;
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
html += '</div>';
|
|
386
|
+
return html;
|
|
387
|
+
} else {
|
|
388
|
+
return `<div class="message-text">${this.escapeHtml(JSON.stringify(content))}</div>`;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
325
392
|
/**
|
|
326
393
|
* Start execution
|
|
327
394
|
*/
|
|
@@ -517,6 +584,94 @@ class AgentGUIClient {
|
|
|
517
584
|
}
|
|
518
585
|
}
|
|
519
586
|
|
|
587
|
+
/**
|
|
588
|
+
* Load and display conversation messages
|
|
589
|
+
*/
|
|
590
|
+
async loadConversationMessages(conversationId) {
|
|
591
|
+
try {
|
|
592
|
+
this.state.currentConversation = { id: conversationId };
|
|
593
|
+
|
|
594
|
+
// Fetch conversation details
|
|
595
|
+
const convResponse = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}`);
|
|
596
|
+
const { conversation } = await convResponse.json();
|
|
597
|
+
|
|
598
|
+
// Fetch messages
|
|
599
|
+
const messagesResponse = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/messages`);
|
|
600
|
+
if (!messagesResponse.ok) {
|
|
601
|
+
throw new Error(`Failed to fetch messages: ${messagesResponse.status}`);
|
|
602
|
+
}
|
|
603
|
+
const messagesData = await messagesResponse.json();
|
|
604
|
+
|
|
605
|
+
// Clear output and display conversation header
|
|
606
|
+
const outputEl = document.getElementById('output');
|
|
607
|
+
if (outputEl) {
|
|
608
|
+
outputEl.innerHTML = `
|
|
609
|
+
<div class="conversation-header">
|
|
610
|
+
<h2>${this.escapeHtml(conversation.title || 'Conversation')}</h2>
|
|
611
|
+
<p class="text-secondary">${conversation.agentType || 'unknown'} • ${new Date(conversation.created_at).toLocaleDateString()}</p>
|
|
612
|
+
</div>
|
|
613
|
+
<div class="conversation-messages">
|
|
614
|
+
${this.renderMessages(messagesData.messages || [])}
|
|
615
|
+
</div>
|
|
616
|
+
`;
|
|
617
|
+
}
|
|
618
|
+
} catch (error) {
|
|
619
|
+
console.error('Failed to load conversation messages:', error);
|
|
620
|
+
this.showError('Failed to load conversation: ' + error.message);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Render messages for display
|
|
626
|
+
*/
|
|
627
|
+
renderMessages(messages) {
|
|
628
|
+
if (messages.length === 0) {
|
|
629
|
+
return '<p class="text-secondary">No messages in this conversation yet</p>';
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return messages.map(msg => {
|
|
633
|
+
let contentHtml = '';
|
|
634
|
+
|
|
635
|
+
// Handle different content types
|
|
636
|
+
if (typeof msg.content === 'string') {
|
|
637
|
+
contentHtml = `<div class="message-text">${this.escapeHtml(msg.content)}</div>`;
|
|
638
|
+
} else if (msg.content && typeof msg.content === 'object' && msg.content.type === 'claude_execution') {
|
|
639
|
+
// Handle Claude execution blocks
|
|
640
|
+
contentHtml = '<div class="message-blocks">';
|
|
641
|
+
if (msg.content.blocks && Array.isArray(msg.content.blocks)) {
|
|
642
|
+
msg.content.blocks.forEach(block => {
|
|
643
|
+
if (block.type === 'text') {
|
|
644
|
+
contentHtml += `<div class="message-text">${this.escapeHtml(block.text)}</div>`;
|
|
645
|
+
} else if (block.type === 'code_block') {
|
|
646
|
+
contentHtml += `<div class="message-code"><pre>${this.escapeHtml(block.code)}</pre></div>`;
|
|
647
|
+
} else if (block.type === 'tool_use') {
|
|
648
|
+
contentHtml += `<div class="message-tool">[Tool: ${this.escapeHtml(block.name)}]</div>`;
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
contentHtml += '</div>';
|
|
653
|
+
} else {
|
|
654
|
+
contentHtml = `<div class="message-text">${this.escapeHtml(JSON.stringify(msg.content))}</div>`;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return `
|
|
658
|
+
<div class="message message-${msg.role}">
|
|
659
|
+
<div class="message-role">${msg.role.charAt(0).toUpperCase() + msg.role.slice(1)}</div>
|
|
660
|
+
${contentHtml}
|
|
661
|
+
<div class="message-timestamp">${new Date(msg.created_at).toLocaleString()}</div>
|
|
662
|
+
</div>
|
|
663
|
+
`;
|
|
664
|
+
}).join('');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Escape HTML to prevent XSS
|
|
669
|
+
*/
|
|
670
|
+
escapeHtml(text) {
|
|
671
|
+
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
672
|
+
return text.replace(/[&<>"']/g, c => map[c]);
|
|
673
|
+
}
|
|
674
|
+
|
|
520
675
|
/**
|
|
521
676
|
* Show error message
|
|
522
677
|
*/
|
|
@@ -65,6 +65,7 @@ class ConversationManager {
|
|
|
65
65
|
createConversationItem(conv) {
|
|
66
66
|
const li = document.createElement('li');
|
|
67
67
|
li.className = 'conversation-item';
|
|
68
|
+
li.dataset.convId = conv.id;
|
|
68
69
|
if (conv.id === this.activeId) li.classList.add('active');
|
|
69
70
|
|
|
70
71
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|