agentgui 1.0.833 → 1.0.834
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/CHANGELOG.md +1 -0
- package/lib/db-queries-chunks.js +195 -0
- package/lib/db-queries-chunks2.js +82 -0
- package/lib/db-queries-cleanup.js +85 -0
- package/lib/db-queries-del.js +142 -0
- package/lib/db-queries-events.js +68 -0
- package/lib/db-queries-import.js +133 -0
- package/lib/db-queries-messages.js +102 -0
- package/lib/db-queries-sessions.js +112 -0
- package/lib/db-queries-streams.js +100 -0
- package/lib/db-queries-tools.js +127 -0
- package/lib/db-queries-voice.js +85 -0
- package/lib/db-queries.js +92 -1412
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
### Refactor
|
|
4
4
|
- Split database.js (651L) into database.js (81L) + database-schema.js (176L) + database-migrations.js (150L) + database-migrations-acp.js (134L); all files ≤200L; no circular imports; migration functions receive db as parameter
|
|
5
5
|
- Split claude-runner.js (1267L) into claude-runner.js (56L, AgentRunner class+helpers), claude-runner-direct.js (117L, runDirect method), claude-runner-acp.js (156L, runACP+_runACPOnce methods), claude-runner-agents.js (105L, AgentRegistry+registrations using acp-protocol.js), claude-runner-run.js (50L, runClaudeWithStreaming export); server.js updated to import from claude-runner-run.js
|
|
6
|
+
- Split db-queries.js (1413L) into db-queries.js (93L, factory + conv queries), db-queries-messages.js (103L), db-queries-sessions.js (113L), db-queries-events.js (69L), db-queries-del.js (143L), db-queries-cleanup.js (86L), db-queries-import.js (134L), db-queries-streams.js (101L), db-queries-chunks.js (196L), db-queries-chunks2.js (83L), db-queries-voice.js (86L), db-queries-tools.js (128L); all ≤200L; each exports addXxxQueries(q, db, prep, generateId); factory calls all helpers and returns q
|
|
6
7
|
|
|
7
8
|
## 2026-04-12
|
|
8
9
|
- refactor: reduce speech-manager.js from 207L to 200L by removing excess blank lines
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
export function addChunkQueries(q, db, prep, generateId) {
|
|
2
|
+
q.createChunk = function(sessionId, conversationId, sequence, type, data) {
|
|
3
|
+
const id = generateId('chunk');
|
|
4
|
+
const now = Date.now();
|
|
5
|
+
const dataBlob = typeof data === 'string' ? data : JSON.stringify(data);
|
|
6
|
+
|
|
7
|
+
const stmt = prep(
|
|
8
|
+
`INSERT INTO chunks (id, sessionId, conversationId, sequence, type, data, created_at)
|
|
9
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
10
|
+
);
|
|
11
|
+
stmt.run(id, sessionId, conversationId, sequence, type, dataBlob, now);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
id,
|
|
15
|
+
sessionId,
|
|
16
|
+
conversationId,
|
|
17
|
+
sequence,
|
|
18
|
+
type,
|
|
19
|
+
data,
|
|
20
|
+
created_at: now
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
q.getChunk = function(id) {
|
|
25
|
+
const stmt = prep(
|
|
26
|
+
`SELECT id, sessionId, conversationId, sequence, type, data, created_at FROM chunks WHERE id = ?`
|
|
27
|
+
);
|
|
28
|
+
const row = stmt.get(id);
|
|
29
|
+
if (!row) return null;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
return {
|
|
33
|
+
...row,
|
|
34
|
+
data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data
|
|
35
|
+
};
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return row;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
q.getSessionChunks = function(sessionId) {
|
|
42
|
+
const stmt = prep(
|
|
43
|
+
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
44
|
+
FROM chunks WHERE sessionId = ? ORDER BY sequence ASC`
|
|
45
|
+
);
|
|
46
|
+
const rows = stmt.all(sessionId);
|
|
47
|
+
return rows.map(row => {
|
|
48
|
+
try {
|
|
49
|
+
return {
|
|
50
|
+
...row,
|
|
51
|
+
data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data
|
|
52
|
+
};
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return row;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
q.getConversationChunkCount = function(conversationId) {
|
|
60
|
+
const stmt = prep('SELECT COUNT(*) as count FROM chunks WHERE conversationId = ?');
|
|
61
|
+
return stmt.get(conversationId).count;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
q.getConversationChunks = function(conversationId) {
|
|
65
|
+
const stmt = prep(
|
|
66
|
+
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
67
|
+
FROM chunks WHERE conversationId = ? ORDER BY created_at ASC`
|
|
68
|
+
);
|
|
69
|
+
const rows = stmt.all(conversationId);
|
|
70
|
+
return rows.map(row => {
|
|
71
|
+
try {
|
|
72
|
+
return {
|
|
73
|
+
...row,
|
|
74
|
+
data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data
|
|
75
|
+
};
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return row;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
q.getConversationChunksSince = function(conversationId, since) {
|
|
83
|
+
const stmt = prep(
|
|
84
|
+
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
85
|
+
FROM chunks WHERE conversationId = ? AND created_at > ? ORDER BY created_at ASC`
|
|
86
|
+
);
|
|
87
|
+
const rows = stmt.all(conversationId, since);
|
|
88
|
+
return rows.map(row => {
|
|
89
|
+
try { return { ...row, data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data }; }
|
|
90
|
+
catch (e) { return row; }
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
q.getRecentConversationChunks = function(conversationId, limit = 500) {
|
|
95
|
+
const sessions = prep(
|
|
96
|
+
`SELECT sessionId, COUNT(*) as n FROM chunks WHERE conversationId = ?
|
|
97
|
+
GROUP BY sessionId ORDER BY MAX(created_at) DESC`
|
|
98
|
+
).all(conversationId);
|
|
99
|
+
if (!sessions.length) return [];
|
|
100
|
+
const included = [];
|
|
101
|
+
let total = 0;
|
|
102
|
+
for (const s of sessions) {
|
|
103
|
+
if (total + s.n > limit && included.length > 0) break;
|
|
104
|
+
included.unshift(s.sessionId);
|
|
105
|
+
total += s.n;
|
|
106
|
+
}
|
|
107
|
+
const placeholders = included.map(() => '?').join(',');
|
|
108
|
+
const rows = prep(
|
|
109
|
+
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
110
|
+
FROM chunks WHERE conversationId = ? AND sessionId IN (${placeholders})
|
|
111
|
+
ORDER BY created_at ASC`
|
|
112
|
+
).all(conversationId, ...included);
|
|
113
|
+
return rows.map(row => {
|
|
114
|
+
try { return { ...row, data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data }; }
|
|
115
|
+
catch (e) { return row; }
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
q.getRecentMessages = function(conversationId, limit = 20) {
|
|
120
|
+
const countStmt = prep('SELECT COUNT(*) as count FROM messages WHERE conversationId = ?');
|
|
121
|
+
const total = countStmt.get(conversationId).count;
|
|
122
|
+
|
|
123
|
+
const stmt = prep(
|
|
124
|
+
'SELECT * FROM messages WHERE conversationId = ? ORDER BY created_at DESC LIMIT ? '
|
|
125
|
+
);
|
|
126
|
+
const messages = stmt.all(conversationId, limit).reverse();
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
messages: messages.map(msg => {
|
|
130
|
+
if (typeof msg.content === 'string') {
|
|
131
|
+
try {
|
|
132
|
+
msg.content = JSON.parse(msg.content);
|
|
133
|
+
} catch (_) {}
|
|
134
|
+
}
|
|
135
|
+
return msg;
|
|
136
|
+
}),
|
|
137
|
+
total,
|
|
138
|
+
limit,
|
|
139
|
+
offset: Math.max(0, total - limit),
|
|
140
|
+
hasMore: total > limit
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
q.getMessagesBefore = function(conversationId, beforeId, limit = 50) {
|
|
145
|
+
const countStmt = prep('SELECT COUNT(*) as count FROM messages WHERE conversationId = ?');
|
|
146
|
+
const total = countStmt.get(conversationId).count;
|
|
147
|
+
|
|
148
|
+
const stmt = prep(`
|
|
149
|
+
SELECT * FROM messages
|
|
150
|
+
WHERE conversationId = ? AND id < (SELECT id FROM messages WHERE id = ?)
|
|
151
|
+
ORDER BY created_at DESC LIMIT ?
|
|
152
|
+
`);
|
|
153
|
+
const messages = stmt.all(conversationId, beforeId, limit).reverse();
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
messages: messages.map(msg => {
|
|
157
|
+
if (typeof msg.content === 'string') {
|
|
158
|
+
try {
|
|
159
|
+
msg.content = JSON.parse(msg.content);
|
|
160
|
+
} catch (_) {}
|
|
161
|
+
}
|
|
162
|
+
return msg;
|
|
163
|
+
}),
|
|
164
|
+
total,
|
|
165
|
+
limit,
|
|
166
|
+
hasMore: total > (limit + 1)
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
q.getChunksBefore = function(conversationId, beforeTimestamp, limit = 500) {
|
|
171
|
+
const total = prep('SELECT COUNT(*) as count FROM chunks WHERE conversationId = ? AND created_at < ?')
|
|
172
|
+
.get(conversationId, beforeTimestamp).count;
|
|
173
|
+
|
|
174
|
+
const rows = prep(`
|
|
175
|
+
SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
176
|
+
FROM chunks
|
|
177
|
+
WHERE conversationId = ? AND created_at < ?
|
|
178
|
+
ORDER BY created_at DESC LIMIT ?
|
|
179
|
+
`).all(conversationId, beforeTimestamp, limit);
|
|
180
|
+
rows.reverse();
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
chunks: rows.map(row => {
|
|
184
|
+
try {
|
|
185
|
+
return { ...row, data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data };
|
|
186
|
+
} catch (e) {
|
|
187
|
+
return row;
|
|
188
|
+
}
|
|
189
|
+
}),
|
|
190
|
+
total,
|
|
191
|
+
limit,
|
|
192
|
+
hasMore: rows.length === limit
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export function addChunkQueries2(q, db, prep, generateId) {
|
|
2
|
+
q.getChunksSince = function(sessionId, timestamp) {
|
|
3
|
+
const stmt = prep(
|
|
4
|
+
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
5
|
+
FROM chunks WHERE sessionId = ? AND created_at > ? ORDER BY sequence ASC`
|
|
6
|
+
);
|
|
7
|
+
const rows = stmt.all(sessionId, timestamp);
|
|
8
|
+
return rows.map(row => {
|
|
9
|
+
try {
|
|
10
|
+
return {
|
|
11
|
+
...row,
|
|
12
|
+
data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data
|
|
13
|
+
};
|
|
14
|
+
} catch (e) {
|
|
15
|
+
return row;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
q.getChunksSinceSeq = function(sessionId, sinceSeq) {
|
|
21
|
+
const stmt = prep(
|
|
22
|
+
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
23
|
+
FROM chunks WHERE sessionId = ? AND sequence > ? ORDER BY sequence ASC`
|
|
24
|
+
);
|
|
25
|
+
const rows = stmt.all(sessionId, sinceSeq);
|
|
26
|
+
return rows.map(row => {
|
|
27
|
+
try {
|
|
28
|
+
return {
|
|
29
|
+
...row,
|
|
30
|
+
data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data
|
|
31
|
+
};
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return row;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
q.deleteSessionChunks = function(sessionId) {
|
|
39
|
+
const stmt = prep('DELETE FROM chunks WHERE sessionId = ?');
|
|
40
|
+
const result = stmt.run(sessionId);
|
|
41
|
+
return result.changes || 0;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
q.getMaxSequence = function(sessionId) {
|
|
45
|
+
const stmt = prep('SELECT MAX(sequence) as max FROM chunks WHERE sessionId = ?');
|
|
46
|
+
const result = stmt.get(sessionId);
|
|
47
|
+
return result?.max ?? -1;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
q.getEmptyConversations = function() {
|
|
51
|
+
const stmt = prep(`
|
|
52
|
+
SELECT c.* FROM conversations c
|
|
53
|
+
LEFT JOIN messages m ON c.id = m.conversationId
|
|
54
|
+
WHERE c.status != 'deleted'
|
|
55
|
+
GROUP BY c.id
|
|
56
|
+
HAVING COUNT(m.id) = 0
|
|
57
|
+
`);
|
|
58
|
+
return stmt.all();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
q.permanentlyDeleteConversation = function(id) {
|
|
62
|
+
return this.deleteConversation(id);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
q.cleanupEmptyConversations = function() {
|
|
66
|
+
const emptyConvs = this.getEmptyConversations();
|
|
67
|
+
let deletedCount = 0;
|
|
68
|
+
|
|
69
|
+
for (const conv of emptyConvs) {
|
|
70
|
+
console.log(`[cleanup] Deleting empty conversation: ${conv.id} (${conv.title || 'Untitled'})`);
|
|
71
|
+
if (this.permanentlyDeleteConversation(conv.id)) {
|
|
72
|
+
deletedCount++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (deletedCount > 0) {
|
|
77
|
+
console.log(`[cleanup] Deleted ${deletedCount} empty conversation(s)`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return deletedCount;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export function addCleanupQueries(q, db, prep, generateId) {
|
|
2
|
+
q.cleanup = function() {
|
|
3
|
+
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
|
4
|
+
const sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
|
|
5
|
+
const now = Date.now();
|
|
6
|
+
|
|
7
|
+
const cleanupStmt = db.transaction(() => {
|
|
8
|
+
prep('DELETE FROM events WHERE created_at < ?').run(thirtyDaysAgo);
|
|
9
|
+
prep('DELETE FROM sessions WHERE completed_at IS NOT NULL AND completed_at < ?').run(thirtyDaysAgo);
|
|
10
|
+
prep('DELETE FROM idempotencyKeys WHERE (created_at + ttl) < ?').run(now);
|
|
11
|
+
|
|
12
|
+
prep('DELETE FROM chunks WHERE sessionId IN (SELECT id FROM sessions WHERE completed_at IS NOT NULL AND completed_at < ?)').run(sevenDaysAgo);
|
|
13
|
+
prep('DELETE FROM stream_updates WHERE sessionId IN (SELECT id FROM sessions WHERE completed_at IS NOT NULL AND completed_at < ?)').run(sevenDaysAgo);
|
|
14
|
+
|
|
15
|
+
prep('DELETE FROM chunks WHERE created_at < ? AND sessionId NOT IN (SELECT id FROM sessions WHERE completed_at IS NULL AND started_at >= ?)').run(sevenDaysAgo, sevenDaysAgo);
|
|
16
|
+
prep('DELETE FROM stream_updates WHERE created_at < ? AND sessionId NOT IN (SELECT id FROM sessions WHERE completed_at IS NULL AND started_at >= ?)').run(sevenDaysAgo, sevenDaysAgo);
|
|
17
|
+
|
|
18
|
+
prep('DELETE FROM voice_cache WHERE expires_at <= ?').run(now);
|
|
19
|
+
|
|
20
|
+
const deletedConvIds = prep("SELECT id FROM conversations WHERE status = 'deleted' AND updated_at < ?").all(sevenDaysAgo).map(r => r.id);
|
|
21
|
+
for (const cid of deletedConvIds) {
|
|
22
|
+
prep('DELETE FROM stream_updates WHERE conversationId = ?').run(cid);
|
|
23
|
+
prep('DELETE FROM chunks WHERE conversationId = ?').run(cid);
|
|
24
|
+
prep('DELETE FROM events WHERE conversationId = ?').run(cid);
|
|
25
|
+
prep('DELETE FROM sessions WHERE conversationId = ?').run(cid);
|
|
26
|
+
prep('DELETE FROM messages WHERE conversationId = ?').run(cid);
|
|
27
|
+
prep('DELETE FROM voice_cache WHERE conversationId = ?').run(cid);
|
|
28
|
+
prep('DELETE FROM thread_states WHERE thread_id = ?').run(cid);
|
|
29
|
+
prep('DELETE FROM checkpoints WHERE thread_id = ?').run(cid);
|
|
30
|
+
prep('DELETE FROM run_metadata WHERE thread_id = ?').run(cid);
|
|
31
|
+
prep('DELETE FROM conversations WHERE id = ?').run(cid);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
prep('DELETE FROM thread_states WHERE created_at < ?').run(thirtyDaysAgo);
|
|
35
|
+
prep('DELETE FROM checkpoints WHERE created_at < ?').run(thirtyDaysAgo);
|
|
36
|
+
prep('DELETE FROM run_metadata WHERE created_at < ? AND status NOT IN (?, ?)').run(thirtyDaysAgo, 'active', 'pending');
|
|
37
|
+
|
|
38
|
+
prep('DELETE FROM workflow_runs WHERE created_at < ?').run(thirtyDaysAgo);
|
|
39
|
+
|
|
40
|
+
const toolIds = prep('SELECT DISTINCT tool_id FROM tool_install_history').all().map(r => r.tool_id);
|
|
41
|
+
for (const tid of toolIds) {
|
|
42
|
+
prep(`DELETE FROM tool_install_history WHERE tool_id = ? AND id NOT IN (
|
|
43
|
+
SELECT id FROM tool_install_history WHERE tool_id = ? ORDER BY created_at DESC LIMIT 50
|
|
44
|
+
)`).run(tid, tid);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
cleanupStmt();
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
db.exec('PRAGMA incremental_vacuum(1000)');
|
|
52
|
+
} catch (_) {
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
q.setIdempotencyKey = function(key, value) {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const ttl = 24 * 60 * 60 * 1000;
|
|
59
|
+
|
|
60
|
+
const stmt = prep(
|
|
61
|
+
'INSERT OR REPLACE INTO idempotencyKeys (key, value, created_at, ttl) VALUES (?, ?, ?, ?)'
|
|
62
|
+
);
|
|
63
|
+
stmt.run(key, JSON.stringify(value), now, ttl);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
q.getIdempotencyKey = function(key) {
|
|
67
|
+
const stmt = prep('SELECT * FROM idempotencyKeys WHERE key = ?');
|
|
68
|
+
const entry = stmt.get(key);
|
|
69
|
+
|
|
70
|
+
if (!entry) return null;
|
|
71
|
+
|
|
72
|
+
const isExpired = Date.now() - entry.created_at > entry.ttl;
|
|
73
|
+
if (isExpired) {
|
|
74
|
+
db.run('DELETE FROM idempotencyKeys WHERE key = ?', [key]);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return entry.value;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
q.clearIdempotencyKey = function(key) {
|
|
82
|
+
db.run('DELETE FROM idempotencyKeys WHERE key = ?', [key]);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
export function addDeleteQueries(q, db, prep, generateId) {
|
|
6
|
+
q.deleteConversation = function(id) {
|
|
7
|
+
const conv = this.getConversation(id);
|
|
8
|
+
if (!conv) return false;
|
|
9
|
+
|
|
10
|
+
const sessionClaudeIds = prep('SELECT DISTINCT claudeSessionId FROM sessions WHERE conversationId = ? AND claudeSessionId IS NOT NULL').all(id).map(r => r.claudeSessionId);
|
|
11
|
+
if (conv.claudeSessionId && !sessionClaudeIds.includes(conv.claudeSessionId)) {
|
|
12
|
+
sessionClaudeIds.push(conv.claudeSessionId);
|
|
13
|
+
}
|
|
14
|
+
for (const csid of sessionClaudeIds) {
|
|
15
|
+
this.deleteClaudeSessionFile(csid);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const deleteStmt = db.transaction(() => {
|
|
19
|
+
const sessionIds = prep('SELECT id FROM sessions WHERE conversationId = ?').all(id).map(r => r.id);
|
|
20
|
+
prep('DELETE FROM stream_updates WHERE conversationId = ?').run(id);
|
|
21
|
+
prep('DELETE FROM chunks WHERE conversationId = ?').run(id);
|
|
22
|
+
prep('DELETE FROM events WHERE conversationId = ?').run(id);
|
|
23
|
+
if (sessionIds.length > 0) {
|
|
24
|
+
const placeholders = sessionIds.map(() => '?').join(',');
|
|
25
|
+
db.prepare(`DELETE FROM stream_updates WHERE sessionId IN (${placeholders})`).run(...sessionIds);
|
|
26
|
+
db.prepare(`DELETE FROM chunks WHERE sessionId IN (${placeholders})`).run(...sessionIds);
|
|
27
|
+
db.prepare(`DELETE FROM events WHERE sessionId IN (${placeholders})`).run(...sessionIds);
|
|
28
|
+
}
|
|
29
|
+
prep('DELETE FROM sessions WHERE conversationId = ?').run(id);
|
|
30
|
+
prep('DELETE FROM messages WHERE conversationId = ?').run(id);
|
|
31
|
+
prep('UPDATE conversations SET status = ?, updated_at = ? WHERE id = ?').run('deleted', Date.now(), id);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
deleteStmt();
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
q.deleteClaudeSessionFile = function(sessionId) {
|
|
39
|
+
try {
|
|
40
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
41
|
+
const projectsDir = path.join(claudeDir, 'projects');
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(projectsDir)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const projects = fs.readdirSync(projectsDir);
|
|
48
|
+
for (const project of projects) {
|
|
49
|
+
const projectPath = path.join(projectsDir, project);
|
|
50
|
+
const sessionFile = path.join(projectPath, `${sessionId}.jsonl`);
|
|
51
|
+
|
|
52
|
+
if (fs.existsSync(sessionFile)) {
|
|
53
|
+
fs.unlinkSync(sessionFile);
|
|
54
|
+
console.log(`[deleteClaudeSessionFile] Deleted Claude session file: ${sessionFile}`);
|
|
55
|
+
|
|
56
|
+
const indexPath = path.join(projectPath, 'sessions-index.json');
|
|
57
|
+
if (fs.existsSync(indexPath)) {
|
|
58
|
+
try {
|
|
59
|
+
const indexContent = fs.readFileSync(indexPath, 'utf8');
|
|
60
|
+
const index = JSON.parse(indexContent);
|
|
61
|
+
if (index.entries && Array.isArray(index.entries)) {
|
|
62
|
+
const originalLength = index.entries.length;
|
|
63
|
+
index.entries = index.entries.filter(entry => entry.sessionId !== sessionId);
|
|
64
|
+
if (index.entries.length < originalLength) {
|
|
65
|
+
fs.writeFileSync(indexPath, JSON.stringify(index, null, 2), { encoding: 'utf8' });
|
|
66
|
+
console.log(`[deleteClaudeSessionFile] Removed session ${sessionId} from sessions-index.json in ${projectPath}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch (indexErr) {
|
|
70
|
+
console.error(`[deleteClaudeSessionFile] Failed to update sessions-index.json in ${projectPath}:`, indexErr.message);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const sessionDir = path.join(projectPath, sessionId);
|
|
75
|
+
if (fs.existsSync(sessionDir)) {
|
|
76
|
+
try {
|
|
77
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
78
|
+
console.log(`[deleteClaudeSessionFile] Deleted Claude session dir: ${sessionDir}`);
|
|
79
|
+
} catch (dirErr) {
|
|
80
|
+
console.error(`[deleteClaudeSessionFile] Failed to delete session dir ${sessionDir}:`, dirErr.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error(`[deleteClaudeSessionFile] Error deleting session ${sessionId}:`, err.message);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
q.deleteAllConversations = function() {
|
|
96
|
+
try {
|
|
97
|
+
const allClaudeSessionIds = prep('SELECT DISTINCT claudeSessionId FROM sessions WHERE claudeSessionId IS NOT NULL').all().map(r => r.claudeSessionId);
|
|
98
|
+
const convClaudeIds = prep('SELECT DISTINCT claudeSessionId FROM conversations WHERE claudeSessionId IS NOT NULL').all().map(r => r.claudeSessionId);
|
|
99
|
+
for (const csid of convClaudeIds) {
|
|
100
|
+
if (!allClaudeSessionIds.includes(csid)) allClaudeSessionIds.push(csid);
|
|
101
|
+
}
|
|
102
|
+
for (const csid of allClaudeSessionIds) {
|
|
103
|
+
this.deleteClaudeSessionFile(csid);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const deleteAllStmt = db.transaction(() => {
|
|
107
|
+
prep('DELETE FROM stream_updates').run();
|
|
108
|
+
prep('DELETE FROM chunks').run();
|
|
109
|
+
prep('DELETE FROM events').run();
|
|
110
|
+
prep('DELETE FROM voice_cache').run();
|
|
111
|
+
prep('DELETE FROM sessions').run();
|
|
112
|
+
prep('DELETE FROM messages').run();
|
|
113
|
+
prep('DELETE FROM conversations').run();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
deleteAllStmt();
|
|
117
|
+
const projectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
118
|
+
if (fs.existsSync(projectsDir)) {
|
|
119
|
+
for (const project of fs.readdirSync(projectsDir)) {
|
|
120
|
+
const pdir = path.join(projectsDir, project);
|
|
121
|
+
try {
|
|
122
|
+
if (!fs.statSync(pdir).isDirectory()) continue;
|
|
123
|
+
for (const entry of fs.readdirSync(pdir, { withFileTypes: true })) {
|
|
124
|
+
if (entry.isFile() && entry.name.endsWith('.jsonl')) {
|
|
125
|
+
fs.unlinkSync(path.join(pdir, entry.name));
|
|
126
|
+
} else if (entry.isDirectory()) {
|
|
127
|
+
fs.rmSync(path.join(pdir, entry.name), { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error('[deleteAllConversations] Failed to clean project dir:', pdir, err.message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
console.log('[deleteAllConversations] Deleted all conversations and associated Claude Code files');
|
|
136
|
+
return true;
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error('[deleteAllConversations] Error deleting all conversations:', err.message);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export function addEventQueries(q, db, prep, generateId) {
|
|
2
|
+
q.createEvent = function(type, data, conversationId = null, sessionId = null, idempotencyKey = null) {
|
|
3
|
+
if (idempotencyKey) {
|
|
4
|
+
const cached = this.getIdempotencyKey(idempotencyKey);
|
|
5
|
+
if (cached) {
|
|
6
|
+
console.log(`[event-idempotency] Event already exists for key ${idempotencyKey}, returning cached`);
|
|
7
|
+
return JSON.parse(cached);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const id = generateId('evt');
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
|
|
14
|
+
const stmt = prep(
|
|
15
|
+
`INSERT INTO events (id, type, conversationId, sessionId, data, created_at) VALUES (?, ?, ?, ?, ?, ?)`
|
|
16
|
+
);
|
|
17
|
+
stmt.run(id, type, conversationId, sessionId, JSON.stringify(data), now);
|
|
18
|
+
|
|
19
|
+
const event = {
|
|
20
|
+
id,
|
|
21
|
+
type,
|
|
22
|
+
conversationId,
|
|
23
|
+
sessionId,
|
|
24
|
+
data,
|
|
25
|
+
created_at: now
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (idempotencyKey) {
|
|
29
|
+
this.setIdempotencyKey(idempotencyKey, event);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return event;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
q.getEvent = function(id) {
|
|
36
|
+
const stmt = prep('SELECT * FROM events WHERE id = ?');
|
|
37
|
+
const row = stmt.get(id);
|
|
38
|
+
if (row) {
|
|
39
|
+
return {
|
|
40
|
+
...row,
|
|
41
|
+
data: JSON.parse(row.data)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
q.getConversationEvents = function(conversationId) {
|
|
48
|
+
const stmt = prep(
|
|
49
|
+
'SELECT * FROM events WHERE conversationId = ? ORDER BY created_at ASC'
|
|
50
|
+
);
|
|
51
|
+
const rows = stmt.all(conversationId);
|
|
52
|
+
return rows.map(row => ({
|
|
53
|
+
...row,
|
|
54
|
+
data: JSON.parse(row.data)
|
|
55
|
+
}));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
q.getSessionEvents = function(sessionId) {
|
|
59
|
+
const stmt = prep(
|
|
60
|
+
'SELECT * FROM events WHERE sessionId = ? ORDER BY created_at ASC'
|
|
61
|
+
);
|
|
62
|
+
const rows = stmt.all(sessionId);
|
|
63
|
+
return rows.map(row => ({
|
|
64
|
+
...row,
|
|
65
|
+
data: JSON.parse(row.data)
|
|
66
|
+
}));
|
|
67
|
+
};
|
|
68
|
+
}
|