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.
Files changed (61) hide show
  1. package/dist/db/indexer.d.ts +8 -0
  2. package/dist/db/indexer.d.ts.map +1 -0
  3. package/dist/db/indexer.js +230 -0
  4. package/dist/db/indexer.js.map +1 -0
  5. package/dist/db/queries.d.ts +39 -0
  6. package/dist/db/queries.d.ts.map +1 -0
  7. package/dist/db/queries.js +352 -0
  8. package/dist/db/queries.js.map +1 -0
  9. package/dist/db/schema.d.ts +3 -0
  10. package/dist/db/schema.d.ts.map +1 -0
  11. package/dist/db/schema.js +111 -0
  12. package/dist/db/schema.js.map +1 -0
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +58 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/parser/history.d.ts +10 -0
  18. package/dist/parser/history.d.ts.map +1 -0
  19. package/dist/parser/history.js +53 -0
  20. package/dist/parser/history.js.map +1 -0
  21. package/dist/parser/jsonl.d.ts +33 -0
  22. package/dist/parser/jsonl.d.ts.map +1 -0
  23. package/dist/parser/jsonl.js +128 -0
  24. package/dist/parser/jsonl.js.map +1 -0
  25. package/dist/parser/project.d.ts +22 -0
  26. package/dist/parser/project.d.ts.map +1 -0
  27. package/dist/parser/project.js +81 -0
  28. package/dist/parser/project.js.map +1 -0
  29. package/dist/parser/subagent.d.ts +11 -0
  30. package/dist/parser/subagent.d.ts.map +1 -0
  31. package/dist/parser/subagent.js +34 -0
  32. package/dist/parser/subagent.js.map +1 -0
  33. package/dist/tools/admin.d.ts +4 -0
  34. package/dist/tools/admin.d.ts.map +1 -0
  35. package/dist/tools/admin.js +38 -0
  36. package/dist/tools/admin.js.map +1 -0
  37. package/dist/tools/projects.d.ts +4 -0
  38. package/dist/tools/projects.d.ts.map +1 -0
  39. package/dist/tools/projects.js +74 -0
  40. package/dist/tools/projects.js.map +1 -0
  41. package/dist/tools/search.d.ts +4 -0
  42. package/dist/tools/search.d.ts.map +1 -0
  43. package/dist/tools/search.js +74 -0
  44. package/dist/tools/search.js.map +1 -0
  45. package/dist/tools/sessions.d.ts +4 -0
  46. package/dist/tools/sessions.d.ts.map +1 -0
  47. package/dist/tools/sessions.js +130 -0
  48. package/dist/tools/sessions.js.map +1 -0
  49. package/dist/types.d.ts +170 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/dist/types.js +3 -0
  52. package/dist/types.js.map +1 -0
  53. package/dist/utils/paths.d.ts +23 -0
  54. package/dist/utils/paths.d.ts.map +1 -0
  55. package/dist/utils/paths.js +62 -0
  56. package/dist/utils/paths.js.map +1 -0
  57. package/dist/utils/text.d.ts +21 -0
  58. package/dist/utils/text.d.ts.map +1 -0
  59. package/dist/utils/text.js +86 -0
  60. package/dist/utils/text.js.map +1 -0
  61. 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":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,EAAqB;IACpD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,cAAc,GAAG,CAAC,CAAC;IAEvB,sBAAsB;IACtB,MAAM,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC;;;;;GAKhC,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC;;;GAGhC,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,EAAE,CAAC,OAAO,CAAC;;;GAGlC,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC;;;GAGhC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,4CAA4C,CAAC,CAAC;IACzE,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,8DAA8D,CAAC,CAAC;IAE3F,+BAA+B;IAC/B,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAkC,CAAC;IACzF,MAAM,oBAAoB,GAAG,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE3F,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAC1C,MAAM,gBAAgB,GAAG,EAAE,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC,GAAG,EAAmC,CAAC;IAC9G,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACnC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;IAED,yCAAyC;IACzC,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACrD,MAAM,YAAY,GAAsF,EAAE,CAAC;IAE3G,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;QACtD,YAAY,CAAC,IAAI,CAAC;YAChB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS,EAAE,KAAK,CAAC,SAAS;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,kCAAkC;IAClC,MAAM,kBAAkB,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,OAA4B,EAAE,EAAE;QACzE,0CAA0C;QAC1C,IAAI,oBAAoB,KAAK,CAAC,EAAE,CAAC;YAC/B,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YAC/B,cAAc;YACd,EAAE,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;QACpE,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;YAClF,YAAY,EAAE,CAAC;QACjB,CAAC;IACH,CAAC,CAAC,CAAC;IACH,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAEjC,wBAAwB;IACxB,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,qBAAqB,YAAY,kBAAkB,CAAC,CAAC;IAEnE,2DAA2D;IAC3D,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,CAAC,MAAM,WAAW,CAAC,CAAC;IAE7D,yEAAyE;IACzE,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAqE,CAAC;IACtG,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QAC7D,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAE7C,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACvC,kDAAkD;YAClD,IAAI,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3C,6DAA6D;gBAC7D,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;oBACtB,IAAI,CAAC;wBACH,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;wBAC7C,MAAM,UAAU,GAAG,EAAE,CAAC,OAAO,CAAC,sDAAsD,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAuC,CAAC;wBACnJ,IAAI,UAAU,EAAE,UAAU,EAAE,CAAC;4BAC3B,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;4BAClD,IAAI,QAAQ,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC;gCAChC,SAAS,CAAC,qBAAqB;4BACjC,CAAC;wBACH,CAAC;oBACH,CAAC;oBAAC,MAAM,CAAC;wBACP,SAAS;oBACX,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,SAAS,CAAC,mCAAmC;gBAC/C,CAAC;YACH,CAAC;YAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAErC,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACtB,yCAAyC;gBACzC,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;oBAE9D,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC;oBAEpF,MAAM,eAAe,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;wBAC1C,iDAAiD;wBACjD,EAAE,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;wBAC/E,EAAE,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;wBAEjF,aAAa,CAAC,GAAG,CACf,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,IAAI,EACZ,WAAW,EACX,MAAM,CAAC,IAAI,EACX,MAAM,CAAC,WAAW,EAClB,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAC9C,MAAM,CAAC,SAAS,EAChB,MAAM,CAAC,YAAY,EACnB,MAAM,CAAC,SAAS,EAChB,CAAC,EAAE,sBAAsB;wBACzB,MAAM,CAAC,QAAQ,CAAC,MAAM,EACtB,GAAG,CACJ,CAAC;wBAEF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;4BAClC,aAAa,CAAC,GAAG,CACf,OAAO,CAAC,SAAS,EACjB,GAAG,CAAC,IAAI,EACR,GAAG,CAAC,OAAO,EACX,GAAG,CAAC,SAAS,EACb,GAAG,CAAC,YAAY,CACjB,CAAC;4BACF,aAAa,EAAE,CAAC;wBAClB,CAAC;wBAED,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;4BACjC,eAAe,CAAC,GAAG,CACjB,OAAO,CAAC,SAAS,EACjB,EAAE,CAAC,QAAQ,EACX,EAAE,CAAC,QAAQ,EACX,EAAE,CAAC,SAAS,CACb,CAAC;4BACF,cAAc,EAAE,CAAC;wBACnB,CAAC;oBACH,CAAC,CAAC,CAAC;oBACH,eAAe,EAAE,CAAC;oBAClB,aAAa,EAAE,CAAC;gBAClB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO,CAAC,KAAK,CAAC,2BAA2B,OAAO,CAAC,SAAS,KAAK,GAAG,EAAE,CAAC,CAAC;gBACxE,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,kEAAkE;gBAClE,MAAM,cAAc,GAAG,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBAC/D,IAAI,cAAc,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAChD,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;oBACxE,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;oBACtC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;oBAC9D,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;oBAEjF,aAAa,CAAC,GAAG,CACf,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,IAAI,EACZ,WAAW,EACX,IAAI,EAAE,oBAAoB;oBAC1B,IAAI,EAAE,kBAAkB;oBACxB,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EACzB,SAAS,EACT,YAAY,EACZ,IAAI,EAAE,gBAAgB;oBACtB,CAAC,EAAE,8BAA8B;oBACjC,CAAC,EACD,GAAG,CACJ,CAAC;oBACF,aAAa,EAAE,CAAC;oBAEhB,yDAAyD;oBACzD,IAAI,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;wBAC5C,IAAI,CAAC;4BACH,MAAM,aAAa,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;4BACzD,IAAI,oBAAoB,GAAG,CAAC,CAAC;4BAC7B,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;gCACnC,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;gCAChD,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;oCACzC,aAAa,CAAC,GAAG,CACf,OAAO,CAAC,SAAS,EACjB,MAAM,EACN,MAAM,EACN,IAAI,EACJ,oBAAoB,EAAE,CACvB,CAAC;oCACF,aAAa,EAAE,CAAC;gCAClB,CAAC;gCACD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;oCAC1C,aAAa,CAAC,GAAG,CACf,OAAO,CAAC,SAAS,EACjB,WAAW,EACX,IAAI,EACJ,IAAI,EACJ,oBAAoB,EAAE,CACvB,CAAC;oCACF,aAAa,EAAE,CAAC;gCAClB,CAAC;4BACH,CAAC;4BACD,uBAAuB;4BACvB,EAAE,CAAC,OAAO,CAAC,4DAA4D,CAAC,CAAC,GAAG,CAC1E,oBAAoB,EACpB,OAAO,CAAC,SAAS,CAClB,CAAC;wBACJ,CAAC;wBAAC,OAAO,GAAG,EAAE,CAAC;4BACb,OAAO,CAAC,KAAK,CAAC,yCAAyC,OAAO,CAAC,SAAS,KAAK,GAAG,EAAE,CAAC,CAAC;wBACtF,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,oFAAoF;IACpF,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACvC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAA+B,CAAC;IACnE,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;YACjF,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;YAChE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,mBAAmB,EAAE,CAAC;QACvD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;QACjE,4CAA4C;QAC5C,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QACtC,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAEpD,aAAa,CAAC,GAAG,CACf,SAAS,EACT,WAAW,EACX,WAAW,EACX,IAAI,EACJ,IAAI,EACJ,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAC/B,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,EAC3C,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,EAC3D,IAAI,EACJ,CAAC,EACD,CAAC,EACD,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CACzB,CAAC;QACF,aAAa,EAAE,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,CAAC,KAAK,CACX,qBAAqB,QAAQ,OAAO,aAAa,cAAc,aAAa,cAAc,YAAY,aAAa,cAAc,cAAc,CAChJ,CAAC;IAEF,OAAO;QACL,QAAQ,EAAE,aAAa;QACvB,QAAQ,EAAE,aAAa;QACvB,eAAe,EAAE,YAAY;QAC7B,WAAW,EAAE,cAAc;QAC3B,WAAW,EAAE,QAAQ;KACtB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EAAqB;IACtD,EAAE,CAAC,IAAI,CAAC;;;;;;;;GAQP,CAAC,CAAC;IACH,OAAO,UAAU,CAAC,EAAE,CAAC,CAAC;AACxB,CAAC"}
@@ -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