agentgui 1.0.396 → 1.0.398
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/package.json +3 -2
- package/scripts/patch-fsbrowse.js +88 -0
- package/server.js +10 -2
- package/static/js/conversations.js +7 -3
- package/test-wave4-ui.mjs +141 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.398",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"homepage": "https://github.com/AnEntrypoint/agentgui#readme",
|
|
18
18
|
"scripts": {
|
|
19
19
|
"start": "node server.js",
|
|
20
|
-
"dev": "node server.js --watch"
|
|
20
|
+
"dev": "node server.js --watch",
|
|
21
|
+
"postinstall": "node scripts/patch-fsbrowse.js"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"@anthropic-ai/claude-code": "^2.1.37",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Patch script to fix Windows path duplication issue in fsbrowse
|
|
4
|
+
* Fixes: Error ENOENT: no such file or directory, scandir 'C:\C:\dev'
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
const fsbrowsePath = path.join(__dirname, '..', 'node_modules', 'fsbrowse', 'index.js');
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(fsbrowsePath)) {
|
|
16
|
+
console.warn('[PATCH] fsbrowse not found, skipping patch');
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
let content = fs.readFileSync(fsbrowsePath, 'utf8');
|
|
22
|
+
|
|
23
|
+
// Check if patch is already applied
|
|
24
|
+
if (content.includes('sanitizedIsAbsoluteOnDrive')) {
|
|
25
|
+
console.log('[PATCH] fsbrowse Windows path fix already applied');
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Replace the makeResolver function with the fixed version
|
|
30
|
+
const oldMakeResolver = `function makeResolver(baseDir) {
|
|
31
|
+
return function resolveWithBaseDir(relPath) {
|
|
32
|
+
const sanitized = sanitizePath(relPath);
|
|
33
|
+
const fullPath = path.resolve(baseDir, sanitized);
|
|
34
|
+
if (!fullPath.startsWith(baseDir)) {
|
|
35
|
+
return { ok: false, error: 'EPATHINJECTION' };
|
|
36
|
+
}
|
|
37
|
+
return { ok: true, path: fullPath };
|
|
38
|
+
};
|
|
39
|
+
}`;
|
|
40
|
+
|
|
41
|
+
const newMakeResolver = `function makeResolver(baseDir) {
|
|
42
|
+
const normalizedBase = path.normalize(baseDir);
|
|
43
|
+
const baseDriveLetter = normalizedBase.match(/^[A-Z]:/i)?.[0];
|
|
44
|
+
|
|
45
|
+
return function resolveWithBaseDir(relPath) {
|
|
46
|
+
const sanitized = sanitizePath(relPath);
|
|
47
|
+
let fullPath;
|
|
48
|
+
|
|
49
|
+
// Extract drive letter from both paths to check for same-drive duplication on Windows
|
|
50
|
+
const sanitizedDriveLetter = sanitized.match(/^[A-Z]:/i)?.[0];
|
|
51
|
+
const sanitizedIsAbsoluteOnDrive = /^[A-Z]:/i.test(sanitized);
|
|
52
|
+
|
|
53
|
+
// If both paths are on the same Windows drive, strip the drive letter from relPath
|
|
54
|
+
// to avoid duplication like C:\\C:\\dev
|
|
55
|
+
if (baseDriveLetter && sanitizedIsAbsoluteOnDrive && sanitizedDriveLetter === baseDriveLetter) {
|
|
56
|
+
// Remove drive letter and leading slashes to make it relative
|
|
57
|
+
const relativePath = sanitized.replace(/^[A-Z]:[\/\\]?/i, '');
|
|
58
|
+
fullPath = path.resolve(normalizedBase, relativePath);
|
|
59
|
+
} else {
|
|
60
|
+
fullPath = path.resolve(normalizedBase, sanitized);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Normalize for consistent comparison
|
|
64
|
+
const normalizedFullPath = path.normalize(fullPath);
|
|
65
|
+
const normalizedComparisonBase = path.normalize(normalizedBase);
|
|
66
|
+
|
|
67
|
+
// Check path injection - convert backslashes to forward slashes for comparison
|
|
68
|
+
const normalizedCheck = normalizedFullPath.replace(/\\\\/g, '/');
|
|
69
|
+
const normalizedBaseCheck = normalizedComparisonBase.replace(/\\\\/g, '/');
|
|
70
|
+
|
|
71
|
+
if (!normalizedCheck.startsWith(normalizedBaseCheck)) {
|
|
72
|
+
return { ok: false, error: 'EPATHINJECTION' };
|
|
73
|
+
}
|
|
74
|
+
return { ok: true, path: normalizedFullPath };
|
|
75
|
+
};
|
|
76
|
+
}`;
|
|
77
|
+
|
|
78
|
+
if (content.includes(oldMakeResolver)) {
|
|
79
|
+
content = content.replace(oldMakeResolver, newMakeResolver);
|
|
80
|
+
fs.writeFileSync(fsbrowsePath, content, 'utf8');
|
|
81
|
+
console.log('[PATCH] fsbrowse Windows path fix applied successfully');
|
|
82
|
+
} else {
|
|
83
|
+
console.warn('[PATCH] Could not find makeResolver function to patch');
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error('[PATCH] Error applying fsbrowse patch:', err.message);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
package/server.js
CHANGED
|
@@ -295,8 +295,10 @@ expressApp.use(BASE_URL + '/files/:conversationId', (req, res, next) => {
|
|
|
295
295
|
if (!conv || !conv.workingDirectory) {
|
|
296
296
|
return res.status(404).json({ error: 'Conversation not found or no working directory' });
|
|
297
297
|
}
|
|
298
|
+
// Normalize the working directory path to avoid Windows path duplication issues
|
|
299
|
+
const normalizedWorkingDir = path.resolve(conv.workingDirectory);
|
|
298
300
|
// Create a fresh fsbrowse router for this conversation's directory
|
|
299
|
-
const router = fsbrowse({ baseDir:
|
|
301
|
+
const router = fsbrowse({ baseDir: normalizedWorkingDir, name: 'Files' });
|
|
300
302
|
// Strip the conversationId param from the path before passing to fsbrowse
|
|
301
303
|
req.baseUrl = BASE_URL + '/files/' + req.params.conversationId;
|
|
302
304
|
router(req, res, next);
|
|
@@ -1073,7 +1075,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
1073
1075
|
|
|
1074
1076
|
if (pathOnly === '/api/conversations' && req.method === 'POST') {
|
|
1075
1077
|
const body = await parseBody(req);
|
|
1076
|
-
|
|
1078
|
+
// Normalize working directory to avoid Windows path issues
|
|
1079
|
+
const normalizedWorkingDir = body.workingDirectory ? path.resolve(body.workingDirectory) : null;
|
|
1080
|
+
const conversation = queries.createConversation(body.agentId, body.title, normalizedWorkingDir, body.model || null);
|
|
1077
1081
|
queries.createEvent('conversation.created', { agentId: body.agentId, workingDirectory: conversation.workingDirectory, model: conversation.model }, conversation.id);
|
|
1078
1082
|
broadcastSync({ type: 'conversation_created', conversation });
|
|
1079
1083
|
sendJSON(req, res, 201, { conversation });
|
|
@@ -1099,6 +1103,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
1099
1103
|
|
|
1100
1104
|
if (req.method === 'POST' || req.method === 'PUT') {
|
|
1101
1105
|
const body = await parseBody(req);
|
|
1106
|
+
// Normalize working directory if present to avoid Windows path issues
|
|
1107
|
+
if (body.workingDirectory) {
|
|
1108
|
+
body.workingDirectory = path.resolve(body.workingDirectory);
|
|
1109
|
+
}
|
|
1102
1110
|
const conv = queries.updateConversation(convMatch[1], body);
|
|
1103
1111
|
if (!conv) { sendJSON(req, res, 404, { error: 'Conversation not found' }); return; }
|
|
1104
1112
|
queries.createEvent('conversation.updated', body, convMatch[1]);
|
|
@@ -420,8 +420,10 @@ class ConversationManager {
|
|
|
420
420
|
const isStreaming = this.streamingConversations.has(conv.id);
|
|
421
421
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
422
422
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
423
|
+
const agent = this.getAgentDisplayName(conv.agentId || conv.agentType);
|
|
424
|
+
const modelLabel = conv.model ? ` (${conv.model})` : '';
|
|
423
425
|
const wd = conv.workingDirectory ? pathBasename(conv.workingDirectory) : '';
|
|
424
|
-
const metaParts = [timestamp];
|
|
426
|
+
const metaParts = [agent + modelLabel, timestamp];
|
|
425
427
|
if (wd) metaParts.push(wd);
|
|
426
428
|
|
|
427
429
|
const titleEl = el.querySelector('.conversation-item-title');
|
|
@@ -446,8 +448,10 @@ class ConversationManager {
|
|
|
446
448
|
|
|
447
449
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
448
450
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
+
const agent = this.getAgentDisplayName(conv.agentId || conv.agentType);
|
|
452
|
+
const modelLabel = conv.model ? ` (${conv.model})` : '';
|
|
453
|
+
const wd = conv.workingDirectory ? pathBasename(conv.workingDirectory) : '';
|
|
454
|
+
const metaParts = [agent + modelLabel, timestamp];
|
|
451
455
|
if (wd) metaParts.push(wd);
|
|
452
456
|
|
|
453
457
|
const streamingBadge = isStreaming
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Wave 4 UI Consistency Test
|
|
4
|
+
* Tests agent/model persistence and display consolidation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import http from 'http';
|
|
8
|
+
|
|
9
|
+
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
|
10
|
+
const API_BASE = `${BASE_URL}/gm/api`;
|
|
11
|
+
|
|
12
|
+
function request(method, path, body = null) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const url = new URL(path, API_BASE);
|
|
15
|
+
const options = {
|
|
16
|
+
method,
|
|
17
|
+
headers: body ? { 'Content-Type': 'application/json' } : {}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const req = http.request(url, options, (res) => {
|
|
21
|
+
let data = '';
|
|
22
|
+
res.on('data', chunk => data += chunk);
|
|
23
|
+
res.on('end', () => {
|
|
24
|
+
try {
|
|
25
|
+
resolve({ status: res.statusCode, data: data ? JSON.parse(data) : null });
|
|
26
|
+
} catch (e) {
|
|
27
|
+
resolve({ status: res.statusCode, data });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
req.on('error', reject);
|
|
33
|
+
if (body) req.write(JSON.stringify(body));
|
|
34
|
+
req.end();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function test() {
|
|
39
|
+
console.log('=== Wave 4 UI Consistency Tests ===\n');
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Test 1: Create conversation with specific agent and model
|
|
43
|
+
console.log('Test 1: Create conversation with agent and model');
|
|
44
|
+
const createRes = await request('POST', '/conversations', {
|
|
45
|
+
agentId: 'claude-code',
|
|
46
|
+
title: 'Wave 4 Test Conversation',
|
|
47
|
+
workingDirectory: '/tmp/test',
|
|
48
|
+
model: 'claude-sonnet-4-5'
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (createRes.status !== 200) {
|
|
52
|
+
console.error('❌ Failed to create conversation:', createRes.status);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const conversation = createRes.data.conversation;
|
|
57
|
+
console.log('✓ Created conversation:', conversation.id);
|
|
58
|
+
console.log(' - agentId:', conversation.agentId);
|
|
59
|
+
console.log(' - model:', conversation.model);
|
|
60
|
+
|
|
61
|
+
// Test 2: Fetch conversation and verify agent/model are returned
|
|
62
|
+
console.log('\nTest 2: Fetch conversation via /full endpoint');
|
|
63
|
+
const fullRes = await request('GET', `/conversations/${conversation.id}/full`);
|
|
64
|
+
|
|
65
|
+
if (fullRes.status !== 200) {
|
|
66
|
+
console.error('❌ Failed to fetch conversation:', fullRes.status);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fullConv = fullRes.data.conversation;
|
|
71
|
+
console.log('✓ Fetched conversation');
|
|
72
|
+
console.log(' - agentId:', fullConv.agentId);
|
|
73
|
+
console.log(' - agentType:', fullConv.agentType);
|
|
74
|
+
console.log(' - model:', fullConv.model);
|
|
75
|
+
|
|
76
|
+
if (!fullConv.agentId && !fullConv.agentType) {
|
|
77
|
+
console.error('❌ agentId/agentType missing from response');
|
|
78
|
+
} else {
|
|
79
|
+
console.log('✓ agentId/agentType present');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!fullConv.model) {
|
|
83
|
+
console.error('❌ model missing from response');
|
|
84
|
+
} else {
|
|
85
|
+
console.log('✓ model present');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Test 3: List conversations and verify agent/model in list
|
|
89
|
+
console.log('\nTest 3: List conversations');
|
|
90
|
+
const listRes = await request('GET', '/conversations');
|
|
91
|
+
|
|
92
|
+
if (listRes.status !== 200) {
|
|
93
|
+
console.error('❌ Failed to list conversations:', listRes.status);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const listedConv = listRes.data.conversations.find(c => c.id === conversation.id);
|
|
98
|
+
if (!listedConv) {
|
|
99
|
+
console.error('❌ Conversation not found in list');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log('✓ Conversation in list');
|
|
104
|
+
console.log(' - agentId:', listedConv.agentId);
|
|
105
|
+
console.log(' - agentType:', listedConv.agentType);
|
|
106
|
+
console.log(' - model:', listedConv.model);
|
|
107
|
+
|
|
108
|
+
// Test 4: Update conversation model
|
|
109
|
+
console.log('\nTest 4: Update conversation model');
|
|
110
|
+
const updateRes = await request('POST', `/conversations/${conversation.id}`, {
|
|
111
|
+
model: 'claude-opus-4-6'
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (updateRes.status !== 200) {
|
|
115
|
+
console.error('❌ Failed to update conversation:', updateRes.status);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const updatedConv = updateRes.data.conversation;
|
|
120
|
+
console.log('✓ Updated conversation');
|
|
121
|
+
console.log(' - model:', updatedConv.model);
|
|
122
|
+
|
|
123
|
+
if (updatedConv.model !== 'claude-opus-4-6') {
|
|
124
|
+
console.error('❌ Model not updated correctly');
|
|
125
|
+
} else {
|
|
126
|
+
console.log('✓ Model updated correctly');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Cleanup
|
|
130
|
+
console.log('\nCleanup: Deleting test conversation');
|
|
131
|
+
await request('DELETE', `/conversations/${conversation.id}`);
|
|
132
|
+
console.log('✓ Deleted test conversation');
|
|
133
|
+
|
|
134
|
+
console.log('\n=== All Tests Passed ===');
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('❌ Test error:', error.message);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
test();
|