@xiaoxiamimengfb/my-opencode-mem 2.12.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 (152) hide show
  1. package/README.md +155 -0
  2. package/dist/config.d.ts +58 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +411 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +427 -0
  8. package/dist/plugin.d.ts +5 -0
  9. package/dist/plugin.d.ts.map +1 -0
  10. package/dist/plugin.js +4 -0
  11. package/dist/services/ai/ai-provider-factory.d.ts +8 -0
  12. package/dist/services/ai/ai-provider-factory.d.ts.map +1 -0
  13. package/dist/services/ai/ai-provider-factory.js +28 -0
  14. package/dist/services/ai/opencode-provider.d.ts +30 -0
  15. package/dist/services/ai/opencode-provider.d.ts.map +1 -0
  16. package/dist/services/ai/opencode-provider.js +332 -0
  17. package/dist/services/ai/provider-config.d.ts +17 -0
  18. package/dist/services/ai/provider-config.d.ts.map +1 -0
  19. package/dist/services/ai/provider-config.js +14 -0
  20. package/dist/services/ai/providers/anthropic-messages.d.ts +12 -0
  21. package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -0
  22. package/dist/services/ai/providers/anthropic-messages.js +184 -0
  23. package/dist/services/ai/providers/base-provider.d.ts +25 -0
  24. package/dist/services/ai/providers/base-provider.d.ts.map +1 -0
  25. package/dist/services/ai/providers/base-provider.js +23 -0
  26. package/dist/services/ai/providers/google-gemini.d.ts +16 -0
  27. package/dist/services/ai/providers/google-gemini.d.ts.map +1 -0
  28. package/dist/services/ai/providers/google-gemini.js +228 -0
  29. package/dist/services/ai/providers/openai-chat-completion.d.ts +13 -0
  30. package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -0
  31. package/dist/services/ai/providers/openai-chat-completion.js +277 -0
  32. package/dist/services/ai/providers/openai-responses.d.ts +14 -0
  33. package/dist/services/ai/providers/openai-responses.d.ts.map +1 -0
  34. package/dist/services/ai/providers/openai-responses.js +182 -0
  35. package/dist/services/ai/session/ai-session-manager.d.ts +21 -0
  36. package/dist/services/ai/session/ai-session-manager.d.ts.map +1 -0
  37. package/dist/services/ai/session/ai-session-manager.js +166 -0
  38. package/dist/services/ai/session/session-types.d.ts +43 -0
  39. package/dist/services/ai/session/session-types.d.ts.map +1 -0
  40. package/dist/services/ai/session/session-types.js +1 -0
  41. package/dist/services/ai/tools/tool-schema.d.ts +41 -0
  42. package/dist/services/ai/tools/tool-schema.d.ts.map +1 -0
  43. package/dist/services/ai/tools/tool-schema.js +24 -0
  44. package/dist/services/ai/validators/user-profile-validator.d.ts +13 -0
  45. package/dist/services/ai/validators/user-profile-validator.d.ts.map +1 -0
  46. package/dist/services/ai/validators/user-profile-validator.js +111 -0
  47. package/dist/services/api-handlers.d.ts +164 -0
  48. package/dist/services/api-handlers.d.ts.map +1 -0
  49. package/dist/services/api-handlers.js +901 -0
  50. package/dist/services/auto-capture.d.ts +3 -0
  51. package/dist/services/auto-capture.d.ts.map +1 -0
  52. package/dist/services/auto-capture.js +306 -0
  53. package/dist/services/cleanup-service.d.ts +23 -0
  54. package/dist/services/cleanup-service.d.ts.map +1 -0
  55. package/dist/services/cleanup-service.js +102 -0
  56. package/dist/services/client.d.ts +118 -0
  57. package/dist/services/client.d.ts.map +1 -0
  58. package/dist/services/client.js +251 -0
  59. package/dist/services/context.d.ts +11 -0
  60. package/dist/services/context.d.ts.map +1 -0
  61. package/dist/services/context.js +24 -0
  62. package/dist/services/deduplication-service.d.ts +30 -0
  63. package/dist/services/deduplication-service.d.ts.map +1 -0
  64. package/dist/services/deduplication-service.js +124 -0
  65. package/dist/services/embedding.d.ts +15 -0
  66. package/dist/services/embedding.d.ts.map +1 -0
  67. package/dist/services/embedding.js +106 -0
  68. package/dist/services/jsonc.d.ts +7 -0
  69. package/dist/services/jsonc.d.ts.map +1 -0
  70. package/dist/services/jsonc.js +76 -0
  71. package/dist/services/language-detector.d.ts +3 -0
  72. package/dist/services/language-detector.d.ts.map +1 -0
  73. package/dist/services/language-detector.js +16 -0
  74. package/dist/services/logger.d.ts +2 -0
  75. package/dist/services/logger.d.ts.map +1 -0
  76. package/dist/services/logger.js +51 -0
  77. package/dist/services/migration-service.d.ts +42 -0
  78. package/dist/services/migration-service.d.ts.map +1 -0
  79. package/dist/services/migration-service.js +250 -0
  80. package/dist/services/privacy.d.ts +3 -0
  81. package/dist/services/privacy.d.ts.map +1 -0
  82. package/dist/services/privacy.js +7 -0
  83. package/dist/services/secret-resolver.d.ts +2 -0
  84. package/dist/services/secret-resolver.d.ts.map +1 -0
  85. package/dist/services/secret-resolver.js +55 -0
  86. package/dist/services/sqlite/connection-manager.d.ts +13 -0
  87. package/dist/services/sqlite/connection-manager.d.ts.map +1 -0
  88. package/dist/services/sqlite/connection-manager.js +74 -0
  89. package/dist/services/sqlite/shard-manager.d.ts +23 -0
  90. package/dist/services/sqlite/shard-manager.d.ts.map +1 -0
  91. package/dist/services/sqlite/shard-manager.js +288 -0
  92. package/dist/services/sqlite/sqlite-bootstrap.d.ts +2 -0
  93. package/dist/services/sqlite/sqlite-bootstrap.d.ts.map +1 -0
  94. package/dist/services/sqlite/sqlite-bootstrap.js +8 -0
  95. package/dist/services/sqlite/types.d.ts +42 -0
  96. package/dist/services/sqlite/types.d.ts.map +1 -0
  97. package/dist/services/sqlite/types.js +1 -0
  98. package/dist/services/sqlite/vector-search.d.ts +29 -0
  99. package/dist/services/sqlite/vector-search.d.ts.map +1 -0
  100. package/dist/services/sqlite/vector-search.js +268 -0
  101. package/dist/services/tags.d.ts +24 -0
  102. package/dist/services/tags.d.ts.map +1 -0
  103. package/dist/services/tags.js +146 -0
  104. package/dist/services/user-memory-learning.d.ts +3 -0
  105. package/dist/services/user-memory-learning.d.ts.map +1 -0
  106. package/dist/services/user-memory-learning.js +231 -0
  107. package/dist/services/user-profile/profile-context.d.ts +2 -0
  108. package/dist/services/user-profile/profile-context.d.ts.map +1 -0
  109. package/dist/services/user-profile/profile-context.js +40 -0
  110. package/dist/services/user-profile/profile-utils.d.ts +3 -0
  111. package/dist/services/user-profile/profile-utils.d.ts.map +1 -0
  112. package/dist/services/user-profile/profile-utils.js +45 -0
  113. package/dist/services/user-profile/types.d.ts +46 -0
  114. package/dist/services/user-profile/types.d.ts.map +1 -0
  115. package/dist/services/user-profile/types.js +1 -0
  116. package/dist/services/user-profile/user-profile-manager.d.ts +23 -0
  117. package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -0
  118. package/dist/services/user-profile/user-profile-manager.js +292 -0
  119. package/dist/services/user-prompt/user-prompt-manager.d.ts +41 -0
  120. package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -0
  121. package/dist/services/user-prompt/user-prompt-manager.js +192 -0
  122. package/dist/services/vector-backends/backend-factory.d.ts +3 -0
  123. package/dist/services/vector-backends/backend-factory.d.ts.map +1 -0
  124. package/dist/services/vector-backends/backend-factory.js +104 -0
  125. package/dist/services/vector-backends/exact-scan-backend.d.ts +39 -0
  126. package/dist/services/vector-backends/exact-scan-backend.d.ts.map +1 -0
  127. package/dist/services/vector-backends/exact-scan-backend.js +63 -0
  128. package/dist/services/vector-backends/types.d.ts +51 -0
  129. package/dist/services/vector-backends/types.d.ts.map +1 -0
  130. package/dist/services/vector-backends/types.js +1 -0
  131. package/dist/services/vector-backends/usearch-backend.d.ts +47 -0
  132. package/dist/services/vector-backends/usearch-backend.d.ts.map +1 -0
  133. package/dist/services/vector-backends/usearch-backend.js +174 -0
  134. package/dist/services/web-server-worker.d.ts +2 -0
  135. package/dist/services/web-server-worker.d.ts.map +1 -0
  136. package/dist/services/web-server-worker.js +283 -0
  137. package/dist/services/web-server.d.ts +31 -0
  138. package/dist/services/web-server.d.ts.map +1 -0
  139. package/dist/services/web-server.js +356 -0
  140. package/dist/types/index.d.ts +19 -0
  141. package/dist/types/index.d.ts.map +1 -0
  142. package/dist/types/index.js +1 -0
  143. package/dist/web/app.d.ts +2 -0
  144. package/dist/web/app.d.ts.map +1 -0
  145. package/dist/web/app.js +1194 -0
  146. package/dist/web/favicon.ico +0 -0
  147. package/dist/web/i18n.d.ts +2 -0
  148. package/dist/web/i18n.d.ts.map +1 -0
  149. package/dist/web/i18n.js +265 -0
  150. package/dist/web/index.html +284 -0
  151. package/dist/web/styles.css +1631 -0
  152. package/package.json +71 -0
