@agentmemory/agentmemory 0.9.3 → 0.9.5
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/README.md +116 -53
- package/dist/cli.mjs +163 -23
- package/dist/cli.mjs.map +1 -1
- package/dist/docker-compose.yml +10 -1
- package/dist/hooks/session-end.mjs +4 -4
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/stop.mjs +1 -1
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/image-refs-DRse_ePx.mjs +3 -0
- package/dist/{image-store-DGvZMMrI.mjs → image-store-DnuCI2RB.mjs} +1 -1
- package/dist/index.mjs +338 -103
- package/dist/index.mjs.map +1 -1
- package/dist/{src-3Snd7D3T.mjs → src-xYHSzz5S.mjs} +264 -41
- package/dist/src-xYHSzz5S.mjs.map +1 -0
- package/dist/{standalone-BG9uPsDK.mjs → standalone-BvKacAId.mjs} +2 -2
- package/dist/{standalone-BG9uPsDK.mjs.map → standalone-BvKacAId.mjs.map} +1 -1
- package/dist/standalone.mjs +9 -2
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-m8Ofn9vV.mjs → tools-registry-BWM0vWeA.mjs} +16 -4
- package/dist/tools-registry-BWM0vWeA.mjs.map +1 -0
- package/docker-compose.yml +10 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/session-end.mjs +4 -4
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +1 -1
- package/plugin/scripts/stop.mjs.map +1 -1
- package/dist/image-refs-CESf9ndJ.mjs +0 -3
- package/dist/src-3Snd7D3T.mjs.map +0 -1
- package/dist/tools-registry-m8Ofn9vV.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -50,7 +50,14 @@ function loadEnvFile() {
|
|
|
50
50
|
if (eqIdx === -1) continue;
|
|
51
51
|
const key = trimmed.slice(0, eqIdx).trim();
|
|
52
52
|
let val = trimmed.slice(eqIdx + 1).trim();
|
|
53
|
-
|
|
53
|
+
const quoteChar = val[0] === "\"" || val[0] === "'" ? val[0] : "";
|
|
54
|
+
if (quoteChar) {
|
|
55
|
+
const closeIdx = val.indexOf(quoteChar, 1);
|
|
56
|
+
if (closeIdx !== -1) val = val.slice(1, closeIdx);
|
|
57
|
+
} else {
|
|
58
|
+
const hashIdx = val.indexOf(" #");
|
|
59
|
+
if (hashIdx !== -1) val = val.slice(0, hashIdx).trim();
|
|
60
|
+
}
|
|
54
61
|
vars[key] = val;
|
|
55
62
|
}
|
|
56
63
|
return vars;
|
|
@@ -123,6 +130,11 @@ function getMergedEnv(overrides) {
|
|
|
123
130
|
function getEnvVar(key) {
|
|
124
131
|
return getMergedEnv()[key];
|
|
125
132
|
}
|
|
133
|
+
function detectLlmProviderKind() {
|
|
134
|
+
const env = getMergedEnv();
|
|
135
|
+
if (hasRealValue(env["ANTHROPIC_API_KEY"]) || hasRealValue(env["GEMINI_API_KEY"]) || hasRealValue(env["GOOGLE_API_KEY"]) || hasRealValue(env["OPENROUTER_API_KEY"]) || hasRealValue(env["MINIMAX_API_KEY"])) return "llm";
|
|
136
|
+
return "noop";
|
|
137
|
+
}
|
|
126
138
|
function loadEmbeddingConfig() {
|
|
127
139
|
const env = getMergedEnv();
|
|
128
140
|
let bm25Weight = parseFloat(env["BM25_WEIGHT"] || "0.4");
|
|
@@ -883,22 +895,38 @@ let imageEmbeddingProvider = null;
|
|
|
883
895
|
function createImageEmbeddingProvider() {
|
|
884
896
|
if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] !== "true") return null;
|
|
885
897
|
if (imageEmbeddingProvider) return imageEmbeddingProvider;
|
|
886
|
-
imageEmbeddingProvider = new ClipEmbeddingProvider();
|
|
898
|
+
imageEmbeddingProvider = withDimensionGuard(new ClipEmbeddingProvider());
|
|
887
899
|
return imageEmbeddingProvider;
|
|
888
900
|
}
|
|
889
901
|
function createEmbeddingProvider() {
|
|
890
902
|
const detected = detectEmbeddingProvider();
|
|
891
903
|
if (!detected) return null;
|
|
892
904
|
switch (detected) {
|
|
893
|
-
case "gemini": return new GeminiEmbeddingProvider(getEnvVar("GEMINI_API_KEY"));
|
|
894
|
-
case "openai": return new OpenAIEmbeddingProvider(getEnvVar("OPENAI_API_KEY"));
|
|
895
|
-
case "voyage": return new VoyageEmbeddingProvider(getEnvVar("VOYAGE_API_KEY"));
|
|
896
|
-
case "cohere": return new CohereEmbeddingProvider(getEnvVar("COHERE_API_KEY"));
|
|
897
|
-
case "openrouter": return new OpenRouterEmbeddingProvider(getEnvVar("OPENROUTER_API_KEY"));
|
|
898
|
-
case "local": return new LocalEmbeddingProvider();
|
|
905
|
+
case "gemini": return withDimensionGuard(new GeminiEmbeddingProvider(getEnvVar("GEMINI_API_KEY")));
|
|
906
|
+
case "openai": return withDimensionGuard(new OpenAIEmbeddingProvider(getEnvVar("OPENAI_API_KEY")));
|
|
907
|
+
case "voyage": return withDimensionGuard(new VoyageEmbeddingProvider(getEnvVar("VOYAGE_API_KEY")));
|
|
908
|
+
case "cohere": return withDimensionGuard(new CohereEmbeddingProvider(getEnvVar("COHERE_API_KEY")));
|
|
909
|
+
case "openrouter": return withDimensionGuard(new OpenRouterEmbeddingProvider(getEnvVar("OPENROUTER_API_KEY")));
|
|
910
|
+
case "local": return withDimensionGuard(new LocalEmbeddingProvider());
|
|
899
911
|
default: return null;
|
|
900
912
|
}
|
|
901
913
|
}
|
|
914
|
+
function withDimensionGuard(provider) {
|
|
915
|
+
const expected = provider.dimensions;
|
|
916
|
+
const check = (v, where) => {
|
|
917
|
+
if (v.length !== expected) throw new Error(`Embedding dimension mismatch in ${provider.name}.${where}: expected ${expected}, got ${v.length}`);
|
|
918
|
+
return v;
|
|
919
|
+
};
|
|
920
|
+
const wrapped = Object.create(provider);
|
|
921
|
+
wrapped.embed = async (t) => check(await provider.embed(t), "embed");
|
|
922
|
+
wrapped.embedBatch = async (ts) => {
|
|
923
|
+
const out = await provider.embedBatch(ts);
|
|
924
|
+
out.forEach((v, i) => check(v, `embedBatch[${i}]`));
|
|
925
|
+
return out;
|
|
926
|
+
};
|
|
927
|
+
if (provider.embedImage) wrapped.embedImage = async (s) => check(await provider.embedImage(s), "embedImage");
|
|
928
|
+
return wrapped;
|
|
929
|
+
}
|
|
902
930
|
|
|
903
931
|
//#endregion
|
|
904
932
|
//#region src/providers/index.ts
|
|
@@ -994,6 +1022,75 @@ var StateKV = class {
|
|
|
994
1022
|
}
|
|
995
1023
|
};
|
|
996
1024
|
|
|
1025
|
+
//#endregion
|
|
1026
|
+
//#region src/state/schema.ts
|
|
1027
|
+
const KV = {
|
|
1028
|
+
sessions: "mem:sessions",
|
|
1029
|
+
observations: (sessionId) => `mem:obs:${sessionId}`,
|
|
1030
|
+
memories: "mem:memories",
|
|
1031
|
+
summaries: "mem:summaries",
|
|
1032
|
+
config: "mem:config",
|
|
1033
|
+
metrics: "mem:metrics",
|
|
1034
|
+
health: "mem:health",
|
|
1035
|
+
embeddings: (obsId) => `mem:emb:${obsId}`,
|
|
1036
|
+
bm25Index: "mem:index:bm25",
|
|
1037
|
+
relations: "mem:relations",
|
|
1038
|
+
profiles: "mem:profiles",
|
|
1039
|
+
claudeBridge: "mem:claude-bridge",
|
|
1040
|
+
graphNodes: "mem:graph:nodes",
|
|
1041
|
+
graphEdges: "mem:graph:edges",
|
|
1042
|
+
semantic: "mem:semantic",
|
|
1043
|
+
procedural: "mem:procedural",
|
|
1044
|
+
teamShared: (teamId) => `mem:team:${teamId}:shared`,
|
|
1045
|
+
teamUsers: (teamId, userId) => `mem:team:${teamId}:users:${userId}`,
|
|
1046
|
+
teamProfile: (teamId) => `mem:team:${teamId}:profile`,
|
|
1047
|
+
audit: "mem:audit",
|
|
1048
|
+
actions: "mem:actions",
|
|
1049
|
+
actionEdges: "mem:action-edges",
|
|
1050
|
+
leases: "mem:leases",
|
|
1051
|
+
routines: "mem:routines",
|
|
1052
|
+
routineRuns: "mem:routine-runs",
|
|
1053
|
+
signals: "mem:signals",
|
|
1054
|
+
checkpoints: "mem:checkpoints",
|
|
1055
|
+
mesh: "mem:mesh",
|
|
1056
|
+
sketches: "mem:sketches",
|
|
1057
|
+
facets: "mem:facets",
|
|
1058
|
+
sentinels: "mem:sentinels",
|
|
1059
|
+
crystals: "mem:crystals",
|
|
1060
|
+
lessons: "mem:lessons",
|
|
1061
|
+
insights: "mem:insights",
|
|
1062
|
+
graphEdgeHistory: "mem:graph:edge-history",
|
|
1063
|
+
enrichedChunks: (sessionId) => `mem:enriched:${sessionId}`,
|
|
1064
|
+
latentEmbeddings: (obsId) => `mem:latent:${obsId}`,
|
|
1065
|
+
retentionScores: "mem:retention",
|
|
1066
|
+
accessLog: "mem:access",
|
|
1067
|
+
imageRefs: "mem:image-refs",
|
|
1068
|
+
imageEmbeddings: "mem:image-embeddings",
|
|
1069
|
+
slots: "mem:slots",
|
|
1070
|
+
globalSlots: "mem:slots:global",
|
|
1071
|
+
state: "mem:state"
|
|
1072
|
+
};
|
|
1073
|
+
const STREAM = {
|
|
1074
|
+
name: "mem-live",
|
|
1075
|
+
group: (sessionId) => sessionId,
|
|
1076
|
+
viewerGroup: "viewer"
|
|
1077
|
+
};
|
|
1078
|
+
function generateId(prefix) {
|
|
1079
|
+
return `${prefix}_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
1080
|
+
}
|
|
1081
|
+
function fingerprintId(prefix, content) {
|
|
1082
|
+
return `${prefix}_${createHash("sha256").update(content).digest("hex").slice(0, 16)}`;
|
|
1083
|
+
}
|
|
1084
|
+
function jaccardSimilarity(a, b) {
|
|
1085
|
+
const setA = new Set(a.split(/\s+/).filter((t) => t.length > 2));
|
|
1086
|
+
const setB = new Set(b.split(/\s+/).filter((t) => t.length > 2));
|
|
1087
|
+
if (setA.size === 0 && setB.size === 0) return 1;
|
|
1088
|
+
if (setA.size === 0 || setB.size === 0) return 0;
|
|
1089
|
+
let intersection = 0;
|
|
1090
|
+
for (const word of setA) if (setB.has(word)) intersection++;
|
|
1091
|
+
return intersection / (setA.size + setB.size - intersection);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
997
1094
|
//#endregion
|
|
998
1095
|
//#region src/state/vector-index.ts
|
|
999
1096
|
function float32ToBase64(arr) {
|
|
@@ -1057,6 +1154,22 @@ var VectorIndex = class VectorIndex {
|
|
|
1057
1154
|
get size() {
|
|
1058
1155
|
return this.vectors.size;
|
|
1059
1156
|
}
|
|
1157
|
+
validateDimensions(expected) {
|
|
1158
|
+
const mismatches = [];
|
|
1159
|
+
const seenDimensions = /* @__PURE__ */ new Set();
|
|
1160
|
+
for (const [obsId, entry] of this.vectors) {
|
|
1161
|
+
const dim = entry.embedding.length;
|
|
1162
|
+
seenDimensions.add(dim);
|
|
1163
|
+
if (dim !== expected) mismatches.push({
|
|
1164
|
+
obsId,
|
|
1165
|
+
dim
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
return {
|
|
1169
|
+
mismatches,
|
|
1170
|
+
seenDimensions
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1060
1173
|
clear() {
|
|
1061
1174
|
this.vectors.clear();
|
|
1062
1175
|
}
|
|
@@ -1100,75 +1213,6 @@ var VectorIndex = class VectorIndex {
|
|
|
1100
1213
|
}
|
|
1101
1214
|
};
|
|
1102
1215
|
|
|
1103
|
-
//#endregion
|
|
1104
|
-
//#region src/state/schema.ts
|
|
1105
|
-
const KV = {
|
|
1106
|
-
sessions: "mem:sessions",
|
|
1107
|
-
observations: (sessionId) => `mem:obs:${sessionId}`,
|
|
1108
|
-
memories: "mem:memories",
|
|
1109
|
-
summaries: "mem:summaries",
|
|
1110
|
-
config: "mem:config",
|
|
1111
|
-
metrics: "mem:metrics",
|
|
1112
|
-
health: "mem:health",
|
|
1113
|
-
embeddings: (obsId) => `mem:emb:${obsId}`,
|
|
1114
|
-
bm25Index: "mem:index:bm25",
|
|
1115
|
-
relations: "mem:relations",
|
|
1116
|
-
profiles: "mem:profiles",
|
|
1117
|
-
claudeBridge: "mem:claude-bridge",
|
|
1118
|
-
graphNodes: "mem:graph:nodes",
|
|
1119
|
-
graphEdges: "mem:graph:edges",
|
|
1120
|
-
semantic: "mem:semantic",
|
|
1121
|
-
procedural: "mem:procedural",
|
|
1122
|
-
teamShared: (teamId) => `mem:team:${teamId}:shared`,
|
|
1123
|
-
teamUsers: (teamId, userId) => `mem:team:${teamId}:users:${userId}`,
|
|
1124
|
-
teamProfile: (teamId) => `mem:team:${teamId}:profile`,
|
|
1125
|
-
audit: "mem:audit",
|
|
1126
|
-
actions: "mem:actions",
|
|
1127
|
-
actionEdges: "mem:action-edges",
|
|
1128
|
-
leases: "mem:leases",
|
|
1129
|
-
routines: "mem:routines",
|
|
1130
|
-
routineRuns: "mem:routine-runs",
|
|
1131
|
-
signals: "mem:signals",
|
|
1132
|
-
checkpoints: "mem:checkpoints",
|
|
1133
|
-
mesh: "mem:mesh",
|
|
1134
|
-
sketches: "mem:sketches",
|
|
1135
|
-
facets: "mem:facets",
|
|
1136
|
-
sentinels: "mem:sentinels",
|
|
1137
|
-
crystals: "mem:crystals",
|
|
1138
|
-
lessons: "mem:lessons",
|
|
1139
|
-
insights: "mem:insights",
|
|
1140
|
-
graphEdgeHistory: "mem:graph:edge-history",
|
|
1141
|
-
enrichedChunks: (sessionId) => `mem:enriched:${sessionId}`,
|
|
1142
|
-
latentEmbeddings: (obsId) => `mem:latent:${obsId}`,
|
|
1143
|
-
retentionScores: "mem:retention",
|
|
1144
|
-
accessLog: "mem:access",
|
|
1145
|
-
imageRefs: "mem:image-refs",
|
|
1146
|
-
imageEmbeddings: "mem:image-embeddings",
|
|
1147
|
-
slots: "mem:slots",
|
|
1148
|
-
globalSlots: "mem:slots:global",
|
|
1149
|
-
state: "mem:state"
|
|
1150
|
-
};
|
|
1151
|
-
const STREAM = {
|
|
1152
|
-
name: "mem-live",
|
|
1153
|
-
group: (sessionId) => sessionId,
|
|
1154
|
-
viewerGroup: "viewer"
|
|
1155
|
-
};
|
|
1156
|
-
function generateId(prefix) {
|
|
1157
|
-
return `${prefix}_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
1158
|
-
}
|
|
1159
|
-
function fingerprintId(prefix, content) {
|
|
1160
|
-
return `${prefix}_${createHash("sha256").update(content).digest("hex").slice(0, 16)}`;
|
|
1161
|
-
}
|
|
1162
|
-
function jaccardSimilarity(a, b) {
|
|
1163
|
-
const setA = new Set(a.split(/\s+/).filter((t) => t.length > 2));
|
|
1164
|
-
const setB = new Set(b.split(/\s+/).filter((t) => t.length > 2));
|
|
1165
|
-
if (setA.size === 0 && setB.size === 0) return 1;
|
|
1166
|
-
if (setA.size === 0 || setB.size === 0) return 0;
|
|
1167
|
-
let intersection = 0;
|
|
1168
|
-
for (const word of setA) if (setB.has(word)) intersection++;
|
|
1169
|
-
return intersection / (setA.size + setB.size - intersection);
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
1216
|
//#endregion
|
|
1173
1217
|
//#region src/functions/graph-retrieval.ts
|
|
1174
1218
|
function buildGraphContext(path) {
|
|
@@ -2040,6 +2084,9 @@ var SearchIndex = class SearchIndex {
|
|
|
2040
2084
|
}
|
|
2041
2085
|
this.sortedTerms = null;
|
|
2042
2086
|
}
|
|
2087
|
+
has(id) {
|
|
2088
|
+
return this.entries.has(id);
|
|
2089
|
+
}
|
|
2043
2090
|
search(query, limit = 20) {
|
|
2044
2091
|
const rawTerms = this.tokenize(query.toLowerCase());
|
|
2045
2092
|
if (rawTerms.length === 0) return [];
|
|
@@ -2183,8 +2230,10 @@ var SearchIndex = class SearchIndex {
|
|
|
2183
2230
|
//#endregion
|
|
2184
2231
|
//#region src/state/index-persistence.ts
|
|
2185
2232
|
const DEBOUNCE_MS = 5e3;
|
|
2233
|
+
const FAILURE_LOG_THROTTLE_MS = 6e4;
|
|
2186
2234
|
var IndexPersistence = class {
|
|
2187
2235
|
timer = null;
|
|
2236
|
+
lastFailureLogAt = 0;
|
|
2188
2237
|
constructor(kv, bm25, vector) {
|
|
2189
2238
|
this.kv = kv;
|
|
2190
2239
|
this.bm25 = bm25;
|
|
@@ -2192,15 +2241,21 @@ var IndexPersistence = class {
|
|
|
2192
2241
|
}
|
|
2193
2242
|
scheduleSave() {
|
|
2194
2243
|
if (this.timer) clearTimeout(this.timer);
|
|
2195
|
-
this.timer = setTimeout(() =>
|
|
2244
|
+
this.timer = setTimeout(() => {
|
|
2245
|
+
this.save().catch((err) => this.logFailure(err));
|
|
2246
|
+
}, DEBOUNCE_MS);
|
|
2196
2247
|
}
|
|
2197
2248
|
async save() {
|
|
2198
2249
|
if (this.timer) {
|
|
2199
2250
|
clearTimeout(this.timer);
|
|
2200
2251
|
this.timer = null;
|
|
2201
2252
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2253
|
+
try {
|
|
2254
|
+
await this.kv.set(KV.bm25Index, "data", this.bm25.serialize());
|
|
2255
|
+
if (this.vector && this.vector.size > 0) await this.kv.set(KV.bm25Index, "vectors", this.vector.serialize());
|
|
2256
|
+
} catch (err) {
|
|
2257
|
+
this.logFailure(err);
|
|
2258
|
+
}
|
|
2204
2259
|
}
|
|
2205
2260
|
async load() {
|
|
2206
2261
|
let bm25 = null;
|
|
@@ -2220,6 +2275,18 @@ var IndexPersistence = class {
|
|
|
2220
2275
|
this.timer = null;
|
|
2221
2276
|
}
|
|
2222
2277
|
}
|
|
2278
|
+
logFailure(err) {
|
|
2279
|
+
const now = Date.now();
|
|
2280
|
+
if (now - this.lastFailureLogAt < FAILURE_LOG_THROTTLE_MS) return;
|
|
2281
|
+
this.lastFailureLogAt = now;
|
|
2282
|
+
const code = err?.code;
|
|
2283
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2284
|
+
logger.warn("index persistence: failed to save BM25/vector index", {
|
|
2285
|
+
code,
|
|
2286
|
+
message,
|
|
2287
|
+
hint: code === "TIMEOUT" ? "iii-engine state::set timed out; recent index updates remain in memory and will retry on the next debounce flush" : void 0
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2223
2290
|
};
|
|
2224
2291
|
|
|
2225
2292
|
//#endregion
|
|
@@ -2440,6 +2507,20 @@ async function deleteAccessLog(kv, memoryId) {
|
|
|
2440
2507
|
|
|
2441
2508
|
//#endregion
|
|
2442
2509
|
//#region src/functions/search.ts
|
|
2510
|
+
function memoryAsIndexable$1(memory) {
|
|
2511
|
+
return {
|
|
2512
|
+
id: memory.id,
|
|
2513
|
+
sessionId: memory.sessionIds[0] ?? "memory",
|
|
2514
|
+
timestamp: memory.createdAt,
|
|
2515
|
+
type: "decision",
|
|
2516
|
+
title: memory.title,
|
|
2517
|
+
facts: [memory.content],
|
|
2518
|
+
narrative: memory.content,
|
|
2519
|
+
concepts: memory.concepts,
|
|
2520
|
+
files: memory.files,
|
|
2521
|
+
importance: memory.strength
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2443
2524
|
let index = null;
|
|
2444
2525
|
function getSearchIndex() {
|
|
2445
2526
|
if (!index) index = new SearchIndex();
|
|
@@ -2448,9 +2529,20 @@ function getSearchIndex() {
|
|
|
2448
2529
|
async function rebuildIndex(kv) {
|
|
2449
2530
|
const idx = getSearchIndex();
|
|
2450
2531
|
idx.clear();
|
|
2451
|
-
const sessions = await kv.list(KV.sessions);
|
|
2452
|
-
if (!sessions.length) return 0;
|
|
2453
2532
|
let count = 0;
|
|
2533
|
+
try {
|
|
2534
|
+
const memories = await kv.list(KV.memories);
|
|
2535
|
+
for (const memory of memories) {
|
|
2536
|
+
if (memory.isLatest === false) continue;
|
|
2537
|
+
if (!memory.title || !memory.content) continue;
|
|
2538
|
+
idx.add(memoryAsIndexable$1(memory));
|
|
2539
|
+
count++;
|
|
2540
|
+
}
|
|
2541
|
+
} catch (err) {
|
|
2542
|
+
logger.warn("rebuildIndex: failed to load memories", { error: err instanceof Error ? err.message : String(err) });
|
|
2543
|
+
}
|
|
2544
|
+
const sessions = await kv.list(KV.sessions);
|
|
2545
|
+
if (!sessions.length) return count;
|
|
2454
2546
|
const obsPerSession = [];
|
|
2455
2547
|
const failedSessions = [];
|
|
2456
2548
|
for (let batch = 0; batch < sessions.length; batch += 10) {
|
|
@@ -4756,6 +4848,20 @@ function registerPatternsFunction(sdk, kv) {
|
|
|
4756
4848
|
|
|
4757
4849
|
//#endregion
|
|
4758
4850
|
//#region src/functions/remember.ts
|
|
4851
|
+
function memoryAsIndexable(memory) {
|
|
4852
|
+
return {
|
|
4853
|
+
id: memory.id,
|
|
4854
|
+
sessionId: memory.sessionIds[0] ?? "memory",
|
|
4855
|
+
timestamp: memory.createdAt,
|
|
4856
|
+
type: "decision",
|
|
4857
|
+
title: memory.title,
|
|
4858
|
+
facts: [memory.content],
|
|
4859
|
+
narrative: memory.content,
|
|
4860
|
+
concepts: memory.concepts,
|
|
4861
|
+
files: memory.files,
|
|
4862
|
+
importance: memory.strength
|
|
4863
|
+
};
|
|
4864
|
+
}
|
|
4759
4865
|
function registerRememberFunction(sdk, kv) {
|
|
4760
4866
|
sdk.registerFunction("mem::remember", async (data) => {
|
|
4761
4867
|
if (!data.content || typeof data.content !== "string" || !data.content.trim()) return {
|
|
@@ -4821,6 +4927,14 @@ function registerRememberFunction(sdk, kv) {
|
|
|
4821
4927
|
await kv.set(KV.memories, supersededMemory.id, supersededMemory);
|
|
4822
4928
|
}
|
|
4823
4929
|
await kv.set(KV.memories, memory.id, memory);
|
|
4930
|
+
try {
|
|
4931
|
+
getSearchIndex().add(memoryAsIndexable(memory));
|
|
4932
|
+
} catch (err) {
|
|
4933
|
+
logger.warn("Failed to index saved memory into BM25", {
|
|
4934
|
+
memId: memory.id,
|
|
4935
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4936
|
+
});
|
|
4937
|
+
}
|
|
4824
4938
|
if (supersededId) await sdk.trigger({
|
|
4825
4939
|
function_id: "mem::cascade-update",
|
|
4826
4940
|
payload: { supersededMemoryId: supersededId },
|
|
@@ -5618,7 +5732,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
5618
5732
|
|
|
5619
5733
|
//#endregion
|
|
5620
5734
|
//#region src/version.ts
|
|
5621
|
-
const VERSION = "0.9.
|
|
5735
|
+
const VERSION = "0.9.5";
|
|
5622
5736
|
|
|
5623
5737
|
//#endregion
|
|
5624
5738
|
//#region src/functions/export-import.ts
|
|
@@ -5739,7 +5853,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
5739
5853
|
"0.9.0",
|
|
5740
5854
|
"0.9.1",
|
|
5741
5855
|
"0.9.2",
|
|
5742
|
-
"0.9.3"
|
|
5856
|
+
"0.9.3",
|
|
5857
|
+
"0.9.4",
|
|
5858
|
+
"0.9.5"
|
|
5743
5859
|
]).has(importData.version)) return {
|
|
5744
5860
|
success: false,
|
|
5745
5861
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -12357,7 +12473,7 @@ function parseJsonlText(text, fallbackSessionId) {
|
|
|
12357
12473
|
const parsed = JSON.parse(line);
|
|
12358
12474
|
if (parsed && typeof parsed === "object") entries.push(parsed);
|
|
12359
12475
|
} catch {}
|
|
12360
|
-
let sessionId =
|
|
12476
|
+
let sessionId = "";
|
|
12361
12477
|
let cwd = "";
|
|
12362
12478
|
let firstTs = "";
|
|
12363
12479
|
let lastTs = "";
|
|
@@ -12418,7 +12534,7 @@ function parseJsonlText(text, fallbackSessionId) {
|
|
|
12418
12534
|
});
|
|
12419
12535
|
} else if (entry.type === "summary" || entry.type === "system") {}
|
|
12420
12536
|
}
|
|
12421
|
-
const effectiveSessionId = sessionId || generateId("sess");
|
|
12537
|
+
const effectiveSessionId = sessionId || fallbackSessionId || generateId("sess");
|
|
12422
12538
|
for (const obs of observations) if (obs.sessionId === "imported") obs.sessionId = effectiveSessionId;
|
|
12423
12539
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
12424
12540
|
return {
|
|
@@ -12528,6 +12644,8 @@ function projectTimeline(observations) {
|
|
|
12528
12644
|
|
|
12529
12645
|
//#endregion
|
|
12530
12646
|
//#region src/functions/replay.ts
|
|
12647
|
+
const MAX_FILES_DEFAULT = 200;
|
|
12648
|
+
const MAX_FILES_UPPER_BOUND = 1e3;
|
|
12531
12649
|
const SENSITIVE_PATH_PATTERNS = [
|
|
12532
12650
|
/(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
|
|
12533
12651
|
/(^|[\\/_.-])credentials?([\\/_.-]|$)/i,
|
|
@@ -12662,8 +12780,11 @@ async function loadObservations(kv, sessionId) {
|
|
|
12662
12780
|
}
|
|
12663
12781
|
async function findJsonlFiles(root, limit = 200) {
|
|
12664
12782
|
const out = [];
|
|
12783
|
+
let discovered = 0;
|
|
12784
|
+
let walked = 0;
|
|
12785
|
+
const traversalCap = Math.max(limit * 50, 5e4);
|
|
12665
12786
|
async function walk(dir) {
|
|
12666
|
-
if (
|
|
12787
|
+
if (walked >= traversalCap) return;
|
|
12667
12788
|
let names;
|
|
12668
12789
|
try {
|
|
12669
12790
|
names = await readdir(dir);
|
|
@@ -12671,7 +12792,8 @@ async function findJsonlFiles(root, limit = 200) {
|
|
|
12671
12792
|
return;
|
|
12672
12793
|
}
|
|
12673
12794
|
for (const name of names) {
|
|
12674
|
-
if (
|
|
12795
|
+
if (walked >= traversalCap) return;
|
|
12796
|
+
walked++;
|
|
12675
12797
|
const full = join(dir, name);
|
|
12676
12798
|
let st;
|
|
12677
12799
|
try {
|
|
@@ -12681,11 +12803,20 @@ async function findJsonlFiles(root, limit = 200) {
|
|
|
12681
12803
|
}
|
|
12682
12804
|
if (st.isSymbolicLink()) continue;
|
|
12683
12805
|
if (st.isDirectory()) await walk(full);
|
|
12684
|
-
else if (st.isFile() && name.endsWith(".jsonl"))
|
|
12806
|
+
else if (st.isFile() && name.endsWith(".jsonl")) {
|
|
12807
|
+
discovered++;
|
|
12808
|
+
if (out.length < limit) out.push(full);
|
|
12809
|
+
}
|
|
12685
12810
|
}
|
|
12686
12811
|
}
|
|
12687
12812
|
await walk(root);
|
|
12688
|
-
|
|
12813
|
+
const traversalCapped = walked >= traversalCap;
|
|
12814
|
+
return {
|
|
12815
|
+
files: out,
|
|
12816
|
+
truncated: discovered > out.length || traversalCapped,
|
|
12817
|
+
discovered,
|
|
12818
|
+
traversalCapped
|
|
12819
|
+
};
|
|
12689
12820
|
}
|
|
12690
12821
|
function registerReplayFunctions(sdk, kv) {
|
|
12691
12822
|
sdk.registerFunction("mem::replay::load", async (data) => {
|
|
@@ -12733,10 +12864,21 @@ function registerReplayFunctions(sdk, kv) {
|
|
|
12733
12864
|
error: "path not found"
|
|
12734
12865
|
};
|
|
12735
12866
|
}
|
|
12867
|
+
const maxFiles = Number.isInteger(data.maxFiles) && data.maxFiles > 0 ? Math.min(data.maxFiles, MAX_FILES_UPPER_BOUND) : MAX_FILES_DEFAULT;
|
|
12736
12868
|
let files = [];
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12869
|
+
let truncated = false;
|
|
12870
|
+
let discovered = 0;
|
|
12871
|
+
let traversalCapped = false;
|
|
12872
|
+
if (stat.isDirectory()) {
|
|
12873
|
+
const found = await findJsonlFiles(abs, maxFiles);
|
|
12874
|
+
files = found.files;
|
|
12875
|
+
truncated = found.truncated;
|
|
12876
|
+
discovered = found.discovered;
|
|
12877
|
+
traversalCapped = found.traversalCapped;
|
|
12878
|
+
} else if (stat.isFile() && abs.endsWith(".jsonl")) {
|
|
12879
|
+
files = [abs];
|
|
12880
|
+
discovered = 1;
|
|
12881
|
+
} else return {
|
|
12740
12882
|
success: false,
|
|
12741
12883
|
error: "path must be a .jsonl file or directory"
|
|
12742
12884
|
};
|
|
@@ -12744,7 +12886,12 @@ function registerReplayFunctions(sdk, kv) {
|
|
|
12744
12886
|
success: true,
|
|
12745
12887
|
imported: 0,
|
|
12746
12888
|
sessionIds: [],
|
|
12747
|
-
observations: 0
|
|
12889
|
+
observations: 0,
|
|
12890
|
+
discovered,
|
|
12891
|
+
truncated,
|
|
12892
|
+
traversalCapped,
|
|
12893
|
+
maxFiles,
|
|
12894
|
+
maxFilesUpperBound: MAX_FILES_UPPER_BOUND
|
|
12748
12895
|
};
|
|
12749
12896
|
const sessionIds = [];
|
|
12750
12897
|
let observationCount = 0;
|
|
@@ -12810,7 +12957,12 @@ function registerReplayFunctions(sdk, kv) {
|
|
|
12810
12957
|
success: true,
|
|
12811
12958
|
imported: files.length,
|
|
12812
12959
|
sessionIds,
|
|
12813
|
-
observations: observationCount
|
|
12960
|
+
observations: observationCount,
|
|
12961
|
+
discovered,
|
|
12962
|
+
truncated,
|
|
12963
|
+
traversalCapped,
|
|
12964
|
+
maxFiles,
|
|
12965
|
+
maxFilesUpperBound: MAX_FILES_UPPER_BOUND
|
|
12814
12966
|
};
|
|
12815
12967
|
});
|
|
12816
12968
|
}
|
|
@@ -13113,12 +13265,11 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
13113
13265
|
sdk.registerFunction("api::config-flags", async (req) => {
|
|
13114
13266
|
const authErr = checkAuth(req, secret);
|
|
13115
13267
|
if (authErr) return authErr;
|
|
13116
|
-
const env = process.env;
|
|
13117
13268
|
return {
|
|
13118
13269
|
status_code: 200,
|
|
13119
13270
|
body: {
|
|
13120
13271
|
version: VERSION,
|
|
13121
|
-
provider:
|
|
13272
|
+
provider: detectLlmProviderKind(),
|
|
13122
13273
|
embeddingProvider: detectEmbeddingProvider() ? "embeddings" : "none",
|
|
13123
13274
|
flags: [
|
|
13124
13275
|
{
|
|
@@ -13416,11 +13567,12 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
13416
13567
|
payload.path = body.path.trim();
|
|
13417
13568
|
}
|
|
13418
13569
|
if (body.maxFiles !== void 0) {
|
|
13419
|
-
|
|
13570
|
+
const n = body.maxFiles;
|
|
13571
|
+
if (!Number.isInteger(n) || n < 1 || n > MAX_FILES_UPPER_BOUND) return {
|
|
13420
13572
|
status_code: 400,
|
|
13421
|
-
body: { error:
|
|
13573
|
+
body: { error: `maxFiles must be an integer between 1 and ${MAX_FILES_UPPER_BOUND}` }
|
|
13422
13574
|
};
|
|
13423
|
-
payload.maxFiles =
|
|
13575
|
+
payload.maxFiles = n;
|
|
13424
13576
|
}
|
|
13425
13577
|
return {
|
|
13426
13578
|
status_code: 202,
|
|
@@ -14336,6 +14488,32 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14336
14488
|
http_method: "GET"
|
|
14337
14489
|
}
|
|
14338
14490
|
});
|
|
14491
|
+
sdk.registerFunction("api::memory-by-id", async (req) => {
|
|
14492
|
+
const authErr = checkAuth(req, secret);
|
|
14493
|
+
if (authErr) return authErr;
|
|
14494
|
+
const id = req.path_params?.["id"];
|
|
14495
|
+
if (!id || typeof id !== "string") return {
|
|
14496
|
+
status_code: 400,
|
|
14497
|
+
body: { error: "id path parameter is required" }
|
|
14498
|
+
};
|
|
14499
|
+
const memory = await kv.get(KV.memories, id);
|
|
14500
|
+
if (!memory) return {
|
|
14501
|
+
status_code: 404,
|
|
14502
|
+
body: { error: `memory not found: ${id}` }
|
|
14503
|
+
};
|
|
14504
|
+
return {
|
|
14505
|
+
status_code: 200,
|
|
14506
|
+
body: { memory }
|
|
14507
|
+
};
|
|
14508
|
+
});
|
|
14509
|
+
sdk.registerTrigger({
|
|
14510
|
+
type: "http",
|
|
14511
|
+
function_id: "api::memory-by-id",
|
|
14512
|
+
config: {
|
|
14513
|
+
api_path: "/agentmemory/memories/:id",
|
|
14514
|
+
http_method: "GET"
|
|
14515
|
+
}
|
|
14516
|
+
});
|
|
14339
14517
|
sdk.registerFunction("api::semantic-list", async (req) => {
|
|
14340
14518
|
const authErr = checkAuth(req, secret);
|
|
14341
14519
|
if (authErr) return authErr;
|
|
@@ -16214,6 +16392,15 @@ function registerEventTriggers(sdk, kv) {
|
|
|
16214
16392
|
error: err instanceof Error ? err.message : String(err)
|
|
16215
16393
|
});
|
|
16216
16394
|
}
|
|
16395
|
+
if (isGraphExtractionEnabled()) try {
|
|
16396
|
+
const compressed = (await kv.list(KV.observations(data.sessionId))).filter((o) => o.title);
|
|
16397
|
+
if (compressed.length > 0) sdk.triggerVoid("mem::graph-extract", { observations: compressed });
|
|
16398
|
+
} catch (err) {
|
|
16399
|
+
logger.warn("graph-extract triggerVoid failed", {
|
|
16400
|
+
sessionId: data.sessionId,
|
|
16401
|
+
error: err instanceof Error ? err.message : String(err)
|
|
16402
|
+
});
|
|
16403
|
+
}
|
|
16217
16404
|
return summary;
|
|
16218
16405
|
});
|
|
16219
16406
|
sdk.registerTrigger({
|
|
@@ -19198,6 +19385,14 @@ function initMetrics(getMeter) {
|
|
|
19198
19385
|
function hasGetMeter(sdk) {
|
|
19199
19386
|
return typeof sdk === "object" && sdk !== null && "getMeter" in sdk && typeof sdk.getMeter === "function";
|
|
19200
19387
|
}
|
|
19388
|
+
let lastUnhandledLogAt = 0;
|
|
19389
|
+
process.on("unhandledRejection", (reason) => {
|
|
19390
|
+
const now = Date.now();
|
|
19391
|
+
if (now - lastUnhandledLogAt < 6e4) return;
|
|
19392
|
+
lastUnhandledLogAt = now;
|
|
19393
|
+
const r = reason;
|
|
19394
|
+
console.warn(`[agentmemory] unhandledRejection (suppressed):`, r?.code ? `${r.code} ${r.function_id ?? ""} ${r.message ?? ""}`.trim() : reason);
|
|
19395
|
+
});
|
|
19201
19396
|
async function main() {
|
|
19202
19397
|
const config = loadConfig();
|
|
19203
19398
|
const embeddingConfig = loadEmbeddingConfig();
|
|
@@ -19215,6 +19410,7 @@ async function main() {
|
|
|
19215
19410
|
console.log(`[agentmemory] Streams: ws://localhost:${config.streamsPort}`);
|
|
19216
19411
|
const sdk = registerWorker(config.engineUrl, {
|
|
19217
19412
|
workerName: "agentmemory",
|
|
19413
|
+
invocationTimeoutMs: 18e4,
|
|
19218
19414
|
otel: {
|
|
19219
19415
|
serviceName: OTEL_CONFIG.serviceName,
|
|
19220
19416
|
serviceVersion: OTEL_CONFIG.serviceVersion,
|
|
@@ -19323,8 +19519,20 @@ async function main() {
|
|
|
19323
19519
|
console.log(`[agentmemory] Loaded persisted BM25 index (${bm25Index.size} docs)`);
|
|
19324
19520
|
}
|
|
19325
19521
|
if (loaded?.vector && vectorIndex && loaded.vector.size > 0) {
|
|
19326
|
-
|
|
19327
|
-
|
|
19522
|
+
const activeDim = embeddingProvider?.dimensions ?? 0;
|
|
19523
|
+
const { mismatches, seenDimensions } = activeDim > 0 ? loaded.vector.validateDimensions(activeDim) : {
|
|
19524
|
+
mismatches: [],
|
|
19525
|
+
seenDimensions: /* @__PURE__ */ new Set()
|
|
19526
|
+
};
|
|
19527
|
+
if (mismatches.length > 0) {
|
|
19528
|
+
const sample = mismatches.slice(0, 5).map((m) => `${m.obsId} (dim=${m.dim})`).join(", ");
|
|
19529
|
+
const distinct = Array.from(seenDimensions).sort((a, b) => a - b).join(", ");
|
|
19530
|
+
if (process.env["AGENTMEMORY_DROP_STALE_INDEX"] === "true") console.warn(`[agentmemory] Persisted vector index has ${mismatches.length} of ${loaded.vector.size} vectors with the wrong dimension. Active provider (${embeddingProvider?.name}) declares ${activeDim}; dimensions seen on disk: ${distinct}. AGENTMEMORY_DROP_STALE_INDEX=true is set — discarding the persisted vectors. Live observations will rebuild the index over time.`);
|
|
19531
|
+
else throw new Error(`[agentmemory] Refusing to start: persisted vector index has ${mismatches.length} of ${loaded.vector.size} vectors with the wrong dimension. Active provider (${embeddingProvider?.name}) declares ${activeDim}; dimensions seen on disk: ${distinct}. First mismatched obsIds: ${sample}. Loading would silently corrupt search (cross-dimension cosine returns 0). Choose one:\n - Re-embed the existing index against the new provider, then start.\n - Set AGENTMEMORY_DROP_STALE_INDEX=true to discard the persisted vectors and rebuild from live observations.\n - Switch the embedding provider back to the one that wrote the index.`);
|
|
19532
|
+
} else {
|
|
19533
|
+
vectorIndex.restoreFrom(loaded.vector);
|
|
19534
|
+
console.log(`[agentmemory] Loaded persisted vector index (${vectorIndex.size} vectors)`);
|
|
19535
|
+
}
|
|
19328
19536
|
}
|
|
19329
19537
|
if (bm25Index.size === 0) {
|
|
19330
19538
|
const indexCount = await rebuildIndex(kv).catch((err) => {
|
|
@@ -19332,9 +19540,36 @@ async function main() {
|
|
|
19332
19540
|
return 0;
|
|
19333
19541
|
});
|
|
19334
19542
|
if (indexCount > 0) {
|
|
19335
|
-
console.log(`[agentmemory] Search index rebuilt: ${indexCount}
|
|
19543
|
+
console.log(`[agentmemory] Search index rebuilt: ${indexCount} entries`);
|
|
19336
19544
|
indexPersistence.scheduleSave();
|
|
19337
19545
|
}
|
|
19546
|
+
} else try {
|
|
19547
|
+
const memories = await kv.list(KV.memories);
|
|
19548
|
+
let backfilled = 0;
|
|
19549
|
+
for (const memory of memories) {
|
|
19550
|
+
if (memory.isLatest === false) continue;
|
|
19551
|
+
if (!memory.title || !memory.content) continue;
|
|
19552
|
+
if (bm25Index.has(memory.id)) continue;
|
|
19553
|
+
bm25Index.add({
|
|
19554
|
+
id: memory.id,
|
|
19555
|
+
sessionId: memory.sessionIds[0] ?? "memory",
|
|
19556
|
+
timestamp: memory.createdAt,
|
|
19557
|
+
type: "decision",
|
|
19558
|
+
title: memory.title,
|
|
19559
|
+
facts: [memory.content],
|
|
19560
|
+
narrative: memory.content,
|
|
19561
|
+
concepts: memory.concepts,
|
|
19562
|
+
files: memory.files,
|
|
19563
|
+
importance: memory.strength
|
|
19564
|
+
});
|
|
19565
|
+
backfilled++;
|
|
19566
|
+
}
|
|
19567
|
+
if (backfilled > 0) {
|
|
19568
|
+
console.log(`[agentmemory] Backfilled ${backfilled} memories into BM25 (legacy gap before #257)`);
|
|
19569
|
+
indexPersistence.scheduleSave();
|
|
19570
|
+
}
|
|
19571
|
+
} catch (err) {
|
|
19572
|
+
console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
|
|
19338
19573
|
}
|
|
19339
19574
|
console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
19340
19575
|
console.log(`[agentmemory] Endpoints: 107 REST + ${getAllTools().length} MCP tools + 6 MCP resources + 3 MCP prompts`);
|