@ai-agent-forge/plugin-memory 0.88.11 → 0.88.12

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.
@@ -15,6 +15,17 @@ export async function createMemoryVectorIndex(options) {
15
15
  const lancedb = await importHostOrBareModule(options.moduleEntryPath ?? "@lancedb/lancedb");
16
16
  const db = await lancedb.connect(options.dbPath);
17
17
  let table = null;
18
+ let closed = false;
19
+ let closePromise;
20
+ const activeCalls = new Set();
21
+ const withOpenIndex = (operation) => {
22
+ if (closed)
23
+ return Promise.reject(new Error("memory vector index is closed"));
24
+ const call = operation();
25
+ activeCalls.add(call);
26
+ void call.then(() => activeCalls.delete(call), () => activeCalls.delete(call));
27
+ return call;
28
+ };
18
29
  const ensureTable = async () => {
19
30
  if (table) {
20
31
  return table;
@@ -39,7 +50,7 @@ export async function createMemoryVectorIndex(options) {
39
50
  };
40
51
  return {
41
52
  replicaId: "memory-vector-index",
42
- upsert: async (entries) => {
53
+ upsert: (entries) => withOpenIndex(async () => {
43
54
  if (entries.length === 0) {
44
55
  return;
45
56
  }
@@ -65,8 +76,8 @@ export async function createMemoryVectorIndex(options) {
65
76
  }
66
77
  // mergeInsert upsert: 同 memoryId 幂等更新, 新 id 插入。
67
78
  await current.mergeInsert("memoryId").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(rows);
68
- },
69
- remove: async (memoryIds) => {
79
+ }),
80
+ remove: (memoryIds) => withOpenIndex(async () => {
70
81
  if (memoryIds.length === 0) {
71
82
  return;
72
83
  }
@@ -76,8 +87,8 @@ export async function createMemoryVectorIndex(options) {
76
87
  }
77
88
  const predicate = `memoryId IN (${memoryIds.map((id) => `'${escapeSqlLiteral(id)}'`).join(",")})`;
78
89
  await current.delete(predicate);
79
- },
80
- queryKnn: async (vector, query) => {
90
+ }),
91
+ queryKnn: (vector, query) => withOpenIndex(async () => {
81
92
  if (vector.length !== dimensions) {
82
93
  throw new Error(`query vector dimension mismatch: got ${vector.length}, index declares ${dimensions}`);
83
94
  }
@@ -91,8 +102,8 @@ export async function createMemoryVectorIndex(options) {
91
102
  .toArray();
92
103
  // 分数口径: 1/(1 + _distance), L2 距离越小分越高, 与 FTS 通道分数同域可融合。
93
104
  return rows.map((row) => ({ memoryId: row.memoryId, score: 1 / (1 + row._distance) }));
94
- },
95
- queryFts: async (queryText, query) => {
105
+ }),
106
+ queryFts: (queryText, query) => withOpenIndex(async () => {
96
107
  const current = await ensureTable();
97
108
  if (!current) {
98
109
  return [];
@@ -104,32 +115,37 @@ export async function createMemoryVectorIndex(options) {
104
115
  // FTS 无距离分: 按返回序 1/(RRF_K + rank + 1) 打分, RRF_K=60 与调用方
105
116
  // 融合层 (RRF k=60) 一致, 保证 KNN/FTS 两通道分数可融合。
106
117
  return rows.map((row, rank) => ({ memoryId: row.memoryId, score: 1 / (RRF_K + rank + 1) }));
107
- },
108
- listMemoryIds: async () => {
118
+ }),
119
+ listMemoryIds: () => withOpenIndex(async () => {
109
120
  const current = await ensureTable();
110
121
  if (!current) {
111
122
  return new Set();
112
123
  }
113
124
  const rows = await current.query().select(["memoryId"]).toArray();
114
125
  return new Set(rows.map((row) => row.memoryId));
115
- },
116
- count: async () => {
126
+ }),
127
+ count: () => withOpenIndex(async () => {
117
128
  const current = await ensureTable();
118
129
  if (!current) {
119
130
  return 0;
120
131
  }
121
132
  return current.countRows();
122
- },
123
- getStoredModelId: async () => {
133
+ }),
134
+ getStoredModelId: () => withOpenIndex(async () => {
124
135
  const current = await ensureTable();
125
136
  if (!current) {
126
137
  return undefined;
127
138
  }
128
139
  const rows = await current.query().select(["modelId"]).limit(1).toArray();
129
140
  return rows[0]?.modelId;
130
- },
131
- close: async () => {
132
- db.close();
141
+ }),
142
+ close: () => {
143
+ closed = true;
144
+ closePromise ??= (async () => {
145
+ await Promise.allSettled([...activeCalls]);
146
+ db.close();
147
+ })();
148
+ return closePromise;
133
149
  },
134
150
  };
135
151
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vector-index.js","sourceRoot":"","sources":["../../src/memory/vector-index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory vector index for hybrid retrieval (混合检索 · LanceDB 适配层).\n *\n * POC-verified (2026-09) against @lancedb/lancedb 0.39.0 on a 100k corpus:\n * vector column must be named `vector` (number[] auto-maps to\n * fixed-size-list<float32>), FTS with `Index.fts({ baseTokenizer: \"icu\" })`\n * covers Chinese text, flat-scan KNN on 100k rows takes ~600ms (预期内).\n *\n * Deliberately NOT implemented (POC 结论, 留二期): IVF_PQ ANN index — flat\n * scan is acceptable at current scale; fused hybrid query via\n * `fullTextQuery()` — that chain does not exist in 0.39.0, the caller composes\n * KNN + FTS itself and fuses with RRF (k=60), which is why this adapter's FTS\n * scores use `1/(60 + rank + 1)`.\n *\n * 边界行为 (POC 观察): 空 table 上建 FTS 索引/查询可能抛错 — 空表查询返回\n * 空数组; 建索引包 try/catch 忽略 (注释见 createBodyFtsIndex)。LanceDB 其他\n * 失败原样抛出, 不吞错、不静默降级。\n *\n * Runtime dep @lancedb/lancedb is lazy-loaded via `await import` (惯例参考\n * packages/agent-forge/src/utils/photon.ts); type positions use type-only\n * imports.\n */\nimport type { Connection, Table as LanceDbTable, Query, VectorQuery } from \"@lancedb/lancedb\";\nimport { importHostOrBareModule } from \"./host-module-import.ts\";\n\n/** Stored row shape: 5 columns; `body` is the FTS text (statement + tags). */\ntype MemoryVectorRecord = {\n\tmemoryId: string;\n\towner: string;\n\tmodelId: string;\n\tbody: string;\n\tvector: number[];\n};\n\ninterface MemoryVectorHitRow {\n\tmemoryId: string;\n\t_distance: number;\n}\n\nconst TABLE_NAME = \"memories\";\n/** RRF 常量, 与调用方融合层一致 (queryFts 注释说明口径)。 */\nconst RRF_K = 60;\n\nfunction escapeSqlLiteral(value: string): string {\n\treturn value.replace(/'/g, \"''\");\n}\n\nexport interface MemoryVectorIndexEntryV1 {\n\treadonly memoryId: string;\n\treadonly owner: string;\n\treadonly modelId: string;\n\treadonly statement: string;\n\treadonly tags: readonly string[];\n\treadonly vector: readonly number[];\n}\n\nexport interface MemoryVectorIndexQueryV1 {\n\treadonly owner: string;\n\treadonly limit: number;\n}\n\nexport interface MemoryVectorHitV1 {\n\treadonly memoryId: string;\n\treadonly score: number;\n}\n\nexport interface MemoryVectorIndexV1 {\n\treadonly replicaId: \"memory-vector-index\";\n\tupsert(entries: readonly MemoryVectorIndexEntryV1[]): Promise<void>;\n\tremove(memoryIds: readonly string[]): Promise<void>;\n\tqueryKnn(vector: readonly number[], query: MemoryVectorIndexQueryV1): Promise<readonly MemoryVectorHitV1[]>;\n\tqueryFts(queryText: string, query: MemoryVectorIndexQueryV1): Promise<readonly MemoryVectorHitV1[]>;\n\tlistMemoryIds(): Promise<ReadonlySet<string>>;\n\tcount(): Promise<number>;\n\tgetStoredModelId(): Promise<string | undefined>;\n\tclose(): Promise<void>;\n}\n\n/**\n * Open (lazily) / create the LanceDB-backed memory vector index at `dbPath`.\n * Table creation is deferred to the first upsert; queries against a\n * not-yet-created table return empty results instead of throwing.\n */\nexport async function createMemoryVectorIndex(options: {\n\treadonly dbPath: string;\n\treadonly dimensions: number;\n\t/** Host-resolved lancedb entry path (install-store visibility); undefined = bare import. */\n\treadonly moduleEntryPath?: string;\n}): Promise<MemoryVectorIndexV1> {\n\tconst dimensions = options.dimensions;\n\tconst lancedb = await importHostOrBareModule<typeof import(\"@lancedb/lancedb\")>(\n\t\toptions.moduleEntryPath ?? \"@lancedb/lancedb\",\n\t);\n\tconst db: Connection = await lancedb.connect(options.dbPath);\n\tlet table: LanceDbTable | null = null;\n\n\tconst ensureTable = async (): Promise<LanceDbTable | null> => {\n\t\tif (table) {\n\t\t\treturn table;\n\t\t}\n\t\tconst names = await db.tableNames();\n\t\tif (!names.includes(TABLE_NAME)) {\n\t\t\treturn null;\n\t\t}\n\t\ttable = await db.openTable(TABLE_NAME);\n\t\treturn table;\n\t};\n\n\t// 空表建 FTS 索引在 LanceDB 0.39 会抛错 (POC 观察); 本适配器仅在首次建表\n\t// (带非空数据) 后建索引, 但仍按契约包 try/catch 容错。忽略建索引失败意味着\n\t// FTS 通道暂不可用 (queryFts 会显式报错), KNN 通道不受影响。\n\tconst createBodyFtsIndex = async (target: LanceDbTable): Promise<void> => {\n\t\ttry {\n\t\t\tawait target.createIndex(\"body\", { config: lancedb.Index.fts({ baseTokenizer: \"icu\" }), replace: true });\n\t\t} catch {\n\t\t\t// 空表/重复建索引等边界忽略; 二期 IVF_PQ 落地时统一补索引管理。\n\t\t}\n\t};\n\n\treturn {\n\t\treplicaId: \"memory-vector-index\",\n\n\t\tupsert: async (entries) => {\n\t\t\tif (entries.length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.vector.length !== dimensions) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`memory vector dimension mismatch for ${entry.memoryId}: got ${entry.vector.length}, index declares ${dimensions}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst rows: MemoryVectorRecord[] = entries.map((entry) => ({\n\t\t\t\tmemoryId: entry.memoryId,\n\t\t\t\towner: entry.owner,\n\t\t\t\tmodelId: entry.modelId,\n\t\t\t\tbody: `${entry.statement} ${entry.tags.join(\" \")}`,\n\t\t\t\tvector: Array.from(entry.vector),\n\t\t\t}));\n\t\t\tconst current = await ensureTable();\n\t\t\tif (!current) {\n\t\t\t\t// mode \"create\" (非 POC 的 \"overwrite\"): 表不存在才建; 并发竞态下\n\t\t\t\t// 若表已被他人创建则显式抛错, 而不是静默清空重写。\n\t\t\t\ttable = await db.createTable(TABLE_NAME, rows, { mode: \"create\" });\n\t\t\t\tawait createBodyFtsIndex(table);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// mergeInsert upsert: 同 memoryId 幂等更新, 新 id 插入。\n\t\t\tawait current.mergeInsert(\"memoryId\").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(rows);\n\t\t},\n\n\t\tremove: async (memoryIds) => {\n\t\t\tif (memoryIds.length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst current = await ensureTable();\n\t\t\tif (!current) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst predicate = `memoryId IN (${memoryIds.map((id) => `'${escapeSqlLiteral(id)}'`).join(\",\")})`;\n\t\t\tawait current.delete(predicate);\n\t\t},\n\n\t\tqueryKnn: async (vector, query) => {\n\t\t\tif (vector.length !== dimensions) {\n\t\t\t\tthrow new Error(`query vector dimension mismatch: got ${vector.length}, index declares ${dimensions}`);\n\t\t\t}\n\t\t\tconst current = await ensureTable();\n\t\t\tif (!current) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst rows: MemoryVectorHitRow[] = await (current.search(Array.from(vector)) as VectorQuery)\n\t\t\t\t.where(`owner = '${escapeSqlLiteral(query.owner)}'`)\n\t\t\t\t.limit(query.limit)\n\t\t\t\t.toArray();\n\t\t\t// 分数口径: 1/(1 + _distance), L2 距离越小分越高, 与 FTS 通道分数同域可融合。\n\t\t\treturn rows.map((row) => ({ memoryId: row.memoryId, score: 1 / (1 + row._distance) }));\n\t\t},\n\n\t\tqueryFts: async (queryText, query) => {\n\t\t\tconst current = await ensureTable();\n\t\t\tif (!current) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst rows: MemoryVectorHitRow[] = await (current.search(queryText) as Query)\n\t\t\t\t.where(`owner = '${escapeSqlLiteral(query.owner)}'`)\n\t\t\t\t.limit(query.limit)\n\t\t\t\t.toArray();\n\t\t\t// FTS 无距离分: 按返回序 1/(RRF_K + rank + 1) 打分, RRF_K=60 与调用方\n\t\t\t// 融合层 (RRF k=60) 一致, 保证 KNN/FTS 两通道分数可融合。\n\t\t\treturn rows.map((row, rank) => ({ memoryId: row.memoryId, score: 1 / (RRF_K + rank + 1) }));\n\t\t},\n\n\t\tlistMemoryIds: async () => {\n\t\t\tconst current = await ensureTable();\n\t\t\tif (!current) {\n\t\t\t\treturn new Set<string>();\n\t\t\t}\n\t\t\tconst rows: { memoryId: string }[] = await current.query().select([\"memoryId\"]).toArray();\n\t\t\treturn new Set(rows.map((row) => row.memoryId));\n\t\t},\n\n\t\tcount: async () => {\n\t\t\tconst current = await ensureTable();\n\t\t\tif (!current) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn current.countRows();\n\t\t},\n\n\t\tgetStoredModelId: async () => {\n\t\t\tconst current = await ensureTable();\n\t\t\tif (!current) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tconst rows: { modelId: string }[] = await current.query().select([\"modelId\"]).limit(1).toArray();\n\t\t\treturn rows[0]?.modelId;\n\t\t},\n\n\t\tclose: async () => {\n\t\t\tdb.close();\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"vector-index.js","sourceRoot":"","sources":["../../src/memory/vector-index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory vector index for hybrid retrieval (混合检索 · LanceDB 适配层).\n *\n * POC-verified (2026-09) against @lancedb/lancedb 0.39.0 on a 100k corpus:\n * vector column must be named `vector` (number[] auto-maps to\n * fixed-size-list<float32>), FTS with `Index.fts({ baseTokenizer: \"icu\" })`\n * covers Chinese text, flat-scan KNN on 100k rows takes ~600ms (预期内).\n *\n * Deliberately NOT implemented (POC 结论, 留二期): IVF_PQ ANN index — flat\n * scan is acceptable at current scale; fused hybrid query via\n * `fullTextQuery()` — that chain does not exist in 0.39.0, the caller composes\n * KNN + FTS itself and fuses with RRF (k=60), which is why this adapter's FTS\n * scores use `1/(60 + rank + 1)`.\n *\n * 边界行为 (POC 观察): 空 table 上建 FTS 索引/查询可能抛错 — 空表查询返回\n * 空数组; 建索引包 try/catch 忽略 (注释见 createBodyFtsIndex)。LanceDB 其他\n * 失败原样抛出, 不吞错、不静默降级。\n *\n * Runtime dep @lancedb/lancedb is lazy-loaded via `await import` (惯例参考\n * packages/agent-forge/src/utils/photon.ts); type positions use type-only\n * imports.\n */\nimport type { Connection, Table as LanceDbTable, Query, VectorQuery } from \"@lancedb/lancedb\";\nimport { importHostOrBareModule } from \"./host-module-import.ts\";\n\n/** Stored row shape: 5 columns; `body` is the FTS text (statement + tags). */\ntype MemoryVectorRecord = {\n\tmemoryId: string;\n\towner: string;\n\tmodelId: string;\n\tbody: string;\n\tvector: number[];\n};\n\ninterface MemoryVectorHitRow {\n\tmemoryId: string;\n\t_distance: number;\n}\n\nconst TABLE_NAME = \"memories\";\n/** RRF 常量, 与调用方融合层一致 (queryFts 注释说明口径)。 */\nconst RRF_K = 60;\n\nfunction escapeSqlLiteral(value: string): string {\n\treturn value.replace(/'/g, \"''\");\n}\n\nexport interface MemoryVectorIndexEntryV1 {\n\treadonly memoryId: string;\n\treadonly owner: string;\n\treadonly modelId: string;\n\treadonly statement: string;\n\treadonly tags: readonly string[];\n\treadonly vector: readonly number[];\n}\n\nexport interface MemoryVectorIndexQueryV1 {\n\treadonly owner: string;\n\treadonly limit: number;\n}\n\nexport interface MemoryVectorHitV1 {\n\treadonly memoryId: string;\n\treadonly score: number;\n}\n\nexport interface MemoryVectorIndexV1 {\n\treadonly replicaId: \"memory-vector-index\";\n\tupsert(entries: readonly MemoryVectorIndexEntryV1[]): Promise<void>;\n\tremove(memoryIds: readonly string[]): Promise<void>;\n\tqueryKnn(vector: readonly number[], query: MemoryVectorIndexQueryV1): Promise<readonly MemoryVectorHitV1[]>;\n\tqueryFts(queryText: string, query: MemoryVectorIndexQueryV1): Promise<readonly MemoryVectorHitV1[]>;\n\tlistMemoryIds(): Promise<ReadonlySet<string>>;\n\tcount(): Promise<number>;\n\tgetStoredModelId(): Promise<string | undefined>;\n\tclose(): Promise<void>;\n}\n\n/**\n * Open (lazily) / create the LanceDB-backed memory vector index at `dbPath`.\n * Table creation is deferred to the first upsert; queries against a\n * not-yet-created table return empty results instead of throwing.\n */\nexport async function createMemoryVectorIndex(options: {\n\treadonly dbPath: string;\n\treadonly dimensions: number;\n\t/** Host-resolved lancedb entry path (install-store visibility); undefined = bare import. */\n\treadonly moduleEntryPath?: string;\n}): Promise<MemoryVectorIndexV1> {\n\tconst dimensions = options.dimensions;\n\tconst lancedb = await importHostOrBareModule<typeof import(\"@lancedb/lancedb\")>(\n\t\toptions.moduleEntryPath ?? \"@lancedb/lancedb\",\n\t);\n\tconst db: Connection = await lancedb.connect(options.dbPath);\n\tlet table: LanceDbTable | null = null;\n\tlet closed = false;\n\tlet closePromise: Promise<void> | undefined;\n\tconst activeCalls = new Set<Promise<unknown>>();\n\tconst withOpenIndex = <T>(operation: () => Promise<T>): Promise<T> => {\n\t\tif (closed) return Promise.reject(new Error(\"memory vector index is closed\"));\n\t\tconst call = operation();\n\t\tactiveCalls.add(call);\n\t\tvoid call.then(\n\t\t\t() => activeCalls.delete(call),\n\t\t\t() => activeCalls.delete(call),\n\t\t);\n\t\treturn call;\n\t};\n\n\tconst ensureTable = async (): Promise<LanceDbTable | null> => {\n\t\tif (table) {\n\t\t\treturn table;\n\t\t}\n\t\tconst names = await db.tableNames();\n\t\tif (!names.includes(TABLE_NAME)) {\n\t\t\treturn null;\n\t\t}\n\t\ttable = await db.openTable(TABLE_NAME);\n\t\treturn table;\n\t};\n\n\t// 空表建 FTS 索引在 LanceDB 0.39 会抛错 (POC 观察); 本适配器仅在首次建表\n\t// (带非空数据) 后建索引, 但仍按契约包 try/catch 容错。忽略建索引失败意味着\n\t// FTS 通道暂不可用 (queryFts 会显式报错), KNN 通道不受影响。\n\tconst createBodyFtsIndex = async (target: LanceDbTable): Promise<void> => {\n\t\ttry {\n\t\t\tawait target.createIndex(\"body\", { config: lancedb.Index.fts({ baseTokenizer: \"icu\" }), replace: true });\n\t\t} catch {\n\t\t\t// 空表/重复建索引等边界忽略; 二期 IVF_PQ 落地时统一补索引管理。\n\t\t}\n\t};\n\n\treturn {\n\t\treplicaId: \"memory-vector-index\",\n\n\t\tupsert: (entries) =>\n\t\t\twithOpenIndex(async () => {\n\t\t\t\tif (entries.length === 0) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tif (entry.vector.length !== dimensions) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`memory vector dimension mismatch for ${entry.memoryId}: got ${entry.vector.length}, index declares ${dimensions}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst rows: MemoryVectorRecord[] = entries.map((entry) => ({\n\t\t\t\t\tmemoryId: entry.memoryId,\n\t\t\t\t\towner: entry.owner,\n\t\t\t\t\tmodelId: entry.modelId,\n\t\t\t\t\tbody: `${entry.statement} ${entry.tags.join(\" \")}`,\n\t\t\t\t\tvector: Array.from(entry.vector),\n\t\t\t\t}));\n\t\t\t\tconst current = await ensureTable();\n\t\t\t\tif (!current) {\n\t\t\t\t\t// mode \"create\" (非 POC 的 \"overwrite\"): 表不存在才建; 并发竞态下\n\t\t\t\t\t// 若表已被他人创建则显式抛错, 而不是静默清空重写。\n\t\t\t\t\ttable = await db.createTable(TABLE_NAME, rows, { mode: \"create\" });\n\t\t\t\t\tawait createBodyFtsIndex(table);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// mergeInsert upsert: 同 memoryId 幂等更新, 新 id 插入。\n\t\t\t\tawait current.mergeInsert(\"memoryId\").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(rows);\n\t\t\t}),\n\n\t\tremove: (memoryIds) =>\n\t\t\twithOpenIndex(async () => {\n\t\t\t\tif (memoryIds.length === 0) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst current = await ensureTable();\n\t\t\t\tif (!current) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst predicate = `memoryId IN (${memoryIds.map((id) => `'${escapeSqlLiteral(id)}'`).join(\",\")})`;\n\t\t\t\tawait current.delete(predicate);\n\t\t\t}),\n\n\t\tqueryKnn: (vector, query) =>\n\t\t\twithOpenIndex(async () => {\n\t\t\t\tif (vector.length !== dimensions) {\n\t\t\t\t\tthrow new Error(`query vector dimension mismatch: got ${vector.length}, index declares ${dimensions}`);\n\t\t\t\t}\n\t\t\t\tconst current = await ensureTable();\n\t\t\t\tif (!current) {\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t\tconst rows: MemoryVectorHitRow[] = await (current.search(Array.from(vector)) as VectorQuery)\n\t\t\t\t\t.where(`owner = '${escapeSqlLiteral(query.owner)}'`)\n\t\t\t\t\t.limit(query.limit)\n\t\t\t\t\t.toArray();\n\t\t\t\t// 分数口径: 1/(1 + _distance), L2 距离越小分越高, 与 FTS 通道分数同域可融合。\n\t\t\t\treturn rows.map((row) => ({ memoryId: row.memoryId, score: 1 / (1 + row._distance) }));\n\t\t\t}),\n\n\t\tqueryFts: (queryText, query) =>\n\t\t\twithOpenIndex(async () => {\n\t\t\t\tconst current = await ensureTable();\n\t\t\t\tif (!current) {\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t\tconst rows: MemoryVectorHitRow[] = await (current.search(queryText) as Query)\n\t\t\t\t\t.where(`owner = '${escapeSqlLiteral(query.owner)}'`)\n\t\t\t\t\t.limit(query.limit)\n\t\t\t\t\t.toArray();\n\t\t\t\t// FTS 无距离分: 按返回序 1/(RRF_K + rank + 1) 打分, RRF_K=60 与调用方\n\t\t\t\t// 融合层 (RRF k=60) 一致, 保证 KNN/FTS 两通道分数可融合。\n\t\t\t\treturn rows.map((row, rank) => ({ memoryId: row.memoryId, score: 1 / (RRF_K + rank + 1) }));\n\t\t\t}),\n\n\t\tlistMemoryIds: () =>\n\t\t\twithOpenIndex(async () => {\n\t\t\t\tconst current = await ensureTable();\n\t\t\t\tif (!current) {\n\t\t\t\t\treturn new Set<string>();\n\t\t\t\t}\n\t\t\t\tconst rows: { memoryId: string }[] = await current.query().select([\"memoryId\"]).toArray();\n\t\t\t\treturn new Set(rows.map((row) => row.memoryId));\n\t\t\t}),\n\n\t\tcount: () =>\n\t\t\twithOpenIndex(async () => {\n\t\t\t\tconst current = await ensureTable();\n\t\t\t\tif (!current) {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\treturn current.countRows();\n\t\t\t}),\n\n\t\tgetStoredModelId: () =>\n\t\t\twithOpenIndex(async () => {\n\t\t\t\tconst current = await ensureTable();\n\t\t\t\tif (!current) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tconst rows: { modelId: string }[] = await current.query().select([\"modelId\"]).limit(1).toArray();\n\t\t\t\treturn rows[0]?.modelId;\n\t\t\t}),\n\n\t\tclose: () => {\n\t\t\tclosed = true;\n\t\t\tclosePromise ??= (async () => {\n\t\t\t\tawait Promise.allSettled([...activeCalls]);\n\t\t\t\tdb.close();\n\t\t\t})();\n\t\t\treturn closePromise;\n\t\t},\n\t};\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-agent-forge/plugin-memory",
3
- "version": "0.88.11",
3
+ "version": "0.88.12",
4
4
  "description": "First-party memory capability plugin for Agent Forge (memory_write/recall/list/forget over the M5 Memory Foundation)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,8 @@
22
22
  "clean": "shx rm -rf dist",
23
23
  "build": "tsgo -p tsconfig.build.json && node scripts/build-inference-bin.mjs",
24
24
  "test": "vitest --run",
25
- "prepublishOnly": "npm run clean && npm run build"
25
+ "prepublishOnly": "npm run clean && npm run build",
26
+ "test:coverage": "vitest --run --coverage"
26
27
  },
27
28
  "keywords": [
28
29
  "agent-forge",
@@ -47,10 +48,10 @@
47
48
  "@huggingface/transformers": "4.3.0",
48
49
  "@lancedb/lancedb": "0.39.0",
49
50
  "typebox": "1.3.27",
50
- "@ai-agent-forge/plugin-sdk": "^0.88.11"
51
+ "@ai-agent-forge/plugin-sdk": "^0.88.12"
51
52
  },
52
53
  "devDependencies": {
53
- "shx": "0.4.0",
54
+ "shx": "0.3.4",
54
55
  "vitest": "4.1.11"
55
56
  },
56
57
  "bin": {
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "agent-forge.plugin.memory",
3
- "version": "0.88.11",
3
+ "version": "0.88.12",
4
4
  "apiVersion": "1-draft",
5
5
  "entry": "./dist/entry.js",
6
6
  "hostVersion": ">=0.84.4",