agentgui 1.0.92 → 1.0.94
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/.prd +1 -0
- package/database.js +7 -5
- package/package.json +5 -3
- package/server.js +82 -4
- package/static/index.html +527 -75
- package/static/js/client.js +17 -10
- package/static/js/conversations.js +165 -9
- package/static/js/features.js +243 -0
- package/static/styles.css +46 -4
package/.prd
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/database.js
CHANGED
|
@@ -197,7 +197,8 @@ try {
|
|
|
197
197
|
projectPath: 'TEXT',
|
|
198
198
|
gitBranch: 'TEXT',
|
|
199
199
|
sourcePath: 'TEXT',
|
|
200
|
-
lastSyncedAt: 'INTEGER'
|
|
200
|
+
lastSyncedAt: 'INTEGER',
|
|
201
|
+
workingDirectory: 'TEXT'
|
|
201
202
|
};
|
|
202
203
|
|
|
203
204
|
let addedColumns = false;
|
|
@@ -228,18 +229,19 @@ function generateId(prefix) {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
export const queries = {
|
|
231
|
-
createConversation(agentId, title = null) {
|
|
232
|
+
createConversation(agentId, title = null, workingDirectory = null) {
|
|
232
233
|
const id = generateId('conv');
|
|
233
234
|
const now = Date.now();
|
|
234
235
|
const stmt = db.prepare(
|
|
235
|
-
`INSERT INTO conversations (id, agentId, title, created_at, updated_at, status) VALUES (?, ?, ?, ?, ?, ?)`
|
|
236
|
+
`INSERT INTO conversations (id, agentId, title, created_at, updated_at, status, workingDirectory) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
236
237
|
);
|
|
237
|
-
stmt.run(id, agentId, title, now, now, 'active');
|
|
238
|
+
stmt.run(id, agentId, title, now, now, 'active', workingDirectory);
|
|
238
239
|
|
|
239
240
|
return {
|
|
240
241
|
id,
|
|
241
242
|
agentId,
|
|
242
243
|
title,
|
|
244
|
+
workingDirectory,
|
|
243
245
|
created_at: now,
|
|
244
246
|
updated_at: now,
|
|
245
247
|
status: 'active'
|
|
@@ -258,7 +260,7 @@ export const queries = {
|
|
|
258
260
|
|
|
259
261
|
getConversationsList() {
|
|
260
262
|
const stmt = db.prepare(
|
|
261
|
-
'SELECT id, title, agentType, created_at, updated_at, messageCount FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
263
|
+
'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
262
264
|
);
|
|
263
265
|
return stmt.all('deleted');
|
|
264
266
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.94",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@anthropic-ai/claude-code": "^1.0.128",
|
|
25
25
|
"better-sqlite3": "^12.6.2",
|
|
26
|
+
"busboy": "^1.6.0",
|
|
27
|
+
"express": "^5.2.1",
|
|
28
|
+
"fsbrowse": "file:../fsbrowse",
|
|
26
29
|
"ws": "^8.14.2"
|
|
27
|
-
}
|
|
28
|
-
"devDependencies": {}
|
|
30
|
+
}
|
|
29
31
|
}
|
package/server.js
CHANGED
|
@@ -4,9 +4,15 @@ import path from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { WebSocketServer } from 'ws';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
|
+
import { createRequire } from 'module';
|
|
7
8
|
import { queries } from './database.js';
|
|
8
9
|
import { runClaudeWithStreaming } from './lib/claude-runner.js';
|
|
9
10
|
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const express = require('express');
|
|
13
|
+
const Busboy = require('busboy');
|
|
14
|
+
const fsbrowse = require('fsbrowse');
|
|
15
|
+
|
|
10
16
|
// System prompt for Claude to format responses as HTML
|
|
11
17
|
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
18
|
|
|
@@ -24,6 +30,70 @@ const watch = process.argv.includes('--no-watch') ? false : (process.argv.includ
|
|
|
24
30
|
const staticDir = path.join(__dirname, 'static');
|
|
25
31
|
if (!fs.existsSync(staticDir)) fs.mkdirSync(staticDir, { recursive: true });
|
|
26
32
|
|
|
33
|
+
// Express sub-app for fsbrowse file browser and file upload
|
|
34
|
+
const expressApp = express();
|
|
35
|
+
|
|
36
|
+
// File upload endpoint - copies dropped files to conversation workingDirectory
|
|
37
|
+
expressApp.post(BASE_URL + '/api/upload/:conversationId', (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const conv = queries.getConversation(req.params.conversationId);
|
|
40
|
+
if (!conv) return res.status(404).json({ error: 'Conversation not found' });
|
|
41
|
+
if (!conv.workingDirectory) return res.status(400).json({ error: 'No working directory set for this conversation' });
|
|
42
|
+
|
|
43
|
+
const uploadDir = conv.workingDirectory;
|
|
44
|
+
if (!fs.existsSync(uploadDir)) {
|
|
45
|
+
fs.mkdirSync(uploadDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const bb = Busboy({ headers: req.headers });
|
|
49
|
+
const fileNames = [];
|
|
50
|
+
const writePromises = [];
|
|
51
|
+
|
|
52
|
+
bb.on('file', (fieldname, file, info) => {
|
|
53
|
+
const safeName = path.basename(info.filename);
|
|
54
|
+
const filePath = path.join(uploadDir, safeName);
|
|
55
|
+
fileNames.push(safeName);
|
|
56
|
+
const p = new Promise((resolve) => {
|
|
57
|
+
const writeStream = fs.createWriteStream(filePath);
|
|
58
|
+
file.pipe(writeStream);
|
|
59
|
+
writeStream.on('finish', resolve);
|
|
60
|
+
writeStream.on('error', () => { file.resume(); resolve(); });
|
|
61
|
+
});
|
|
62
|
+
writePromises.push(p);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
bb.on('finish', () => {
|
|
66
|
+
Promise.all(writePromises).then(() => {
|
|
67
|
+
res.json({ ok: true, files: fileNames, count: fileNames.length });
|
|
68
|
+
}).catch(() => {
|
|
69
|
+
res.json({ ok: true, files: fileNames, count: fileNames.length });
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
bb.on('error', (err) => {
|
|
74
|
+
res.status(500).json({ error: 'Upload failed: ' + err.message });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
req.pipe(bb);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
res.status(500).json({ error: err.message });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// fsbrowse file browser - mounted per conversation workingDirectory
|
|
84
|
+
// Route: /gm/files/:conversationId/*
|
|
85
|
+
expressApp.use(BASE_URL + '/files/:conversationId', (req, res, next) => {
|
|
86
|
+
const conv = queries.getConversation(req.params.conversationId);
|
|
87
|
+
if (!conv || !conv.workingDirectory) {
|
|
88
|
+
return res.status(404).json({ error: 'Conversation not found or no working directory' });
|
|
89
|
+
}
|
|
90
|
+
// Create a fresh fsbrowse router for this conversation's directory
|
|
91
|
+
const router = fsbrowse({ baseDir: conv.workingDirectory });
|
|
92
|
+
// Strip the conversationId param from the path before passing to fsbrowse
|
|
93
|
+
req.baseUrl = BASE_URL + '/files/' + req.params.conversationId;
|
|
94
|
+
router(req, res, next);
|
|
95
|
+
});
|
|
96
|
+
|
|
27
97
|
function discoverAgents() {
|
|
28
98
|
const agents = [];
|
|
29
99
|
const binaries = [
|
|
@@ -59,6 +129,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
59
129
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
60
130
|
if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; }
|
|
61
131
|
|
|
132
|
+
// Route file upload and fsbrowse requests through Express sub-app
|
|
133
|
+
const pathOnly = req.url.split('?')[0];
|
|
134
|
+
if (pathOnly.startsWith(BASE_URL + '/api/upload/') || pathOnly.startsWith(BASE_URL + '/files/')) {
|
|
135
|
+
return expressApp(req, res);
|
|
136
|
+
}
|
|
137
|
+
|
|
62
138
|
if (req.url === '/') { res.writeHead(302, { Location: BASE_URL + '/' }); res.end(); return; }
|
|
63
139
|
|
|
64
140
|
if (!req.url.startsWith(BASE_URL + '/') && req.url !== BASE_URL) {
|
|
@@ -79,8 +155,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
79
155
|
|
|
80
156
|
if (pathOnly === '/api/conversations' && req.method === 'POST') {
|
|
81
157
|
const body = await parseBody(req);
|
|
82
|
-
const conversation = queries.createConversation(body.agentId, body.title);
|
|
83
|
-
queries.createEvent('conversation.created', { agentId: body.agentId }, conversation.id);
|
|
158
|
+
const conversation = queries.createConversation(body.agentId, body.title, body.workingDirectory || null);
|
|
159
|
+
queries.createEvent('conversation.created', { agentId: body.agentId, workingDirectory: conversation.workingDirectory }, conversation.id);
|
|
84
160
|
broadcastSync({ type: 'conversation_created', conversation });
|
|
85
161
|
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
86
162
|
res.end(JSON.stringify({ conversation }));
|
|
@@ -377,7 +453,8 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
377
453
|
try {
|
|
378
454
|
debugLog(`[stream] Starting: conversationId=${conversationId}, sessionId=${sessionId}, agentId=${agentId}, skipPermissions=${skipPermissions}`);
|
|
379
455
|
|
|
380
|
-
const
|
|
456
|
+
const conv = queries.getConversation(conversationId);
|
|
457
|
+
const cwd = conv?.workingDirectory || '/config';
|
|
381
458
|
const actualAgentId = agentId || 'claude-code';
|
|
382
459
|
|
|
383
460
|
debugLog(`[stream] Calling runClaudeWithStreaming with config: skipPermissions=${skipPermissions}`);
|
|
@@ -505,7 +582,8 @@ async function processMessage(conversationId, messageId, content, agentId) {
|
|
|
505
582
|
try {
|
|
506
583
|
debugLog(`[processMessage] Starting: conversationId=${conversationId}, agentId=${agentId}`);
|
|
507
584
|
|
|
508
|
-
const
|
|
585
|
+
const conv = queries.getConversation(conversationId);
|
|
586
|
+
const cwd = conv?.workingDirectory || '/config';
|
|
509
587
|
const actualAgentId = agentId || 'claude-code';
|
|
510
588
|
|
|
511
589
|
// Handle both string content and object content (for structured messages)
|