opencode-mem 2.11.1 → 2.11.3

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.
@@ -13,6 +13,7 @@ export declare class HNSWIndex {
13
13
  private initialized;
14
14
  constructor(dimensions: number, indexPath: string);
15
15
  private ensureInitialized;
16
+ private vectorToArray;
16
17
  insert(id: string, vector: Float32Array): Promise<void>;
17
18
  insertBatch(items: HNSWIndexData[]): Promise<void>;
18
19
  search(queryVector: Float32Array, k: number): Promise<{
@@ -28,6 +29,7 @@ export declare class HNSWIndexManager {
28
29
  private baseDir;
29
30
  constructor(baseDir: string);
30
31
  getIndex(scope: string, scopeHash: string, shardIndex: number): HNSWIndex;
32
+ getTagsIndex(scope: string, scopeHash: string, shardIndex: number): HNSWIndex;
31
33
  rebuildFromShard(db: any, scope: string, scopeHash: string, shardIndex: number): Promise<void>;
32
34
  deleteIndex(scope: string, scopeHash: string, shardIndex: number): Promise<void>;
33
35
  cleanupOrphanedIndexes(validKeys: Set<string>): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"hnsw-index.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/hnsw-index.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,WAAW,CAAkB;gBAEzB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;YAKnC,iBAAiB;IAyCzB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvD,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBlD,MAAM,CAAC,WAAW,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAkBzF,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B,QAAQ,IAAI,MAAM;CAGnB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAO3B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS;IAWnE,gBAAgB,CACpB,EAAE,EAAE,GAAG,EACP,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAmBV,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAehF,sBAAsB,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CA0BpE"}
1
+ {"version":3,"file":"hnsw-index.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/hnsw-index.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,WAAW,CAAkB;gBAEzB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;YAKnC,iBAAiB;IAyC/B,OAAO,CAAC,aAAa;IAIf,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvD,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBlD,MAAM,CAAC,WAAW,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAkBzF,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B,QAAQ,IAAI,MAAM;CAGnB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAO3B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS;IAWzE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS;IAWvE,gBAAgB,CACpB,EAAE,EAAE,GAAG,EACP,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAoCV,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhF,sBAAsB,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CA0BpE"}
@@ -1,15 +1,8 @@
1
- import { mkdirSync, existsSync, writeFileSync, readFileSync, unlinkSync, readdirSync, statSync, } from "node:fs";
1
+ import { mkdirSync, existsSync, writeFileSync, readFileSync, unlinkSync, readdirSync, } from "node:fs";
2
2
  import { join, dirname, basename } from "node:path";
3
3
  import { log } from "../logger.js";
4
4
  import { CONFIG } from "../../config.js";
5
- let HNSWLib = null;
6
- async function loadHNSWLib() {
7
- if (!HNSWLib) {
8
- const module = await import("hnswlib-wasm");
9
- HNSWLib = module;
10
- }
11
- return HNSWLib;
12
- }
5
+ import { HierarchicalNSW } from "hnswlib-node";
13
6
  export class HNSWIndex {
14
7
  index = null;
15
8
  idMap = new Map();
@@ -26,15 +19,14 @@ export class HNSWIndex {
26
19
  async ensureInitialized() {
27
20
  if (this.initialized)
28
21
  return;
29
- const hnsw = await loadHNSWLib();
30
22
  const dir = dirname(this.indexPath);
31
23
  if (!existsSync(dir)) {
32
24
  mkdirSync(dir, { recursive: true });
33
25
  }
34
26
  if (existsSync(this.indexPath)) {
35
27
  try {
36
- this.index = new hnsw.HierarchicalNSW("cosine", this.dimensions);
37
- this.index.readIndex(this.indexPath);
28
+ this.index = new HierarchicalNSW("cosine", this.dimensions);
29
+ await this.index.readIndex(this.indexPath);
38
30
  const metaPath = this.indexPath + ".meta";
39
31
  if (existsSync(metaPath)) {
40
32
  const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
@@ -49,15 +41,20 @@ export class HNSWIndex {
49
41
  path: this.indexPath,
50
42
  error: String(error),
51
43
  });
52
- this.index = new hnsw.HierarchicalNSW("cosine", this.dimensions, this.maxElements);
44
+ this.index = new HierarchicalNSW("cosine", this.dimensions);
45
+ await this.index.initIndex(this.maxElements);
53
46
  }
54
47
  }
55
48
  else {
56
- this.index = new hnsw.HierarchicalNSW("cosine", this.dimensions, this.maxElements);
49
+ this.index = new HierarchicalNSW("cosine", this.dimensions);
50
+ await this.index.initIndex(this.maxElements);
57
51
  log("HNSW index created", { path: this.indexPath, dimensions: this.dimensions });
58
52
  }
59
53
  this.initialized = true;
60
54
  }
55
+ vectorToArray(vector) {
56
+ return Array.from(vector);
57
+ }
61
58
  async insert(id, vector) {
62
59
  await this.ensureInitialized();
63
60
  if (this.reverseMap.has(id)) {
@@ -65,7 +62,7 @@ export class HNSWIndex {
65
62
  this.index.markDelete(internalId);
66
63
  }
67
64
  const internalId = this.nextId++;
68
- this.index.addPoint(vector, internalId);
65
+ await this.index.addPoint(this.vectorToArray(vector), internalId);
69
66
  this.idMap.set(internalId, id);
70
67
  this.reverseMap.set(id, internalId);
71
68
  await this.save();
@@ -78,7 +75,7 @@ export class HNSWIndex {
78
75
  this.index.markDelete(internalId);
79
76
  }
80
77
  const internalId = this.nextId++;
81
- this.index.addPoint(item.vector, internalId);
78
+ await this.index.addPoint(this.vectorToArray(item.vector), internalId);
82
79
  this.idMap.set(internalId, item.id);
83
80
  this.reverseMap.set(item.id, internalId);
84
81
  }
@@ -87,11 +84,11 @@ export class HNSWIndex {
87
84
  async search(queryVector, k) {
88
85
  await this.ensureInitialized();
89
86
  try {
90
- const results = this.index.searchKnn(queryVector, k);
87
+ const results = await this.index.searchKnn(this.vectorToArray(queryVector), k);
91
88
  return results.neighbors
92
89
  .map((internalId, idx) => ({
93
90
  id: this.idMap.get(internalId) || "",
94
- distance: results.distances[idx],
91
+ distance: results.distances[idx] ?? 0,
95
92
  }))
96
93
  .filter((r) => r.id);
97
94
  }
@@ -117,7 +114,7 @@ export class HNSWIndex {
117
114
  if (!existsSync(dir)) {
118
115
  mkdirSync(dir, { recursive: true });
119
116
  }
120
- this.index.writeIndex(this.indexPath);
117
+ await this.index.writeIndex(this.indexPath);
121
118
  const metaPath = this.indexPath + ".meta";
122
119
  const meta = {
123
120
  nextId: this.nextId,
@@ -147,34 +144,61 @@ export class HNSWIndexManager {
147
144
  }
148
145
  return this.indexes.get(key);
149
146
  }
147
+ getTagsIndex(scope, scopeHash, shardIndex) {
148
+ const key = `${scope}_${scopeHash}_${shardIndex}_tags`;
149
+ if (!this.indexes.has(key)) {
150
+ const indexPath = join(this.baseDir, scope + "s", `${key}.hnsw`);
151
+ this.indexes.set(key, new HNSWIndex(CONFIG.embeddingDimensions, indexPath));
152
+ }
153
+ return this.indexes.get(key);
154
+ }
150
155
  async rebuildFromShard(db, scope, scopeHash, shardIndex) {
151
- const index = this.getIndex(scope, scopeHash, shardIndex);
152
- const rows = db.prepare("SELECT id, vector FROM memories").all();
153
- const items = [];
156
+ const contentIndex = this.getIndex(scope, scopeHash, shardIndex);
157
+ const tagsIndex = this.getTagsIndex(scope, scopeHash, shardIndex);
158
+ const rows = db.prepare("SELECT id, vector, tags_vector FROM memories").all();
159
+ const contentItems = [];
160
+ const tagsItems = [];
154
161
  for (const row of rows) {
155
162
  if (row.vector) {
156
163
  const vector = new Float32Array(row.vector.buffer);
157
- items.push({ id: row.id, vector });
164
+ contentItems.push({ id: row.id, vector });
165
+ }
166
+ if (row.tags_vector) {
167
+ const tagsVector = new Float32Array(row.tags_vector.buffer);
168
+ tagsItems.push({ id: row.id, vector: tagsVector });
158
169
  }
159
170
  }
160
- if (items.length > 0) {
161
- await index.insertBatch(items);
162
- log("HNSW index rebuilt", { scope, scopeHash, shardIndex, count: items.length });
171
+ if (contentItems.length > 0) {
172
+ await contentIndex.insertBatch(contentItems);
163
173
  }
174
+ if (tagsItems.length > 0) {
175
+ await tagsIndex.insertBatch(tagsItems);
176
+ }
177
+ log("HNSW indexes rebuilt", {
178
+ scope,
179
+ scopeHash,
180
+ shardIndex,
181
+ content: contentItems.length,
182
+ tags: tagsItems.length,
183
+ });
164
184
  }
165
185
  async deleteIndex(scope, scopeHash, shardIndex) {
166
- const key = `${scope}_${scopeHash}_${shardIndex}`;
167
- this.indexes.delete(key);
168
- const indexPath = join(this.baseDir, scope + "s", `${key}.hnsw`);
169
- const metaPath = indexPath + ".meta";
170
- try {
171
- if (existsSync(indexPath))
172
- unlinkSync(indexPath);
173
- if (existsSync(metaPath))
174
- unlinkSync(metaPath);
175
- }
176
- catch (error) {
177
- log("Error deleting HNSW index files", { path: indexPath, error: String(error) });
186
+ const contentKey = `${scope}_${scopeHash}_${shardIndex}`;
187
+ const tagsKey = `${scope}_${scopeHash}_${shardIndex}_tags`;
188
+ this.indexes.delete(contentKey);
189
+ this.indexes.delete(tagsKey);
190
+ for (const key of [contentKey, tagsKey]) {
191
+ const indexPath = join(this.baseDir, scope + "s", `${key}.hnsw`);
192
+ const metaPath = indexPath + ".meta";
193
+ try {
194
+ if (existsSync(indexPath))
195
+ unlinkSync(indexPath);
196
+ if (existsSync(metaPath))
197
+ unlinkSync(metaPath);
198
+ }
199
+ catch (error) {
200
+ log("Error deleting HNSW index files", { path: indexPath, error: String(error) });
201
+ }
178
202
  }
179
203
  }
180
204
  async cleanupOrphanedIndexes(validKeys) {
@@ -1 +1 @@
1
- {"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExE,QAAA,MAAM,QAAQ,sCAAgB,CAAC;AAC/B,KAAK,YAAY,GAAG,OAAO,QAAQ,CAAC,SAAS,CAAC;AAI9C,qBAAa,YAAY;IACvB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI;IAoCvE,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;IAkEpB,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;IASlF,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;IAUhB,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,gBAAgB,CACpB,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAIhB,eAAe,IAAI,gBAAgB;CAGpC;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
1
+ {"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExE,QAAA,MAAM,QAAQ,sCAAgB,CAAC;AAC/B,KAAK,YAAY,GAAG,OAAO,QAAQ,CAAC,SAAS,CAAC;AAI9C,qBAAa,YAAY;IACvB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI;IAqDvE,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;IA6EpB,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;IAkBlF,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;IA4BhB,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,gBAAgB,CACpB,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAIhB,eAAe,IAAI,gBAAgB;CAGpC;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
@@ -9,27 +9,45 @@ export class VectorSearch {
9
9
  insertVector(db, record, shard) {
10
10
  const insertMemory = db.prepare(`
11
11
  INSERT INTO memories (
12
- id, content, vector, container_tag, tags, type, created_at, updated_at,
12
+ id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
13
13
  metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
14
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
14
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
15
15
  `);
16
16
  const vectorBuffer = new Uint8Array(record.vector.buffer);
17
- insertMemory.run(record.id, record.content, vectorBuffer, 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);
17
+ const tagsVectorBuffer = record.tagsVector ? new Uint8Array(record.tagsVector.buffer) : null;
18
+ insertMemory.run(record.id, record.content, vectorBuffer, tagsVectorBuffer, 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);
18
19
  if (shard && record.vector) {
19
- const index = hnswIndexManager.getIndex(shard.scope, shard.scopeHash, shard.shardIndex);
20
- index.insert(record.id, record.vector).catch((err) => {
21
- log("HNSW insert error", { memoryId: record.id, error: String(err) });
20
+ const contentIndex = hnswIndexManager.getIndex(shard.scope, shard.scopeHash, shard.shardIndex);
21
+ contentIndex.insert(record.id, record.vector).catch((err) => {
22
+ log("HNSW content insert error", { memoryId: record.id, error: String(err) });
22
23
  });
24
+ if (record.tagsVector) {
25
+ const tagsIndex = hnswIndexManager.getTagsIndex(shard.scope, shard.scopeHash, shard.shardIndex);
26
+ tagsIndex.insert(record.id, record.tagsVector).catch((err) => {
27
+ log("HNSW tags insert error", { memoryId: record.id, error: String(err) });
28
+ });
29
+ }
23
30
  }
24
31
  }
25
32
  async searchInShard(shard, queryVector, containerTag, limit, queryText) {
26
33
  const db = connectionManager.getConnection(shard.dbPath);
27
- const index = hnswIndexManager.getIndex(shard.scope, shard.scopeHash, shard.shardIndex);
28
- const contentResults = await index.search(queryVector, limit * 4);
34
+ const contentIndex = hnswIndexManager.getIndex(shard.scope, shard.scopeHash, shard.shardIndex);
35
+ const tagsIndex = hnswIndexManager.getTagsIndex(shard.scope, shard.scopeHash, shard.shardIndex);
36
+ const contentResults = await contentIndex.search(queryVector, limit * 4);
37
+ const tagsResults = await tagsIndex.search(queryVector, limit * 4);
29
38
  const scoreMap = new Map();
30
39
  for (const r of contentResults) {
31
40
  scoreMap.set(r.id, { contentSim: 1 - r.distance, tagsSim: 0 });
32
41
  }
42
+ for (const r of tagsResults) {
43
+ const entry = scoreMap.get(r.id);
44
+ if (entry) {
45
+ entry.tagsSim = 1 - r.distance;
46
+ }
47
+ else {
48
+ scoreMap.set(r.id, { contentSim: 0, tagsSim: 1 - r.distance });
49
+ }
50
+ }
33
51
  const ids = Array.from(scoreMap.keys());
34
52
  if (ids.length === 0)
35
53
  return [];
@@ -55,8 +73,8 @@ export class VectorSearch {
55
73
  const matches = queryWords.filter((w) => memoryTags.some((t) => t.includes(w) || w.includes(t))).length;
56
74
  exactMatchBoost = matches / Math.max(queryWords.length, 1);
57
75
  }
58
- const tagSim = Math.max(scores.tagsSim, exactMatchBoost);
59
- const similarity = tagSim * 0.8 + scores.contentSim * 0.2;
76
+ const finalTagsSim = Math.max(scores.tagsSim, exactMatchBoost);
77
+ const similarity = scores.contentSim * 0.6 + finalTagsSim * 0.4;
60
78
  return {
61
79
  id: row.id,
62
80
  memory: row.content,
@@ -92,16 +110,22 @@ export class VectorSearch {
92
110
  async deleteVector(db, memoryId, shard) {
93
111
  db.prepare(`DELETE FROM memories WHERE id = ?`).run(memoryId);
94
112
  if (shard) {
95
- const index = hnswIndexManager.getIndex(shard.scope, shard.scopeHash, shard.shardIndex);
96
- await index.delete(memoryId);
113
+ const contentIndex = hnswIndexManager.getIndex(shard.scope, shard.scopeHash, shard.shardIndex);
114
+ const tagsIndex = hnswIndexManager.getTagsIndex(shard.scope, shard.scopeHash, shard.shardIndex);
115
+ await Promise.all([contentIndex.delete(memoryId), tagsIndex.delete(memoryId)]);
97
116
  }
98
117
  }
99
118
  async updateVector(db, memoryId, vector, shard, tagsVector) {
100
119
  const vectorBuffer = new Uint8Array(vector.buffer);
101
- db.prepare(`UPDATE memories SET vector = ? WHERE id = ?`).run(vectorBuffer, memoryId);
120
+ const tagsVectorBuffer = tagsVector ? new Uint8Array(tagsVector.buffer) : null;
121
+ db.prepare(`UPDATE memories SET vector = ?, tags_vector = ? WHERE id = ?`).run(vectorBuffer, tagsVectorBuffer, memoryId);
102
122
  if (shard && vector) {
103
- const index = hnswIndexManager.getIndex(shard.scope, shard.scopeHash, shard.shardIndex);
104
- await index.insert(memoryId, vector);
123
+ const contentIndex = hnswIndexManager.getIndex(shard.scope, shard.scopeHash, shard.shardIndex);
124
+ await contentIndex.insert(memoryId, vector);
125
+ if (tagsVector) {
126
+ const tagsIndex = hnswIndexManager.getTagsIndex(shard.scope, shard.scopeHash, shard.shardIndex);
127
+ await tagsIndex.insert(memoryId, tagsVector);
128
+ }
105
129
  }
106
130
  }
107
131
  listMemories(db, containerTag, limit) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-mem",
3
- "version": "2.11.1",
3
+ "version": "2.11.3",
4
4
  "description": "OpenCode plugin that gives coding agents persistent memory using local vector database",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",
@@ -36,7 +36,7 @@
36
36
  "@opencode-ai/plugin": "^1.0.162",
37
37
  "@xenova/transformers": "^2.17.2",
38
38
  "franc-min": "^6.2.0",
39
- "hnswlib-wasm": "^0.8.2",
39
+ "hnswlib-node": "^3.0.0",
40
40
  "iso-639-3": "^3.0.1"
41
41
  },
42
42
  "devDependencies": {