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 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.92",
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 cwd = '/config';
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 cwd = '/config';
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)