agentgui 1.0.15 → 1.0.16

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/acp-launcher.js CHANGED
@@ -33,19 +33,62 @@ function findBinary(paths) {
33
33
  return null;
34
34
  }
35
35
 
36
- const RIPPLEUI_SYSTEM_PROMPT = `ALWAYS respond with HTML using RippleUI components. The chat renders HTML. Use: cards (class='card'), alerts (class='alert alert-info'), tables (class='table table-zebra'), badges (class='badge badge-primary'), buttons (class='btn btn-primary'). Wrap all responses in styled HTML with Tailwind CSS utility classes for layout.
37
-
38
- RIPPLEUI COMPONENTS:
39
- Cards: <div class="card bg-base-100 shadow-lg p-6"><h2 class="text-xl font-bold mb-2">Title</h2><p>Content</p></div>
40
- Alerts: <div class="alert alert-info"><span>Message</span></div>
41
- Tables: <div class="overflow-x-auto"><table class="table table-zebra"><thead><tr><th>Col</th></tr></thead><tbody><tr><td>Val</td></tr></tbody></table></div>
42
- Badges: <span class="badge badge-primary">Tag</span>
43
- Buttons: <button class="btn btn-primary">Action</button>
44
- Code: <pre class="bg-base-200 p-4 rounded-lg overflow-x-auto"><code>code here</code></pre>
45
- Lists: <ul class="list-none space-y-2"><li class="p-3 bg-base-200 rounded-lg">Item</li></ul>
46
-
47
- Use Tailwind CSS utility classes for layout (flex, grid, gap-4, p-4, rounded, shadow).
48
- ALWAYS wrap responses in styled HTML. Never send plain unstyled text.`;
36
+ const RIPPLEUI_SYSTEM_PROMPT = `CRITICAL: ALWAYS respond with styled HTML using RippleUI components. This is a web-based chat interface that ONLY renders HTML beautifully. NEVER respond with plain text.
37
+
38
+ FOR EVERY RESPONSE:
39
+ 1. Wrap your ENTIRE response in a container div with proper styling
40
+ 2. Use semantic HTML (h1-h6 for headings, p for paragraphs, ul/ol for lists)
41
+ 3. Format code in <pre><code> blocks with language class
42
+ 4. Use cards for organized information blocks
43
+ 5. Use alerts for important information
44
+ 6. Use tables for tabular data
45
+ 7. Style everything with Tailwind CSS classes
46
+
47
+ RESPONSE STRUCTURE TEMPLATE:
48
+ \`\`\`html
49
+ <div class="space-y-4 p-4">
50
+ <h2 class="text-2xl font-bold">Main Heading</h2>
51
+ <p class="text-gray-700">Introductory text here.</p>
52
+
53
+ <div class="card bg-base-100 shadow-lg p-4">
54
+ <h3 class="text-lg font-semibold mb-2">Section Title</h3>
55
+ <p>Content goes here.</p>
56
+ </div>
57
+
58
+ <pre class="bg-base-200 p-4 rounded-lg overflow-x-auto"><code class="language-javascript">// Code example
59
+ const example = () => { };</code></pre>
60
+
61
+ <ul class="list-none space-y-2 ml-4">
62
+ <li class="p-2 bg-base-200 rounded">• Item one</li>
63
+ <li class="p-2 bg-base-200 rounded">• Item two</li>
64
+ </ul>
65
+ </div>
66
+ \`\`\`
67
+
68
+ COMPONENT REFERENCE:
69
+ - Cards: <div class="card bg-base-100 shadow-lg p-6"><h3 class="font-bold">Title</h3><p>Content</p></div>
70
+ - Alerts: <div class="alert alert-info"><span>Message</span></div>
71
+ - Tables: <table class="table table-zebra"><thead><tr><th>Header</th></tr></thead><tbody><tr><td>Data</td></tr></tbody></table>
72
+ - Badges: <span class="badge badge-primary">Label</span>
73
+ - Code: <pre class="bg-base-200 p-4 rounded-lg overflow-x-auto"><code class="language-{lang}">{code}</code></pre>
74
+ - Lists: <ul class="list-none space-y-2"><li class="p-2 bg-base-200 rounded">Item</li></ul>
75
+
76
+ REQUIRED FOR ALL RESPONSES:
77
+ ✓ Wrap response in HTML (usually a div with space-y-4 for vertical spacing)
78
+ ✓ Use Tailwind classes: text-xl, font-bold, p-4, mb-3, rounded-lg, shadow, etc.
79
+ ✓ Always use proper semantic HTML tags
80
+ ✓ Format code blocks with language class for syntax highlighting
81
+ ✓ Use colors: bg-blue-50, text-gray-700, border-blue-200 for visual hierarchy
82
+ ✓ NEVER respond with raw text - ALWAYS wrap in HTML container
83
+
84
+ EXAMPLES OF GOOD RESPONSES:
85
+ - Questions: Wrap answer in a card with bold question and styled answer
86
+ - Code: Use <pre><code> with language class
87
+ - Lists: Use <ul> or <ol> with proper styling
88
+ - Instructions: Use numbered or bulleted lists in cards
89
+ - Explanations: Use cards for each concept, bold headings, clear spacing
90
+
91
+ Remember: The user sees raw HTML output. Make it beautiful and readable.`;
49
92
 
