agentgui 1.0.16 → 1.0.18
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 +68 -50
- package/html-wrapper.js +117 -0
- package/package.json +1 -1
- package/server.js +12 -3
|
@@ -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,62 +33,80 @@ function findBinary(paths) {
|
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const RIPPLEUI_SYSTEM_PROMPT = `CRITICAL:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
4
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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:
|
|
48
50
|
\`\`\`html
|
|
49
|
-
<div class="space-y-4 p-
|
|
50
|
-
<h2 class="text-2xl font-bold">
|
|
51
|
-
<
|
|
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>
|
|
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>
|
|
56
55
|
</div>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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>
|
|
64
75
|
</ul>
|
|
65
76
|
</div>
|
|
66
77
|
\`\`\`
|
|
67
78
|
|
|
68
|
-
COMPONENT
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
✓
|
|
78
|
-
✓ Use
|
|
79
|
-
✓ Always
|
|
80
|
-
✓
|
|
81
|
-
✓
|
|
82
|
-
✓ NEVER respond
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
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.`;
|
|
92
110
|
|
|
93
111
|
export default class ACPConnection {
|
|
94
112
|
constructor() {
|
package/html-wrapper.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Wrapper for Claude Responses
|
|
3
|
+
* Converts plain text/markdown responses into beautiful HTML
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class HTMLWrapper {
|
|
7
|
+
/**
|
|
8
|
+
* Wrap plain text response in HTML with RippleUI styling
|
|
9
|
+
*/
|
|
10
|
+
static wrapResponse(text) {
|
|
11
|
+
if (!text || typeof text !== 'string') return text;
|
|
12
|
+
|
|
13
|
+
// If already HTML, return as-is
|
|
14
|
+
if (text.trim().startsWith('<')) return text;
|
|
15
|
+
|
|
16
|
+
// Parse markdown-style text and convert to HTML
|
|
17
|
+
const lines = text.split('\n');
|
|
18
|
+
const html = this.parseMarkdownToHTML(lines);
|
|
19
|
+
|
|
20
|
+
// Wrap in container
|
|
21
|
+
return `<div class="space-y-4 p-6 max-w-4xl">${html}</div>`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static parseMarkdownToHTML(lines) {
|
|
25
|
+
let html = '';
|
|
26
|
+
let inCodeBlock = false;
|
|
27
|
+
let codeLanguage = 'text';
|
|
28
|
+
let codeContent = '';
|
|
29
|
+
let listItems = [];
|
|
30
|
+
let inList = false;
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const line = lines[i];
|
|
34
|
+
|
|
35
|
+
// Code blocks
|
|
36
|
+
if (line.match(/^```(\w+)?$/)) {
|
|
37
|
+
if (!inCodeBlock) {
|
|
38
|
+
// Start code block
|
|
39
|
+
inCodeBlock = true;
|
|
40
|
+
codeLanguage = line.match(/```(\w+)?/)?.[1] || 'text';
|
|
41
|
+
codeContent = '';
|
|
42
|
+
} else {
|
|
43
|
+
// End code block
|
|
44
|
+
inCodeBlock = false;
|
|
45
|
+
html += `<pre class="bg-gray-900 text-white p-4 rounded-lg overflow-x-auto"><code class="language-${codeLanguage}">${this.escapeHtml(codeContent)}</code></pre>`;
|
|
46
|
+
codeContent = '';
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (inCodeBlock) {
|
|
52
|
+
codeContent += (codeContent ? '\n' : '') + line;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Headings
|
|
57
|
+
if (line.match(/^#+\s/)) {
|
|
58
|
+
const level = line.match(/^#+/)[0].length;
|
|
59
|
+
const heading = line.replace(/^#+\s/, '');
|
|
60
|
+
html += `<h${level} class="text-${level === 1 ? '3xl' : level === 2 ? '2xl' : 'xl'} font-bold text-gray-900 mt-4">${this.escapeHtml(heading)}</h${level}>`;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Lists
|
|
65
|
+
if (line.match(/^[-*•]\s/) || line.match(/^\d+\.\s/)) {
|
|
66
|
+
const itemText = line.replace(/^[-*•\d+.]\s+/, '');
|
|
67
|
+
if (!inList) {
|
|
68
|
+
inList = true;
|
|
69
|
+
listItems = [];
|
|
70
|
+
}
|
|
71
|
+
listItems.push(itemText);
|
|
72
|
+
continue;
|
|
73
|
+
} else if (inList && line.trim()) {
|
|
74
|
+
// End list
|
|
75
|
+
html += `<ul class="list-none space-y-2 ml-0"><li class="p-3 bg-gray-100 rounded border-l-4 border-blue-500">${listItems.map(item => `• ${this.escapeHtml(item)}`).join('</li><li class="p-3 bg-gray-100 rounded border-l-4 border-blue-500">')}</li></ul>`;
|
|
76
|
+
inList = false;
|
|
77
|
+
listItems = [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Empty lines
|
|
81
|
+
if (!line.trim()) {
|
|
82
|
+
if (!html.endsWith('</p>')) html += '<br>';
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Regular paragraphs
|
|
87
|
+
if (line.trim()) {
|
|
88
|
+
// Format inline markdown
|
|
89
|
+
let formatted = this.escapeHtml(line);
|
|
90
|
+
formatted = formatted.replace(/\*\*(.*?)\*\*/g, '<strong class="font-bold">$1</strong>');
|
|
91
|
+
formatted = formatted.replace(/\*(.*?)\*/g, '<em class="italic">$1</em>');
|
|
92
|
+
formatted = formatted.replace(/`([^`]+)`/g, '<code class="bg-gray-200 px-2 py-1 rounded font-mono text-sm">$1</code>');
|
|
93
|
+
|
|
94
|
+
html += `<p class="text-gray-700 leading-relaxed">${formatted}</p>`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Close any remaining list
|
|
99
|
+
if (inList) {
|
|
100
|
+
html += `<ul class="list-none space-y-2 ml-0"><li class="p-3 bg-gray-100 rounded border-l-4 border-blue-500">${listItems.map(item => `• ${this.escapeHtml(item)}`).join('</li><li class="p-3 bg-gray-100 rounded border-l-4 border-blue-500">')}</li></ul>`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return html;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static escapeHtml(text) {
|
|
107
|
+
if (typeof text !== 'string') return '';
|
|
108
|
+
return text
|
|
109
|
+
.replace(/&/g, '&')
|
|
110
|
+
.replace(/</g, '<')
|
|
111
|
+
.replace(/>/g, '>')
|
|
112
|
+
.replace(/"/g, '"')
|
|
113
|
+
.replace(/'/g, ''');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default HTMLWrapper;
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -8,6 +8,7 @@ import { execSync } from 'child_process';
|
|
|
8
8
|
import { queries } from './database.js';
|
|
9
9
|
import ACPConnection from './acp-launcher.js';
|
|
10
10
|
import { ResponseFormatter } from './response-formatter.js';
|
|
11
|
+
import { HTMLWrapper } from './html-wrapper.js';
|
|
11
12
|
|
|
12
13
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const PORT = process.env.PORT || 3000;
|
|
@@ -337,7 +338,13 @@ async function processMessage(conversationId, messageId, sessionId, content, age
|
|
|
337
338
|
const result = await conn.sendPrompt(content);
|
|
338
339
|
conn.onUpdate = null;
|
|
339
340
|
|
|
340
|
-
|
|
341
|
+
let responseText = fullText || result?.result || (result?.stopReason ? `Completed: ${result.stopReason}` : 'No response.');
|
|
342
|
+
|
|
343
|
+
// Wrap response in HTML if it's not already
|
|
344
|
+
const isHTML = responseText.trim().startsWith('<');
|
|
345
|
+
if (!isHTML) {
|
|
346
|
+
responseText = HTMLWrapper.wrapResponse(responseText);
|
|
347
|
+
}
|
|
341
348
|
|
|
342
349
|
// Segment and format the response for better display
|
|
343
350
|
const segments = ResponseFormatter.segmentResponse(responseText);
|
|
@@ -348,12 +355,14 @@ async function processMessage(conversationId, messageId, sessionId, content, age
|
|
|
348
355
|
blocks,
|
|
349
356
|
segments,
|
|
350
357
|
metadata,
|
|
351
|
-
updateChunks
|
|
358
|
+
updateChunks,
|
|
359
|
+
isHTML: true
|
|
352
360
|
} : {
|
|
353
361
|
text: responseText,
|
|
354
362
|
segments,
|
|
355
363
|
metadata,
|
|
356
|
-
updateChunks
|
|
364
|
+
updateChunks,
|
|
365
|
+
isHTML: true
|
|
357
366
|
};
|
|
358
367
|
|
|
359
368
|
const assistantMessage = queries.createMessage(conversationId, 'assistant', messageContent);
|