past-conversations-mcp 1.0.0
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/dist/db/indexer.d.ts +8 -0
- package/dist/db/indexer.d.ts.map +1 -0
- package/dist/db/indexer.js +230 -0
- package/dist/db/indexer.js.map +1 -0
- package/dist/db/queries.d.ts +39 -0
- package/dist/db/queries.d.ts.map +1 -0
- package/dist/db/queries.js +352 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.d.ts +3 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +111 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/history.d.ts +10 -0
- package/dist/parser/history.d.ts.map +1 -0
- package/dist/parser/history.js +53 -0
- package/dist/parser/history.js.map +1 -0
- package/dist/parser/jsonl.d.ts +33 -0
- package/dist/parser/jsonl.d.ts.map +1 -0
- package/dist/parser/jsonl.js +128 -0
- package/dist/parser/jsonl.js.map +1 -0
- package/dist/parser/project.d.ts +22 -0
- package/dist/parser/project.d.ts.map +1 -0
- package/dist/parser/project.js +81 -0
- package/dist/parser/project.js.map +1 -0
- package/dist/parser/subagent.d.ts +11 -0
- package/dist/parser/subagent.d.ts.map +1 -0
- package/dist/parser/subagent.js +34 -0
- package/dist/parser/subagent.js.map +1 -0
- package/dist/tools/admin.d.ts +4 -0
- package/dist/tools/admin.d.ts.map +1 -0
- package/dist/tools/admin.js +38 -0
- package/dist/tools/admin.js.map +1 -0
- package/dist/tools/projects.d.ts +4 -0
- package/dist/tools/projects.d.ts.map +1 -0
- package/dist/tools/projects.js +74 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/search.d.ts +4 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +74 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/sessions.d.ts +4 -0
- package/dist/tools/sessions.d.ts.map +1 -0
- package/dist/tools/sessions.js +130 -0
- package/dist/tools/sessions.js.map +1 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/paths.d.ts +23 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +62 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/text.d.ts +21 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +86 -0
- package/dist/utils/text.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import type { IndexStats } from "../types.js";
|
|
3
|
+
export declare function buildIndex(db: Database.Database): Promise<IndexStats>;
|
|
4
|
+
/**
|
|
5
|
+
* Force a complete re-index by dropping all data first.
|
|
6
|
+
*/
|
|
7
|
+
export declare function rebuildIndex(db: Database.Database): Promise<IndexStats>;
|
|
8
|
+
//# sourceMappingURL=indexer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/db/indexer.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAQtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,wBAAsB,UAAU,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAqS3E;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAW7E"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { statSync } from "fs";
|
|
2
|
+
import { parseConversationFile } from "../parser/jsonl.js";
|
|
3
|
+
import { streamHistory } from "../parser/history.js";
|
|
4
|
+
import { discoverProjects, listSubagentFiles } from "../parser/project.js";
|
|
5
|
+
import { parseSubagentFile } from "../parser/subagent.js";
|
|
6
|
+
import { slugToPath } from "../utils/paths.js";
|
|
7
|
+
import { HISTORY_FILE } from "../utils/paths.js";
|
|
8
|
+
export async function buildIndex(db) {
|
|
9
|
+
const startTime = Date.now();
|
|
10
|
+
let sessionsCount = 0;
|
|
11
|
+
let messagesCount = 0;
|
|
12
|
+
let historyCount = 0;
|
|
13
|
+
let toolUsageCount = 0;
|
|
14
|
+
// Prepared statements
|
|
15
|
+
const insertSession = db.prepare(`
|
|
16
|
+
INSERT OR REPLACE INTO sessions
|
|
17
|
+
(session_id, project_slug, project_path, slug, custom_title, first_prompt,
|
|
18
|
+
started_at, last_activity, git_branch, has_full_transcript, message_count, indexed_at)
|
|
19
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
20
|
+
`);
|
|
21
|
+
const insertMessage = db.prepare(`
|
|
22
|
+
INSERT OR IGNORE INTO messages (session_id, role, content, timestamp, message_index)
|
|
23
|
+
VALUES (?, ?, ?, ?, ?)
|
|
24
|
+
`);
|
|
25
|
+
const insertToolUsage = db.prepare(`
|
|
26
|
+
INSERT INTO tool_usage (session_id, tool_name, file_path, timestamp)
|
|
27
|
+
VALUES (?, ?, ?, ?)
|
|
28
|
+
`);
|
|
29
|
+
const insertHistory = db.prepare(`
|
|
30
|
+
INSERT INTO history (session_id, project_path, display, timestamp)
|
|
31
|
+
VALUES (?, ?, ?, ?)
|
|
32
|
+
`);
|
|
33
|
+
const getMeta = db.prepare(`SELECT value FROM index_meta WHERE key = ?`);
|
|
34
|
+
const setMeta = db.prepare(`INSERT OR REPLACE INTO index_meta (key, value) VALUES (?, ?)`);
|
|
35
|
+
// Check what's already indexed
|
|
36
|
+
const lastHistoryOffset = getMeta.get("history_offset");
|
|
37
|
+
const lastHistoryOffsetNum = lastHistoryOffset ? parseInt(lastHistoryOffset.value, 10) : 0;
|
|
38
|
+
const indexedSessions = new Set();
|
|
39
|
+
const existingSessions = db.prepare(`SELECT session_id FROM sessions`).all();
|
|
40
|
+
for (const row of existingSessions) {
|
|
41
|
+
indexedSessions.add(row.session_id);
|
|
42
|
+
}
|
|
43
|
+
// ---- Phase 1: Index history.jsonl ----
|
|
44
|
+
console.error("[indexer] Indexing history.jsonl...");
|
|
45
|
+
const historyBatch = [];
|
|
46
|
+
for await (const entry of streamHistory(HISTORY_FILE)) {
|
|
47
|
+
historyBatch.push({
|
|
48
|
+
sessionId: entry.sessionId,
|
|
49
|
+
project: entry.project,
|
|
50
|
+
display: entry.display,
|
|
51
|
+
timestamp: entry.timestamp,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// Insert history in a transaction
|
|
55
|
+
const insertHistoryBatch = db.transaction((entries) => {
|
|
56
|
+
// Clear existing history for full rebuild
|
|
57
|
+
if (lastHistoryOffsetNum === 0) {
|
|
58
|
+
db.exec("DELETE FROM history");
|
|
59
|
+
// Rebuild FTS
|
|
60
|
+
db.exec("INSERT INTO history_fts(history_fts) VALUES('rebuild')");
|
|
61
|
+
}
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
insertHistory.run(entry.sessionId, entry.project, entry.display, entry.timestamp);
|
|
64
|
+
historyCount++;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
insertHistoryBatch(historyBatch);
|
|
68
|
+
// Update history offset
|
|
69
|
+
try {
|
|
70
|
+
const historySize = statSync(HISTORY_FILE).size;
|
|
71
|
+
setMeta.run("history_offset", String(historySize));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// ignore
|
|
75
|
+
}
|
|
76
|
+
console.error(`[indexer] Indexed ${historyCount} history entries`);
|
|
77
|
+
// ---- Phase 2: Discover and index conversation files ----
|
|
78
|
+
console.error("[indexer] Discovering projects...");
|
|
79
|
+
const projects = discoverProjects();
|
|
80
|
+
console.error(`[indexer] Found ${projects.length} projects`);
|
|
81
|
+
// Build a map of history entries by sessionId for enriching session data
|
|
82
|
+
const historyBySession = new Map();
|
|
83
|
+
for (const entry of historyBatch) {
|
|
84
|
+
const existing = historyBySession.get(entry.sessionId) ?? [];
|
|
85
|
+
existing.push(entry);
|
|
86
|
+
historyBySession.set(entry.sessionId, existing);
|
|
87
|
+
}
|
|
88
|
+
for (const project of projects) {
|
|
89
|
+
const projectPath = slugToPath(project.slug);
|
|
90
|
+
for (const session of project.sessions) {
|
|
91
|
+
// Skip already-indexed sessions (for incremental)
|
|
92
|
+
if (indexedSessions.has(session.sessionId)) {
|
|
93
|
+
// Check if the jsonl file has been modified since last index
|
|
94
|
+
if (session.jsonlPath) {
|
|
95
|
+
try {
|
|
96
|
+
const fileStat = statSync(session.jsonlPath);
|
|
97
|
+
const sessionRow = db.prepare(`SELECT indexed_at FROM sessions WHERE session_id = ?`).get(session.sessionId);
|
|
98
|
+
if (sessionRow?.indexed_at) {
|
|
99
|
+
const indexedAt = new Date(sessionRow.indexed_at);
|
|
100
|
+
if (fileStat.mtime <= indexedAt) {
|
|
101
|
+
continue; // Already up-to-date
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
continue; // Old-format-only, already indexed
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const now = new Date().toISOString();
|
|
114
|
+
if (session.jsonlPath) {
|
|
115
|
+
// Full JSONL conversation file available
|
|
116
|
+
try {
|
|
117
|
+
const parsed = await parseConversationFile(session.jsonlPath);
|
|
118
|
+
const firstPrompt = parsed.messages.find((m) => m.role === "user")?.content ?? null;
|
|
119
|
+
const insertSessionTx = db.transaction(() => {
|
|
120
|
+
// Clear old data for this session (for re-index)
|
|
121
|
+
db.prepare("DELETE FROM messages WHERE session_id = ?").run(session.sessionId);
|
|
122
|
+
db.prepare("DELETE FROM tool_usage WHERE session_id = ?").run(session.sessionId);
|
|
123
|
+
insertSession.run(session.sessionId, project.slug, projectPath, parsed.slug, parsed.customTitle, firstPrompt ? firstPrompt.slice(0, 500) : null, parsed.startedAt, parsed.lastActivity, parsed.gitBranch, 1, // has_full_transcript
|
|
124
|
+
parsed.messages.length, now);
|
|
125
|
+
for (const msg of parsed.messages) {
|
|
126
|
+
insertMessage.run(session.sessionId, msg.role, msg.content, msg.timestamp, msg.messageIndex);
|
|
127
|
+
messagesCount++;
|
|
128
|
+
}
|
|
129
|
+
for (const tu of parsed.toolUses) {
|
|
130
|
+
insertToolUsage.run(session.sessionId, tu.toolName, tu.filePath, tu.timestamp);
|
|
131
|
+
toolUsageCount++;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
insertSessionTx();
|
|
135
|
+
sessionsCount++;
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
console.error(`[indexer] Error parsing ${session.jsonlPath}: ${err}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// Old-format-only: create a basic session entry from history data
|
|
143
|
+
const historyEntries = historyBySession.get(session.sessionId);
|
|
144
|
+
if (historyEntries && historyEntries.length > 0) {
|
|
145
|
+
const sorted = historyEntries.sort((a, b) => a.timestamp - b.timestamp);
|
|
146
|
+
const firstPrompt = sorted[0].display;
|
|
147
|
+
const startedAt = new Date(sorted[0].timestamp).toISOString();
|
|
148
|
+
const lastActivity = new Date(sorted[sorted.length - 1].timestamp).toISOString();
|
|
149
|
+
insertSession.run(session.sessionId, project.slug, projectPath, null, // no slug available
|
|
150
|
+
null, // no custom title
|
|
151
|
+
firstPrompt.slice(0, 500), startedAt, lastActivity, null, // no git branch
|
|
152
|
+
0, // has_full_transcript = false
|
|
153
|
+
0, now);
|
|
154
|
+
sessionsCount++;
|
|
155
|
+
// Optionally parse subagent files for additional context
|
|
156
|
+
if (session.hasSubagents && session.dirPath) {
|
|
157
|
+
try {
|
|
158
|
+
const subagentFiles = listSubagentFiles(session.dirPath);
|
|
159
|
+
let subagentMessageIndex = 0;
|
|
160
|
+
for (const saFile of subagentFiles) {
|
|
161
|
+
const summary = await parseSubagentFile(saFile);
|
|
162
|
+
for (const prompt of summary.userPrompts) {
|
|
163
|
+
insertMessage.run(session.sessionId, "user", prompt, null, subagentMessageIndex++);
|
|
164
|
+
messagesCount++;
|
|
165
|
+
}
|
|
166
|
+
for (const text of summary.assistantTexts) {
|
|
167
|
+
insertMessage.run(session.sessionId, "assistant", text, null, subagentMessageIndex++);
|
|
168
|
+
messagesCount++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Update message count
|
|
172
|
+
db.prepare("UPDATE sessions SET message_count = ? WHERE session_id = ?").run(subagentMessageIndex, session.sessionId);
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
console.error(`[indexer] Error parsing subagents for ${session.sessionId}: ${err}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Also create session entries for sessions that are ONLY in history (no dir at all)
|
|
183
|
+
const allSessionIds = new Set();
|
|
184
|
+
for (const project of projects) {
|
|
185
|
+
for (const session of project.sessions) {
|
|
186
|
+
allSessionIds.add(session.sessionId);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const historyOnlySessions = new Map();
|
|
190
|
+
for (const entry of historyBatch) {
|
|
191
|
+
if (!allSessionIds.has(entry.sessionId) && !indexedSessions.has(entry.sessionId)) {
|
|
192
|
+
const existing = historyOnlySessions.get(entry.sessionId) ?? [];
|
|
193
|
+
existing.push(entry);
|
|
194
|
+
historyOnlySessions.set(entry.sessionId, existing);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const [sessionId, entries] of historyOnlySessions) {
|
|
198
|
+
const sorted = entries.sort((a, b) => a.timestamp - b.timestamp);
|
|
199
|
+
// Derive project slug from the project path
|
|
200
|
+
const projectPath = sorted[0].project;
|
|
201
|
+
const projectSlug = projectPath.replace(/\//g, "-");
|
|
202
|
+
insertSession.run(sessionId, projectSlug, projectPath, null, null, sorted[0].display.slice(0, 500), new Date(sorted[0].timestamp).toISOString(), new Date(sorted[sorted.length - 1].timestamp).toISOString(), null, 0, 0, new Date().toISOString());
|
|
203
|
+
sessionsCount++;
|
|
204
|
+
}
|
|
205
|
+
const duration = Date.now() - startTime;
|
|
206
|
+
console.error(`[indexer] Done in ${duration}ms: ${sessionsCount} sessions, ${messagesCount} messages, ${historyCount} history, ${toolUsageCount} tool usages`);
|
|
207
|
+
return {
|
|
208
|
+
sessions: sessionsCount,
|
|
209
|
+
messages: messagesCount,
|
|
210
|
+
history_entries: historyCount,
|
|
211
|
+
tool_usages: toolUsageCount,
|
|
212
|
+
duration_ms: duration,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Force a complete re-index by dropping all data first.
|
|
217
|
+
*/
|
|
218
|
+
export async function rebuildIndex(db) {
|
|
219
|
+
db.exec(`
|
|
220
|
+
DELETE FROM messages;
|
|
221
|
+
DELETE FROM tool_usage;
|
|
222
|
+
DELETE FROM history;
|
|
223
|
+
DELETE FROM sessions;
|
|
224
|
+
DELETE FROM index_meta;
|
|
225
|
+
INSERT INTO messages_fts(messages_fts) VALUES('rebuild');
|
|
226
|
+
INSERT INTO history_fts(history_fts) VALUES('rebuild');
|
|
227
|
+
`);
|
|
228
|
+
return buildIndex(db);
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=indexer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../src/db/indexer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import type { SearchResult, SessionListItem, SessionMessage, SessionContext, ProjectInfo, HistorySearchResult } from "../types.js";
|
|
3
|
+
export declare function searchConversations(db: Database.Database, params: {
|
|
4
|
+
query: string;
|
|
5
|
+
project?: string;
|
|
6
|
+
date_from?: string;
|
|
7
|
+
date_to?: string;
|
|
8
|
+
role?: string;
|
|
9
|
+
limit?: number;
|
|
10
|
+
}): SearchResult[];
|
|
11
|
+
export declare function searchHistory(db: Database.Database, params: {
|
|
12
|
+
query: string;
|
|
13
|
+
project?: string;
|
|
14
|
+
limit?: number;
|
|
15
|
+
}): HistorySearchResult[];
|
|
16
|
+
export declare function listSessions(db: Database.Database, params: {
|
|
17
|
+
project?: string;
|
|
18
|
+
query?: string;
|
|
19
|
+
date_from?: string;
|
|
20
|
+
date_to?: string;
|
|
21
|
+
sort?: string;
|
|
22
|
+
limit?: number;
|
|
23
|
+
}): SessionListItem[];
|
|
24
|
+
export declare function getSession(db: Database.Database, params: {
|
|
25
|
+
session_id: string;
|
|
26
|
+
include?: string;
|
|
27
|
+
offset?: number;
|
|
28
|
+
limit?: number;
|
|
29
|
+
}): {
|
|
30
|
+
metadata: SessionListItem | null;
|
|
31
|
+
messages: SessionMessage[];
|
|
32
|
+
};
|
|
33
|
+
export declare function getSessionContext(db: Database.Database, sessionId: string): SessionContext | null;
|
|
34
|
+
export declare function listProjects(db: Database.Database): ProjectInfo[];
|
|
35
|
+
export declare function getProjectMemory(db: Database.Database, projectQuery: string): Array<{
|
|
36
|
+
filename: string;
|
|
37
|
+
content: string;
|
|
38
|
+
}>;
|
|
39
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/db/queries.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAOtC,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,cAAc,EACd,WAAW,EACX,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAIrB,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE;IACN,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,YAAY,EAAE,CAyChB;AAID,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE;IACN,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,mBAAmB,EAAE,CAwBvB;AAID,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE;IACN,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,eAAe,EAAE,CAsDnB;AAID,wBAAgB,UAAU,CACxB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE;IACN,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA;IAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,cAAc,EAAE,CAAA;CAAE,CAmDlE;AAqFD,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,GAChB,cAAc,GAAG,IAAI,CA0EvB;AAID,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,WAAW,EAAE,CAmDjE;AAID,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,YAAY,EAAE,MAAM,GACnB,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAyB9C"}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { slugToPath } from "../utils/paths.js";
|
|
4
|
+
import { PROJECTS_DIR } from "../utils/paths.js";
|
|
5
|
+
import { listSubagentFiles } from "../parser/project.js";
|
|
6
|
+
// ---- search_conversations ----
|
|
7
|
+
export function searchConversations(db, params) {
|
|
8
|
+
const limit = Math.min(params.limit ?? 20, 100);
|
|
9
|
+
let sql = `
|
|
10
|
+
SELECT
|
|
11
|
+
m.content AS text,
|
|
12
|
+
m.role,
|
|
13
|
+
m.session_id,
|
|
14
|
+
s.project_path AS project,
|
|
15
|
+
s.slug,
|
|
16
|
+
m.timestamp,
|
|
17
|
+
rank
|
|
18
|
+
FROM messages_fts
|
|
19
|
+
JOIN messages m ON m.id = messages_fts.rowid
|
|
20
|
+
JOIN sessions s ON s.session_id = m.session_id
|
|
21
|
+
WHERE messages_fts MATCH ?
|
|
22
|
+
`;
|
|
23
|
+
const bindParams = [params.query];
|
|
24
|
+
if (params.project) {
|
|
25
|
+
sql += ` AND s.project_path LIKE ?`;
|
|
26
|
+
bindParams.push(`%${params.project}%`);
|
|
27
|
+
}
|
|
28
|
+
if (params.date_from) {
|
|
29
|
+
sql += ` AND m.timestamp >= ?`;
|
|
30
|
+
bindParams.push(params.date_from);
|
|
31
|
+
}
|
|
32
|
+
if (params.date_to) {
|
|
33
|
+
sql += ` AND m.timestamp <= ?`;
|
|
34
|
+
bindParams.push(params.date_to);
|
|
35
|
+
}
|
|
36
|
+
if (params.role && params.role !== "any") {
|
|
37
|
+
sql += ` AND m.role = ?`;
|
|
38
|
+
bindParams.push(params.role);
|
|
39
|
+
}
|
|
40
|
+
sql += ` ORDER BY rank LIMIT ?`;
|
|
41
|
+
bindParams.push(limit);
|
|
42
|
+
const rows = db.prepare(sql).all(...bindParams);
|
|
43
|
+
return rows;
|
|
44
|
+
}
|
|
45
|
+
// ---- search_history ----
|
|
46
|
+
export function searchHistory(db, params) {
|
|
47
|
+
const limit = Math.min(params.limit ?? 30, 100);
|
|
48
|
+
let sql = `
|
|
49
|
+
SELECT
|
|
50
|
+
h.display,
|
|
51
|
+
h.session_id,
|
|
52
|
+
h.project_path AS project,
|
|
53
|
+
h.timestamp
|
|
54
|
+
FROM history_fts
|
|
55
|
+
JOIN history h ON h.id = history_fts.rowid
|
|
56
|
+
WHERE history_fts MATCH ?
|
|
57
|
+
`;
|
|
58
|
+
const bindParams = [params.query];
|
|
59
|
+
if (params.project) {
|
|
60
|
+
sql += ` AND h.project_path LIKE ?`;
|
|
61
|
+
bindParams.push(`%${params.project}%`);
|
|
62
|
+
}
|
|
63
|
+
sql += ` ORDER BY rank LIMIT ?`;
|
|
64
|
+
bindParams.push(limit);
|
|
65
|
+
return db.prepare(sql).all(...bindParams);
|
|
66
|
+
}
|
|
67
|
+
// ---- list_sessions ----
|
|
68
|
+
export function listSessions(db, params) {
|
|
69
|
+
const limit = Math.min(params.limit ?? 30, 200);
|
|
70
|
+
const sortDir = params.sort === "oldest" ? "ASC" : "DESC";
|
|
71
|
+
let sql = `
|
|
72
|
+
SELECT
|
|
73
|
+
session_id,
|
|
74
|
+
project_path AS project,
|
|
75
|
+
slug,
|
|
76
|
+
COALESCE(custom_title, first_prompt) AS title,
|
|
77
|
+
started_at,
|
|
78
|
+
last_activity,
|
|
79
|
+
message_count,
|
|
80
|
+
has_full_transcript
|
|
81
|
+
FROM sessions
|
|
82
|
+
WHERE 1=1
|
|
83
|
+
`;
|
|
84
|
+
const bindParams = [];
|
|
85
|
+
if (params.project) {
|
|
86
|
+
sql += ` AND project_path LIKE ?`;
|
|
87
|
+
bindParams.push(`%${params.project}%`);
|
|
88
|
+
}
|
|
89
|
+
if (params.query) {
|
|
90
|
+
sql += ` AND (custom_title LIKE ? OR first_prompt LIKE ?)`;
|
|
91
|
+
bindParams.push(`%${params.query}%`, `%${params.query}%`);
|
|
92
|
+
}
|
|
93
|
+
if (params.date_from) {
|
|
94
|
+
sql += ` AND started_at >= ?`;
|
|
95
|
+
bindParams.push(params.date_from);
|
|
96
|
+
}
|
|
97
|
+
if (params.date_to) {
|
|
98
|
+
sql += ` AND started_at <= ?`;
|
|
99
|
+
bindParams.push(params.date_to);
|
|
100
|
+
}
|
|
101
|
+
sql += ` ORDER BY last_activity ${sortDir} LIMIT ?`;
|
|
102
|
+
bindParams.push(limit);
|
|
103
|
+
const rows = db.prepare(sql).all(...bindParams);
|
|
104
|
+
return rows.map((r) => ({
|
|
105
|
+
...r,
|
|
106
|
+
has_full_transcript: r.has_full_transcript === 1,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
// ---- get_session ----
|
|
110
|
+
export function getSession(db, params) {
|
|
111
|
+
const offset = params.offset ?? 0;
|
|
112
|
+
const limit = Math.min(params.limit ?? 100, 500);
|
|
113
|
+
// Get session metadata
|
|
114
|
+
const session = db.prepare(`
|
|
115
|
+
SELECT
|
|
116
|
+
session_id,
|
|
117
|
+
project_path AS project,
|
|
118
|
+
slug,
|
|
119
|
+
COALESCE(custom_title, first_prompt) AS title,
|
|
120
|
+
started_at,
|
|
121
|
+
last_activity,
|
|
122
|
+
message_count,
|
|
123
|
+
has_full_transcript
|
|
124
|
+
FROM sessions WHERE session_id = ?
|
|
125
|
+
`).get(params.session_id);
|
|
126
|
+
if (!session) {
|
|
127
|
+
return { metadata: null, messages: [] };
|
|
128
|
+
}
|
|
129
|
+
const metadata = {
|
|
130
|
+
...session,
|
|
131
|
+
has_full_transcript: session.has_full_transcript === 1,
|
|
132
|
+
};
|
|
133
|
+
if (params.include === "all") {
|
|
134
|
+
// Return full conversation from JSONL file (including tool use info)
|
|
135
|
+
return { metadata, messages: getFullSessionMessages(db, params.session_id, offset, limit) };
|
|
136
|
+
}
|
|
137
|
+
// Default: just text messages from the index
|
|
138
|
+
const messages = db.prepare(`
|
|
139
|
+
SELECT role, content AS text, timestamp
|
|
140
|
+
FROM messages
|
|
141
|
+
WHERE session_id = ?
|
|
142
|
+
ORDER BY message_index
|
|
143
|
+
LIMIT ? OFFSET ?
|
|
144
|
+
`).all(params.session_id, limit, offset);
|
|
145
|
+
return { metadata, messages };
|
|
146
|
+
}
|
|
147
|
+
// For "all" mode: read from JSONL file to include tool_use info
|
|
148
|
+
function getFullSessionMessages(db, sessionId, offset, limit) {
|
|
149
|
+
// Find the JSONL file
|
|
150
|
+
const session = db.prepare(`SELECT project_slug FROM sessions WHERE session_id = ?`).get(sessionId);
|
|
151
|
+
if (!session)
|
|
152
|
+
return [];
|
|
153
|
+
const jsonlPath = join(PROJECTS_DIR, session.project_slug, sessionId + ".jsonl");
|
|
154
|
+
if (!existsSync(jsonlPath)) {
|
|
155
|
+
// Fall back to indexed messages
|
|
156
|
+
return db.prepare(`
|
|
157
|
+
SELECT role, content AS text, timestamp
|
|
158
|
+
FROM messages WHERE session_id = ?
|
|
159
|
+
ORDER BY message_index LIMIT ? OFFSET ?
|
|
160
|
+
`).all(sessionId, limit, offset);
|
|
161
|
+
}
|
|
162
|
+
// We need to synchronously read this, but it's fine for single-session retrieval
|
|
163
|
+
// Read the file and parse
|
|
164
|
+
const messages = [];
|
|
165
|
+
const lines = readFileSync(jsonlPath, "utf-8").split("\n");
|
|
166
|
+
let idx = 0;
|
|
167
|
+
for (const line of lines) {
|
|
168
|
+
if (!line.trim())
|
|
169
|
+
continue;
|
|
170
|
+
try {
|
|
171
|
+
const record = JSON.parse(line);
|
|
172
|
+
if (record.type === "user" && record.message) {
|
|
173
|
+
const content = record.message.content;
|
|
174
|
+
if (typeof content === "string" && content.trim()) {
|
|
175
|
+
if (idx >= offset && messages.length < limit) {
|
|
176
|
+
messages.push({
|
|
177
|
+
role: "user",
|
|
178
|
+
text: content,
|
|
179
|
+
timestamp: record.timestamp ?? null,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
idx++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else if (record.type === "assistant" && record.message) {
|
|
186
|
+
const content = record.message.content;
|
|
187
|
+
if (!Array.isArray(content))
|
|
188
|
+
continue;
|
|
189
|
+
for (const block of content) {
|
|
190
|
+
if (block.type === "text" && block.text?.trim()) {
|
|
191
|
+
if (idx >= offset && messages.length < limit) {
|
|
192
|
+
messages.push({
|
|
193
|
+
role: "assistant",
|
|
194
|
+
text: block.text,
|
|
195
|
+
timestamp: record.timestamp ?? null,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
idx++;
|
|
199
|
+
}
|
|
200
|
+
else if (block.type === "tool_use") {
|
|
201
|
+
if (idx >= offset && messages.length < limit) {
|
|
202
|
+
const inputStr = JSON.stringify(block.input);
|
|
203
|
+
messages.push({
|
|
204
|
+
role: "assistant",
|
|
205
|
+
text: "",
|
|
206
|
+
timestamp: record.timestamp ?? null,
|
|
207
|
+
tool_name: block.name,
|
|
208
|
+
tool_input_summary: inputStr.length > 200 ? inputStr.slice(0, 200) + "..." : inputStr,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
idx++;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// skip
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return messages;
|
|
221
|
+
}
|
|
222
|
+
// ---- get_session_context ----
|
|
223
|
+
export function getSessionContext(db, sessionId) {
|
|
224
|
+
const session = db.prepare(`
|
|
225
|
+
SELECT session_id, project_path, slug, custom_title, first_prompt,
|
|
226
|
+
started_at, last_activity, git_branch, has_full_transcript
|
|
227
|
+
FROM sessions WHERE session_id = ?
|
|
228
|
+
`).get(sessionId);
|
|
229
|
+
if (!session)
|
|
230
|
+
return null;
|
|
231
|
+
// Get all user messages
|
|
232
|
+
const userMessages = db.prepare(`
|
|
233
|
+
SELECT content FROM messages
|
|
234
|
+
WHERE session_id = ? AND role = 'user'
|
|
235
|
+
ORDER BY message_index
|
|
236
|
+
`).all(sessionId);
|
|
237
|
+
// Get all assistant messages
|
|
238
|
+
const assistantMessages = db.prepare(`
|
|
239
|
+
SELECT content FROM messages
|
|
240
|
+
WHERE session_id = ? AND role = 'assistant'
|
|
241
|
+
ORDER BY message_index
|
|
242
|
+
`).all(sessionId);
|
|
243
|
+
// Get tool usage summary
|
|
244
|
+
const toolUsage = db.prepare(`
|
|
245
|
+
SELECT tool_name, COUNT(*) as count
|
|
246
|
+
FROM tool_usage WHERE session_id = ?
|
|
247
|
+
GROUP BY tool_name ORDER BY count DESC
|
|
248
|
+
`).all(sessionId);
|
|
249
|
+
const toolsUsed = {};
|
|
250
|
+
for (const tu of toolUsage) {
|
|
251
|
+
toolsUsed[tu.tool_name] = tu.count;
|
|
252
|
+
}
|
|
253
|
+
// Get files touched
|
|
254
|
+
const files = db.prepare(`
|
|
255
|
+
SELECT DISTINCT file_path FROM tool_usage
|
|
256
|
+
WHERE session_id = ? AND file_path IS NOT NULL
|
|
257
|
+
ORDER BY file_path
|
|
258
|
+
`).all(sessionId);
|
|
259
|
+
// Count subagents
|
|
260
|
+
let subagentCount = 0;
|
|
261
|
+
const projectSlug = db.prepare(`SELECT project_slug FROM sessions WHERE session_id = ?`).get(sessionId);
|
|
262
|
+
if (projectSlug) {
|
|
263
|
+
const sessionDir = join(PROJECTS_DIR, projectSlug.project_slug, sessionId);
|
|
264
|
+
if (existsSync(sessionDir)) {
|
|
265
|
+
subagentCount = listSubagentFiles(sessionDir).length;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
title: session.custom_title ?? session.first_prompt,
|
|
270
|
+
project: session.project_path,
|
|
271
|
+
slug: session.slug,
|
|
272
|
+
date_range: { start: session.started_at, end: session.last_activity },
|
|
273
|
+
user_prompts: userMessages.map((m) => m.content),
|
|
274
|
+
assistant_summaries: assistantMessages.map((m) => m.content),
|
|
275
|
+
tools_used: toolsUsed,
|
|
276
|
+
files_touched: files.map((f) => f.file_path),
|
|
277
|
+
subagent_count: subagentCount,
|
|
278
|
+
git_branch: session.git_branch,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
// ---- list_projects ----
|
|
282
|
+
export function listProjects(db) {
|
|
283
|
+
const rows = db.prepare(`
|
|
284
|
+
SELECT
|
|
285
|
+
project_slug AS slug,
|
|
286
|
+
project_path,
|
|
287
|
+
COUNT(DISTINCT session_id) AS session_count,
|
|
288
|
+
MIN(started_at) AS first_activity,
|
|
289
|
+
MAX(last_activity) AS last_activity
|
|
290
|
+
FROM sessions
|
|
291
|
+
GROUP BY project_slug
|
|
292
|
+
ORDER BY last_activity DESC
|
|
293
|
+
`).all();
|
|
294
|
+
// Count prompts per project from history
|
|
295
|
+
const promptCounts = db.prepare(`
|
|
296
|
+
SELECT project_path, COUNT(*) AS count
|
|
297
|
+
FROM history GROUP BY project_path
|
|
298
|
+
`).all();
|
|
299
|
+
const promptMap = new Map();
|
|
300
|
+
for (const pc of promptCounts) {
|
|
301
|
+
promptMap.set(pc.project_path, pc.count);
|
|
302
|
+
}
|
|
303
|
+
return rows.map((r) => {
|
|
304
|
+
// Find memory files
|
|
305
|
+
const memoryDir = join(PROJECTS_DIR, r.slug, "memory");
|
|
306
|
+
let memoryFiles = [];
|
|
307
|
+
if (existsSync(memoryDir)) {
|
|
308
|
+
try {
|
|
309
|
+
memoryFiles = readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
// ignore
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
project_path: r.project_path ?? slugToPath(r.slug),
|
|
317
|
+
slug: r.slug,
|
|
318
|
+
session_count: r.session_count,
|
|
319
|
+
total_prompts: promptMap.get(r.project_path) ?? 0,
|
|
320
|
+
date_range: { start: r.first_activity, end: r.last_activity },
|
|
321
|
+
memory_files: memoryFiles,
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
// ---- get_project_memory ----
|
|
326
|
+
export function getProjectMemory(db, projectQuery) {
|
|
327
|
+
// Find the project slug
|
|
328
|
+
const session = db.prepare(`
|
|
329
|
+
SELECT DISTINCT project_slug FROM sessions
|
|
330
|
+
WHERE project_path LIKE ? OR project_slug LIKE ?
|
|
331
|
+
LIMIT 1
|
|
332
|
+
`).get(`%${projectQuery}%`, `%${projectQuery}%`);
|
|
333
|
+
if (!session)
|
|
334
|
+
return [];
|
|
335
|
+
const memoryDir = join(PROJECTS_DIR, session.project_slug, "memory");
|
|
336
|
+
if (!existsSync(memoryDir))
|
|
337
|
+
return [];
|
|
338
|
+
const results = [];
|
|
339
|
+
for (const file of readdirSync(memoryDir)) {
|
|
340
|
+
if (!file.endsWith(".md"))
|
|
341
|
+
continue;
|
|
342
|
+
try {
|
|
343
|
+
const content = readFileSync(join(memoryDir, file), "utf-8");
|
|
344
|
+
results.push({ filename: file, content });
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// skip unreadable files
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return results;
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=queries.js.map
|