50
93
  export default class ACPConnection {
51
94
  constructor() {
@@ -255,13 +298,44 @@ export default class ACPConnection {
255
298
  return this.sendRequest('session/set_mode', { sessionId: this.sessionId, modeId });
256
299
  }
257
300
 
258
- async injectSkills() {
301
+ async injectSkills(additionalContext = '') {
259
302
  if (this.printMode) return {};
260
- return this.sendRequest('session/skill_inject', {
261
- sessionId: this.sessionId,
262
- skills: [],
263
- notification: [{ type: 'text', text: RIPPLEUI_SYSTEM_PROMPT }]
264
- }).catch(() => null);
303
+
304
+ // Combine the system prompt with any additional context
305
+ const systemPrompt = additionalContext ? `${RIPPLEUI_SYSTEM_PROMPT}\n\n---\n\n${additionalContext}` : RIPPLEUI_SYSTEM_PROMPT;
306
+
307
+ try {
308
+ const result = await this.sendRequest('session/skill_inject', {
309
+ sessionId: this.sessionId,
310
+ skills: [],
311
+ notification: [{ type: 'text', text: systemPrompt }]
312
+ });
313
+ return result;
314
+ } catch (e) {
315
+ // skill_inject may not be supported, try alternative methods
316
+ console.log(`[ACP] skill_inject failed (may not be supported): ${e.message}`);
317
+ return null;
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Inject system prompt as initial context
323
+ */
324
+ async injectSystemContext() {
325
+ if (this.printMode) return {};
326
+
327
+ // Some versions may need the system prompt sent as the first message context
328
+ try {
329
+ await this.sendRequest('session/context', {
330
+ sessionId: this.sessionId,
331
+ context: RIPPLEUI_SYSTEM_PROMPT,
332
+ role: 'system'
333
+ });
334
+ return { success: true };
335
+ } catch (e) {
336
+ // This method may not be supported, silently fail
337
+ return null;
338
+ }
265
339
  }
266
340
 
267
341
  async sendPrompt(prompt) {
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Hot Reload Manager
3
+ * Enables live reloading of client code and graceful server restarts without losing connections
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+
12
+ export class HotReloadManager {
13
+ constructor(staticDir, options = {}) {
14
+ this.staticDir = staticDir;
15
+ this.watchedFiles = new Map();
16
+ this.hotReloadClients = [];
17
+ this.debounceTimers = new Map();
18
+ this.debounceDelay = options.debounceDelay || 300;
19
+ this.enabled = options.enabled !== false;
20
+ }
21
+
22
+ /**
23
+ * Start watching files for changes
24
+ */
25
+ start() {
26
+ if (!this.enabled) return;
27
+
28
+ try {
29
+ this.watchDirectory(this.staticDir);
30
+ console.log('[HotReload] Watching for changes:', this.staticDir);
31
+ } catch (e) {
32
+ console.error('[HotReload] Failed to start:', e.message);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Watch directory recursively
38
+ */
39
+ watchDirectory(dir) {
40
+ try {
41
+ const files = fs.readdirSync(dir, { withFileTypes: true });
42
+
43
+ for (const file of files) {
44
+ const fullPath = path.join(dir, file.name);
45
+
46
+ if (file.isDirectory()) {
47
+ this.watchDirectory(fullPath);
48
+ } else {
49
+ this.watchFile(fullPath);
50
+ }
51
+ }
52
+ } catch (e) {
53
+ console.error('[HotReload] Error watching directory:', e.message);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Watch individual file for changes
59
+ */
60
+ watchFile(filePath) {
61
+ if (this.watchedFiles.has(filePath)) return;
62
+
63
+ try {
64
+ fs.watchFile(filePath, { interval: 100 }, (curr, prev) => {
65
+ if (curr.mtime > prev.mtime) {
66
+ this.onFileChanged(filePath);
67
+ }
68
+ });
69
+
70
+ this.watchedFiles.set(filePath, true);
71
+ } catch (e) {
72
+ console.error('[HotReload] Error watching file:', e.message);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Handle file change with debouncing
78
+ */
79
+ onFileChanged(filePath) {
80
+ // Clear existing timer for this file
81
+ if (this.debounceTimers.has(filePath)) {
82
+ clearTimeout(this.debounceTimers.get(filePath));
83
+ }
84
+
85
+ // Set new debounced timer
86
+ const timer = setTimeout(() => {
87
+ this.debounceTimers.delete(filePath);
88
+ const relPath = path.relative(this.staticDir, filePath);
89
+
90
+ console.log(`[HotReload] File changed: ${relPath}`);
91
+ this.broadcastReload();
92
+ }, this.debounceDelay);
93
+
94
+ this.debounceTimers.set(filePath, timer);
95
+ }
96
+
97
+ /**
98
+ * Register a WebSocket client for hot reload
99
+ */
100
+ registerClient(ws) {
101
+ if (!this.enabled) return;
102
+ this.hotReloadClients.push(ws);
103
+
104
+ ws.on('close', () => {
105
+ const idx = this.hotReloadClients.indexOf(ws);
106
+ if (idx > -1) this.hotReloadClients.splice(idx, 1);
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Broadcast reload signal to all connected clients
112
+ */
113
+ broadcastReload() {
114
+ const message = JSON.stringify({ type: 'reload', timestamp: Date.now() });
115
+
116
+ for (const ws of this.hotReloadClients) {
117
+ if (ws.readyState === 1) { // WebSocket.OPEN
118
+ try {
119
+ ws.send(message);
120
+ } catch (e) {
121
+ // Client may have disconnected
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Cleanup watchers
129
+ */
130
+ stop() {
131
+ for (const filePath of this.watchedFiles.keys()) {
132
+ try {
133
+ fs.unwatchFile(filePath);
134
+ } catch (e) {}
135
+ }
136
+
137
+ for (const timer of this.debounceTimers.values()) {
138
+ clearTimeout(timer);
139
+ }
140
+
141
+ this.watchedFiles.clear();
142
+ this.debounceTimers.clear();
143
+ this.hotReloadClients = [];
144
+ }
145
+
146
+ /**
147
+ * Get HTML snippet to inject for hot reload
148
+ */
149
+ getClientScript(baseUrl = '') {
150
+ if (!this.enabled) return '';
151
+
152
+ return `
153
+ <script>
154
+ (function() {
155
+ const baseUrl = '${baseUrl}';
156
+ const ws = new WebSocket((location.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + location.host + baseUrl + '/hot-reload');
157
+
158
+ ws.onmessage = function(event) {
159
+ try {
160
+ const data = JSON.parse(event.data);
161
+ if (data.type === 'reload') {
162
+ console.log('[HotReload] Reloading page...');
163
+ location.reload();
164
+ }
165
+ } catch (e) {
166
+ console.error('[HotReload] Error parsing message:', e);
167
+ }
168
+ };
169
+
170
+ ws.onerror = function(e) {
171
+ console.log('[HotReload] WebSocket error:', e);
172
+ };
173
+
174
+ ws.onclose = function() {
175
+ console.log('[HotReload] Connection closed, will attempt to reconnect...');
176
+ setTimeout(function() {
177
+ location.reload();
178
+ }, 2000);
179
+ };
180
+ })();
181
+ </script>
182
+ `;
183
+ }
184
+ }
185
+
186
+ export default HotReloadManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -32,7 +32,9 @@ async function getACP(agentId, cwd) {
32
32
  await conn.initialize();
33
33
  await conn.newSession(cwd);
34
34
  await conn.setSessionMode('bypassPermissions');
35
- await conn.injectSkills(['html_rendering', 'image_display', 'scrot', 'fs_access']);
35
+ // Inject system prompt to ensure HTML/RippleUI formatting
36
+ await conn.injectSkills();
37
+ await conn.injectSystemContext();
36
38
  acpPool.set(agentId, conn);
37
39
  console.log(`ACP connection ready for ${agentId} in ${cwd}`);
38
40
  return conn;
package/static/app.js CHANGED
@@ -525,10 +525,15 @@ class GMGUIApp {
525
525
 
526
526
  looksLikeHtml(text) {
527
527
  const trimmed = text.trim();
528
+ // Check for HTML tags at the start
528
529
  if (/^<[a-z][\s\S]*>/i.test(trimmed)) return true;
529
- if (/<\/(div|span|p|table|ul|ol|h[1-6]|section|article|header|footer|nav|main|aside|details|summary|figure|figcaption|blockquote|pre|code|a|strong|em|img|br|hr)>/i.test(trimmed)) return true;
530
+ // Check for closing tags of common HTML elements
531
+ if (/<\/(div|span|p|table|ul|ol|h[1-6]|section|article|header|footer|nav|main|aside|details|summary|figure|figcaption|blockquote|pre|code|a|strong|em|img|br|hr|button|input|form|label)>/i.test(trimmed)) return true;
532
+ // Check for Tailwind/RippleUI classes (strong indicator of HTML)
533
+ if (/class\s*=\s*["'][^"']*(?:card|alert|badge|btn|table|space-y|p-\d+|text-|bg-|rounded|shadow)/.test(trimmed)) return true;
534
+ // Count HTML tags
530
535
  const tagCount = (trimmed.match(/<[a-z][^>]*>/gi) || []).length;
531
- if (tagCount >= 3) return true;
536
+ if (tagCount >= 2) return true; // Lower threshold for HTML detection
532
537
  return false;
533
538
  }
534
539