@aeriondyseti/vector-memory-mcp 1.0.2-dev.0 → 1.1.0-dev.2
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/package.json +4 -4
- package/dist/src/config/index.d.ts +10 -0
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js +13 -0
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/db/conversation-history.repository.d.ts +24 -0
- package/dist/src/db/conversation-history.repository.d.ts.map +1 -0
- package/dist/src/db/conversation-history.repository.js +184 -0
- package/dist/src/db/conversation-history.repository.js.map +1 -0
- package/dist/src/db/conversation-history.schema.d.ts +10 -0
- package/dist/src/db/conversation-history.schema.d.ts.map +1 -0
- package/dist/src/db/conversation-history.schema.js +31 -0
- package/dist/src/db/conversation-history.schema.js.map +1 -0
- package/dist/src/db/lancedb-utils.d.ts +35 -0
- package/dist/src/db/lancedb-utils.d.ts.map +1 -0
- package/dist/src/db/lancedb-utils.js +77 -0
- package/dist/src/db/lancedb-utils.js.map +1 -0
- package/dist/src/db/memory.repository.d.ts +2 -12
- package/dist/src/db/memory.repository.d.ts.map +1 -1
- package/dist/src/db/memory.repository.js +13 -56
- package/dist/src/db/memory.repository.js.map +1 -1
- package/dist/src/db/schema.d.ts +4 -1
- package/dist/src/db/schema.d.ts.map +1 -1
- package/dist/src/db/schema.js +8 -4
- package/dist/src/db/schema.js.map +1 -1
- package/dist/src/index.js +8 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/handlers.d.ts +3 -0
- package/dist/src/mcp/handlers.d.ts.map +1 -1
- package/dist/src/mcp/handlers.js +149 -17
- package/dist/src/mcp/handlers.js.map +1 -1
- package/dist/src/mcp/tools.d.ts +3 -0
- package/dist/src/mcp/tools.d.ts.map +1 -1
- package/dist/src/mcp/tools.js +54 -3
- package/dist/src/mcp/tools.js.map +1 -1
- package/dist/src/services/conversation-history.service.d.ts +64 -0
- package/dist/src/services/conversation-history.service.d.ts.map +1 -0
- package/dist/src/services/conversation-history.service.js +244 -0
- package/dist/src/services/conversation-history.service.js.map +1 -0
- package/dist/src/services/memory.service.d.ts +24 -0
- package/dist/src/services/memory.service.d.ts.map +1 -1
- package/dist/src/services/memory.service.js +58 -0
- package/dist/src/services/memory.service.js.map +1 -1
- package/dist/src/services/session-parser.d.ts +59 -0
- package/dist/src/services/session-parser.d.ts.map +1 -0
- package/dist/src/services/session-parser.js +147 -0
- package/dist/src/services/session-parser.js.map +1 -0
- package/dist/src/types/conversation-history.d.ts +74 -0
- package/dist/src/types/conversation-history.d.ts.map +1 -0
- package/dist/src/types/conversation-history.js +2 -0
- package/dist/src/types/conversation-history.js.map +1 -0
- package/dist/src/types/memory.d.ts +4 -2
- package/dist/src/types/memory.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/config/index.ts +23 -0
- package/src/db/conversation-history.repository.ts +255 -0
- package/src/db/conversation-history.schema.ts +40 -0
- package/src/db/lancedb-utils.ts +97 -0
- package/src/db/memory.repository.ts +18 -67
- package/src/db/schema.ts +17 -21
- package/src/index.ts +16 -0
- package/src/mcp/handlers.ts +178 -22
- package/src/mcp/tools.ts +66 -3
- package/src/services/conversation-history.service.ts +320 -0
- package/src/services/memory.service.ts +74 -0
- package/src/services/session-parser.ts +232 -0
- package/src/types/conversation-history.ts +82 -0
- package/src/types/memory.ts +4 -3
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createReadStream } from "fs";
|
|
2
|
+
import { readdir, stat } from "fs/promises";
|
|
3
|
+
import { createInterface } from "readline";
|
|
4
|
+
import { basename, join } from "path";
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
/**
|
|
7
|
+
* Extract the text content from a JSONL message line.
|
|
8
|
+
* - User messages: content is string OR array (skip tool_result blocks)
|
|
9
|
+
* - Assistant messages: content is array of blocks (keep only type=text)
|
|
10
|
+
* Returns null if no usable text content.
|
|
11
|
+
*/
|
|
12
|
+
function extractTextContent(line) {
|
|
13
|
+
const content = line.message?.content;
|
|
14
|
+
if (content == null)
|
|
15
|
+
return null;
|
|
16
|
+
// User messages can be a plain string
|
|
17
|
+
if (typeof content === "string") {
|
|
18
|
+
return content.trim() || null;
|
|
19
|
+
}
|
|
20
|
+
// Array of content blocks — extract text blocks only
|
|
21
|
+
const textParts = [];
|
|
22
|
+
for (const block of content) {
|
|
23
|
+
if (block.type === "text" && block.text) {
|
|
24
|
+
textParts.push(block.text);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const joined = textParts.join("\n").trim();
|
|
28
|
+
return joined || null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse a single Claude Code JSONL session file.
|
|
32
|
+
*
|
|
33
|
+
* @param filePath - Absolute path to the .jsonl file
|
|
34
|
+
* @param fromByte - Byte offset to start reading from (for incremental parsing).
|
|
35
|
+
* When non-zero, messageIndex starts from startIndex.
|
|
36
|
+
* @param startIndex - Starting message index (for incremental parsing).
|
|
37
|
+
* @param knownFileSize - If already known (e.g. from discoverSessionFiles), avoids a redundant stat().
|
|
38
|
+
*/
|
|
39
|
+
export async function parseSessionFile(filePath, fromByte = 0, startIndex = 0, knownFileSize) {
|
|
40
|
+
const fileSize = knownFileSize ?? (await stat(filePath)).size;
|
|
41
|
+
// Extract session ID from filename (e.g. "abc-123.jsonl" → "abc-123")
|
|
42
|
+
const sessionId = basename(filePath, ".jsonl");
|
|
43
|
+
const messages = [];
|
|
44
|
+
let messageIndex = startIndex;
|
|
45
|
+
let firstMessageAt = null;
|
|
46
|
+
let lastMessageAt = null;
|
|
47
|
+
let gitBranch = null;
|
|
48
|
+
let project = null;
|
|
49
|
+
const stream = createReadStream(filePath, {
|
|
50
|
+
encoding: "utf-8",
|
|
51
|
+
start: fromByte,
|
|
52
|
+
});
|
|
53
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
54
|
+
for await (const rawLine of rl) {
|
|
55
|
+
if (!rawLine.trim())
|
|
56
|
+
continue;
|
|
57
|
+
let line;
|
|
58
|
+
try {
|
|
59
|
+
line = JSON.parse(rawLine);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Skip malformed lines
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Only process user and assistant messages
|
|
66
|
+
if (line.type !== "user" && line.type !== "assistant")
|
|
67
|
+
continue;
|
|
68
|
+
// Capture metadata from first line that has it
|
|
69
|
+
if (gitBranch == null && line.gitBranch)
|
|
70
|
+
gitBranch = line.gitBranch;
|
|
71
|
+
if (project == null && line.cwd)
|
|
72
|
+
project = line.cwd;
|
|
73
|
+
const role = line.type === "user" ? "user" : "assistant";
|
|
74
|
+
const text = extractTextContent(line);
|
|
75
|
+
if (!text)
|
|
76
|
+
continue;
|
|
77
|
+
const timestamp = line.timestamp ? new Date(line.timestamp) : new Date();
|
|
78
|
+
if (firstMessageAt == null)
|
|
79
|
+
firstMessageAt = timestamp;
|
|
80
|
+
lastMessageAt = timestamp;
|
|
81
|
+
messages.push({
|
|
82
|
+
id: randomUUID(),
|
|
83
|
+
sessionId,
|
|
84
|
+
role,
|
|
85
|
+
messageIndex,
|
|
86
|
+
content: text,
|
|
87
|
+
timestamp,
|
|
88
|
+
metadata: {
|
|
89
|
+
...(line.gitBranch ? { gitBranch: line.gitBranch } : {}),
|
|
90
|
+
...(line.cwd ? { cwd: line.cwd } : {}),
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
messageIndex++;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
sessionId,
|
|
97
|
+
filePath,
|
|
98
|
+
fileSize,
|
|
99
|
+
messages,
|
|
100
|
+
firstMessageAt,
|
|
101
|
+
lastMessageAt,
|
|
102
|
+
gitBranch,
|
|
103
|
+
project,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Discover all .jsonl session files in a directory.
|
|
108
|
+
* Stat calls are parallelized for efficiency.
|
|
109
|
+
*/
|
|
110
|
+
export async function discoverSessionFiles(sessionDir) {
|
|
111
|
+
let entries;
|
|
112
|
+
try {
|
|
113
|
+
entries = await readdir(sessionDir);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const jsonlEntries = entries.filter((e) => e.endsWith(".jsonl"));
|
|
119
|
+
const settled = await Promise.allSettled(jsonlEntries.map(async (entry) => {
|
|
120
|
+
const filePath = join(sessionDir, entry);
|
|
121
|
+
const fileStats = await stat(filePath);
|
|
122
|
+
if (!fileStats.isFile())
|
|
123
|
+
return null;
|
|
124
|
+
return {
|
|
125
|
+
sessionId: entry.replace(/\.jsonl$/, ""),
|
|
126
|
+
filePath,
|
|
127
|
+
fileSize: fileStats.size,
|
|
128
|
+
};
|
|
129
|
+
}));
|
|
130
|
+
return settled
|
|
131
|
+
.filter((r) => r.status === "fulfilled")
|
|
132
|
+
.map((r) => r.value)
|
|
133
|
+
.filter((v) => v != null);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Auto-detect the Claude Code sessions directory.
|
|
137
|
+
* Returns null if not found.
|
|
138
|
+
*/
|
|
139
|
+
export function detectSessionPath() {
|
|
140
|
+
const home = process.env.HOME ?? process.env.USERPROFILE;
|
|
141
|
+
if (!home)
|
|
142
|
+
return null;
|
|
143
|
+
// Claude Code stores sessions at ~/.claude/projects/<project-slug>/<session-id>.jsonl
|
|
144
|
+
// We return the projects dir — the caller iterates project subdirs
|
|
145
|
+
return join(home, ".claude", "projects");
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=session-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-parser.js","sourceRoot":"","sources":["../../../src/services/session-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { WithRrfScore } from "./memory.js";
|
|
2
|
+
export type MessageRole = "user" | "assistant";
|
|
3
|
+
/**
|
|
4
|
+
* A single indexed message from a conversation session log.
|
|
5
|
+
* One entry per user/assistant text message — tool calls and results are excluded.
|
|
6
|
+
*/
|
|
7
|
+
export interface ConversationHistoryEntry {
|
|
8
|
+
id: string;
|
|
9
|
+
content: string;
|
|
10
|
+
embedding: number[];
|
|
11
|
+
sessionId: string;
|
|
12
|
+
role: MessageRole;
|
|
13
|
+
messageIndex: number;
|
|
14
|
+
timestamp: Date;
|
|
15
|
+
metadata: Record<string, unknown>;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
}
|
|
18
|
+
export type ConversationHistoryHybridRow = WithRrfScore<ConversationHistoryEntry>;
|
|
19
|
+
/**
|
|
20
|
+
* Summary of an indexed session for list_indexed_sessions.
|
|
21
|
+
*/
|
|
22
|
+
export interface IndexedSessionSummary {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
messageCount: number;
|
|
25
|
+
firstMessageAt: Date;
|
|
26
|
+
lastMessageAt: Date;
|
|
27
|
+
indexedAt: Date;
|
|
28
|
+
project?: string;
|
|
29
|
+
gitBranch?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Full indexed session record including storage-level fields (file path, size).
|
|
33
|
+
* Used by the repository for upsert operations and by the parser for tracking.
|
|
34
|
+
*/
|
|
35
|
+
export interface IndexedSession extends IndexedSessionSummary {
|
|
36
|
+
filePath: string;
|
|
37
|
+
fileSize: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Discriminated union for merged search results.
|
|
41
|
+
* Narrow on `source` to access type-specific fields.
|
|
42
|
+
*/
|
|
43
|
+
export interface MemorySearchResult {
|
|
44
|
+
source: "memory";
|
|
45
|
+
id: string;
|
|
46
|
+
content: string;
|
|
47
|
+
metadata: Record<string, unknown>;
|
|
48
|
+
score: number;
|
|
49
|
+
createdAt: Date;
|
|
50
|
+
updatedAt: Date;
|
|
51
|
+
supersededBy: string | null;
|
|
52
|
+
}
|
|
53
|
+
export interface HistorySearchResult {
|
|
54
|
+
source: "conversation_history";
|
|
55
|
+
id: string;
|
|
56
|
+
content: string;
|
|
57
|
+
metadata: Record<string, unknown>;
|
|
58
|
+
score: number;
|
|
59
|
+
sessionId: string;
|
|
60
|
+
role: MessageRole;
|
|
61
|
+
messageIndex: number;
|
|
62
|
+
timestamp: Date;
|
|
63
|
+
}
|
|
64
|
+
export type SearchResult = MemorySearchResult | HistorySearchResult;
|
|
65
|
+
/**
|
|
66
|
+
* Summary returned by indexConversations() and reindexSession().
|
|
67
|
+
*/
|
|
68
|
+
export interface IndexingSummary {
|
|
69
|
+
sessionsDiscovered: number;
|
|
70
|
+
sessionsIndexed: number;
|
|
71
|
+
sessionsSkipped: number;
|
|
72
|
+
messagesIndexed: number;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=conversation-history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-history.d.ts","sourceRoot":"","sources":["../../../src/types/conversation-history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,4BAA4B,GAAG,YAAY,CAAC,wBAAwB,CAAC,CAAC;AAElF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,IAAI,CAAC;IACrB,aAAa,EAAE,IAAI,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAe,SAAQ,qBAAqB;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,QAAQ,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,sBAAsB,CAAC;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,YAAY,GAAG,kBAAkB,GAAG,mBAAmB,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;CACzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-history.js","sourceRoot":"","sources":["../../../src/types/conversation-history.ts"],"names":[],"mappings":""}
|
|
@@ -22,7 +22,9 @@ export interface IntentProfile {
|
|
|
22
22
|
};
|
|
23
23
|
jitter: number;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
/** Augments any entity type with an RRF score from hybrid search. */
|
|
26
|
+
export type WithRrfScore<T> = T & {
|
|
26
27
|
rrfScore: number;
|
|
27
|
-
}
|
|
28
|
+
};
|
|
29
|
+
export type HybridRow = WithRrfScore<Memory>;
|
|
28
30
|
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/types/memory.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYpE;AAED,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,CAAC;AAEhG,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/types/memory.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYpE;AAED,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,CAAC;AAEhG,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qEAAqE;AACrE,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,MAAM,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aeriondyseti/vector-memory-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0-dev.2",
|
|
4
4
|
"description": "A zero-configuration RAG memory server for MCP clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
],
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "
|
|
20
|
+
"url": "https://github.com/aeriondyseti/vector-memory-mcp"
|
|
21
21
|
},
|
|
22
22
|
"author": "AerionDyseti",
|
|
23
23
|
"bugs": {
|
|
24
|
-
"url": "https://github.com/
|
|
24
|
+
"url": "https://github.com/aeriondyseti/vector-memory-mcp/issues"
|
|
25
25
|
},
|
|
26
|
-
"homepage": "https://github.com/
|
|
26
|
+
"homepage": "https://github.com/aeriondyseti/vector-memory-mcp#readme",
|
|
27
27
|
"scripts": {
|
|
28
28
|
"start": "node dist/src/index.js",
|
|
29
29
|
"start:bun": "bun run src/index.ts",
|
package/src/config/index.ts
CHANGED
|
@@ -14,6 +14,11 @@ export interface Config {
|
|
|
14
14
|
httpHost: string;
|
|
15
15
|
enableHttp: boolean;
|
|
16
16
|
transportMode: TransportMode;
|
|
17
|
+
conversationHistory: {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
sessionPath: string | null; // null = auto-detect Claude Code session dir
|
|
20
|
+
historyWeight: number; // 0.0-1.0, applied to history scores when merging with memories
|
|
21
|
+
};
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
export interface ConfigOverrides {
|
|
@@ -21,6 +26,11 @@ export interface ConfigOverrides {
|
|
|
21
26
|
httpPort?: number;
|
|
22
27
|
enableHttp?: boolean;
|
|
23
28
|
transportMode?: TransportMode;
|
|
29
|
+
conversationHistory?: {
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
sessionPath?: string;
|
|
32
|
+
historyWeight?: number;
|
|
33
|
+
};
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
// Defaults - always use repo-local .vector-memory folder
|
|
@@ -47,6 +57,11 @@ export function loadConfig(overrides: ConfigOverrides = {}): Config {
|
|
|
47
57
|
httpHost: DEFAULT_HTTP_HOST,
|
|
48
58
|
enableHttp,
|
|
49
59
|
transportMode,
|
|
60
|
+
conversationHistory: {
|
|
61
|
+
enabled: overrides.conversationHistory?.enabled ?? false,
|
|
62
|
+
sessionPath: overrides.conversationHistory?.sessionPath ?? null,
|
|
63
|
+
historyWeight: overrides.conversationHistory?.historyWeight ?? 0.5,
|
|
64
|
+
},
|
|
50
65
|
};
|
|
51
66
|
}
|
|
52
67
|
|
|
@@ -59,6 +74,8 @@ export function parseCliArgs(argv: string[]): ConfigOverrides {
|
|
|
59
74
|
"--db-file": String,
|
|
60
75
|
"--port": Number,
|
|
61
76
|
"--no-http": Boolean,
|
|
77
|
+
"--conversation-history": Boolean,
|
|
78
|
+
"--session-path": String,
|
|
62
79
|
|
|
63
80
|
// Aliases
|
|
64
81
|
"-d": "--db-file",
|
|
@@ -71,6 +88,12 @@ export function parseCliArgs(argv: string[]): ConfigOverrides {
|
|
|
71
88
|
dbPath: args["--db-file"],
|
|
72
89
|
httpPort: args["--port"],
|
|
73
90
|
enableHttp: args["--no-http"] ? false : undefined,
|
|
91
|
+
conversationHistory: args["--conversation-history"] || args["--session-path"]
|
|
92
|
+
? {
|
|
93
|
+
enabled: args["--conversation-history"],
|
|
94
|
+
sessionPath: args["--session-path"],
|
|
95
|
+
}
|
|
96
|
+
: undefined,
|
|
74
97
|
};
|
|
75
98
|
}
|
|
76
99
|
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import * as lancedb from "@lancedb/lancedb";
|
|
2
|
+
import { type Table } from "@lancedb/lancedb";
|
|
3
|
+
import {
|
|
4
|
+
CONVERSATION_HISTORY_TABLE,
|
|
5
|
+
INDEXED_SESSIONS_TABLE,
|
|
6
|
+
conversationHistorySchema,
|
|
7
|
+
indexedSessionsSchema,
|
|
8
|
+
} from "./conversation-history.schema.js";
|
|
9
|
+
import {
|
|
10
|
+
arrowVectorToArray,
|
|
11
|
+
getOrCreateTable,
|
|
12
|
+
createFtsMutex,
|
|
13
|
+
createRerankerMutex,
|
|
14
|
+
escapeLanceDbString,
|
|
15
|
+
} from "./lancedb-utils.js";
|
|
16
|
+
import type {
|
|
17
|
+
ConversationHistoryEntry,
|
|
18
|
+
ConversationHistoryHybridRow,
|
|
19
|
+
IndexedSession,
|
|
20
|
+
IndexedSessionSummary,
|
|
21
|
+
MessageRole,
|
|
22
|
+
} from "../types/conversation-history.js";
|
|
23
|
+
|
|
24
|
+
export class ConversationHistoryRepository {
|
|
25
|
+
// Cached table handles — initialized once, retained for instance lifetime
|
|
26
|
+
private tablePromise: Promise<Table> | null = null;
|
|
27
|
+
private sessionsTablePromise: Promise<Table> | null = null;
|
|
28
|
+
|
|
29
|
+
// FTS index mutex — once created, the promise is never cleared (index persists in LanceDB)
|
|
30
|
+
private ensureFtsIndex: () => Promise<void>;
|
|
31
|
+
|
|
32
|
+
// Cached reranker — k=60 is constant, no need to recreate per search
|
|
33
|
+
private getReranker = createRerankerMutex();
|
|
34
|
+
|
|
35
|
+
constructor(private db: lancedb.Connection) {
|
|
36
|
+
this.ensureFtsIndex = createFtsMutex(() => this.getTable());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private getTable(): Promise<Table> {
|
|
40
|
+
if (!this.tablePromise) {
|
|
41
|
+
this.tablePromise = getOrCreateTable(
|
|
42
|
+
this.db,
|
|
43
|
+
CONVERSATION_HISTORY_TABLE,
|
|
44
|
+
conversationHistorySchema
|
|
45
|
+
).catch((e) => {
|
|
46
|
+
this.tablePromise = null;
|
|
47
|
+
throw e;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return this.tablePromise;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private getSessionsTable(): Promise<Table> {
|
|
54
|
+
if (!this.sessionsTablePromise) {
|
|
55
|
+
this.sessionsTablePromise = getOrCreateTable(
|
|
56
|
+
this.db,
|
|
57
|
+
INDEXED_SESSIONS_TABLE,
|
|
58
|
+
indexedSessionsSchema
|
|
59
|
+
).catch((e) => {
|
|
60
|
+
this.sessionsTablePromise = null;
|
|
61
|
+
throw e;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return this.sessionsTablePromise;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private rowToEntry(row: Record<string, unknown>): ConversationHistoryEntry {
|
|
68
|
+
return {
|
|
69
|
+
id: row.id as string,
|
|
70
|
+
content: row.content as string,
|
|
71
|
+
embedding: arrowVectorToArray(row.vector),
|
|
72
|
+
sessionId: row.session_id as string,
|
|
73
|
+
role: row.role as MessageRole,
|
|
74
|
+
messageIndex: row.message_index as number,
|
|
75
|
+
timestamp: new Date(row.timestamp as number),
|
|
76
|
+
metadata: JSON.parse(row.metadata as string),
|
|
77
|
+
createdAt: new Date(row.created_at as number),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private rowToSessionSummary(
|
|
82
|
+
row: Record<string, unknown>
|
|
83
|
+
): IndexedSessionSummary {
|
|
84
|
+
return {
|
|
85
|
+
sessionId: row.session_id as string,
|
|
86
|
+
messageCount: row.message_count as number,
|
|
87
|
+
firstMessageAt: new Date(row.first_message_at as number),
|
|
88
|
+
lastMessageAt: new Date(row.last_message_at as number),
|
|
89
|
+
indexedAt: new Date(row.indexed_at as number),
|
|
90
|
+
// Use null check (not truthiness) — empty string is a valid value distinct from null
|
|
91
|
+
...(row.project != null ? { project: row.project as string } : {}),
|
|
92
|
+
...(row.git_branch != null ? { gitBranch: row.git_branch as string } : {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private rowToSession(row: Record<string, unknown>): IndexedSession {
|
|
97
|
+
return {
|
|
98
|
+
...this.rowToSessionSummary(row),
|
|
99
|
+
filePath: row.file_path as string,
|
|
100
|
+
fileSize: row.file_size as number,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- Conversation History Operations ---
|
|
105
|
+
|
|
106
|
+
async insert(entries: ConversationHistoryEntry[]): Promise<void> {
|
|
107
|
+
if (entries.length === 0) return;
|
|
108
|
+
|
|
109
|
+
const table = await this.getTable();
|
|
110
|
+
await table.add(
|
|
111
|
+
entries.map((entry) => ({
|
|
112
|
+
id: entry.id,
|
|
113
|
+
vector: entry.embedding,
|
|
114
|
+
content: entry.content,
|
|
115
|
+
session_id: entry.sessionId,
|
|
116
|
+
role: entry.role,
|
|
117
|
+
message_index: entry.messageIndex,
|
|
118
|
+
timestamp: entry.timestamp.getTime(),
|
|
119
|
+
metadata: JSON.stringify(entry.metadata),
|
|
120
|
+
created_at: entry.createdAt.getTime(),
|
|
121
|
+
}))
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async findHybrid(
|
|
126
|
+
embedding: number[],
|
|
127
|
+
query: string,
|
|
128
|
+
limit: number
|
|
129
|
+
): Promise<ConversationHistoryHybridRow[]> {
|
|
130
|
+
await this.ensureFtsIndex();
|
|
131
|
+
|
|
132
|
+
const table = await this.getTable();
|
|
133
|
+
const reranker = await this.getReranker();
|
|
134
|
+
|
|
135
|
+
const results = await table
|
|
136
|
+
.query()
|
|
137
|
+
.nearestTo(embedding)
|
|
138
|
+
.fullTextSearch(query)
|
|
139
|
+
.rerank(reranker)
|
|
140
|
+
.limit(limit)
|
|
141
|
+
.toArray();
|
|
142
|
+
|
|
143
|
+
return results.map((row) => {
|
|
144
|
+
const entry = this.rowToEntry(row as Record<string, unknown>);
|
|
145
|
+
return {
|
|
146
|
+
...entry,
|
|
147
|
+
rrfScore: (row._relevance_score as number) ?? 0,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async findBySessionId(sessionId: string): Promise<ConversationHistoryEntry[]> {
|
|
153
|
+
const table = await this.getTable();
|
|
154
|
+
const results = await table
|
|
155
|
+
.query()
|
|
156
|
+
.where(`session_id = '${escapeLanceDbString(sessionId)}'`)
|
|
157
|
+
.toArray();
|
|
158
|
+
|
|
159
|
+
return results.map((row) =>
|
|
160
|
+
this.rowToEntry(row as Record<string, unknown>)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async deleteBySessionId(sessionId: string): Promise<number> {
|
|
165
|
+
const table = await this.getTable();
|
|
166
|
+
|
|
167
|
+
// Select only id — avoids deserializing embedding vectors just for a count
|
|
168
|
+
const existing = await table
|
|
169
|
+
.query()
|
|
170
|
+
.where(`session_id = '${escapeLanceDbString(sessionId)}'`)
|
|
171
|
+
.select(["id"])
|
|
172
|
+
.toArray();
|
|
173
|
+
const count = existing.length;
|
|
174
|
+
|
|
175
|
+
if (count > 0) {
|
|
176
|
+
await table.delete(`session_id = '${escapeLanceDbString(sessionId)}'`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return count;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- Indexed Sessions Tracking ---
|
|
183
|
+
|
|
184
|
+
async getIndexedSession(
|
|
185
|
+
sessionId: string
|
|
186
|
+
): Promise<IndexedSession | null> {
|
|
187
|
+
const table = await this.getSessionsTable();
|
|
188
|
+
const results = await table
|
|
189
|
+
.query()
|
|
190
|
+
.where(`session_id = '${escapeLanceDbString(sessionId)}'`)
|
|
191
|
+
.limit(1)
|
|
192
|
+
.toArray();
|
|
193
|
+
|
|
194
|
+
if (results.length === 0) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return this.rowToSession(results[0] as Record<string, unknown>);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async upsertIndexedSession(session: IndexedSession): Promise<void> {
|
|
202
|
+
const table = await this.getSessionsTable();
|
|
203
|
+
const existing = await table
|
|
204
|
+
.query()
|
|
205
|
+
.where(`session_id = '${escapeLanceDbString(session.sessionId)}'`)
|
|
206
|
+
.limit(1)
|
|
207
|
+
.toArray();
|
|
208
|
+
|
|
209
|
+
const row = {
|
|
210
|
+
session_id: session.sessionId,
|
|
211
|
+
file_path: session.filePath,
|
|
212
|
+
file_size: session.fileSize,
|
|
213
|
+
message_count: session.messageCount,
|
|
214
|
+
first_message_at: session.firstMessageAt.getTime(),
|
|
215
|
+
last_message_at: session.lastMessageAt.getTime(),
|
|
216
|
+
indexed_at: session.indexedAt.getTime(),
|
|
217
|
+
project: session.project ?? null,
|
|
218
|
+
git_branch: session.gitBranch ?? null,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (existing.length === 0) {
|
|
222
|
+
await table.add([row]);
|
|
223
|
+
} else {
|
|
224
|
+
await table.update({
|
|
225
|
+
where: `session_id = '${escapeLanceDbString(session.sessionId)}'`,
|
|
226
|
+
values: row,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async listIndexedSessions(): Promise<IndexedSessionSummary[]> {
|
|
232
|
+
const table = await this.getSessionsTable();
|
|
233
|
+
const results = await table.query().toArray();
|
|
234
|
+
|
|
235
|
+
return results.map((row) =>
|
|
236
|
+
this.rowToSessionSummary(row as Record<string, unknown>)
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async deleteIndexedSession(sessionId: string): Promise<boolean> {
|
|
241
|
+
const table = await this.getSessionsTable();
|
|
242
|
+
const existing = await table
|
|
243
|
+
.query()
|
|
244
|
+
.where(`session_id = '${escapeLanceDbString(sessionId)}'`)
|
|
245
|
+
.limit(1)
|
|
246
|
+
.toArray();
|
|
247
|
+
|
|
248
|
+
if (existing.length === 0) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await table.delete(`session_id = '${escapeLanceDbString(sessionId)}'`);
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Schema,
|
|
3
|
+
Field,
|
|
4
|
+
Utf8,
|
|
5
|
+
Int32,
|
|
6
|
+
Float64,
|
|
7
|
+
} from "apache-arrow";
|
|
8
|
+
import { vectorField, timestampField } from "./schema.js";
|
|
9
|
+
|
|
10
|
+
export const CONVERSATION_HISTORY_TABLE = "conversation_history";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tracks which sessions have been indexed and their file sizes,
|
|
14
|
+
* enabling idempotent incremental indexing.
|
|
15
|
+
*/
|
|
16
|
+
export const INDEXED_SESSIONS_TABLE = "indexed_sessions";
|
|
17
|
+
|
|
18
|
+
export const conversationHistorySchema = new Schema([
|
|
19
|
+
new Field("id", new Utf8(), false),
|
|
20
|
+
vectorField(),
|
|
21
|
+
new Field("content", new Utf8(), false),
|
|
22
|
+
new Field("session_id", new Utf8(), false),
|
|
23
|
+
new Field("role", new Utf8(), false), // "user" | "assistant"
|
|
24
|
+
new Field("message_index", new Int32(), false),
|
|
25
|
+
timestampField("timestamp"),
|
|
26
|
+
new Field("metadata", new Utf8(), false), // JSON string
|
|
27
|
+
timestampField("created_at"),
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
export const indexedSessionsSchema = new Schema([
|
|
31
|
+
new Field("session_id", new Utf8(), false),
|
|
32
|
+
new Field("file_path", new Utf8(), false),
|
|
33
|
+
new Field("file_size", new Float64(), false), // Float64 avoids Int32 overflow and BigInt handling
|
|
34
|
+
new Field("message_count", new Int32(), false),
|
|
35
|
+
timestampField("first_message_at"),
|
|
36
|
+
timestampField("last_message_at"),
|
|
37
|
+
timestampField("indexed_at"),
|
|
38
|
+
new Field("project", new Utf8(), true), // Nullable
|
|
39
|
+
new Field("git_branch", new Utf8(), true), // Nullable
|
|
40
|
+
]);
|