@hasna/mementos 0.10.10 → 0.10.11
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/cli/index.js +143 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/memories.d.ts +20 -0
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/lib/built-in-hooks.d.ts.map +1 -1
- package/dist/lib/embeddings.d.ts +20 -0
- package/dist/lib/embeddings.d.ts.map +1 -0
- package/dist/mcp/index.js +183 -0
- package/dist/server/index.js +143 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2584,6 +2584,17 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
|
|
|
2584
2584
|
CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
|
|
2585
2585
|
CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
|
|
2586
2586
|
INSERT OR IGNORE INTO _migrations (id) VALUES (14);
|
|
2587
|
+
`,
|
|
2588
|
+
`
|
|
2589
|
+
CREATE TABLE IF NOT EXISTS memory_embeddings (
|
|
2590
|
+
memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
|
|
2591
|
+
embedding TEXT NOT NULL,
|
|
2592
|
+
model TEXT NOT NULL DEFAULT 'tfidf-512',
|
|
2593
|
+
dimensions INTEGER NOT NULL DEFAULT 512,
|
|
2594
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2595
|
+
);
|
|
2596
|
+
CREATE INDEX IF NOT EXISTS idx_memory_embeddings_model ON memory_embeddings(model);
|
|
2597
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (15);
|
|
2587
2598
|
`
|
|
2588
2599
|
];
|
|
2589
2600
|
});
|
|
@@ -2645,6 +2656,78 @@ var init_types = __esm(() => {
|
|
|
2645
2656
|
};
|
|
2646
2657
|
});
|
|
2647
2658
|
|
|
2659
|
+
// src/lib/embeddings.ts
|
|
2660
|
+
function cosineSimilarity(a, b) {
|
|
2661
|
+
if (a.length !== b.length || a.length === 0)
|
|
2662
|
+
return 0;
|
|
2663
|
+
let dot = 0, magA = 0, magB = 0;
|
|
2664
|
+
for (let i = 0;i < a.length; i++) {
|
|
2665
|
+
dot += a[i] * b[i];
|
|
2666
|
+
magA += a[i] * a[i];
|
|
2667
|
+
magB += b[i] * b[i];
|
|
2668
|
+
}
|
|
2669
|
+
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
2670
|
+
return denom === 0 ? 0 : dot / denom;
|
|
2671
|
+
}
|
|
2672
|
+
async function openAIEmbed(text, apiKey) {
|
|
2673
|
+
const res = await fetch(OPENAI_EMBED_URL, {
|
|
2674
|
+
method: "POST",
|
|
2675
|
+
headers: {
|
|
2676
|
+
"Content-Type": "application/json",
|
|
2677
|
+
Authorization: `Bearer ${apiKey}`
|
|
2678
|
+
},
|
|
2679
|
+
body: JSON.stringify({
|
|
2680
|
+
model: EMBED_MODEL,
|
|
2681
|
+
input: text.slice(0, 8192)
|
|
2682
|
+
}),
|
|
2683
|
+
signal: AbortSignal.timeout(1e4)
|
|
2684
|
+
});
|
|
2685
|
+
if (!res.ok) {
|
|
2686
|
+
throw new Error(`OpenAI embedding API ${res.status}: ${await res.text()}`);
|
|
2687
|
+
}
|
|
2688
|
+
const data = await res.json();
|
|
2689
|
+
return data.data[0].embedding;
|
|
2690
|
+
}
|
|
2691
|
+
function tfidfVector(text) {
|
|
2692
|
+
const DIMS = 512;
|
|
2693
|
+
const vec = new Float32Array(DIMS);
|
|
2694
|
+
const tokens = text.toLowerCase().match(/\b\w+\b/g) ?? [];
|
|
2695
|
+
for (const token of tokens) {
|
|
2696
|
+
let hash = 2166136261;
|
|
2697
|
+
for (let i = 0;i < token.length; i++) {
|
|
2698
|
+
hash ^= token.charCodeAt(i);
|
|
2699
|
+
hash = hash * 16777619 >>> 0;
|
|
2700
|
+
}
|
|
2701
|
+
vec[hash % DIMS] += 1;
|
|
2702
|
+
}
|
|
2703
|
+
let norm = 0;
|
|
2704
|
+
for (let i = 0;i < DIMS; i++)
|
|
2705
|
+
norm += vec[i] * vec[i];
|
|
2706
|
+
norm = Math.sqrt(norm);
|
|
2707
|
+
if (norm > 0)
|
|
2708
|
+
for (let i = 0;i < DIMS; i++)
|
|
2709
|
+
vec[i] /= norm;
|
|
2710
|
+
return Array.from(vec);
|
|
2711
|
+
}
|
|
2712
|
+
async function generateEmbedding(text) {
|
|
2713
|
+
const apiKey = process.env["OPENAI_API_KEY"];
|
|
2714
|
+
if (apiKey) {
|
|
2715
|
+
try {
|
|
2716
|
+
const embedding2 = await openAIEmbed(text, apiKey);
|
|
2717
|
+
return { embedding: embedding2, model: EMBED_MODEL, dimensions: EMBED_DIMENSIONS };
|
|
2718
|
+
} catch {}
|
|
2719
|
+
}
|
|
2720
|
+
const embedding = tfidfVector(text);
|
|
2721
|
+
return { embedding, model: "tfidf-512", dimensions: 512 };
|
|
2722
|
+
}
|
|
2723
|
+
function serializeEmbedding(embedding) {
|
|
2724
|
+
return JSON.stringify(embedding);
|
|
2725
|
+
}
|
|
2726
|
+
function deserializeEmbedding(raw) {
|
|
2727
|
+
return JSON.parse(raw);
|
|
2728
|
+
}
|
|
2729
|
+
var OPENAI_EMBED_URL = "https://api.openai.com/v1/embeddings", EMBED_MODEL = "text-embedding-3-small", EMBED_DIMENSIONS = 1536;
|
|
2730
|
+
|
|
2648
2731
|
// src/lib/redact.ts
|
|
2649
2732
|
function redactSecrets(text) {
|
|
2650
2733
|
let result = text;
|
|
@@ -2821,8 +2904,10 @@ var exports_memories = {};
|
|
|
2821
2904
|
__export(exports_memories, {
|
|
2822
2905
|
updateMemory: () => updateMemory,
|
|
2823
2906
|
touchMemory: () => touchMemory,
|
|
2907
|
+
semanticSearch: () => semanticSearch,
|
|
2824
2908
|
parseMemoryRow: () => parseMemoryRow,
|
|
2825
2909
|
listMemories: () => listMemories,
|
|
2910
|
+
indexMemoryEmbedding: () => indexMemoryEmbedding,
|
|
2826
2911
|
incrementRecallCount: () => incrementRecallCount,
|
|
2827
2912
|
getMemoryVersions: () => getMemoryVersions,
|
|
2828
2913
|
getMemoryByKey: () => getMemoryByKey,
|
|
@@ -3273,6 +3358,52 @@ function getMemoryVersions(memoryId, db) {
|
|
|
3273
3358
|
return [];
|
|
3274
3359
|
}
|
|
3275
3360
|
}
|
|
3361
|
+
async function indexMemoryEmbedding(memoryId, text, db) {
|
|
3362
|
+
try {
|
|
3363
|
+
const d = db || getDatabase();
|
|
3364
|
+
const { embedding, model, dimensions } = await generateEmbedding(text);
|
|
3365
|
+
const serialized = serializeEmbedding(embedding);
|
|
3366
|
+
d.run(`INSERT INTO memory_embeddings (memory_id, embedding, model, dimensions)
|
|
3367
|
+
VALUES (?, ?, ?, ?)
|
|
3368
|
+
ON CONFLICT(memory_id) DO UPDATE SET embedding=excluded.embedding, model=excluded.model, dimensions=excluded.dimensions, created_at=datetime('now')`, [memoryId, serialized, model, dimensions]);
|
|
3369
|
+
} catch {}
|
|
3370
|
+
}
|
|
3371
|
+
async function semanticSearch(queryText, options = {}, db) {
|
|
3372
|
+
const d = db || getDatabase();
|
|
3373
|
+
const { threshold = 0.5, limit = 10, scope, agent_id, project_id } = options;
|
|
3374
|
+
const { embedding: queryEmbedding } = await generateEmbedding(queryText);
|
|
3375
|
+
const conditions = ["m.status = 'active'", "e.embedding IS NOT NULL"];
|
|
3376
|
+
const params = [];
|
|
3377
|
+
if (scope) {
|
|
3378
|
+
conditions.push("m.scope = ?");
|
|
3379
|
+
params.push(scope);
|
|
3380
|
+
}
|
|
3381
|
+
if (agent_id) {
|
|
3382
|
+
conditions.push("m.agent_id = ?");
|
|
3383
|
+
params.push(agent_id);
|
|
3384
|
+
}
|
|
3385
|
+
if (project_id) {
|
|
3386
|
+
conditions.push("m.project_id = ?");
|
|
3387
|
+
params.push(project_id);
|
|
3388
|
+
}
|
|
3389
|
+
const where = conditions.join(" AND ");
|
|
3390
|
+
const rows = d.prepare(`SELECT m.*, e.embedding FROM memories m
|
|
3391
|
+
JOIN memory_embeddings e ON e.memory_id = m.id
|
|
3392
|
+
WHERE ${where}`).all(...params);
|
|
3393
|
+
const scored = [];
|
|
3394
|
+
for (const row of rows) {
|
|
3395
|
+
try {
|
|
3396
|
+
const docEmbedding = deserializeEmbedding(row.embedding);
|
|
3397
|
+
const score = cosineSimilarity(queryEmbedding, docEmbedding);
|
|
3398
|
+
if (score >= threshold) {
|
|
3399
|
+
const { embedding: _, ...memRow } = row;
|
|
3400
|
+
scored.push({ memory: parseMemoryRow(memRow), score: Math.round(score * 1000) / 1000 });
|
|
3401
|
+
}
|
|
3402
|
+
} catch {}
|
|
3403
|
+
}
|
|
3404
|
+
scored.sort((a, b) => b.score - a.score);
|
|
3405
|
+
return scored.slice(0, limit);
|
|
3406
|
+
}
|
|
3276
3407
|
var RECALL_PROMOTE_THRESHOLD = 3;
|
|
3277
3408
|
var init_memories = __esm(() => {
|
|
3278
3409
|
init_types();
|
|
@@ -5494,6 +5625,18 @@ var init_built_in_hooks = __esm(() => {
|
|
|
5494
5625
|
});
|
|
5495
5626
|
}
|
|
5496
5627
|
});
|
|
5628
|
+
hookRegistry.register({
|
|
5629
|
+
type: "PostMemorySave",
|
|
5630
|
+
blocking: false,
|
|
5631
|
+
builtin: true,
|
|
5632
|
+
priority: 300,
|
|
5633
|
+
description: "Generate and store vector embedding for semantic memory search",
|
|
5634
|
+
handler: async (ctx) => {
|
|
5635
|
+
const { indexMemoryEmbedding: indexMemoryEmbedding2 } = await Promise.resolve().then(() => (init_memories(), exports_memories));
|
|
5636
|
+
const text = [ctx.memory.value, ctx.memory.summary].filter(Boolean).join(" ");
|
|
5637
|
+
indexMemoryEmbedding2(ctx.memory.id, text);
|
|
5638
|
+
}
|
|
5639
|
+
});
|
|
5497
5640
|
hookRegistry.register({
|
|
5498
5641
|
type: "PostMemoryInject",
|
|
5499
5642
|
blocking: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAmCtC,wBAAgB,SAAS,IAAI,MAAM,CAkBlC;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAmCtC,wBAAgB,SAAS,IAAI,MAAM,CAkBlC;AA0aD,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAerD;AA+BD,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAED,wBAAgB,IAAI,IAAI,MAAM,CAE7B;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAef"}
|
package/dist/db/memories.d.ts
CHANGED
|
@@ -17,4 +17,24 @@ export declare function touchMemory(id: string, db?: Database): void;
|
|
|
17
17
|
export declare function incrementRecallCount(id: string, db?: Database): void;
|
|
18
18
|
export declare function cleanExpiredMemories(db?: Database): number;
|
|
19
19
|
export declare function getMemoryVersions(memoryId: string, db?: Database): MemoryVersion[];
|
|
20
|
+
export interface SemanticSearchResult {
|
|
21
|
+
memory: Memory;
|
|
22
|
+
score: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Store or update the embedding for a memory. Called asynchronously after saves.
|
|
26
|
+
* Non-blocking: failures are silently ignored.
|
|
27
|
+
*/
|
|
28
|
+
export declare function indexMemoryEmbedding(memoryId: string, text: string, db?: Database): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Semantic search across memories using cosine similarity.
|
|
31
|
+
* Falls back gracefully if no embeddings exist yet.
|
|
32
|
+
*/
|
|
33
|
+
export declare function semanticSearch(queryText: string, options?: {
|
|
34
|
+
threshold?: number;
|
|
35
|
+
limit?: number;
|
|
36
|
+
scope?: string;
|
|
37
|
+
agent_id?: string;
|
|
38
|
+
project_id?: string;
|
|
39
|
+
}, db?: Database): Promise<SemanticSearchResult[]>;
|
|
20
40
|
//# sourceMappingURL=memories.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memories.d.ts","sourceRoot":"","sources":["../../src/db/memories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAyB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,MAAM,EACN,YAAY,EACZ,aAAa,EACb,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"memories.d.ts","sourceRoot":"","sources":["../../src/db/memories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAyB,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,MAAM,EACN,YAAY,EACZ,aAAa,EACb,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AA+B3B,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAwBnE;AAMD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,iBAAiB,EACxB,UAAU,GAAE,UAAoB,EAChC,EAAE,CAAC,EAAE,QAAQ,GACZ,MAAM,CAqKR;AAMD,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAOlE;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,EAAE,CAAC,EAAE,QAAQ,GACZ,MAAM,GAAG,IAAI,CA4Bf;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,EAAE,CAAC,EAAE,QAAQ,GACZ,MAAM,EAAE,CAuBV;AAMD,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,EAAE,CA4G3E;AAMD,wBAAgB,YAAY,CAC1B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,iBAAiB,EACxB,EAAE,CAAC,EAAE,QAAQ,GACZ,MAAM,CAiHR;AAMD,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAW/D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CAmBvE;AAMD,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAM3D;AAUD,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAsBpE;AAMD,wBAAgB,oBAAoB,CAAC,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CAiB1D;AAMD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,aAAa,EAAE,CAwBlF;AAMD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAcvG;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CAChB,EACN,EAAE,CAAC,EAAE,QAAQ,GACZ,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAuCjC"}
|
package/dist/index.js
CHANGED
|
@@ -490,6 +490,17 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
|
|
|
490
490
|
CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
|
|
491
491
|
CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
|
|
492
492
|
INSERT OR IGNORE INTO _migrations (id) VALUES (14);
|
|
493
|
+
`,
|
|
494
|
+
`
|
|
495
|
+
CREATE TABLE IF NOT EXISTS memory_embeddings (
|
|
496
|
+
memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
|
|
497
|
+
embedding TEXT NOT NULL,
|
|
498
|
+
model TEXT NOT NULL DEFAULT 'tfidf-512',
|
|
499
|
+
dimensions INTEGER NOT NULL DEFAULT 512,
|
|
500
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
501
|
+
);
|
|
502
|
+
CREATE INDEX IF NOT EXISTS idx_memory_embeddings_model ON memory_embeddings(model);
|
|
503
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (15);
|
|
493
504
|
`
|
|
494
505
|
];
|
|
495
506
|
var _db = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"built-in-hooks.d.ts","sourceRoot":"","sources":["../../src/lib/built-in-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"built-in-hooks.d.ts","sourceRoot":"","sources":["../../src/lib/built-in-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAyIlD,wBAAgB,kBAAkB,IAAI,IAAI,CAyBzC;AAuBD,wBAAgB,cAAc,IAAI,IAAI,CAGrC;AAGD,YAAY,EAAE,QAAQ,EAAE,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedding generation and cosine similarity for semantic memory search.
|
|
3
|
+
*
|
|
4
|
+
* Uses OpenAI text-embedding-3-small if OPENAI_API_KEY is set,
|
|
5
|
+
* falls back to a simple TF-IDF term frequency vector otherwise.
|
|
6
|
+
*/
|
|
7
|
+
export declare function cosineSimilarity(a: number[], b: number[]): number;
|
|
8
|
+
export interface EmbeddingResult {
|
|
9
|
+
embedding: number[];
|
|
10
|
+
model: string;
|
|
11
|
+
dimensions: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generate an embedding for a text string.
|
|
15
|
+
* Uses OpenAI if OPENAI_API_KEY is available, otherwise uses TF-IDF hash trick.
|
|
16
|
+
*/
|
|
17
|
+
export declare function generateEmbedding(text: string): Promise<EmbeddingResult>;
|
|
18
|
+
export declare function serializeEmbedding(embedding: number[]): string;
|
|
19
|
+
export declare function deserializeEmbedding(raw: string): number[];
|
|
20
|
+
//# sourceMappingURL=embeddings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embeddings.d.ts","sourceRoot":"","sources":["../../src/lib/embeddings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAUjE;AAwDD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAY9E;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAE9D;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE1D"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -550,10 +550,93 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
|
|
|
550
550
|
CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
|
|
551
551
|
CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
|
|
552
552
|
INSERT OR IGNORE INTO _migrations (id) VALUES (14);
|
|
553
|
+
`,
|
|
554
|
+
`
|
|
555
|
+
CREATE TABLE IF NOT EXISTS memory_embeddings (
|
|
556
|
+
memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
|
|
557
|
+
embedding TEXT NOT NULL,
|
|
558
|
+
model TEXT NOT NULL DEFAULT 'tfidf-512',
|
|
559
|
+
dimensions INTEGER NOT NULL DEFAULT 512,
|
|
560
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
561
|
+
);
|
|
562
|
+
CREATE INDEX IF NOT EXISTS idx_memory_embeddings_model ON memory_embeddings(model);
|
|
563
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (15);
|
|
553
564
|
`
|
|
554
565
|
];
|
|
555
566
|
});
|
|
556
567
|
|
|
568
|
+
// src/lib/embeddings.ts
|
|
569
|
+
function cosineSimilarity(a, b) {
|
|
570
|
+
if (a.length !== b.length || a.length === 0)
|
|
571
|
+
return 0;
|
|
572
|
+
let dot = 0, magA = 0, magB = 0;
|
|
573
|
+
for (let i = 0;i < a.length; i++) {
|
|
574
|
+
dot += a[i] * b[i];
|
|
575
|
+
magA += a[i] * a[i];
|
|
576
|
+
magB += b[i] * b[i];
|
|
577
|
+
}
|
|
578
|
+
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
579
|
+
return denom === 0 ? 0 : dot / denom;
|
|
580
|
+
}
|
|
581
|
+
async function openAIEmbed(text, apiKey) {
|
|
582
|
+
const res = await fetch(OPENAI_EMBED_URL, {
|
|
583
|
+
method: "POST",
|
|
584
|
+
headers: {
|
|
585
|
+
"Content-Type": "application/json",
|
|
586
|
+
Authorization: `Bearer ${apiKey}`
|
|
587
|
+
},
|
|
588
|
+
body: JSON.stringify({
|
|
589
|
+
model: EMBED_MODEL,
|
|
590
|
+
input: text.slice(0, 8192)
|
|
591
|
+
}),
|
|
592
|
+
signal: AbortSignal.timeout(1e4)
|
|
593
|
+
});
|
|
594
|
+
if (!res.ok) {
|
|
595
|
+
throw new Error(`OpenAI embedding API ${res.status}: ${await res.text()}`);
|
|
596
|
+
}
|
|
597
|
+
const data = await res.json();
|
|
598
|
+
return data.data[0].embedding;
|
|
599
|
+
}
|
|
600
|
+
function tfidfVector(text) {
|
|
601
|
+
const DIMS = 512;
|
|
602
|
+
const vec = new Float32Array(DIMS);
|
|
603
|
+
const tokens = text.toLowerCase().match(/\b\w+\b/g) ?? [];
|
|
604
|
+
for (const token of tokens) {
|
|
605
|
+
let hash = 2166136261;
|
|
606
|
+
for (let i = 0;i < token.length; i++) {
|
|
607
|
+
hash ^= token.charCodeAt(i);
|
|
608
|
+
hash = hash * 16777619 >>> 0;
|
|
609
|
+
}
|
|
610
|
+
vec[hash % DIMS] += 1;
|
|
611
|
+
}
|
|
612
|
+
let norm = 0;
|
|
613
|
+
for (let i = 0;i < DIMS; i++)
|
|
614
|
+
norm += vec[i] * vec[i];
|
|
615
|
+
norm = Math.sqrt(norm);
|
|
616
|
+
if (norm > 0)
|
|
617
|
+
for (let i = 0;i < DIMS; i++)
|
|
618
|
+
vec[i] /= norm;
|
|
619
|
+
return Array.from(vec);
|
|
620
|
+
}
|
|
621
|
+
async function generateEmbedding(text) {
|
|
622
|
+
const apiKey = process.env["OPENAI_API_KEY"];
|
|
623
|
+
if (apiKey) {
|
|
624
|
+
try {
|
|
625
|
+
const embedding2 = await openAIEmbed(text, apiKey);
|
|
626
|
+
return { embedding: embedding2, model: EMBED_MODEL, dimensions: EMBED_DIMENSIONS };
|
|
627
|
+
} catch {}
|
|
628
|
+
}
|
|
629
|
+
const embedding = tfidfVector(text);
|
|
630
|
+
return { embedding, model: "tfidf-512", dimensions: 512 };
|
|
631
|
+
}
|
|
632
|
+
function serializeEmbedding(embedding) {
|
|
633
|
+
return JSON.stringify(embedding);
|
|
634
|
+
}
|
|
635
|
+
function deserializeEmbedding(raw) {
|
|
636
|
+
return JSON.parse(raw);
|
|
637
|
+
}
|
|
638
|
+
var OPENAI_EMBED_URL = "https://api.openai.com/v1/embeddings", EMBED_MODEL = "text-embedding-3-small", EMBED_DIMENSIONS = 1536;
|
|
639
|
+
|
|
557
640
|
// src/lib/redact.ts
|
|
558
641
|
function redactSecrets(text) {
|
|
559
642
|
let result = text;
|
|
@@ -726,8 +809,10 @@ var exports_memories = {};
|
|
|
726
809
|
__export(exports_memories, {
|
|
727
810
|
updateMemory: () => updateMemory,
|
|
728
811
|
touchMemory: () => touchMemory,
|
|
812
|
+
semanticSearch: () => semanticSearch,
|
|
729
813
|
parseMemoryRow: () => parseMemoryRow,
|
|
730
814
|
listMemories: () => listMemories,
|
|
815
|
+
indexMemoryEmbedding: () => indexMemoryEmbedding,
|
|
731
816
|
incrementRecallCount: () => incrementRecallCount,
|
|
732
817
|
getMemoryVersions: () => getMemoryVersions,
|
|
733
818
|
getMemoryByKey: () => getMemoryByKey,
|
|
@@ -1178,6 +1263,52 @@ function getMemoryVersions(memoryId, db) {
|
|
|
1178
1263
|
return [];
|
|
1179
1264
|
}
|
|
1180
1265
|
}
|
|
1266
|
+
async function indexMemoryEmbedding(memoryId, text, db) {
|
|
1267
|
+
try {
|
|
1268
|
+
const d = db || getDatabase();
|
|
1269
|
+
const { embedding, model, dimensions } = await generateEmbedding(text);
|
|
1270
|
+
const serialized = serializeEmbedding(embedding);
|
|
1271
|
+
d.run(`INSERT INTO memory_embeddings (memory_id, embedding, model, dimensions)
|
|
1272
|
+
VALUES (?, ?, ?, ?)
|
|
1273
|
+
ON CONFLICT(memory_id) DO UPDATE SET embedding=excluded.embedding, model=excluded.model, dimensions=excluded.dimensions, created_at=datetime('now')`, [memoryId, serialized, model, dimensions]);
|
|
1274
|
+
} catch {}
|
|
1275
|
+
}
|
|
1276
|
+
async function semanticSearch(queryText, options = {}, db) {
|
|
1277
|
+
const d = db || getDatabase();
|
|
1278
|
+
const { threshold = 0.5, limit = 10, scope, agent_id, project_id } = options;
|
|
1279
|
+
const { embedding: queryEmbedding } = await generateEmbedding(queryText);
|
|
1280
|
+
const conditions = ["m.status = 'active'", "e.embedding IS NOT NULL"];
|
|
1281
|
+
const params = [];
|
|
1282
|
+
if (scope) {
|
|
1283
|
+
conditions.push("m.scope = ?");
|
|
1284
|
+
params.push(scope);
|
|
1285
|
+
}
|
|
1286
|
+
if (agent_id) {
|
|
1287
|
+
conditions.push("m.agent_id = ?");
|
|
1288
|
+
params.push(agent_id);
|
|
1289
|
+
}
|
|
1290
|
+
if (project_id) {
|
|
1291
|
+
conditions.push("m.project_id = ?");
|
|
1292
|
+
params.push(project_id);
|
|
1293
|
+
}
|
|
1294
|
+
const where = conditions.join(" AND ");
|
|
1295
|
+
const rows = d.prepare(`SELECT m.*, e.embedding FROM memories m
|
|
1296
|
+
JOIN memory_embeddings e ON e.memory_id = m.id
|
|
1297
|
+
WHERE ${where}`).all(...params);
|
|
1298
|
+
const scored = [];
|
|
1299
|
+
for (const row of rows) {
|
|
1300
|
+
try {
|
|
1301
|
+
const docEmbedding = deserializeEmbedding(row.embedding);
|
|
1302
|
+
const score = cosineSimilarity(queryEmbedding, docEmbedding);
|
|
1303
|
+
if (score >= threshold) {
|
|
1304
|
+
const { embedding: _, ...memRow } = row;
|
|
1305
|
+
scored.push({ memory: parseMemoryRow(memRow), score: Math.round(score * 1000) / 1000 });
|
|
1306
|
+
}
|
|
1307
|
+
} catch {}
|
|
1308
|
+
}
|
|
1309
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1310
|
+
return scored.slice(0, limit);
|
|
1311
|
+
}
|
|
1181
1312
|
var RECALL_PROMOTE_THRESHOLD = 3;
|
|
1182
1313
|
var init_memories = __esm(() => {
|
|
1183
1314
|
init_types();
|
|
@@ -3265,6 +3396,18 @@ var init_built_in_hooks = __esm(() => {
|
|
|
3265
3396
|
});
|
|
3266
3397
|
}
|
|
3267
3398
|
});
|
|
3399
|
+
hookRegistry.register({
|
|
3400
|
+
type: "PostMemorySave",
|
|
3401
|
+
blocking: false,
|
|
3402
|
+
builtin: true,
|
|
3403
|
+
priority: 300,
|
|
3404
|
+
description: "Generate and store vector embedding for semantic memory search",
|
|
3405
|
+
handler: async (ctx) => {
|
|
3406
|
+
const { indexMemoryEmbedding: indexMemoryEmbedding2 } = await Promise.resolve().then(() => (init_memories(), exports_memories));
|
|
3407
|
+
const text = [ctx.memory.value, ctx.memory.summary].filter(Boolean).join(" ");
|
|
3408
|
+
indexMemoryEmbedding2(ctx.memory.id, text);
|
|
3409
|
+
}
|
|
3410
|
+
});
|
|
3268
3411
|
hookRegistry.register({
|
|
3269
3412
|
type: "PostMemoryInject",
|
|
3270
3413
|
blocking: false,
|
|
@@ -9550,6 +9693,46 @@ ${lines.join(`
|
|
|
9550
9693
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9551
9694
|
}
|
|
9552
9695
|
});
|
|
9696
|
+
server.tool("memory_search_semantic", "Semantic (meaning-based) memory search using vector embeddings. Finds memories by conceptual similarity, not keyword match. Uses OpenAI embeddings if OPENAI_API_KEY is set, otherwise TF-IDF.", {
|
|
9697
|
+
query: exports_external.string().describe("Natural language query"),
|
|
9698
|
+
threshold: exports_external.coerce.number().min(0).max(1).optional().describe("Minimum cosine similarity score (default: 0.5)"),
|
|
9699
|
+
limit: exports_external.coerce.number().optional().describe("Max results (default: 10)"),
|
|
9700
|
+
scope: exports_external.enum(["global", "shared", "private"]).optional(),
|
|
9701
|
+
agent_id: exports_external.string().optional(),
|
|
9702
|
+
project_id: exports_external.string().optional(),
|
|
9703
|
+
index_missing: exports_external.coerce.boolean().optional().describe("If true, index any memories that lack embeddings before searching")
|
|
9704
|
+
}, async (args) => {
|
|
9705
|
+
try {
|
|
9706
|
+
ensureAutoProject();
|
|
9707
|
+
if (args.index_missing) {
|
|
9708
|
+
const db = getDatabase();
|
|
9709
|
+
const unindexed = db.prepare(`SELECT id, value, summary FROM memories
|
|
9710
|
+
WHERE status = 'active' AND id NOT IN (SELECT memory_id FROM memory_embeddings)
|
|
9711
|
+
LIMIT 100`).all();
|
|
9712
|
+
await Promise.all(unindexed.map((m) => indexMemoryEmbedding(m.id, [m.value, m.summary].filter(Boolean).join(" "))));
|
|
9713
|
+
}
|
|
9714
|
+
let effectiveProjectId = args.project_id;
|
|
9715
|
+
if (!args.project_id && args.agent_id) {
|
|
9716
|
+
effectiveProjectId = resolveProjectId(args.agent_id, null) ?? undefined;
|
|
9717
|
+
}
|
|
9718
|
+
const results = await semanticSearch(args.query, {
|
|
9719
|
+
threshold: args.threshold,
|
|
9720
|
+
limit: args.limit,
|
|
9721
|
+
scope: args.scope,
|
|
9722
|
+
agent_id: args.agent_id,
|
|
9723
|
+
project_id: effectiveProjectId
|
|
9724
|
+
});
|
|
9725
|
+
if (results.length === 0) {
|
|
9726
|
+
return { content: [{ type: "text", text: `No semantically similar memories found for: "${args.query}". Try a lower threshold or call with index_missing:true to generate embeddings first.` }] };
|
|
9727
|
+
}
|
|
9728
|
+
const lines = results.map((r, i) => `${i + 1}. [score:${r.score}] [${r.memory.scope}/${r.memory.category}] ${r.memory.key} = ${r.memory.value.slice(0, 120)}${r.memory.value.length > 120 ? "..." : ""}`);
|
|
9729
|
+
return { content: [{ type: "text", text: `${results.length} semantic result(s) for "${args.query}":
|
|
9730
|
+
${lines.join(`
|
|
9731
|
+
`)}` }] };
|
|
9732
|
+
} catch (e) {
|
|
9733
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9734
|
+
}
|
|
9735
|
+
});
|
|
9553
9736
|
server.tool("memory_stats", "Get aggregate statistics about stored memories", {}, async () => {
|
|
9554
9737
|
try {
|
|
9555
9738
|
const db = getDatabase();
|
package/dist/server/index.js
CHANGED
|
@@ -545,10 +545,93 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
|
|
|
545
545
|
CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
|
|
546
546
|
CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
|
|
547
547
|
INSERT OR IGNORE INTO _migrations (id) VALUES (14);
|
|
548
|
+
`,
|
|
549
|
+
`
|
|
550
|
+
CREATE TABLE IF NOT EXISTS memory_embeddings (
|
|
551
|
+
memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
|
|
552
|
+
embedding TEXT NOT NULL,
|
|
553
|
+
model TEXT NOT NULL DEFAULT 'tfidf-512',
|
|
554
|
+
dimensions INTEGER NOT NULL DEFAULT 512,
|
|
555
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
556
|
+
);
|
|
557
|
+
CREATE INDEX IF NOT EXISTS idx_memory_embeddings_model ON memory_embeddings(model);
|
|
558
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (15);
|
|
548
559
|
`
|
|
549
560
|
];
|
|
550
561
|
});
|
|
551
562
|
|
|
563
|
+
// src/lib/embeddings.ts
|
|
564
|
+
function cosineSimilarity(a, b) {
|
|
565
|
+
if (a.length !== b.length || a.length === 0)
|
|
566
|
+
return 0;
|
|
567
|
+
let dot = 0, magA = 0, magB = 0;
|
|
568
|
+
for (let i = 0;i < a.length; i++) {
|
|
569
|
+
dot += a[i] * b[i];
|
|
570
|
+
magA += a[i] * a[i];
|
|
571
|
+
magB += b[i] * b[i];
|
|
572
|
+
}
|
|
573
|
+
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
574
|
+
return denom === 0 ? 0 : dot / denom;
|
|
575
|
+
}
|
|
576
|
+
async function openAIEmbed(text, apiKey) {
|
|
577
|
+
const res = await fetch(OPENAI_EMBED_URL, {
|
|
578
|
+
method: "POST",
|
|
579
|
+
headers: {
|
|
580
|
+
"Content-Type": "application/json",
|
|
581
|
+
Authorization: `Bearer ${apiKey}`
|
|
582
|
+
},
|
|
583
|
+
body: JSON.stringify({
|
|
584
|
+
model: EMBED_MODEL,
|
|
585
|
+
input: text.slice(0, 8192)
|
|
586
|
+
}),
|
|
587
|
+
signal: AbortSignal.timeout(1e4)
|
|
588
|
+
});
|
|
589
|
+
if (!res.ok) {
|
|
590
|
+
throw new Error(`OpenAI embedding API ${res.status}: ${await res.text()}`);
|
|
591
|
+
}
|
|
592
|
+
const data = await res.json();
|
|
593
|
+
return data.data[0].embedding;
|
|
594
|
+
}
|
|
595
|
+
function tfidfVector(text) {
|
|
596
|
+
const DIMS = 512;
|
|
597
|
+
const vec = new Float32Array(DIMS);
|
|
598
|
+
const tokens = text.toLowerCase().match(/\b\w+\b/g) ?? [];
|
|
599
|
+
for (const token of tokens) {
|
|
600
|
+
let hash = 2166136261;
|
|
601
|
+
for (let i = 0;i < token.length; i++) {
|
|
602
|
+
hash ^= token.charCodeAt(i);
|
|
603
|
+
hash = hash * 16777619 >>> 0;
|
|
604
|
+
}
|
|
605
|
+
vec[hash % DIMS] += 1;
|
|
606
|
+
}
|
|
607
|
+
let norm = 0;
|
|
608
|
+
for (let i = 0;i < DIMS; i++)
|
|
609
|
+
norm += vec[i] * vec[i];
|
|
610
|
+
norm = Math.sqrt(norm);
|
|
611
|
+
if (norm > 0)
|
|
612
|
+
for (let i = 0;i < DIMS; i++)
|
|
613
|
+
vec[i] /= norm;
|
|
614
|
+
return Array.from(vec);
|
|
615
|
+
}
|
|
616
|
+
async function generateEmbedding(text) {
|
|
617
|
+
const apiKey = process.env["OPENAI_API_KEY"];
|
|
618
|
+
if (apiKey) {
|
|
619
|
+
try {
|
|
620
|
+
const embedding2 = await openAIEmbed(text, apiKey);
|
|
621
|
+
return { embedding: embedding2, model: EMBED_MODEL, dimensions: EMBED_DIMENSIONS };
|
|
622
|
+
} catch {}
|
|
623
|
+
}
|
|
624
|
+
const embedding = tfidfVector(text);
|
|
625
|
+
return { embedding, model: "tfidf-512", dimensions: 512 };
|
|
626
|
+
}
|
|
627
|
+
function serializeEmbedding(embedding) {
|
|
628
|
+
return JSON.stringify(embedding);
|
|
629
|
+
}
|
|
630
|
+
function deserializeEmbedding(raw) {
|
|
631
|
+
return JSON.parse(raw);
|
|
632
|
+
}
|
|
633
|
+
var OPENAI_EMBED_URL = "https://api.openai.com/v1/embeddings", EMBED_MODEL = "text-embedding-3-small", EMBED_DIMENSIONS = 1536;
|
|
634
|
+
|
|
552
635
|
// src/lib/redact.ts
|
|
553
636
|
function redactSecrets(text) {
|
|
554
637
|
let result = text;
|
|
@@ -721,8 +804,10 @@ var exports_memories = {};
|
|
|
721
804
|
__export(exports_memories, {
|
|
722
805
|
updateMemory: () => updateMemory,
|
|
723
806
|
touchMemory: () => touchMemory,
|
|
807
|
+
semanticSearch: () => semanticSearch,
|
|
724
808
|
parseMemoryRow: () => parseMemoryRow,
|
|
725
809
|
listMemories: () => listMemories,
|
|
810
|
+
indexMemoryEmbedding: () => indexMemoryEmbedding,
|
|
726
811
|
incrementRecallCount: () => incrementRecallCount,
|
|
727
812
|
getMemoryVersions: () => getMemoryVersions,
|
|
728
813
|
getMemoryByKey: () => getMemoryByKey,
|
|
@@ -1173,6 +1258,52 @@ function getMemoryVersions(memoryId, db) {
|
|
|
1173
1258
|
return [];
|
|
1174
1259
|
}
|
|
1175
1260
|
}
|
|
1261
|
+
async function indexMemoryEmbedding(memoryId, text, db) {
|
|
1262
|
+
try {
|
|
1263
|
+
const d = db || getDatabase();
|
|
1264
|
+
const { embedding, model, dimensions } = await generateEmbedding(text);
|
|
1265
|
+
const serialized = serializeEmbedding(embedding);
|
|
1266
|
+
d.run(`INSERT INTO memory_embeddings (memory_id, embedding, model, dimensions)
|
|
1267
|
+
VALUES (?, ?, ?, ?)
|
|
1268
|
+
ON CONFLICT(memory_id) DO UPDATE SET embedding=excluded.embedding, model=excluded.model, dimensions=excluded.dimensions, created_at=datetime('now')`, [memoryId, serialized, model, dimensions]);
|
|
1269
|
+
} catch {}
|
|
1270
|
+
}
|
|
1271
|
+
async function semanticSearch(queryText, options = {}, db) {
|
|
1272
|
+
const d = db || getDatabase();
|
|
1273
|
+
const { threshold = 0.5, limit = 10, scope, agent_id, project_id } = options;
|
|
1274
|
+
const { embedding: queryEmbedding } = await generateEmbedding(queryText);
|
|
1275
|
+
const conditions = ["m.status = 'active'", "e.embedding IS NOT NULL"];
|
|
1276
|
+
const params = [];
|
|
1277
|
+
if (scope) {
|
|
1278
|
+
conditions.push("m.scope = ?");
|
|
1279
|
+
params.push(scope);
|
|
1280
|
+
}
|
|
1281
|
+
if (agent_id) {
|
|
1282
|
+
conditions.push("m.agent_id = ?");
|
|
1283
|
+
params.push(agent_id);
|
|
1284
|
+
}
|
|
1285
|
+
if (project_id) {
|
|
1286
|
+
conditions.push("m.project_id = ?");
|
|
1287
|
+
params.push(project_id);
|
|
1288
|
+
}
|
|
1289
|
+
const where = conditions.join(" AND ");
|
|
1290
|
+
const rows = d.prepare(`SELECT m.*, e.embedding FROM memories m
|
|
1291
|
+
JOIN memory_embeddings e ON e.memory_id = m.id
|
|
1292
|
+
WHERE ${where}`).all(...params);
|
|
1293
|
+
const scored = [];
|
|
1294
|
+
for (const row of rows) {
|
|
1295
|
+
try {
|
|
1296
|
+
const docEmbedding = deserializeEmbedding(row.embedding);
|
|
1297
|
+
const score = cosineSimilarity(queryEmbedding, docEmbedding);
|
|
1298
|
+
if (score >= threshold) {
|
|
1299
|
+
const { embedding: _, ...memRow } = row;
|
|
1300
|
+
scored.push({ memory: parseMemoryRow(memRow), score: Math.round(score * 1000) / 1000 });
|
|
1301
|
+
}
|
|
1302
|
+
} catch {}
|
|
1303
|
+
}
|
|
1304
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1305
|
+
return scored.slice(0, limit);
|
|
1306
|
+
}
|
|
1176
1307
|
var RECALL_PROMOTE_THRESHOLD = 3;
|
|
1177
1308
|
var init_memories = __esm(() => {
|
|
1178
1309
|
init_types();
|
|
@@ -3620,6 +3751,18 @@ hookRegistry.register({
|
|
|
3620
3751
|
});
|
|
3621
3752
|
}
|
|
3622
3753
|
});
|
|
3754
|
+
hookRegistry.register({
|
|
3755
|
+
type: "PostMemorySave",
|
|
3756
|
+
blocking: false,
|
|
3757
|
+
builtin: true,
|
|
3758
|
+
priority: 300,
|
|
3759
|
+
description: "Generate and store vector embedding for semantic memory search",
|
|
3760
|
+
handler: async (ctx) => {
|
|
3761
|
+
const { indexMemoryEmbedding: indexMemoryEmbedding2 } = await Promise.resolve().then(() => (init_memories(), exports_memories));
|
|
3762
|
+
const text = [ctx.memory.value, ctx.memory.summary].filter(Boolean).join(" ");
|
|
3763
|
+
indexMemoryEmbedding2(ctx.memory.id, text);
|
|
3764
|
+
}
|
|
3765
|
+
});
|
|
3623
3766
|
hookRegistry.register({
|
|
3624
3767
|
type: "PostMemoryInject",
|
|
3625
3768
|
blocking: false,
|