@@ -0,0 +1,288 @@
1
+ import { getDatabase } from "./sqlite-bootstrap.js";
2
+ import { join, basename } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { CONFIG } from "../../config.js";
5
+ import { connectionManager } from "./connection-manager.js";
6
+ import { log } from "../logger.js";
7
+ import { vectorSearch } from "./vector-search.js";
8
+ const Database = getDatabase();
9
+ const METADATA_DB_NAME = "metadata.db";
10
+ export class ShardManager {
11
+ metadataDb;
12
+ metadataPath;
13
+ constructor() {
14
+ this.metadataPath = join(CONFIG.storagePath, METADATA_DB_NAME);
15
+ this.metadataDb = connectionManager.getConnection(this.metadataPath);
16
+ this.initMetadataDb();
17
+ }
18
+ initMetadataDb() {
19
+ this.metadataDb.run(`
20
+ CREATE TABLE IF NOT EXISTS shards (
21
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
22
+ scope TEXT NOT NULL,
23
+ scope_hash TEXT NOT NULL,
24
+ shard_index INTEGER NOT NULL,
25
+ db_path TEXT NOT NULL,
26
+ vector_count INTEGER DEFAULT 0,
27
+ is_active INTEGER DEFAULT 1,
28
+ created_at INTEGER NOT NULL,
29
+ UNIQUE(scope, scope_hash, shard_index)
30
+ )
31
+ `);
32
+ this.metadataDb.run(`
33
+ CREATE INDEX IF NOT EXISTS idx_active_shards
34
+ ON shards(scope, scope_hash, is_active)
35
+ `);
36
+ }
37
+ getShardPath(scope, scopeHash, shardIndex) {
38
+ const dir = join(CONFIG.storagePath, `${scope}s`);
39
+ return join(dir, `${scope}_${scopeHash}_shard_${shardIndex}.db`);
40
+ }
41
+ resolveStoredPath(storedPath, scope) {
42
+ const fileName = basename(storedPath);
43
+ return join(CONFIG.storagePath, `${scope}s`, fileName);
44
+ }
45
+ getActiveShard(scope, scopeHash) {
46
+ const stmt = this.metadataDb.prepare(`
47
+ SELECT * FROM shards
48
+ WHERE scope = ? AND scope_hash = ? AND is_active = 1
49
+ ORDER BY shard_index DESC LIMIT 1
50
+ `);
51
+ const row = stmt.get(scope, scopeHash);
52
+ if (!row)
53
+ return null;
54
+ return {
55
+ id: row.id,
56
+ scope: row.scope,
57
+ scopeHash: row.scope_hash,
58
+ shardIndex: row.shard_index,
59
+ dbPath: this.resolveStoredPath(row.db_path, row.scope),
60
+ vectorCount: row.vector_count,
61
+ isActive: row.is_active === 1,
62
+ createdAt: row.created_at,
63
+ };
64
+ }
65
+ getAllShards(scope, scopeHash) {
66
+ let stmt;
67
+ let rows;
68
+ if (scopeHash === "") {
69
+ stmt = this.metadataDb.prepare(`
70
+ SELECT * FROM shards
71
+ WHERE scope = ?
72
+ ORDER BY shard_index ASC
73
+ `);
74
+ rows = stmt.all(scope);
75
+ }
76
+ else {
77
+ stmt = this.metadataDb.prepare(`
78
+ SELECT * FROM shards
79
+ WHERE scope = ? AND scope_hash = ?
80
+ ORDER BY shard_index ASC
81
+ `);
82
+ rows = stmt.all(scope, scopeHash);
83
+ }
84
+ return rows.map((row) => ({
85
+ id: row.id,
86
+ scope: row.scope,
87
+ scopeHash: row.scope_hash,
88
+ shardIndex: row.shard_index,
89
+ dbPath: this.resolveStoredPath(row.db_path, row.scope),
90
+ vectorCount: row.vector_count,
91
+ isActive: row.is_active === 1,
92
+ createdAt: row.created_at,
93
+ }));
94
+ }
95
+ createShard(scope, scopeHash, shardIndex) {
96
+ const fullPath = this.getShardPath(scope, scopeHash, shardIndex);
97
+ const storedPath = join(`${scope}s`, basename(fullPath)).replace(/\\/g, "/");
98
+ const now = Date.now();
99
+ const stmt = this.metadataDb.prepare(`
100
+ INSERT INTO shards (scope, scope_hash, shard_index, db_path, vector_count, is_active, created_at)
101
+ VALUES (?, ?, ?, ?, 0, 1, ?)
102
+ `);
103
+ const result = stmt.run(scope, scopeHash, shardIndex, storedPath, now);
104
+ const db = connectionManager.getConnection(fullPath);
105
+ this.initShardDb(db);
106
+ return {
107
+ id: Number(result.lastInsertRowid),
108
+ scope,
109
+ scopeHash,
110
+ shardIndex,
111
+ dbPath: fullPath,
112
+ vectorCount: 0,
113
+ isActive: true,
114
+ createdAt: now,
115
+ };
116
+ }
117
+ initShardDb(db) {
118
+ db.run(`
119
+ CREATE TABLE IF NOT EXISTS shard_metadata (
120
+ key TEXT PRIMARY KEY,
121
+ value TEXT NOT NULL
122
+ )
123
+ `);
124
+ db.run(`
125
+ INSERT OR REPLACE INTO shard_metadata (key, value)
126
+ VALUES ('embedding_dimensions', '${CONFIG.embeddingDimensions}')
127
+ `);
128
+ db.run(`
129
+ INSERT OR REPLACE INTO shard_metadata (key, value)
130
+ VALUES ('embedding_model', '${CONFIG.embeddingModel}')
131
+ `);
132
+ db.run(`
133
+ CREATE TABLE IF NOT EXISTS memories (
134
+ id TEXT PRIMARY KEY,
135
+ content TEXT NOT NULL,
136
+ vector BLOB NOT NULL,
137
+ tags_vector BLOB,
138
+ container_tag TEXT NOT NULL,
139
+ tags TEXT,
140
+ type TEXT,
141
+ created_at INTEGER NOT NULL,
142
+ updated_at INTEGER NOT NULL,
143
+ metadata TEXT,
144
+ display_name TEXT,
145
+ user_name TEXT,
146
+ user_email TEXT,
147
+ project_path TEXT,
148
+ project_name TEXT,
149
+ git_repo_url TEXT,
150
+ is_pinned INTEGER DEFAULT 0
151
+ )
152
+ `);
153
+ db.run(`CREATE INDEX IF NOT EXISTS idx_container_tag ON memories(container_tag)`);
154
+ db.run(`CREATE INDEX IF NOT EXISTS idx_type ON memories(type)`);
155
+ db.run(`CREATE INDEX IF NOT EXISTS idx_created_at ON memories(created_at DESC)`);
156
+ db.run(`CREATE INDEX IF NOT EXISTS idx_is_pinned ON memories(is_pinned)`);
157
+ }
158
+ isShardValid(shard) {
159
+ if (!existsSync(shard.dbPath)) {
160
+ log("Shard DB file missing", { dbPath: shard.dbPath, shardId: shard.id });
161
+ return false;
162
+ }
163
+ try {
164
+ const db = connectionManager.getConnection(shard.dbPath);
165
+ const result = db
166
+ .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='memories'`)
167
+ .get();
168
+ if (!result) {
169
+ log("Shard DB missing 'memories' table", {
170
+ dbPath: shard.dbPath,
171
+ shardId: shard.id,
172
+ });
173
+ return false;
174
+ }
175
+ return true;
176
+ }
177
+ catch (error) {
178
+ log("Error validating shard DB", {
179
+ dbPath: shard.dbPath,
180
+ error: String(error),
181
+ });
182
+ return false;
183
+ }
184
+ }
185
+ ensureShardTables(shard) {
186
+ try {
187
+ const db = connectionManager.getConnection(shard.dbPath);
188
+ this.initShardDb(db);
189
+ }
190
+ catch (error) {
191
+ log("Error ensuring shard tables", {
192
+ dbPath: shard.dbPath,
193
+ error: String(error),
194
+ });
195
+ }
196
+ }
197
+ getWriteShard(scope, scopeHash) {
198
+ let shard = this.getActiveShard(scope, scopeHash);
199
+ if (!shard) {
200
+ return this.createShard(scope, scopeHash, 0);
201
+ }
202
+ if (!this.isShardValid(shard)) {
203
+ log("Active shard is invalid, recreating", {
204
+ scope,
205
+ scopeHash,
206
+ shardIndex: shard.shardIndex,
207
+ dbPath: shard.dbPath,
208
+ });
209
+ connectionManager.closeConnection(shard.dbPath);
210
+ const deleteStmt = this.metadataDb.prepare(`DELETE FROM shards WHERE id = ?`);
211
+ deleteStmt.run(shard.id);
212
+ return this.createShard(scope, scopeHash, shard.shardIndex);
213
+ }
214
+ if (shard.vectorCount >= CONFIG.maxVectorsPerShard) {
215
+ this.markShardReadOnly(shard.id);
216
+ return this.createShard(scope, scopeHash, shard.shardIndex + 1);
217
+ }
218
+ return shard;
219
+ }
220
+ markShardReadOnly(shardId) {
221
+ const stmt = this.metadataDb.prepare(`
222
+ UPDATE shards SET is_active = 0 WHERE id = ?
223
+ `);
224
+ stmt.run(shardId);
225
+ }
226
+ incrementVectorCount(shardId) {
227
+ const stmt = this.metadataDb.prepare(`
228
+ UPDATE shards SET vector_count = vector_count + 1 WHERE id = ?
229
+ `);
230
+ stmt.run(shardId);
231
+ }
232
+ decrementVectorCount(shardId) {
233
+ const stmt = this.metadataDb.prepare(`
234
+ UPDATE shards SET vector_count = vector_count - 1 WHERE id = ? AND vector_count > 0
235
+ `);
236
+ stmt.run(shardId);
237
+ }
238
+ getShardByPath(dbPath) {
239
+ const fileName = basename(dbPath);
240
+ const stmt = this.metadataDb.prepare(`SELECT * FROM shards WHERE db_path LIKE '%' || ?`);
241
+ const row = stmt.get(fileName);
242
+ if (!row)
243
+ return null;
244
+ return {
245
+ id: row.id,
246
+ scope: row.scope,
247
+ scopeHash: row.scope_hash,
248
+ shardIndex: row.shard_index,
249
+ dbPath: this.resolveStoredPath(row.db_path, row.scope),
250
+ vectorCount: row.vector_count,
251
+ isActive: row.is_active === 1,
252
+ createdAt: row.created_at,
253
+ };
254
+ }
255
+ async deleteShard(shardId) {
256
+ const stmt = this.metadataDb.prepare(`SELECT * FROM shards WHERE id = ?`);
257
+ const row = stmt.get(shardId);
258
+ if (row) {
259
+ const fullPath = this.resolveStoredPath(row.db_path, row.scope);
260
+ await vectorSearch.deleteShardIndexes({
261
+ id: row.id,
262
+ scope: row.scope,
263
+ scopeHash: row.scope_hash,
264
+ shardIndex: row.shard_index,
265
+ dbPath: fullPath,
266
+ vectorCount: row.vector_count,
267
+ isActive: row.is_active === 1,
268
+ createdAt: row.created_at,
269
+ });
270
+ connectionManager.closeConnection(fullPath);
271
+ try {
272
+ const fs = require("node:fs");
273
+ if (fs.existsSync(fullPath)) {
274
+ fs.unlinkSync(fullPath);
275
+ }
276
+ }
277
+ catch (error) {
278
+ log("Error deleting shard file", {
279
+ dbPath: fullPath,
280
+ error: String(error),
281
+ });
282
+ }
283
+ const deleteStmt = this.metadataDb.prepare(`DELETE FROM shards WHERE id = ?`);
284
+ deleteStmt.run(shardId);
285
+ }
286
+ }
287
+ }
288
+ export const shardManager = new ShardManager();
@@ -0,0 +1,2 @@
1
+ export declare function getDatabase(): typeof import("bun:sqlite").Database;
2
+ //# sourceMappingURL=sqlite-bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-bootstrap.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/sqlite-bootstrap.ts"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,IAAI,cAAc,YAAY,EAAE,QAAQ,CAMlE"}
@@ -0,0 +1,8 @@
1
+ let Database;
2
+ export function getDatabase() {
3
+ if (!Database) {
4
+ const bunSqlite = require("bun:sqlite");
5
+ Database = bunSqlite.Database;
6
+ }
7
+ return Database;
8
+ }
@@ -0,0 +1,42 @@
1
+ export interface ShardInfo {
2
+ id: number;
3
+ scope: "user" | "project";
4
+ scopeHash: string;
5
+ shardIndex: number;
6
+ dbPath: string;
7
+ vectorCount: number;
8
+ isActive: boolean;
9
+ createdAt: number;
10
+ }
11
+ export interface MemoryRecord {
12
+ id: string;
13
+ content: string;
14
+ vector: Float32Array;
15
+ tagsVector?: Float32Array;
16
+ containerTag: string;
17
+ tags?: string;
18
+ type?: string;
19
+ createdAt: number;
20
+ updatedAt: number;
21
+ metadata?: string;
22
+ displayName?: string;
23
+ userName?: string;
24
+ userEmail?: string;
25
+ projectPath?: string;
26
+ projectName?: string;
27
+ gitRepoUrl?: string;
28
+ }
29
+ export interface SearchResult {
30
+ id: string;
31
+ memory: string;
32
+ similarity: number;
33
+ tags?: string[];
34
+ metadata?: Record<string, unknown>;
35
+ displayName?: string;
36
+ userName?: string;
37
+ userEmail?: string;
38
+ projectPath?: string;
39
+ projectName?: string;
40
+ gitRepoUrl?: string;
41
+ }
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import type { MemoryRecord, SearchResult, ShardInfo } from "./types.js";
2
+ import type { VectorBackend } from "../vector-backends/types.js";
3
+ declare const Database: typeof import("bun:sqlite").Database;
4
+ type DatabaseType = typeof Database.prototype;
5
+ export declare class VectorSearch {
6
+ private readonly backendPromise;
7
+ private readonly fallbackBackend;
8
+ constructor(backend?: VectorBackend, fallbackBackend?: VectorBackend);
9
+ private getBackend;
10
+ insertVector(db: DatabaseType, record: MemoryRecord, shard?: ShardInfo): Promise<void>;
11
+ searchInShard(shard: ShardInfo, queryVector: Float32Array, containerTag: string, limit: number, queryText?: string): Promise<SearchResult[]>;
12
+ searchAcrossShards(shards: ShardInfo[], queryVector: Float32Array, containerTag: string, limit: number, similarityThreshold: number, queryText?: string): Promise<SearchResult[]>;
13
+ deleteVector(db: DatabaseType, memoryId: string, shard?: ShardInfo): Promise<void>;
14
+ updateVector(db: DatabaseType, memoryId: string, vector: Float32Array, shard?: ShardInfo, tagsVector?: Float32Array): Promise<void>;
15
+ listMemories(db: DatabaseType, containerTag: string, limit: number): any[];
16
+ getAllMemories(db: DatabaseType): any[];
17
+ getMemoryById(db: DatabaseType, memoryId: string): any | null;
18
+ getMemoriesBySessionID(db: DatabaseType, sessionID: string): any[];
19
+ countVectors(db: DatabaseType, containerTag: string): number;
20
+ countAllVectors(db: DatabaseType): number;
21
+ getDistinctTags(db: DatabaseType): any[];
22
+ pinMemory(db: DatabaseType, memoryId: string): void;
23
+ unpinMemory(db: DatabaseType, memoryId: string): void;
24
+ rebuildIndexForShard(db: DatabaseType, scope: string, scopeHash: string, shardIndex: number): Promise<void>;
25
+ deleteShardIndexes(shard: ShardInfo): Promise<void>;
26
+ }
27
+ export declare const vectorSearch: VectorSearch;
28
+ export {};
29
+ //# sourceMappingURL=vector-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,QAAA,MAAM,QAAQ,sCAAgB,CAAC;AAC/B,KAAK,YAAY,GAAG,OAAO,QAAQ,CAAC,SAAS,CAAC;AAM9C,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgB;gBAEpC,OAAO,CAAC,EAAE,aAAa,EAAE,eAAe,GAAE,aAAsC;YAO9E,UAAU;IAIlB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAyCtF,aAAa,CACjB,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC;IAyHpB,kBAAkB,CACtB,MAAM,EAAE,SAAS,EAAE,EACnB,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,mBAAmB,EAAE,MAAM,EAC3B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC;IAiBpB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlF,YAAY,CAChB,EAAE,EAAE,YAAY,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,KAAK,CAAC,EAAE,SAAS,EACjB,UAAU,CAAC,EAAE,YAAY,GACxB,OAAO,CAAC,IAAI,CAAC;IAkBhB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;IAW1E,cAAc,CAAC,EAAE,EAAE,YAAY,GAAG,GAAG,EAAE;IAKvC,aAAa,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAK7D,sBAAsB,CAAC,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE;IAgBlE,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAM5D,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM;IAMzC,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,GAAG,EAAE;IAexC,SAAS,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKnD,WAAW,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK/C,oBAAoB,CACxB,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAgBV,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CAI1D;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
@@ -0,0 +1,268 @@
1
+ import { getDatabase } from "./sqlite-bootstrap.js";
2
+ import { connectionManager } from "./connection-manager.js";
3
+ import { log } from "../logger.js";
4
+ import { CONFIG } from "../../config.js";
5
+ import { createVectorBackend } from "../vector-backends/backend-factory.js";
6
+ import { ExactScanBackend } from "../vector-backends/exact-scan-backend.js";
7
+ const Database = getDatabase();
8
+ function toBlob(vector) {
9
+ return vector ? new Uint8Array(vector.buffer) : null;
10
+ }
11
+ export class VectorSearch {
12
+ backendPromise;
13
+ fallbackBackend;
14
+ constructor(backend, fallbackBackend = new ExactScanBackend()) {
15
+ this.backendPromise = backend
16
+ ? Promise.resolve(backend)
17
+ : createVectorBackend({ vectorBackend: CONFIG.vectorBackend });
18
+ this.fallbackBackend = fallbackBackend;
19
+ }
20
+ async getBackend() {
21
+ return this.backendPromise;
22
+ }
23
+ async insertVector(db, record, shard) {
24
+ const insertMemory = db.prepare(`
25
+ INSERT INTO memories (
26
+ id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
27
+ metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
28
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
29
+ `);
30
+ insertMemory.run(record.id, record.content, toBlob(record.vector), toBlob(record.tagsVector), record.containerTag, record.tags || null, record.type || null, record.createdAt, record.updatedAt, record.metadata || null, record.displayName || null, record.userName || null, record.userEmail || null, record.projectPath || null, record.projectName || null, record.gitRepoUrl || null);
31
+ try {
32
+ if (shard) {
33
+ const backend = await this.getBackend();
34
+ await backend.insert({ id: record.id, vector: record.vector, shard, kind: "content" });
35
+ if (record.tagsVector) {
36
+ await backend.insert({ id: record.id, vector: record.tagsVector, shard, kind: "tags" });
37
+ }
38
+ }
39
+ }
40
+ catch (error) {
41
+ db.prepare(`DELETE FROM memories WHERE id = ?`).run(record.id);
42
+ throw error;
43
+ }
44
+ }
45
+ async searchInShard(shard, queryVector, containerTag, limit, queryText) {
46
+ const db = connectionManager.getConnection(shard.dbPath);
47
+ const backend = await this.getBackend();
48
+ let contentResults;
49
+ let tagsResults;
50
+ try {
51
+ await backend.rebuildFromShard({ db, shard, kind: "content" });
52
+ await backend.rebuildFromShard({ db, shard, kind: "tags" });
53
+ contentResults = await backend.search({
54
+ db,
55
+ shard,
56
+ kind: "content",
57
+ queryVector,
58
+ limit: limit * 4,
59
+ });
60
+ tagsResults = await backend.search({
61
+ db,
62
+ shard,
63
+ kind: "tags",
64
+ queryVector,
65
+ limit: limit * 4,
66
+ });
67
+ }
68
+ catch (error) {
69
+ log("Vector search degraded to exact scan in shard", {
70
+ shardId: shard.id,
71
+ backend: backend.getBackendName(),
72
+ error: String(error),
73
+ });
74
+ await this.fallbackBackend.rebuildFromShard({ db, shard, kind: "content" });
75
+ await this.fallbackBackend.rebuildFromShard({ db, shard, kind: "tags" });
76
+ contentResults = await this.fallbackBackend.search({
77
+ db,
78
+ shard,
79
+ kind: "content",
80
+ queryVector,
81
+ limit: limit * 4,
82
+ });
83
+ tagsResults = await this.fallbackBackend.search({
84
+ db,
85
+ shard,
86
+ kind: "tags",
87
+ queryVector,
88
+ limit: limit * 4,
89
+ });
90
+ }
91
+ const scoreMap = new Map();
92
+ for (const r of contentResults) {
93
+ scoreMap.set(r.id, { contentSim: 1 - r.distance, tagsSim: 0 });
94
+ }
95
+ for (const r of tagsResults) {
96
+ const entry = scoreMap.get(r.id);
97
+ if (entry) {
98
+ entry.tagsSim = 1 - r.distance;
99
+ }
100
+ else {
101
+ scoreMap.set(r.id, { contentSim: 0, tagsSim: 1 - r.distance });
102
+ }
103
+ }
104
+ const ids = Array.from(scoreMap.keys());
105
+ if (ids.length === 0)
106
+ return [];
107
+ const placeholders = ids.map(() => "?").join(",");
108
+ const rows = db
109
+ .prepare(`
110
+ SELECT * FROM memories
111
+ WHERE id IN (${placeholders}) AND container_tag = ?
112
+ `)
113
+ .all(...ids, containerTag);
114
+ const queryWords = queryText
115
+ ? queryText
116
+ .toLowerCase()
117
+ .split(/[\s,]+/)
118
+ .filter((w) => w.length > 1)
119
+ : [];
120
+ const hydratedResults = rows.map((row) => {
121
+ const scores = scoreMap.get(row.id);
122
+ const memoryTagsStr = row.tags || "";
123
+ const memoryTags = memoryTagsStr.split(",").map((t) => t.trim().toLowerCase());
124
+ let exactMatchBoost = 0;
125
+ if (queryWords.length > 0 && memoryTags.length > 0) {
126
+ const matches = queryWords.filter((w) => memoryTags.some((t) => t.includes(w) || w.includes(t))).length;
127
+ exactMatchBoost = matches / Math.max(queryWords.length, 1);
128
+ }
129
+ const finalTagsSim = Math.max(scores.tagsSim, exactMatchBoost);
130
+ const similarity = scores.contentSim * 0.6 + finalTagsSim * 0.4;
131
+ return {
132
+ id: row.id,
133
+ memory: row.content,
134
+ similarity,
135
+ tags: memoryTagsStr ? memoryTagsStr.split(",") : [],
136
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
137
+ containerTag: row.container_tag,
138
+ displayName: row.display_name,
139
+ userName: row.user_name,
140
+ userEmail: row.user_email,
141
+ projectPath: row.project_path,
142
+ projectName: row.project_name,
143
+ gitRepoUrl: row.git_repo_url,
144
+ isPinned: row.is_pinned,
145
+ };
146
+ });
147
+ hydratedResults.sort((a, b) => b.similarity - a.similarity);
148
+ return hydratedResults;
149
+ }
150
+ async searchAcrossShards(shards, queryVector, containerTag, limit, similarityThreshold, queryText) {
151
+ const shardPromises = shards.map(async (shard) => {
152
+ try {
153
+ return await this.searchInShard(shard, queryVector, containerTag, limit, queryText);
154
+ }
155
+ catch (error) {
156
+ log("Shard search error", { shardId: shard.id, error: String(error) });
157
+ return [];
158
+ }
159
+ });
160
+ const resultsArray = await Promise.all(shardPromises);
161
+ const allResults = resultsArray.flat();
162
+ allResults.sort((a, b) => b.similarity - a.similarity);
163
+ return allResults.filter((r) => r.similarity >= similarityThreshold).slice(0, limit);
164
+ }
165
+ async deleteVector(db, memoryId, shard) {
166
+ db.prepare(`DELETE FROM memories WHERE id = ?`).run(memoryId);
167
+ if (shard) {
168
+ const backend = await this.getBackend();
169
+ await backend.delete({ id: memoryId, shard, kind: "content" });
170
+ await backend.delete({ id: memoryId, shard, kind: "tags" });
171
+ }
172
+ }
173
+ async updateVector(db, memoryId, vector, shard, tagsVector) {
174
+ db.prepare(`UPDATE memories SET vector = ?, tags_vector = ? WHERE id = ?`).run(toBlob(vector), toBlob(tagsVector), memoryId);
175
+ if (shard) {
176
+ const backend = await this.getBackend();
177
+ await backend.insert({ id: memoryId, vector, shard, kind: "content" });
178
+ if (tagsVector) {
179
+ await backend.insert({ id: memoryId, vector: tagsVector, shard, kind: "tags" });
180
+ }
181
+ else {
182
+ await backend.delete({ id: memoryId, shard, kind: "tags" });
183
+ }
184
+ }
185
+ }
186
+ listMemories(db, containerTag, limit) {
187
+ const stmt = db.prepare(`
188
+ SELECT * FROM memories
189
+ WHERE container_tag = ?
190
+ ORDER BY created_at DESC
191
+ LIMIT ?
192
+ `);
193
+ return stmt.all(containerTag, limit);
194
+ }
195
+ getAllMemories(db) {
196
+ const stmt = db.prepare(`SELECT * FROM memories ORDER BY created_at DESC`);
197
+ return stmt.all();
198
+ }
199
+ getMemoryById(db, memoryId) {
200
+ const stmt = db.prepare(`SELECT * FROM memories WHERE id = ?`);
201
+ return stmt.get(memoryId);
202
+ }
203
+ getMemoriesBySessionID(db, sessionID) {
204
+ const stmt = db.prepare(`
205
+ SELECT * FROM memories
206
+ WHERE metadata LIKE ?
207
+ ORDER BY created_at DESC
208
+ `);
209
+ const rows = stmt.all(`%"sessionID":"${sessionID}"%`);
210
+ return rows.map((row) => ({
211
+ ...row,
212
+ tags: row.tags ? row.tags.split(",") : [],
213
+ metadata: row.metadata ? JSON.parse(row.metadata) : {},
214
+ }));
215
+ }
216
+ countVectors(db, containerTag) {
217
+ const stmt = db.prepare(`SELECT COUNT(*) as count FROM memories WHERE container_tag = ?`);
218
+ const result = stmt.get(containerTag);
219
+ return result.count;
220
+ }
221
+ countAllVectors(db) {
222
+ const stmt = db.prepare(`SELECT COUNT(*) as count FROM memories`);
223
+ const result = stmt.get();
224
+ return result.count;
225
+ }
226
+ getDistinctTags(db) {
227
+ const stmt = db.prepare(`
228
+ SELECT DISTINCT
229
+ container_tag,
230
+ display_name,
231
+ user_name,
232
+ user_email,
233
+ project_path,
234
+ project_name,
235
+ git_repo_url
236
+ FROM memories
237
+ `);
238
+ return stmt.all();
239
+ }
240
+ pinMemory(db, memoryId) {
241
+ const stmt = db.prepare(`UPDATE memories SET is_pinned = 1 WHERE id = ?`);
242
+ stmt.run(memoryId);
243
+ }
244
+ unpinMemory(db, memoryId) {
245
+ const stmt = db.prepare(`UPDATE memories SET is_pinned = 0 WHERE id = ?`);
246
+ stmt.run(memoryId);
247
+ }
248
+ async rebuildIndexForShard(db, scope, scopeHash, shardIndex) {
249
+ const backend = await this.getBackend();
250
+ const shard = {
251
+ id: 0,
252
+ scope: scope,
253
+ scopeHash,
254
+ shardIndex,
255
+ dbPath: "",
256
+ vectorCount: 0,
257
+ isActive: true,
258
+ createdAt: Date.now(),
259
+ };
260
+ await backend.rebuildFromShard({ db, shard, kind: "content" });
261
+ await backend.rebuildFromShard({ db, shard, kind: "tags" });
262
+ }
263
+ async deleteShardIndexes(shard) {
264
+ const backend = await this.getBackend();
265
+ await backend.deleteShardIndexes({ shard });
266
+ }
267
+ }
268
+ export const vectorSearch = new VectorSearch();
@@ -0,0 +1,24 @@
1
+ export interface TagInfo {
2
+ tag: string;
3
+ displayName: string;
4
+ userName?: string;
5
+ userEmail?: string;
6
+ projectPath?: string;
7
+ projectName?: string;
8
+ gitRepoUrl?: string;
9
+ }
10
+ export declare function getGitEmail(): string | null;
11
+ export declare function getGitName(): string | null;
12
+ export declare function getGitRepoUrl(directory: string): string | null;
13
+ export declare function getGitCommonDir(directory: string): string | null;
14
+ export declare function getGitTopLevel(directory: string): string | null;
15
+ export declare function getProjectRoot(directory: string): string;
16
+ export declare function getProjectIdentity(directory: string): string;
17
+ export declare function getProjectName(directory: string): string;
18
+ export declare function getUserTagInfo(): TagInfo;
19
+ export declare function getProjectTagInfo(directory: string): TagInfo;
20
+ export declare function getTags(directory: string): {
21
+ user: TagInfo;
22
+ project: TagInfo;
23
+ };
24
+ //# sourceMappingURL=tags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tags.d.ts","sourceRoot":"","sources":["../../src/services/tags.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,WAAW,IAAI,MAAM,GAAG,IAAI,CAU3C;AAED,wBAAgB,UAAU,IAAI,MAAM,GAAG,IAAI,CAU1C;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW9D;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwBhE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW/D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAYxD;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAY5D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxD;AAED,wBAAgB,cAAc,IAAI,OAAO,CAoBxC;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAa5D;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG;IAC1C,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB,CAKA"}