agentgui 1.0.5 → 1.0.6
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/database.js +23 -32
- package/package.json +2 -1
- package/server.js +8 -2
- package/static/app.js +39 -11
package/database.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
+
import { createRequire } from 'module';
|
|
4
5
|
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
5
7
|
const dbDir = path.join(os.homedir(), '.gmgui');
|
|
6
8
|
const dbFilePath = path.join(dbDir, 'data.db');
|
|
7
9
|
const oldJsonPath = path.join(dbDir, 'data.json');
|
|
@@ -28,7 +30,7 @@ try {
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
function initSchema() {
|
|
31
|
-
db.
|
|
33
|
+
db.exec(`
|
|
32
34
|
CREATE TABLE IF NOT EXISTS conversations (
|
|
33
35
|
id TEXT PRIMARY KEY,
|
|
34
36
|
agentId TEXT NOT NULL,
|
|
@@ -36,13 +38,11 @@ function initSchema() {
|
|
|
36
38
|
created_at INTEGER NOT NULL,
|
|
37
39
|
updated_at INTEGER NOT NULL,
|
|
38
40
|
status TEXT DEFAULT 'active'
|
|
39
|
-
)
|
|
40
|
-
`);
|
|
41
|
+
);
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_agent ON conversations(agentId);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_updated ON conversations(updated_at DESC);
|
|
44
45
|
|
|
45
|
-
db.run(`
|
|
46
46
|
CREATE TABLE IF NOT EXISTS messages (
|
|
47
47
|
id TEXT PRIMARY KEY,
|
|
48
48
|
conversationId TEXT NOT NULL,
|
|
@@ -50,12 +50,10 @@ function initSchema() {
|
|
|
50
50
|
content TEXT NOT NULL,
|
|
51
51
|
created_at INTEGER NOT NULL,
|
|
52
52
|
FOREIGN KEY (conversationId) REFERENCES conversations(id)
|
|
53
|
-
)
|
|
54
|
-
`);
|
|
53
|
+
);
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_messages_conversation ON messages(conversationId);
|
|
57
56
|
|
|
58
|
-
db.run(`
|
|
59
57
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
60
58
|
id TEXT PRIMARY KEY,
|
|
61
59
|
conversationId TEXT NOT NULL,
|
|
@@ -65,13 +63,11 @@ function initSchema() {
|
|
|
65
63
|
response TEXT,
|
|
66
64
|
error TEXT,
|
|
67
65
|
FOREIGN KEY (conversationId) REFERENCES conversations(id)
|
|
68
|
-
)
|
|
69
|
-
`);
|
|
66
|
+
);
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_conversation ON sessions(conversationId);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(conversationId, status);
|
|
73
70
|
|
|
74
|
-
db.run(`
|
|
75
71
|
CREATE TABLE IF NOT EXISTS events (
|
|
76
72
|
id TEXT PRIMARY KEY,
|
|
77
73
|
type TEXT NOT NULL,
|
|
@@ -81,21 +77,19 @@ function initSchema() {
|
|
|
81
77
|
created_at INTEGER NOT NULL,
|
|
82
78
|
FOREIGN KEY (conversationId) REFERENCES conversations(id),
|
|
83
79
|
FOREIGN KEY (sessionId) REFERENCES sessions(id)
|
|
84
|
-
)
|
|
85
|
-
`);
|
|
80
|
+
);
|
|
86
81
|
|
|
87
|
-
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_events_conversation ON events(conversationId);
|
|
88
83
|
|
|
89
|
-
db.run(`
|
|
90
84
|
CREATE TABLE IF NOT EXISTS idempotencyKeys (
|
|
91
85
|
key TEXT PRIMARY KEY,
|
|
92
86
|
value TEXT NOT NULL,
|
|
93
87
|
created_at INTEGER NOT NULL,
|
|
94
88
|
ttl INTEGER NOT NULL
|
|
95
|
-
)
|
|
96
|
-
`);
|
|
89
|
+
);
|
|
97
90
|
|
|
98
|
-
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_idempotency_created ON idempotencyKeys(created_at);
|
|
92
|
+
`);
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
function migrateFromJson() {
|
|
@@ -393,10 +387,10 @@ export const queries = {
|
|
|
393
387
|
const conv = this.getConversation(id);
|
|
394
388
|
if (!conv) return false;
|
|
395
389
|
|
|
396
|
-
db.
|
|
397
|
-
db.
|
|
398
|
-
db.
|
|
399
|
-
db.
|
|
390
|
+
db.prepare('DELETE FROM events WHERE conversationId = ?').run(id);
|
|
391
|
+
db.prepare('DELETE FROM sessions WHERE conversationId = ?').run(id);
|
|
392
|
+
db.prepare('DELETE FROM messages WHERE conversationId = ?').run(id);
|
|
393
|
+
db.prepare('DELETE FROM conversations WHERE id = ?').run(id);
|
|
400
394
|
|
|
401
395
|
return true;
|
|
402
396
|
},
|
|
@@ -404,14 +398,11 @@ export const queries = {
|
|
|
404
398
|
cleanup() {
|
|
405
399
|
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
|
406
400
|
|
|
407
|
-
db.
|
|
408
|
-
db.run(
|
|
409
|
-
'DELETE FROM sessions WHERE completed_at IS NOT NULL AND completed_at < ?',
|
|
410
|
-
[thirtyDaysAgo]
|
|
411
|
-
);
|
|
401
|
+
db.prepare('DELETE FROM events WHERE created_at < ?').run(thirtyDaysAgo);
|
|
402
|
+
db.prepare('DELETE FROM sessions WHERE completed_at IS NOT NULL AND completed_at < ?').run(thirtyDaysAgo);
|
|
412
403
|
|
|
413
404
|
const now = Date.now();
|
|
414
|
-
db.
|
|
405
|
+
db.prepare('DELETE FROM idempotencyKeys WHERE (created_at + ttl) < ?').run(now);
|
|
415
406
|
},
|
|
416
407
|
|
|
417
408
|
setIdempotencyKey(key, value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"test:all": "npm run test:integration && npm run test"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"better-sqlite3": "^12.6.2",
|
|
29
30
|
"ws": "^8.14.2"
|
|
30
31
|
}
|
|
31
32
|
}
|
package/server.js
CHANGED
|
@@ -11,7 +11,7 @@ import ACPConnection from './acp-launcher.js';
|
|
|
11
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const PORT = process.env.PORT || 3000;
|
|
13
13
|
const BASE_URL = (process.env.BASE_URL || '/gm').replace(/\/+$/, '');
|
|
14
|
-
const watch = process.argv.includes('--watch');
|
|
14
|
+
const watch = process.argv.includes('--no-watch') ? false : (process.argv.includes('--watch') || process.env.HOT_RELOAD !== 'false');
|
|
15
15
|
|
|
16
16
|
const staticDir = path.join(__dirname, 'static');
|
|
17
17
|
if (!fs.existsSync(staticDir)) fs.mkdirSync(staticDir, { recursive: true });
|
|
@@ -126,6 +126,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
126
126
|
if (req.method === 'DELETE') {
|
|
127
127
|
const deleted = queries.deleteConversation(convMatch[1]);
|
|
128
128
|
if (!deleted) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Not found' })); return; }
|
|
129
|
+
broadcastSync({ type: 'conversation_deleted', conversationId: convMatch[1] });
|
|
129
130
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
130
131
|
res.end(JSON.stringify({ deleted: true }));
|
|
131
132
|
return;
|
|
@@ -290,7 +291,7 @@ function serveFile(filePath, res) {
|
|
|
290
291
|
if (err) { res.writeHead(500); res.end('Server error'); return; }
|
|
291
292
|
let content = data.toString();
|
|
292
293
|
if (ext === '.html') {
|
|
293
|
-
const baseTag = `<script>window.__BASE_URL='${BASE_URL}'
|
|
294
|
+
const baseTag = `<script>window.__BASE_URL='${BASE_URL}';</script>`;
|
|
294
295
|
content = content.replace('<head>', '<head>\n ' + baseTag);
|
|
295
296
|
if (watch) {
|
|
296
297
|
content += `\n<script>(function(){const ws=new WebSocket('ws://'+location.host+'${BASE_URL}/hot-reload');ws.onmessage=e=>{if(JSON.parse(e.data).type==='reload')location.reload()};})();</script>`;
|
|
@@ -421,6 +422,11 @@ function onServerReady() {
|
|
|
421
422
|
console.log(`GMGUI running on http://localhost:${PORT}${BASE_URL}/`);
|
|
422
423
|
console.log(`Agents: ${discoveredAgents.map(a => a.name).join(', ') || 'none'}`);
|
|
423
424
|
console.log(`Hot reload: ${watch ? 'on' : 'off'}`);
|
|
425
|
+
// Auto-import Claude Code conversations
|
|
426
|
+
const imported = queries.importClaudeCodeConversations();
|
|
427
|
+
if (imported.length > 0) {
|
|
428
|
+
console.log(`Auto-imported ${imported.filter(i => i.status === 'imported').length} Claude Code conversations`);
|
|
429
|
+
}
|
|
424
430
|
}
|
|
425
431
|
|
|
426
432
|
server.listen(PORT, onServerReady);
|
package/static/app.js
CHANGED
|
@@ -98,12 +98,31 @@ class GMGUIApp {
|
|
|
98
98
|
this.setupEventListeners();
|
|
99
99
|
await this.fetchHome();
|
|
100
100
|
await this.fetchAgents();
|
|
101
|
+
await this.autoImportClaudeCode();
|
|
101
102
|
await this.fetchConversations();
|
|
102
103
|
this.connectSyncWebSocket();
|
|
103
104
|
this.setupCrossTabSync();
|
|
105
|
+
this.startPeriodicSync();
|
|
104
106
|
this.renderAll();
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
startPeriodicSync() {
|
|
110
|
+
// Rapid sync every 10 seconds: check for new Claude Code conversations and sync
|
|
111
|
+
setInterval(() => {
|
|
112
|
+
this.autoImportClaudeCode().then(() => {
|
|
113
|
+
this.fetchConversations().then(() => this.renderChatHistory());
|
|
114
|
+
});
|
|
115
|
+
}, 10000);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async autoImportClaudeCode() {
|
|
119
|
+
try {
|
|
120
|
+
await fetch(BASE_URL + '/api/import/claude-code');
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error('autoImportClaudeCode:', e);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
107
126
|
connectSyncWebSocket() {
|
|
108
127
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
109
128
|
this.syncWs = new ReconnectingWebSocket(
|
|
@@ -259,6 +278,11 @@ class GMGUIApp {
|
|
|
259
278
|
}
|
|
260
279
|
|
|
261
280
|
setupEventListeners() {
|
|
281
|
+
window.addEventListener('focus', () => {
|
|
282
|
+
this.autoImportClaudeCode().then(() => {
|
|
283
|
+
this.fetchConversations().then(() => this.renderChatHistory());
|
|
284
|
+
});
|
|
285
|
+
});
|
|
262
286
|
const input = document.getElementById('messageInput');
|
|
263
287
|
if (input) {
|
|
264
288
|
input.addEventListener('keydown', (e) => {
|
|
@@ -396,20 +420,24 @@ class GMGUIApp {
|
|
|
396
420
|
async deleteConversation(id) {
|
|
397
421
|
try {
|
|
398
422
|
const res = await fetch(`${BASE_URL}/api/conversations/${id}`, { method: 'DELETE' });
|
|
423
|
+
if (!res.ok) {
|
|
424
|
+
console.error('deleteConversation failed:', res.status);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
this.conversations.delete(id);
|
|
428
|
+
if (this.currentConversation === id) {
|
|
429
|
+
this.currentConversation = null;
|
|
430
|
+
const first = Array.from(this.conversations.values())[0];
|
|
431
|
+
if (first) {
|
|
432
|
+
this.displayConversation(first.id);
|
|
433
|
+
} else {
|
|
434
|
+
this.showWelcome();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
this.renderChatHistory();
|
|
399
438
|
} catch (e) {
|
|
400
439
|
console.error('deleteConversation:', e);
|
|
401
440
|
}
|
|
402
|
-
this.conversations.delete(id);
|
|
403
|
-
if (this.currentConversation === id) {
|
|
404
|
-
this.currentConversation = null;
|
|
405
|
-
const first = Array.from(this.conversations.values())[0];
|
|
406
|
-
if (first) {
|
|
407
|
-
this.displayConversation(first.id);
|
|
408
|
-
} else {
|
|
409
|
-
this.showWelcome();
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
this.renderChatHistory();
|
|
413
441
|
}
|
|
414
442
|
|
|
415
443
|
showWelcome() {
|