agentgui 1.0.9 → 1.0.11
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 +150 -205
- package/database.js +145 -96
- package/package.json +3 -3
- package/server.js +1 -1
- package/static/app.js +114 -53
- package/static/index.html +37 -36
- package/static/styles.css +27 -39
- package/install.sh +0 -147
- package/static/rippleui.css +0 -208
package/acp-launcher.js
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
const CLAUDE_BIN = '/home/user/.local/bin/claude';
|
|
7
|
+
const API_KEY_PATH = path.join(os.homedir(), '.claude', 'oauth-api-key');
|
|
8
|
+
const API_URL = 'https://api.anthropic.com/v1/messages';
|
|
9
|
+
|
|
10
|
+
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.
|
|
11
|
+
|
|
12
|
+
RIPPLEUI COMPONENTS:
|
|
13
|
+
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>
|
|
14
|
+
Alerts: <div class="alert alert-info"><span>Message</span></div>
|
|
15
|
+
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>
|
|
16
|
+
Badges: <span class="badge badge-primary">Tag</span>
|
|
17
|
+
Buttons: <button class="btn btn-primary">Action</button>
|
|
18
|
+
Code: <pre class="bg-base-200 p-4 rounded-lg overflow-x-auto"><code>code here</code></pre>
|
|
19
|
+
Lists: <ul class="list-none space-y-2"><li class="p-3 bg-base-200 rounded-lg">Item</li></ul>
|
|
20
|
+
|
|
21
|
+
Use Tailwind CSS utility classes for layout (flex, grid, gap-4, p-4, rounded, shadow).
|
|
22
|
+
ALWAYS wrap responses in styled HTML. Never send plain unstyled text.`;
|
|
3
23
|
|
|
4
24
|
export default class ACPConnection {
|
|
5
25
|
constructor() {
|
|
@@ -9,86 +29,96 @@ export default class ACPConnection {
|
|
|
9
29
|
this.pendingRequests = new Map();
|
|
10
30
|
this.sessionId = null;
|
|
11
31
|
this.onUpdate = null;
|
|
32
|
+
this.printMode = false;
|
|
33
|
+
this.cwd = '/config';
|
|
12
34
|
}
|
|
13
35
|
|
|
14
36
|
async connect(agentType, cwd) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
37
|
+
this.cwd = cwd;
|
|
38
|
+
|
|
39
|
+
const acpSetup = async () => {
|
|
40
|
+
await this._spawnACP(agentType, cwd);
|
|
41
|
+
await this.sendRequest('initialize', {
|
|
42
|
+
protocolVersion: 1,
|
|
43
|
+
clientCapabilities: { fs: { readTextFile: true, writeTextFile: true } },
|
|
44
|
+
}, 4000);
|
|
45
|
+
const result = await this.sendRequest('session/new', { cwd, mcpServers: [] }, 4000);
|
|
46
|
+
this.sessionId = result.sessionId;
|
|
47
|
+
await this.sendRequest('session/set_mode', { sessionId: this.sessionId, modeId: 'bypassPermissions' }, 2000);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const deadline = new Promise((_, reject) => setTimeout(() => reject(new Error('ACP handshake timeout (5s)')), 5000));
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
await Promise.race([acpSetup(), deadline]);
|
|
54
|
+
console.log(`[ACP] Connected via ACP bridge (${agentType})`);
|
|
55
|
+
} catch (acpErr) {
|
|
56
|
+
console.log(`[ACP] Bridge failed: ${acpErr.message}`);
|
|
57
|
+
console.log(`[ACP] Falling back to claude --print mode`);
|
|
58
|
+
this.printMode = true;
|
|
59
|
+
this.sessionId = 'print-' + Date.now();
|
|
60
|
+
if (this.child) {
|
|
61
|
+
try { this.child.kill('SIGTERM'); } catch (_) {}
|
|
62
|
+
this.child = null;
|
|
63
|
+
}
|
|
64
|
+
for (const [id, req] of this.pendingRequests) {
|
|
65
|
+
clearTimeout(req.timeoutId);
|
|
66
|
+
}
|
|
67
|
+
this.pendingRequests.clear();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
19
70
|
|
|
71
|
+
_spawnACP(agentType, cwd) {
|
|
20
72
|
return new Promise((resolve, reject) => {
|
|
21
|
-
|
|
73
|
+
const env = { ...process.env };
|
|
74
|
+
delete env.NODE_OPTIONS;
|
|
75
|
+
delete env.NODE_INSPECT;
|
|
76
|
+
delete env.NODE_DEBUG;
|
|
77
|
+
|
|
22
78
|
try {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this.child = spawn('claude-code-acp', [], { cwd, stdio: ['pipe', 'pipe', 'pipe'], env, shell: false });
|
|
27
|
-
}
|
|
28
|
-
spawned = true;
|
|
79
|
+
const cmd = agentType === 'opencode' ? 'opencode' : 'claude-code-acp';
|
|
80
|
+
const args = agentType === 'opencode' ? ['acp'] : [];
|
|
81
|
+
this.child = spawn(cmd, args, { cwd, stdio: ['pipe', 'pipe', 'pipe'], env, shell: false });
|
|
29
82
|
} catch (err) {
|
|
30
|
-
reject(new Error(`Failed to spawn ACP
|
|
83
|
+
reject(new Error(`Failed to spawn ACP: ${err.message}`));
|
|
31
84
|
return;
|
|
32
85
|
}
|
|
33
86
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.child.stderr.on('data', d => console.error(`[ACP:${agentType}:stderr]`, d.toString().trim()));
|
|
39
|
-
this.child.on('error', err => {
|
|
40
|
-
clearTimeout(timeoutId);
|
|
41
|
-
console.error(`[ACP:${agentType}:error]`, err.message);
|
|
42
|
-
reject(new Error(`ACP process error (${agentType}): ${err.message}`));
|
|
43
|
-
});
|
|
44
|
-
this.child.on('exit', (code, signal) => {
|
|
45
|
-
clearTimeout(timeoutId);
|
|
46
|
-
console.log(`[ACP:${agentType}] exited code=${code} signal=${signal}`);
|
|
87
|
+
this.child.stderr.on('data', d => console.error(`[ACP:stderr]`, d.toString().trim()));
|
|
88
|
+
this.child.on('error', err => reject(new Error(`ACP spawn error: ${err.message}`)));
|
|
89
|
+
this.child.on('exit', () => {
|
|
47
90
|
this.child = null;
|
|
48
91
|
for (const [id, req] of this.pendingRequests) {
|
|
49
92
|
req.reject(new Error('ACP process exited'));
|
|
50
93
|
clearTimeout(req.timeoutId);
|
|
51
94
|
}
|
|
52
95
|
this.pendingRequests.clear();
|
|
53
|
-
if (!spawned) {
|
|
54
|
-
reject(new Error(`ACP process (${agentType}) exited before connection established`));
|
|
55
|
-
}
|
|
56
96
|
});
|
|
57
97
|
|
|
58
98
|
this.child.stdout.setEncoding('utf8');
|
|
59
99
|
this.child.stdout.on('data', data => {
|
|
60
|
-
clearTimeout(timeoutId);
|
|
61
100
|
this.buffer += data;
|
|
62
101
|
const lines = this.buffer.split('\n');
|
|
63
102
|
this.buffer = lines.pop() || '';
|
|
64
103
|
for (const line of lines) {
|
|
65
104
|
if (!line.trim()) continue;
|
|
66
|
-
try {
|
|
67
|
-
|
|
68
|
-
} catch (e) {
|
|
69
|
-
console.error('[ACP:parse]', line.substring(0, 200), e.message);
|
|
70
|
-
}
|
|
105
|
+
try { this.handleMessage(JSON.parse(line)); }
|
|
106
|
+
catch (e) { console.error('[ACP:parse]', line.substring(0, 200), e.message); }
|
|
71
107
|
}
|
|
72
108
|
});
|
|
73
109
|
|
|
74
|
-
setTimeout(
|
|
110
|
+
setTimeout(resolve, 300);
|
|
75
111
|
});
|
|
76
112
|
}
|
|
77
113
|
|
|
78
114
|
handleMessage(msg) {
|
|
79
|
-
if (msg.method) {
|
|
80
|
-
this.handleIncoming(msg);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
115
|
+
if (msg.method) { this.handleIncoming(msg); return; }
|
|
83
116
|
if (msg.id !== undefined && this.pendingRequests.has(msg.id)) {
|
|
84
117
|
const req = this.pendingRequests.get(msg.id);
|
|
85
118
|
this.pendingRequests.delete(msg.id);
|
|
86
119
|
clearTimeout(req.timeoutId);
|
|
87
|
-
if (msg.error)
|
|
88
|
-
|
|
89
|
-
} else {
|
|
90
|
-
req.resolve(msg.result);
|
|
91
|
-
}
|
|
120
|
+
if (msg.error) req.reject(new Error(msg.error.message || JSON.stringify(msg.error)));
|
|
121
|
+
else req.resolve(msg.result);
|
|
92
122
|
}
|
|
93
123
|
}
|
|
94
124
|
|
|
@@ -104,23 +134,13 @@ export default class ACPConnection {
|
|
|
104
134
|
return;
|
|
105
135
|
}
|
|
106
136
|
if (msg.method === 'fs/read_text_file' && msg.id !== undefined) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
110
|
-
this.sendResponse(msg.id, { content });
|
|
111
|
-
} catch (e) {
|
|
112
|
-
this.sendError(msg.id, -32000, e.message);
|
|
113
|
-
}
|
|
137
|
+
try { this.sendResponse(msg.id, { content: fs.readFileSync(msg.params?.path, 'utf-8') }); }
|
|
138
|
+
catch (e) { this.sendError(msg.id, -32000, e.message); }
|
|
114
139
|
return;
|
|
115
140
|
}
|
|
116
141
|
if (msg.method === 'fs/write_text_file' && msg.id !== undefined) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
120
|
-
this.sendResponse(msg.id, null);
|
|
121
|
-
} catch (e) {
|
|
122
|
-
this.sendError(msg.id, -32000, e.message);
|
|
123
|
-
}
|
|
142
|
+
try { fs.writeFileSync(msg.params?.path, msg.params?.content, 'utf-8'); this.sendResponse(msg.id, null); }
|
|
143
|
+
catch (e) { this.sendError(msg.id, -32000, e.message); }
|
|
124
144
|
return;
|
|
125
145
|
}
|
|
126
146
|
}
|
|
@@ -161,6 +181,7 @@ export default class ACPConnection {
|
|
|
161
181
|
}
|
|
162
182
|
|
|
163
183
|
async initialize() {
|
|
184
|
+
if (this.printMode) return {};
|
|
164
185
|
return this.sendRequest('initialize', {
|
|
165
186
|
protocolVersion: 1,
|
|
166
187
|
clientCapabilities: { fs: { readTextFile: true, writeTextFile: true } },
|
|
@@ -168,177 +189,101 @@ export default class ACPConnection {
|
|
|
168
189
|
}
|
|
169
190
|
|
|
170
191
|
async newSession(cwd) {
|
|
192
|
+
if (this.printMode) {
|
|
193
|
+
this.cwd = cwd;
|
|
194
|
+
return { sessionId: this.sessionId };
|
|
195
|
+
}
|
|
171
196
|
const result = await this.sendRequest('session/new', { cwd, mcpServers: [] }, 120000);
|
|
172
197
|
this.sessionId = result.sessionId;
|
|
173
198
|
return result;
|
|
174
199
|
}
|
|
175
200
|
|
|
176
201
|
async setSessionMode(modeId) {
|
|
202
|
+
if (this.printMode) return {};
|
|
177
203
|
return this.sendRequest('session/set_mode', { sessionId: this.sessionId, modeId });
|
|
178
204
|
}
|
|
179
205
|
|
|
180
|
-
async injectSkills(
|
|
181
|
-
|
|
182
|
-
'html_rendering': {
|
|
183
|
-
name: 'HTML Rendering',
|
|
184
|
-
description: 'Render styled HTML blocks directly in the chat interface',
|
|
185
|
-
capability: 'Send a sessionUpdate with this exact format:\n{\n "sessionUpdate": "html_content",\n "content": {\n "html": "<div style=\\"padding:1rem; border:1px solid #ccc; border-radius:0.5rem; background:#f9f9f9\\">Your HTML content here</div>",\n "title": "Optional title for the block",\n "id": "optional-unique-id"\n }\n}\nThe HTML will render as a styled square/box block in the conversation.'
|
|
186
|
-
},
|
|
187
|
-
'image_display': {
|
|
188
|
-
name: 'Image Display',
|
|
189
|
-
description: 'Display images from the filesystem in styled blocks',
|
|
190
|
-
capability: 'Send a sessionUpdate with this exact format:\n{\n "sessionUpdate": "image_content",\n "content": {\n "path": "/path/to/image.png",\n "title": "Optional image title",\n "alt": "Alt text for accessibility"\n }\n}\nSupported formats: PNG, JPEG, GIF, WebP, SVG. The image will render in a styled block.'
|
|
191
|
-
},
|
|
192
|
-
'scrot': {
|
|
193
|
-
name: 'Screenshot Utility',
|
|
194
|
-
description: 'Capture screenshots of the desktop or specific windows',
|
|
195
|
-
capability: 'Use scrot command-line tool to capture and save images to filesystem, then display using image_display skill'
|
|
196
|
-
},
|
|
197
|
-
'fs_access': {
|
|
198
|
-
name: 'Filesystem Access',
|
|
199
|
-
description: 'Read and write files, browse directories',
|
|
200
|
-
capability: 'Full read/write access to user home directory and workspace'
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const skillsToInject = skills
|
|
205
|
-
.map(s => skillDescriptions[s])
|
|
206
|
-
.filter(Boolean);
|
|
207
|
-
|
|
208
|
-
if (skillsToInject.length === 0) return;
|
|
209
|
-
|
|
210
|
-
const prompt = [
|
|
211
|
-
{
|
|
212
|
-
type: 'text',
|
|
213
|
-
text: `CRITICAL: HTML-FIRST VISUAL THINKING & COMMUNICATION MANDATE
|
|
214
|
-
|
|
215
|
-
YOU MUST EXPRESS YOUR ENTIRE THOUGHT PROCESS EXCLUSIVELY THROUGH HTML BLOCKS.
|
|
216
|
-
PLAIN TEXT EXPLANATIONS ARE FORBIDDEN - REPLACE ALL TEXT WITH VISUAL HTML.
|
|
217
|
-
|
|
218
|
-
ABSOLUTE REQUIREMENTS:
|
|
219
|
-
1. EVERY response must be 100% visual HTML-based communication
|
|
220
|
-
2. All thinking, analysis, and explanations MUST be rendered as styled HTML blocks
|
|
221
|
-
3. Plain text responses will be treated as incomplete work
|
|
222
|
-
4. Each HTML block must have clear visual indicators of its purpose and status
|
|
223
|
-
5. Structure your thinking visually using progressive disclosure patterns
|
|
224
|
-
|
|
225
|
-
THOUGHT PROCESS VISUALIZATION PATTERNS:
|
|
226
|
-
|
|
227
|
-
═══ PATTERN 1: STEP-BY-STEP ANALYSIS WITH NUMBERED VISUAL BLOCKS ═══
|
|
228
|
-
Use numbered visual blocks to show sequential thinking:
|
|
229
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='padding:1.5rem;border-left:4px solid #3b82f6;background:#eff6ff;border-radius:0.5rem'><div style='display:flex;gap:1rem;margin-bottom:0.75rem'><div style='font-weight:bold;color:#1e40af;background:#dbeafe;padding:0.25rem 0.75rem;border-radius:0.25rem;min-width:3rem;text-align:center'>STEP 1</div><div style='flex:1'><strong>Understanding the Problem</strong><br/>First, we analyze what we're dealing with...</div></div></div>", "title": "Analysis Progress"}}
|
|
230
|
-
|
|
231
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='padding:1.5rem;border-left:4px solid #8b5cf6;background:#faf5ff;border-radius:0.5rem'><div style='display:flex;gap:1rem;margin-bottom:0.75rem'><div style='font-weight:bold;color:#5b21b6;background:#ede9fe;padding:0.25rem 0.75rem;border-radius:0.25rem;min-width:3rem;text-align:center'>STEP 2</div><div style='flex:1'><strong>Exploring Options</strong><br/>Consider these approaches...</div></div></div>", "title": "Analysis Progress"}}
|
|
232
|
-
|
|
233
|
-
═══ PATTERN 2: DECISION TREE WITH BRANCHING VISUAL STRUCTURE ═══
|
|
234
|
-
Show branching logic and decision paths:
|
|
235
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='padding:1.5rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#f9fafb;font-family:monospace'><div style='margin-bottom:1rem'><div style='font-weight:bold;color:#1f2937'>Root Decision</div><div style='margin-left:1rem;margin-top:0.5rem;padding-left:1rem;border-left:2px solid #d1d5db'><div style='color:#059669;font-weight:bold'>✓ IF condition A ➜ Path 1</div><div style='color:#dc2626;font-weight:bold'>✗ ELSE ➜ Path 2</div></div></div></div>", "title": "Decision Logic"}}
|
|
236
|
-
|
|
237
|
-
═══ PATTERN 3: PROGRESS INDICATOR WITH VISUAL STATUS ═══
|
|
238
|
-
Show completion and progress visually:
|
|
239
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='padding:1.5rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#f9fafb'><div style='margin-bottom:1rem'><div style='display:flex;gap:0.5rem;margin-bottom:0.5rem'><span style='color:#059669;font-weight:bold'>✓ THINKING</span><span style='color:#059669;font-weight:bold'>✓ ANALYZING</span><span style='color:#f59e0b;font-weight:bold'>◐ DETERMINING</span><span style='color:#d1d5db;font-weight:bold'>○ IMPLEMENTING</span></div><div style='width:100%;height:0.5rem;background:#e5e7eb;border-radius:0.25rem;overflow:hidden'><div style='width:75%;height:100%;background:#3b82f6'></div></div><div style='text-align:right;font-size:0.875rem;color:#6b7280'>75% complete</div></div></div>", "title": "Thought Process Status"}}
|
|
240
|
-
|
|
241
|
-
═══ PATTERN 4: EXPANDABLE/COLLAPSIBLE REASONING SECTIONS ═══
|
|
242
|
-
Structure nested thinking with visual hierarchy:
|
|
243
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='padding:1.5rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#f9fafb'><div style='cursor:pointer;user-select:none;margin-bottom:0.75rem'><div style='font-weight:bold;color:#1f2937;display:flex;align-items:center;gap:0.5rem'><span style='display:inline-block;width:1.5rem'>▶ REASONING:</span><span>Why this approach works</span></div></div><div style='margin-left:1rem;padding:0.75rem;background:#f3f4f6;border-left:2px solid #9ca3af;border-radius:0.25rem'><div>Key insight: The most direct path minimizes complexity...</div></div></div>", "title": "Detailed Analysis"}}
|
|
244
|
-
|
|
245
|
-
═══ PATTERN 5: COLOR-CODED STATUS INDICATORS ═══
|
|
246
|
-
Use colors to indicate thinking state and conclusions:
|
|
247
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='display:grid;grid-template-columns:repeat(4,1fr);gap:0.75rem;padding:1.5rem'><div style='padding:1rem;border-radius:0.5rem;background:#dbeafe;border:2px solid #0ea5e9;text-align:center'><div style='font-weight:bold;color:#0c4a6e;font-size:0.875rem'>THINKING</div><div style='color:#0c4a6e;margin-top:0.5rem'>🧠</div></div><div style='padding:1rem;border-radius:0.5rem;background:#fef3c7;border:2px solid #fbbf24;text-align:center'><div style='font-weight:bold;color:#78350f;font-size:0.875rem'>ANALYZING</div><div style='color:#78350f;margin-top:0.5rem'>🔍</div></div><div style='padding:1rem;border-radius:0.5rem;background:#dcfce7;border:2px solid #22c55e;text-align:center'><div style='font-weight:bold;color:#15803d;font-size:0.875rem'>DONE</div><div style='color:#15803d;margin-top:0.5rem'>✓</div></div><div style='padding:1rem;border-radius:0.5rem;background:#fee2e2;border:2px solid #ef4444;text-align:center'><div style='font-weight:bold;color:#7f1d1d;font-size:0.875rem'>BLOCKED</div><div style='color:#7f1d1d;margin-top:0.5rem'>⚠</div></div></div>", "title": "Status Indicators"}}
|
|
248
|
-
|
|
249
|
-
═══ PRACTICAL EXAMPLES: VISUALIZE YOUR THINKING ═══
|
|
250
|
-
|
|
251
|
-
EXAMPLE 1: Problem Analysis Visualization
|
|
252
|
-
Instead of: "I need to analyze this problem in parts"
|
|
253
|
-
Do this:
|
|
254
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='padding:1.5rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#f9fafb'><h3 style='margin-top:0;color:#1f2937'>Problem Analysis</h3><div style='margin-top:1rem'><div style='padding:0.75rem;background:#dbeafe;border-left:4px solid #0ea5e9;margin-bottom:0.5rem;border-radius:0.25rem'><strong>Part 1:</strong> Context and constraints</div><div style='padding:0.75rem;background:#dbeafe;border-left:4px solid #0ea5e9;margin-bottom:0.5rem;border-radius:0.25rem'><strong>Part 2:</strong> Key variables and dependencies</div><div style='padding:0.75rem;background:#dbeafe;border-left:4px solid #0ea5e9;border-radius:0.25rem'><strong>Part 3:</strong> Potential failure points</div></div></div>", "title": "Analysis Breakdown"}}
|
|
255
|
-
|
|
256
|
-
EXAMPLE 2: Decision Making Process
|
|
257
|
-
Instead of: "Let me think about the options"
|
|
258
|
-
Do this:
|
|
259
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='padding:1.5rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#f9fafb'><h3 style='margin-top:0;color:#1f2937'>Decision Matrix</h3><table style='width:100%;border-collapse:collapse;margin-top:1rem'><tr style='background:#f3f4f6'><th style='border:1px solid #e5e7eb;padding:0.75rem;text-align:left'>Option</th><th style='border:1px solid #e5e7eb;padding:0.75rem'>Pros</th><th style='border:1px solid #e5e7eb;padding:0.75rem'>Cons</th><th style='border:1px solid #e5e7eb;padding:0.75rem'>Score</th></tr><tr><td style='border:1px solid #e5e7eb;padding:0.75rem'>Option A</td><td style='border:1px solid #e5e7eb;padding:0.75rem;color:#059669'>Fast, simple</td><td style='border:1px solid #e5e7eb;padding:0.75rem;color:#dc2626'>Limited scope</td><td style='border:1px solid #e5e7eb;padding:0.75rem;font-weight:bold'>7/10</td></tr><tr><td style='border:1px solid #e5e7eb;padding:0.75rem'>Option B</td><td style='border:1px solid #e5e7eb;padding:0.75rem;color:#059669'>Comprehensive</td><td style='border:1px solid #e5e7eb;padding:0.75rem;color:#dc2626'>More complex</td><td style='border:1px solid #e5e7eb;padding:0.75rem;font-weight:bold'>9/10</td></tr></table></div>", "title": "Options Evaluation"}}
|
|
260
|
-
|
|
261
|
-
EXAMPLE 3: Logical Reasoning Flow
|
|
262
|
-
Instead of: "Here's my reasoning..."
|
|
263
|
-
Do this:
|
|
264
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='padding:1.5rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#f9fafb'><h3 style='margin-top:0;color:#1f2937'>Reasoning Chain</h3><div style='margin-top:1rem'><div style='display:flex;align-items:center;margin-bottom:1rem'><div style='background:#dbeafe;border-radius:50%;width:2rem;height:2rem;display:flex;align-items:center;justify-content:center;font-weight:bold;color:#0c4a6e;flex-shrink:0'>1</div><div style='margin-left:1rem;flex:1'>Observation: The system shows pattern X</div></div><div style='margin-left:1rem;border-left:2px solid #0ea5e9;height:1rem'></div><div style='display:flex;align-items:center;margin-bottom:1rem'><div style='background:#fef3c7;border-radius:50%;width:2rem;height:2rem;display:flex;align-items:center;justify-content:center;font-weight:bold;color:#78350f;flex-shrink:0;margin-left:1rem'>2</div><div style='margin-left:1rem;flex:1'>Analysis: X implies Y based on principle Z</div></div><div style='margin-left:1rem;border-left:2px solid #fbbf24;height:1rem'></div><div style='display:flex;align-items:center'><div style='background:#dcfce7;border-radius:50%;width:2rem;height:2rem;display:flex;align-items:center;justify-content:center;font-weight:bold;color:#15803d;flex-shrink:0;margin-left:1rem'>3</div><div style='margin-left:1rem;flex:1'>Conclusion: Therefore, approach A is optimal</div></div></div></div>", "title": "Logical Flow"}}
|
|
265
|
-
|
|
266
|
-
EXAMPLE 4: Solution Alternatives with Confidence
|
|
267
|
-
Instead of: "There are different ways to solve this"
|
|
268
|
-
Do this:
|
|
269
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='padding:1.5rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#f9fafb'><h3 style='margin-top:0;color:#1f2937'>Solution Alternatives</h3><div style='margin-top:1rem;display:grid;gap:1rem'><div style='padding:1rem;background:#dcfce7;border:2px solid #22c55e;border-radius:0.5rem'><div style='font-weight:bold;color:#15803d'>Solution A: Direct Implementation</div><div style='margin-top:0.5rem;font-size:0.875rem'>Confidence: <span style='color:#15803d;font-weight:bold'>95%</span></div></div><div style='padding:1rem;background:#fef3c7;border:2px solid #fbbf24;border-radius:0.5rem'><div style='font-weight:bold;color:#78350f'>Solution B: Iterative Approach</div><div style='margin-top:0.5rem;font-size:0.875rem'>Confidence: <span style='color:#78350f;font-weight:bold'>75%</span></div></div><div style='padding:1rem;background:#fee2e2;border:2px solid #ef4444;border-radius:0.5rem'><div style='font-weight:bold;color:#7f1d1d'>Solution C: Experimental Method</div><div style='margin-top:0.5rem;font-size:0.875rem'>Confidence: <span style='color:#7f1d1d;font-weight:bold'>50%</span></div></div></div></div>", "title": "Alternative Approaches"}}
|
|
270
|
-
|
|
271
|
-
EXAMPLE 5: Final Conclusion with Confidence Indicator
|
|
272
|
-
Instead of: "In conclusion..."
|
|
273
|
-
Do this:
|
|
274
|
-
{"sessionUpdate": "html_content", "content": {"html": "<div style='padding:1.5rem;border-left:6px solid #059669;background:#f0fdf4;border-radius:0.5rem'><h3 style='margin-top:0;color:#15803d;display:flex;align-items:center;gap:0.5rem'><span style='font-size:1.5em'>✓</span>Final Conclusion</h3><div style='margin-top:0.75rem;color:#166534'><strong>Primary Finding:</strong> The recommended approach is X because of reasons A, B, and C.</div><div style='margin-top:0.75rem'><div style='display:flex;align-items:center;gap:0.75rem'><span style='font-weight:bold'>Confidence Level:</span><div style='flex:1;height:1rem;background:#d1d5db;border-radius:0.25rem;overflow:hidden'><div style='width:92%;height:100%;background:#10b981'></div></div><span style='font-weight:bold'>92%</span></div></div></div>", "title": "Conclusion"}}
|
|
275
|
-
|
|
276
|
-
═══ ESSENTIAL GUIDELINES ═══
|
|
277
|
-
|
|
278
|
-
VISUAL HIERARCHY:
|
|
279
|
-
- Use size, color, and spacing to guide attention
|
|
280
|
-
- Most important insights get the largest/brightest blocks
|
|
281
|
-
- Supporting details in smaller, lighter blocks
|
|
282
|
-
- Use section headers to organize complex thinking
|
|
283
|
-
|
|
284
|
-
ICONS & SYMBOLS (Use these for visual clarity):
|
|
285
|
-
- ✓ = Complete, correct, confirmed
|
|
286
|
-
- ✗ = Incomplete, incorrect, rejected
|
|
287
|
-
- ◐ = In progress, partial
|
|
288
|
-
- ○ = Pending, not started
|
|
289
|
-
- → = Implies, leads to, flows to
|
|
290
|
-
- ⚠ = Warning, caution, issue
|
|
291
|
-
- 🧠 = Thinking, analyzing
|
|
292
|
-
- 🔍 = Investigating, examining
|
|
293
|
-
- 📊 = Data, metrics, analysis
|
|
294
|
-
|
|
295
|
-
STYLING RULES:
|
|
296
|
-
- Every block must have: padding, border, border-radius, background color
|
|
297
|
-
- Use consistent color scheme: blue for thinking, yellow for analysis, green for complete, red for blocked
|
|
298
|
-
- Never use plain white backgrounds - use light grays (#f9fafb, #f3f4f6)
|
|
299
|
-
- Minimum border: 1px solid #e5e7eb
|
|
300
|
-
- Minimum padding: 1.5rem for block containers
|
|
301
|
-
|
|
302
|
-
RippleUI COMPATIBILITY:
|
|
303
|
-
When possible, use RippleUI classes instead of inline styles:
|
|
304
|
-
- Color classes: bg-primary, bg-secondary, text-primary, text-secondary
|
|
305
|
-
- Spacing: p-4, p-6, m-2, gap-3
|
|
306
|
-
- Borders: border-color, rounded-lg
|
|
307
|
-
- But inline styles are acceptable when needed for dynamic values
|
|
308
|
-
|
|
309
|
-
MULTI-BLOCK FLOW:
|
|
310
|
-
Send separate sessionUpdate calls for each visual block:
|
|
311
|
-
- First block: Analysis/problem statement
|
|
312
|
-
- Middle blocks: Reasoning, options, decision logic
|
|
313
|
-
- Final block: Conclusion with confidence
|
|
314
|
-
|
|
315
|
-
NO PLAIN TEXT:
|
|
316
|
-
- Do not explain your thinking in regular text messages
|
|
317
|
-
- All explanations must be in HTML blocks
|
|
318
|
-
- Plain text is for direct command responses only (like "Done" or error messages)
|
|
319
|
-
- Any substantive communication MUST be visual HTML
|
|
320
|
-
|
|
321
|
-
Available skills: ${skillsToInject.map(s => s.name).join(', ')}`
|
|
322
|
-
}
|
|
323
|
-
];
|
|
324
|
-
|
|
206
|
+
async injectSkills() {
|
|
207
|
+
if (this.printMode) return {};
|
|
325
208
|
return this.sendRequest('session/skill_inject', {
|
|
326
209
|
sessionId: this.sessionId,
|
|
327
|
-
skills:
|
|
328
|
-
notification:
|
|
210
|
+
skills: [],
|
|
211
|
+
notification: [{ type: 'text', text: RIPPLEUI_SYSTEM_PROMPT }]
|
|
329
212
|
}).catch(() => null);
|
|
330
213
|
}
|
|
331
214
|
|
|
332
215
|
async sendPrompt(prompt) {
|
|
216
|
+
if (this.printMode) return this._sendPrintPrompt(prompt);
|
|
333
217
|
const promptContent = Array.isArray(prompt) ? prompt : [{ type: 'text', text: prompt }];
|
|
334
218
|
return this.sendRequest('session/prompt', { sessionId: this.sessionId, prompt: promptContent }, 300000);
|
|
335
219
|
}
|
|
336
220
|
|
|
221
|
+
async _sendPrintPrompt(prompt) {
|
|
222
|
+
const text = typeof prompt === 'string' ? prompt : (Array.isArray(prompt) ? prompt.map(p => p.text || '').join('\n') : String(prompt));
|
|
223
|
+
let apiKey;
|
|
224
|
+
try { apiKey = fs.readFileSync(API_KEY_PATH, 'utf-8').trim(); }
|
|
225
|
+
catch (e) { throw new Error('No API key found at ' + API_KEY_PATH); }
|
|
226
|
+
|
|
227
|
+
const body = JSON.stringify({
|
|
228
|
+
model: 'claude-sonnet-4-20250514',
|
|
229
|
+
max_tokens: 4096,
|
|
230
|
+
system: RIPPLEUI_SYSTEM_PROMPT,
|
|
231
|
+
messages: [{ role: 'user', content: text }],
|
|
232
|
+
stream: true,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const res = await fetch(API_URL, {
|
|
236
|
+
method: 'POST',
|
|
237
|
+
headers: {
|
|
238
|
+
'Content-Type': 'application/json',
|
|
239
|
+
'x-api-key': apiKey,
|
|
240
|
+
'anthropic-version': '2023-06-01',
|
|
241
|
+
},
|
|
242
|
+
body,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!res.ok) {
|
|
246
|
+
const errText = await res.text();
|
|
247
|
+
throw new Error(`Anthropic API ${res.status}: ${errText.substring(0, 200)}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let fullText = '';
|
|
251
|
+
const reader = res.body.getReader();
|
|
252
|
+
const decoder = new TextDecoder();
|
|
253
|
+
let buf = '';
|
|
254
|
+
|
|
255
|
+
while (true) {
|
|
256
|
+
const { done, value } = await reader.read();
|
|
257
|
+
if (done) break;
|
|
258
|
+
buf += decoder.decode(value, { stream: true });
|
|
259
|
+
const lines = buf.split('\n');
|
|
260
|
+
buf = lines.pop() || '';
|
|
261
|
+
for (const line of lines) {
|
|
262
|
+
if (!line.startsWith('data: ')) continue;
|
|
263
|
+
const data = line.slice(6);
|
|
264
|
+
if (data === '[DONE]') continue;
|
|
265
|
+
try {
|
|
266
|
+
const evt = JSON.parse(data);
|
|
267
|
+
if (evt.type === 'content_block_delta' && evt.delta?.text) {
|
|
268
|
+
fullText += evt.delta.text;
|
|
269
|
+
if (this.onUpdate) {
|
|
270
|
+
this.onUpdate({ update: { sessionUpdate: 'agent_message_chunk', content: { text: evt.delta.text } } });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch (_) {}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return { stopReason: 'end_turn', result: fullText };
|
|
278
|
+
}
|
|
279
|
+
|
|
337
280
|
isRunning() {
|
|
281
|
+
if (this.printMode) return true;
|
|
338
282
|
return this.child && !this.child.killed;
|
|
339
283
|
}
|
|
340
284
|
|
|
341
285
|
async terminate() {
|
|
286
|
+
if (this.printMode) { this.printMode = false; return; }
|
|
342
287
|
if (!this.child) return;
|
|
343
288
|
this.child.stdin.end();
|
|
344
289
|
this.child.kill('SIGTERM');
|