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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.85",
3
+ "version": "1.0.87",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
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
- if (routePath === '/api/conversations' && req.method === 'GET') {
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 (routePath === '/api/conversations' && req.method === 'POST') {
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 = routePath.match(/^\/api\/conversations\/([^/]+)$/);
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 = routePath.match(/^\/api\/conversations\/([^/]+)\/messages$/);
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 = routePath.match(/^\/api\/conversations\/([^/]+)\/stream$/);
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 = routePath.match(/^\/api\/conversations\/([^/]+)\/messages\/([^/]+)$/);
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 = routePath.match(/^\/api\/sessions\/([^/]+)$/);
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 (routePath.match(/^\/api\/conversations\/([^/]+)\/sessions\/latest$/) && req.method === 'GET') {
203
- const convId = routePath.match(/^\/api\/conversations\/([^/]+)\/sessions\/latest$/)[1];
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 = routePath.match(/^\/api\/sessions\/([^/]+)\/execution$/);
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
- const outputs = await runClaudeWithStreaming(content, cwd, actualAgentId, config);
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
- const outputs = await runClaudeWithStreaming(content, cwd, actualAgentId);
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 {
@@ -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
- this.renderer.clear();
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 = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
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)}`;