agentgui 1.0.15 → 1.0.17
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/RECENT_UPDATES.md +209 -0
- package/acp-launcher.js +111 -19
- package/hot-reload-manager.js +186 -0
- package/package.json +1 -1
- package/server.js +3 -1
- package/static/app.js +7 -2
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# AgentGUI Recent Updates
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
AgentGUI has been significantly enhanced to use local Claude Code OAuth authentication and provide rich, metadata-aware response rendering.
|
|
5
|
+
|
|
6
|
+
## Key Changes
|
|
7
|
+
|
|
8
|
+
### 1. OAuth Authentication via Local Claude Code
|
|
9
|
+
**File**: `acp-launcher.js`, `server.js`
|
|
10
|
+
|
|
11
|
+
- **Binary Discovery**: Automatically finds `claude-code-acp` in common installation locations
|
|
12
|
+
- `/config/.gmweb/npm-global/bin/claude-code-acp`
|
|
13
|
+
- `~/.local/bin/claude-code-acp`
|
|
14
|
+
- Falls back to PATH if configured
|
|
15
|
+
|
|
16
|
+
- **PATH Management**: Enhanced environment variable handling to ensure npm global binaries are discoverable
|
|
17
|
+
|
|
18
|
+
- **Timeout Optimization**:
|
|
19
|
+
- Initialize: 4s → 10s
|
|
20
|
+
- Session creation: 4s → 30s
|
|
21
|
+
- Mode setting: 2s → 10s
|
|
22
|
+
- Handshake deadline: 5s → 60s
|
|
23
|
+
|
|
24
|
+
**Benefits**:
|
|
25
|
+
- Uses existing Claude Code authentication
|
|
26
|
+
- No additional API key setup needed
|
|
27
|
+
- Seamless integration with system auth
|
|
28
|
+
|
|
29
|
+
### 2. Response Segmentation & Metadata Extraction
|
|
30
|
+
**Files**: `response-formatter.js`, `server.js`, `static/app.js`
|
|
31
|
+
|
|
32
|
+
#### ResponseFormatter
|
|
33
|
+
- Intelligent parsing of Claude responses into semantic units:
|
|
34
|
+
- Code blocks (with language detection)
|
|
35
|
+
- Headings (h1-h6)
|
|
36
|
+
- Text paragraphs
|
|
37
|
+
- Blockquotes
|
|
38
|
+
- Lists
|
|
39
|
+
|
|
40
|
+
- Metadata extraction:
|
|
41
|
+
- Tool calls and function names
|
|
42
|
+
- Thinking/reasoning blocks
|
|
43
|
+
- Task references
|
|
44
|
+
- Subagent usage
|
|
45
|
+
|
|
46
|
+
- Proper formatting with inline code highlighting
|
|
47
|
+
|
|
48
|
+
#### Frontend Rendering
|
|
49
|
+
- `renderSegment()`: Beautiful display of each segment type
|
|
50
|
+
- `renderMetadata()`: Rich metadata sidebar with:
|
|
51
|
+
- Tools used (with code highlighting)
|
|
52
|
+
- Thinking blocks (collapsible details element)
|
|
53
|
+
- Subagents employed
|
|
54
|
+
- Tasks referenced
|
|
55
|
+
|
|
56
|
+
### 3. Rich HTML/RippleUI Responses
|
|
57
|
+
**Files**: `acp-launcher.js`, `static/app.js`
|
|
58
|
+
|
|
59
|
+
#### System Prompt Enhancement
|
|
60
|
+
Comprehensive instruction set forcing Claude to respond with HTML:
|
|
61
|
+
- RippleUI components (cards, alerts, tables, badges)
|
|
62
|
+
- Tailwind CSS styling
|
|
63
|
+
- Semantic HTML structure
|
|
64
|
+
- Code block formatting with language hints
|
|
65
|
+
- Never raw text - always wrapped HTML
|
|
66
|
+
|
|
67
|
+
#### HTML Detection Improvement
|
|
68
|
+
- Detects HTML by tags, structure, AND Tailwind classes
|
|
69
|
+
- Lower tag count threshold for detection (2 vs 3)
|
|
70
|
+
- Better recognition of styled components
|
|
71
|
+
|
|
72
|
+
### 4. Professional CSS Styling
|
|
73
|
+
**File**: `static/styles.css`
|
|
74
|
+
|
|
75
|
+
Added comprehensive styling for:
|
|
76
|
+
- Code blocks with language-specific colors
|
|
77
|
+
- Inline code with syntax highlighting
|
|
78
|
+
- Markdown formatting (bold, italic, code)
|
|
79
|
+
- Collapsible thinking blocks
|
|
80
|
+
- Metadata sections with visual hierarchy
|
|
81
|
+
- Print-friendly styles
|
|
82
|
+
- Dark mode support
|
|
83
|
+
|
|
84
|
+
### 5. Hot Reload Infrastructure
|
|
85
|
+
**File**: `hot-reload-manager.js`
|
|
86
|
+
|
|
87
|
+
Prepared for hot reloading:
|
|
88
|
+
- File watching with debouncing
|
|
89
|
+
- WebSocket-based reload signaling
|
|
90
|
+
- Graceful client-side reloading
|
|
91
|
+
- Ready for future implementation
|
|
92
|
+
|
|
93
|
+
## System Architecture
|
|
94
|
+
|
|
95
|
+
### Ports
|
|
96
|
+
- Production: **9897** (via system startup)
|
|
97
|
+
- Development: **3000** (via `npm start`)
|
|
98
|
+
|
|
99
|
+
### Request Flow
|
|
100
|
+
```
|
|
101
|
+
User Message
|
|
102
|
+
↓
|
|
103
|
+
API endpoint (/api/conversations/:id/messages)
|
|
104
|
+
↓
|
|
105
|
+
Server (processMessage)
|
|
106
|
+
↓
|
|
107
|
+
getACP() - Gets or creates connection
|
|
108
|
+
↓
|
|
109
|
+
conn.sendPrompt() - Sends to Claude Code via ACP bridge
|
|
110
|
+
↓
|
|
111
|
+
ResponseFormatter.segmentResponse()
|
|
112
|
+
↓
|
|
113
|
+
Database storage with segments + metadata
|
|
114
|
+
↓
|
|
115
|
+
Frontend display with rich rendering
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Connection Management
|
|
119
|
+
- ACP Pool: Maintains persistent connections per agent
|
|
120
|
+
- OAuth via local Claude Code credentials
|
|
121
|
+
- Graceful error handling and fallback
|
|
122
|
+
- Automatic reconnection
|
|
123
|
+
|
|
124
|
+
## Display Examples
|
|
125
|
+
|
|
126
|
+
### Segmented Response
|
|
127
|
+
Plain text becomes:
|
|
128
|
+
```
|
|
129
|
+
[Heading] Problem Analysis
|
|
130
|
+
[Text] Explanation paragraph
|
|
131
|
+
[Code] javascript function example
|
|
132
|
+
[Metadata] Tools used, reasoning blocks
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Metadata Display
|
|
136
|
+
- **Tools Used**: List of functions/tools called
|
|
137
|
+
- **Reasoning**: Collapsible thinking process
|
|
138
|
+
- **Subagents**: External agents employed
|
|
139
|
+
- **Tasks**: Itemized task list
|
|
140
|
+
|
|
141
|
+
## Configuration
|
|
142
|
+
|
|
143
|
+
### Environment Variables
|
|
144
|
+
```bash
|
|
145
|
+
PORT=9897 # Server port (default: 3000)
|
|
146
|
+
BASE_URL=/gm # Route prefix (default: /gm)
|
|
147
|
+
HOT_RELOAD=false # Disable hot reload (default: true)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Database
|
|
151
|
+
- Location: `~/.gmgui/data.db`
|
|
152
|
+
- Persists conversations, messages, sessions
|
|
153
|
+
- Auto-created on first run
|
|
154
|
+
|
|
155
|
+
## Testing
|
|
156
|
+
|
|
157
|
+
### Quick Start
|
|
158
|
+
```bash
|
|
159
|
+
npm start # Start on port 3000
|
|
160
|
+
PORT=9897 npm start # Start on port 9897
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Test Endpoints
|
|
164
|
+
```bash
|
|
165
|
+
# Get agents
|
|
166
|
+
curl http://localhost:3000/gm/api/agents
|
|
167
|
+
|
|
168
|
+
# Create conversation
|
|
169
|
+
curl -X POST http://localhost:3000/gm/api/conversations \
|
|
170
|
+
-H "Content-Type: application/json" \
|
|
171
|
+
-d '{"agentId": "claude-code", "title": "Test"}'
|
|
172
|
+
|
|
173
|
+
# Send message
|
|
174
|
+
curl -X POST http://localhost:3000/gm/api/conversations/{id}/messages \
|
|
175
|
+
-H "Content-Type: application/json" \
|
|
176
|
+
-d '{"agentId": "claude-code", "content": "Hello"}'
|
|
177
|
+
|
|
178
|
+
# Get messages
|
|
179
|
+
curl http://localhost:3000/gm/api/conversations/{id}/messages
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Future Enhancements
|
|
183
|
+
|
|
184
|
+
1. **Full Hot Reloading**: Complete implementation of HotReloadManager
|
|
185
|
+
2. **Streaming Responses**: Real-time message streaming to client
|
|
186
|
+
3. **Task Tracking**: Enhanced task and subagent visualization
|
|
187
|
+
4. **Export Functions**: Share/export conversations as HTML/PDF
|
|
188
|
+
5. **Theme Customization**: Allow user-defined themes
|
|
189
|
+
|
|
190
|
+
## Files Modified
|
|
191
|
+
|
|
192
|
+
- `acp-launcher.js` - OAuth and timeout fixes, system prompt
|
|
193
|
+
- `server.js` - Response segmentation, metadata extraction
|
|
194
|
+
- `static/app.js` - Rich rendering, HTML detection
|
|
195
|
+
- `static/styles.css` - Professional styling for all components
|
|
196
|
+
- `response-formatter.js` - NEW: Response parsing and formatting
|
|
197
|
+
- `hot-reload-manager.js` - NEW: Hot reload infrastructure
|
|
198
|
+
|
|
199
|
+
## Git Commits
|
|
200
|
+
|
|
201
|
+
1. **Fix OAuth Connection** - Binary discovery and PATH management
|
|
202
|
+
2. **Increase ACP Timeouts** - Proper timeout values for connection establishment
|
|
203
|
+
3. **Rich Response Formatting** - Segmentation and metadata rendering
|
|
204
|
+
4. **Enforce HTML Responses** - System prompt and detection improvements
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
**Last Updated**: February 3, 2026
|
|
209
|
+
**Version**: 1.0.15
|
package/acp-launcher.js
CHANGED
|
@@ -33,19 +33,80 @@ function findBinary(paths) {
|
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const RIPPLEUI_SYSTEM_PROMPT = `
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
const RIPPLEUI_SYSTEM_PROMPT = `CRITICAL INSTRUCTION: You are responding in a web-based HTML interface. EVERY response must be formatted as beautiful, styled HTML using RippleUI and Tailwind CSS. This is NOT a text-based interface - users see raw HTML rendered in their browser.
|
|
37
|
+
|
|
38
|
+
YOUR RESPONSE FORMAT MUST BE:
|
|
39
|
+
Wrap your ENTIRE response in a single HTML container with these elements:
|
|
40
|
+
|
|
41
|
+
\`\`\`html
|
|
42
|
+
<div class="space-y-4 p-6 max-w-4xl">
|
|
43
|
+
<!-- Main content goes here -->
|
|
44
|
+
</div>
|
|
45
|
+
\`\`\`
|
|
46
|
+
|
|
47
|
+
STRUCTURE YOUR RESPONSES LIKE THIS:
|
|
48
|
+
|
|
49
|
+
For questions/answers:
|
|
50
|
+
\`\`\`html
|
|
51
|
+
<div class="space-y-4 p-6">
|
|
52
|
+
<h2 class="text-2xl font-bold text-gray-900">Your Answer</h2>
|
|
53
|
+
<div class="card bg-blue-50 border-l-4 border-blue-500 p-4">
|
|
54
|
+
<p class="text-gray-700">Your detailed answer here</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
\`\`\`
|
|
58
|
+
|
|
59
|
+
For code:
|
|
60
|
+
\`\`\`html
|
|
61
|
+
<div class="space-y-4 p-6">
|
|
62
|
+
<h3 class="text-xl font-bold">Code Example</h3>
|
|
63
|
+
<pre class="bg-gray-900 text-white p-4 rounded-lg overflow-x-auto"><code>// Your code here
|
|
64
|
+
function example() { }</code></pre>
|
|
65
|
+
</div>
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
For lists:
|
|
69
|
+
\`\`\`html
|
|
70
|
+
<div class="space-y-4 p-6">
|
|
71
|
+
<h3 class="text-xl font-bold">Items</h3>
|
|
72
|
+
<ul class="list-none space-y-2">
|
|
73
|
+
<li class="p-3 bg-gray-100 rounded border-l-4 border-gray-400">• Item one</li>
|
|
74
|
+
<li class="p-3 bg-gray-100 rounded border-l-4 border-gray-400">• Item two</li>
|
|
75
|
+
</ul>
|
|
76
|
+
</div>
|
|
77
|
+
\`\`\`
|
|
78
|
+
|
|
79
|
+
COMPONENT LIBRARY:
|
|
80
|
+
- Card: <div class="card bg-white shadow-lg p-6 rounded-lg"><h4 class="font-bold">Title</h4><p>Content</p></div>
|
|
81
|
+
- Alert: <div class="alert bg-red-100 border-l-4 border-red-500 p-4"><span class="text-red-800">Warning message</span></div>
|
|
82
|
+
- Success: <div class="alert bg-green-100 border-l-4 border-green-500 p-4"><span class="text-green-800">Success</span></div>
|
|
83
|
+
- Table: <table class="w-full border-collapse border border-gray-300"><thead class="bg-gray-100"><tr><th class="p-2 text-left">Col</th></tr></thead><tbody><tr><td class="p-2 border border-gray-300">Data</td></tr></tbody></table>
|
|
84
|
+
- Badge: <span class="inline-block bg-blue-500 text-white px-3 py-1 rounded-full text-sm">Label</span>
|
|
85
|
+
- Code inline: <code class="bg-gray-200 px-2 py-1 rounded text-red-600 font-mono">code</code>
|
|
86
|
+
|
|
87
|
+
MANDATORY RULES:
|
|
88
|
+
✓ EVERY response MUST be wrapped in a div with class "space-y-4 p-6"
|
|
89
|
+
✓ Use semantic HTML: <h1>-<h6>, <p>, <ul>, <ol>, <table>, <pre>
|
|
90
|
+
✓ Always add Tailwind classes for styling: colors, padding, margins, rounded corners
|
|
91
|
+
✓ Code blocks MUST use <pre><code> with language class like \`class="language-javascript"\`
|
|
92
|
+
✓ NEVER send plain text without HTML wrapping
|
|
93
|
+
✓ NEVER respond outside of HTML container
|
|
94
|
+
✓ Use color classes: text-gray-700, bg-blue-50, border-blue-500
|
|
95
|
+
✓ Make visual hierarchy clear: use different font sizes, colors, cards
|
|
96
|
+
|
|
97
|
+
EXAMPLES OF COMPLETE RESPONSES:
|
|
98
|
+
|
|
99
|
+
Example 1 - Answer:
|
|
100
|
+
<div class="space-y-4 p-6"><h2 class="text-2xl font-bold">Explanation</h2><p class="text-gray-700">Here is the detailed explanation...</p></div>
|
|
101
|
+
|
|
102
|
+
Example 2 - Code:
|
|
103
|
+
<div class="space-y-4 p-6"><h3 class="text-xl font-bold">JavaScript Function</h3><pre class="bg-gray-900 text-white p-4 rounded overflow-x-auto"><code>const greet = () => console.log('Hello');</code></pre></div>
|
|
104
|
+
|
|
105
|
+
Example 3 - Multiple sections:
|
|
106
|
+
<div class="space-y-4 p-6"><h2 class="text-2xl font-bold">Topic</h2><div class="card bg-white shadow p-4"><h3 class="font-bold">Section 1</h3><p>Content here</p></div><div class="card bg-white shadow p-4"><h3 class="font-bold">Section 2</h3><p>More content</p></div></div>
|
|
107
|
+
|
|
108
|
+
YOU MUST ALWAYS OUTPUT VALID, COMPLETE HTML.
|
|
109
|
+
The user's interface shows YOUR HTML directly - make it beautiful, well-organized, and professional.`;
|
|
49
110
|
|
|
50
111
|
export default class ACPConnection {
|
|
51
112
|
constructor() {
|
|
@@ -255,13 +316,44 @@ export default class ACPConnection {
|
|
|
255
316
|
return this.sendRequest('session/set_mode', { sessionId: this.sessionId, modeId });
|
|
256
317
|
}
|
|
257
318
|
|
|
258
|
-
async injectSkills() {
|
|
319
|
+
async injectSkills(additionalContext = '') {
|
|
259
320
|
if (this.printMode) return {};
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
321
|
+
|
|
322
|
+
// Combine the system prompt with any additional context
|
|
323
|
+
const systemPrompt = additionalContext ? `${RIPPLEUI_SYSTEM_PROMPT}\n\n---\n\n${additionalContext}` : RIPPLEUI_SYSTEM_PROMPT;
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const result = await this.sendRequest('session/skill_inject', {
|
|
327
|
+
sessionId: this.sessionId,
|
|
328
|
+
skills: [],
|
|
329
|
+
notification: [{ type: 'text', text: systemPrompt }]
|
|
330
|
+
});
|
|
331
|
+
return result;
|
|
332
|
+
} catch (e) {
|
|
333
|
+
// skill_inject may not be supported, try alternative methods
|
|
334
|
+
console.log(`[ACP] skill_inject failed (may not be supported): ${e.message}`);
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Inject system prompt as initial context
|
|
341
|
+
*/
|
|
342
|
+
async injectSystemContext() {
|
|
343
|
+
if (this.printMode) return {};
|
|
344
|
+
|
|
345
|
+
// Some versions may need the system prompt sent as the first message context
|
|
346
|
+
try {
|
|
347
|
+
await this.sendRequest('session/context', {
|
|
348
|
+
sessionId: this.sessionId,
|
|
349
|
+
context: RIPPLEUI_SYSTEM_PROMPT,
|
|
350
|
+
role: 'system'
|
|
351
|
+
});
|
|
352
|
+
return { success: true };
|
|
353
|
+
} catch (e) {
|
|
354
|
+
// This method may not be supported, silently fail
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
265
357
|
}
|
|
266
358
|
|
|
267
359
|
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
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
|
-
|
|
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
|
-
|
|
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 >=
|
|
536
|
+
if (tagCount >= 2) return true; // Lower threshold for HTML detection
|
|
532
537
|
return false;
|
|
533
538
|
}
|
|
534
539
|
|