@agentmemory/agentmemory 0.9.2 → 0.9.4

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/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
- if (val.startsWith("\"") && val.endsWith("\"") || val.startsWith("'") && val.endsWith("'")) val = val.slice(1, -1);
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");
@@ -2183,8 +2195,10 @@ var SearchIndex = class SearchIndex {
2183
2195
  //#endregion
2184
2196
  //#region src/state/index-persistence.ts
2185
2197
  const DEBOUNCE_MS = 5e3;
2198
+ const FAILURE_LOG_THROTTLE_MS = 6e4;
2186
2199
  var IndexPersistence = class {
2187
2200
  timer = null;
2201
+ lastFailureLogAt = 0;
2188
2202
  constructor(kv, bm25, vector) {
2189
2203
  this.kv = kv;
2190
2204
  this.bm25 = bm25;
@@ -2192,15 +2206,21 @@ var IndexPersistence = class {
2192
2206
  }
2193
2207
  scheduleSave() {
2194
2208
  if (this.timer) clearTimeout(this.timer);
2195
- this.timer = setTimeout(() => this.save(), DEBOUNCE_MS);
2209
+ this.timer = setTimeout(() => {
2210
+ this.save().catch((err) => this.logFailure(err));
2211
+ }, DEBOUNCE_MS);
2196
2212
  }
2197
2213
  async save() {
2198
2214
  if (this.timer) {
2199
2215
  clearTimeout(this.timer);
2200
2216
  this.timer = null;
2201
2217
  }
2202
- await this.kv.set(KV.bm25Index, "data", this.bm25.serialize());
2203
- if (this.vector && this.vector.size > 0) await this.kv.set(KV.bm25Index, "vectors", this.vector.serialize());
2218
+ try {
2219
+ await this.kv.set(KV.bm25Index, "data", this.bm25.serialize());
2220
+ if (this.vector && this.vector.size > 0) await this.kv.set(KV.bm25Index, "vectors", this.vector.serialize());
2221
+ } catch (err) {
2222
+ this.logFailure(err);
2223
+ }
2204
2224
  }
2205
2225
  async load() {
2206
2226
  let bm25 = null;
@@ -2220,6 +2240,18 @@ var IndexPersistence = class {
2220
2240
  this.timer = null;
2221
2241
  }
2222
2242
  }
2243
+ logFailure(err) {
2244
+ const now = Date.now();
2245
+ if (now - this.lastFailureLogAt < FAILURE_LOG_THROTTLE_MS) return;
2246
+ this.lastFailureLogAt = now;
2247
+ const code = err?.code;
2248
+ const message = err instanceof Error ? err.message : String(err);
2249
+ logger.warn("index persistence: failed to save BM25/vector index", {
2250
+ code,
2251
+ message,
2252
+ 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
2253
+ });
2254
+ }
2223
2255
  };
2224
2256
 
2225
2257
  //#endregion
@@ -5618,7 +5650,7 @@ function registerAutoForgetFunction(sdk, kv) {
5618
5650
 
5619
5651
  //#endregion
5620
5652
  //#region src/version.ts
5621
- const VERSION = "0.9.2";
5653
+ const VERSION = "0.9.4";
5622
5654
 
5623
5655
  //#endregion
5624
5656
  //#region src/functions/export-import.ts
@@ -5738,7 +5770,9 @@ function registerExportImportFunction(sdk, kv) {
5738
5770
  "0.8.13",
5739
5771
  "0.9.0",
5740
5772
  "0.9.1",
5741
- "0.9.2"
5773
+ "0.9.2",
5774
+ "0.9.3",
5775
+ "0.9.4"
5742
5776
  ]).has(importData.version)) return {
5743
5777
  success: false,
5744
5778
  error: `Unsupported export version: ${importData.version}`
@@ -12356,7 +12390,7 @@ function parseJsonlText(text, fallbackSessionId) {
12356
12390
  const parsed = JSON.parse(line);
12357
12391
  if (parsed && typeof parsed === "object") entries.push(parsed);
12358
12392
  } catch {}
12359
- let sessionId = fallbackSessionId || "";
12393
+ let sessionId = "";
12360
12394
  let cwd = "";
12361
12395
  let firstTs = "";
12362
12396
  let lastTs = "";
@@ -12417,7 +12451,7 @@ function parseJsonlText(text, fallbackSessionId) {
12417
12451
  });
12418
12452
  } else if (entry.type === "summary" || entry.type === "system") {}
12419
12453
  }
12420
- const effectiveSessionId = sessionId || generateId("sess");
12454
+ const effectiveSessionId = sessionId || fallbackSessionId || generateId("sess");
12421
12455
  for (const obs of observations) if (obs.sessionId === "imported") obs.sessionId = effectiveSessionId;
12422
12456
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
12423
12457
  return {
@@ -12527,6 +12561,8 @@ function projectTimeline(observations) {
12527
12561
 
12528
12562
  //#endregion
12529
12563
  //#region src/functions/replay.ts
12564
+ const MAX_FILES_DEFAULT = 200;
12565
+ const MAX_FILES_UPPER_BOUND = 1e3;
12530
12566
  const SENSITIVE_PATH_PATTERNS = [
12531
12567
  /(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
12532
12568
  /(^|[\\/_.-])credentials?([\\/_.-]|$)/i,
@@ -12661,8 +12697,11 @@ async function loadObservations(kv, sessionId) {
12661
12697
  }
12662
12698
  async function findJsonlFiles(root, limit = 200) {
12663
12699
  const out = [];
12700
+ let discovered = 0;
12701
+ let walked = 0;
12702
+ const traversalCap = Math.max(limit * 50, 5e4);
12664
12703
  async function walk(dir) {
12665
- if (out.length >= limit) return;
12704
+ if (walked >= traversalCap) return;
12666
12705
  let names;
12667
12706
  try {
12668
12707
  names = await readdir(dir);
@@ -12670,7 +12709,8 @@ async function findJsonlFiles(root, limit = 200) {
12670
12709
  return;
12671
12710
  }
12672
12711
  for (const name of names) {
12673
- if (out.length >= limit) return;
12712
+ if (walked >= traversalCap) return;
12713
+ walked++;
12674
12714
  const full = join(dir, name);
12675
12715
  let st;
12676
12716
  try {
@@ -12680,11 +12720,20 @@ async function findJsonlFiles(root, limit = 200) {
12680
12720
  }
12681
12721
  if (st.isSymbolicLink()) continue;
12682
12722
  if (st.isDirectory()) await walk(full);
12683
- else if (st.isFile() && name.endsWith(".jsonl")) out.push(full);
12723
+ else if (st.isFile() && name.endsWith(".jsonl")) {
12724
+ discovered++;
12725
+ if (out.length < limit) out.push(full);
12726
+ }
12684
12727
  }
12685
12728
  }
12686
12729
  await walk(root);
12687
- return out;
12730
+ const traversalCapped = walked >= traversalCap;
12731
+ return {
12732
+ files: out,
12733
+ truncated: discovered > out.length || traversalCapped,
12734
+ discovered,
12735
+ traversalCapped
12736
+ };
12688
12737
  }
12689
12738
  function registerReplayFunctions(sdk, kv) {
12690
12739
  sdk.registerFunction("mem::replay::load", async (data) => {
@@ -12732,10 +12781,21 @@ function registerReplayFunctions(sdk, kv) {
12732
12781
  error: "path not found"
12733
12782
  };
12734
12783
  }
12784
+ const maxFiles = Number.isInteger(data.maxFiles) && data.maxFiles > 0 ? Math.min(data.maxFiles, MAX_FILES_UPPER_BOUND) : MAX_FILES_DEFAULT;
12735
12785
  let files = [];
12736
- if (stat.isDirectory()) files = await findJsonlFiles(abs, data.maxFiles || 200);
12737
- else if (stat.isFile() && abs.endsWith(".jsonl")) files = [abs];
12738
- else return {
12786
+ let truncated = false;
12787
+ let discovered = 0;
12788
+ let traversalCapped = false;
12789
+ if (stat.isDirectory()) {
12790
+ const found = await findJsonlFiles(abs, maxFiles);
12791
+ files = found.files;
12792
+ truncated = found.truncated;
12793
+ discovered = found.discovered;
12794
+ traversalCapped = found.traversalCapped;
12795
+ } else if (stat.isFile() && abs.endsWith(".jsonl")) {
12796
+ files = [abs];
12797
+ discovered = 1;
12798
+ } else return {
12739
12799
  success: false,
12740
12800
  error: "path must be a .jsonl file or directory"
12741
12801
  };
@@ -12743,7 +12803,12 @@ function registerReplayFunctions(sdk, kv) {
12743
12803
  success: true,
12744
12804
  imported: 0,
12745
12805
  sessionIds: [],
12746
- observations: 0
12806
+ observations: 0,
12807
+ discovered,
12808
+ truncated,
12809
+ traversalCapped,
12810
+ maxFiles,
12811
+ maxFilesUpperBound: MAX_FILES_UPPER_BOUND
12747
12812
  };
12748
12813
  const sessionIds = [];
12749
12814
  let observationCount = 0;
@@ -12809,7 +12874,12 @@ function registerReplayFunctions(sdk, kv) {
12809
12874
  success: true,
12810
12875
  imported: files.length,
12811
12876
  sessionIds,
12812
- observations: observationCount
12877
+ observations: observationCount,
12878
+ discovered,
12879
+ truncated,
12880
+ traversalCapped,
12881
+ maxFiles,
12882
+ maxFilesUpperBound: MAX_FILES_UPPER_BOUND
12813
12883
  };
12814
12884
  });
12815
12885
  }
@@ -13036,6 +13106,28 @@ function requireConfiguredSecret(secret, feature) {
13036
13106
  body: { error: `${feature} requires AGENTMEMORY_SECRET` }
13037
13107
  };
13038
13108
  }
13109
+ function flagDisabledResponse(opts) {
13110
+ return {
13111
+ status_code: 503,
13112
+ body: opts
13113
+ };
13114
+ }
13115
+ function graphDisabledResponse() {
13116
+ return flagDisabledResponse({
13117
+ error: "Knowledge graph not enabled",
13118
+ flag: "GRAPH_EXTRACTION_ENABLED",
13119
+ enableHow: "Set GRAPH_EXTRACTION_ENABLED=true and restart. Requires an LLM provider key.",
13120
+ docsHref: "https://github.com/rohitg00/agentmemory#knowledge-graph"
13121
+ });
13122
+ }
13123
+ function consolidationDisabledResponse() {
13124
+ return flagDisabledResponse({
13125
+ error: "Consolidation pipeline not enabled",
13126
+ flag: "CONSOLIDATION_ENABLED",
13127
+ enableHow: "Set CONSOLIDATION_ENABLED=true and restart. Requires an LLM provider key.",
13128
+ docsHref: "https://github.com/rohitg00/agentmemory#consolidation"
13129
+ });
13130
+ }
13039
13131
  function asNonEmptyString$1(value) {
13040
13132
  if (typeof value !== "string") return null;
13041
13133
  const trimmed = value.trim();
@@ -13087,6 +13179,77 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13087
13179
  http_method: "GET"
13088
13180
  }
13089
13181
  });
13182
+ sdk.registerFunction("api::config-flags", async (req) => {
13183
+ const authErr = checkAuth(req, secret);
13184
+ if (authErr) return authErr;
13185
+ return {
13186
+ status_code: 200,
13187
+ body: {
13188
+ version: VERSION,
13189
+ provider: detectLlmProviderKind(),
13190
+ embeddingProvider: detectEmbeddingProvider() ? "embeddings" : "none",
13191
+ flags: [
13192
+ {
13193
+ key: "GRAPH_EXTRACTION_ENABLED",
13194
+ label: "Knowledge graph extraction",
13195
+ enabled: isGraphExtractionEnabled(),
13196
+ default: false,
13197
+ affects: ["Graph", "Dashboard"],
13198
+ needsLlm: true,
13199
+ description: "Extracts entities and relations from observations into a knowledge graph.",
13200
+ enableHow: "Set GRAPH_EXTRACTION_ENABLED=true and provide an LLM key, then restart.",
13201
+ docsHref: "https://github.com/rohitg00/agentmemory#knowledge-graph"
13202
+ },
13203
+ {
13204
+ key: "CONSOLIDATION_ENABLED",
13205
+ label: "Memory consolidation",
13206
+ enabled: isConsolidationEnabled(),
13207
+ default: false,
13208
+ affects: [
13209
+ "Dashboard",
13210
+ "Memories",
13211
+ "Crystals"
13212
+ ],
13213
+ needsLlm: true,
13214
+ description: "Periodically summarizes sessions into semantic facts + procedures.",
13215
+ enableHow: "Set CONSOLIDATION_ENABLED=true and provide an LLM key, then restart.",
13216
+ docsHref: "https://github.com/rohitg00/agentmemory#consolidation"
13217
+ },
13218
+ {
13219
+ key: "AGENTMEMORY_AUTO_COMPRESS",
13220
+ label: "LLM-powered observation compression",
13221
+ enabled: isAutoCompressEnabled(),
13222
+ default: false,
13223
+ affects: ["Memories", "Timeline"],
13224
+ needsLlm: true,
13225
+ description: "Every observation is compressed by the LLM for richer summaries (costs tokens). OFF uses zero-LLM synthetic compression.",
13226
+ enableHow: "Set AGENTMEMORY_AUTO_COMPRESS=true and provide an LLM key.",
13227
+ docsHref: "https://github.com/rohitg00/agentmemory/issues/138"
13228
+ },
13229
+ {
13230
+ key: "AGENTMEMORY_INJECT_CONTEXT",
13231
+ label: "In-conversation context injection",
13232
+ enabled: isContextInjectionEnabled(),
13233
+ default: false,
13234
+ affects: ["Hooks"],
13235
+ needsLlm: false,
13236
+ description: "Hooks write recalled context into Claude Code's conversation. OFF captures in the background without injecting.",
13237
+ enableHow: "Set AGENTMEMORY_INJECT_CONTEXT=true and restart.",
13238
+ docsHref: "https://github.com/rohitg00/agentmemory/issues/143"
13239
+ }
13240
+ ]
13241
+ }
13242
+ };
13243
+ });
13244
+ sdk.registerTrigger({
13245
+ type: "http",
13246
+ function_id: "api::config-flags",
13247
+ config: {
13248
+ api_path: "/agentmemory/config/flags",
13249
+ http_method: "GET",
13250
+ middleware_function_ids: ["middleware::api-auth"]
13251
+ }
13252
+ });
13090
13253
  sdk.registerFunction("api::health", async (req) => {
13091
13254
  const health = await getLatestHealth(kv);
13092
13255
  const functionMetrics = metricsStore ? await metricsStore.getAll() : [];
@@ -13321,11 +13484,12 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13321
13484
  payload.path = body.path.trim();
13322
13485
  }
13323
13486
  if (body.maxFiles !== void 0) {
13324
- if (!Number.isInteger(body.maxFiles) || body.maxFiles < 1) return {
13487
+ const n = body.maxFiles;
13488
+ if (!Number.isInteger(n) || n < 1 || n > MAX_FILES_UPPER_BOUND) return {
13325
13489
  status_code: 400,
13326
- body: { error: "maxFiles must be a positive integer" }
13490
+ body: { error: `maxFiles must be an integer between 1 and ${MAX_FILES_UPPER_BOUND}` }
13327
13491
  };
13328
- payload.maxFiles = body.maxFiles;
13492
+ payload.maxFiles = n;
13329
13493
  }
13330
13494
  return {
13331
13495
  status_code: 202,
@@ -13907,10 +14071,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13907
14071
  })
13908
14072
  };
13909
14073
  } catch {
13910
- return {
13911
- status_code: 404,
13912
- body: { error: "Knowledge graph not enabled" }
13913
- };
14074
+ return graphDisabledResponse();
13914
14075
  }
13915
14076
  });
13916
14077
  sdk.registerTrigger({
@@ -13933,10 +14094,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13933
14094
  })
13934
14095
  };
13935
14096
  } catch {
13936
- return {
13937
- status_code: 404,
13938
- body: { error: "Knowledge graph not enabled" }
13939
- };
14097
+ return graphDisabledResponse();
13940
14098
  }
13941
14099
  });
13942
14100
  sdk.registerTrigger({
@@ -13963,10 +14121,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13963
14121
  })
13964
14122
  };
13965
14123
  } catch {
13966
- return {
13967
- status_code: 404,
13968
- body: { error: "Knowledge graph not enabled" }
13969
- };
14124
+ return graphDisabledResponse();
13970
14125
  }
13971
14126
  });
13972
14127
  sdk.registerTrigger({
@@ -13989,10 +14144,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13989
14144
  })
13990
14145
  };
13991
14146
  } catch {
13992
- return {
13993
- status_code: 404,
13994
- body: { error: "Consolidation pipeline not enabled" }
13995
- };
14147
+ return consolidationDisabledResponse();
13996
14148
  }
13997
14149
  });
13998
14150
  sdk.registerTrigger({
@@ -14253,6 +14405,32 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14253
14405
  http_method: "GET"
14254
14406
  }
14255
14407
  });
14408
+ sdk.registerFunction("api::memory-by-id", async (req) => {
14409
+ const authErr = checkAuth(req, secret);
14410
+ if (authErr) return authErr;
14411
+ const id = req.path_params?.["id"];
14412
+ if (!id || typeof id !== "string") return {
14413
+ status_code: 400,
14414
+ body: { error: "id path parameter is required" }
14415
+ };
14416
+ const memory = await kv.get(KV.memories, id);
14417
+ if (!memory) return {
14418
+ status_code: 404,
14419
+ body: { error: `memory not found: ${id}` }
14420
+ };
14421
+ return {
14422
+ status_code: 200,
14423
+ body: { memory }
14424
+ };
14425
+ });
14426
+ sdk.registerTrigger({
14427
+ type: "http",
14428
+ function_id: "api::memory-by-id",
14429
+ config: {
14430
+ api_path: "/agentmemory/memories/:id",
14431
+ http_method: "GET"
14432
+ }
14433
+ });
14256
14434
  sdk.registerFunction("api::semantic-list", async (req) => {
14257
14435
  const authErr = checkAuth(req, secret);
14258
14436
  if (authErr) return authErr;
@@ -16131,6 +16309,15 @@ function registerEventTriggers(sdk, kv) {
16131
16309
  error: err instanceof Error ? err.message : String(err)
16132
16310
  });
16133
16311
  }
16312
+ if (isGraphExtractionEnabled()) try {
16313
+ const compressed = (await kv.list(KV.observations(data.sessionId))).filter((o) => o.title);
16314
+ if (compressed.length > 0) sdk.triggerVoid("mem::graph-extract", { observations: compressed });
16315
+ } catch (err) {
16316
+ logger.warn("graph-extract triggerVoid failed", {
16317
+ sessionId: data.sessionId,
16318
+ error: err instanceof Error ? err.message : String(err)
16319
+ });
16320
+ }
16134
16321
  return summary;
16135
16322
  });
16136
16323
  sdk.registerTrigger({
@@ -19115,6 +19302,14 @@ function initMetrics(getMeter) {
19115
19302
  function hasGetMeter(sdk) {
19116
19303
  return typeof sdk === "object" && sdk !== null && "getMeter" in sdk && typeof sdk.getMeter === "function";
19117
19304
  }
19305
+ let lastUnhandledLogAt = 0;
19306
+ process.on("unhandledRejection", (reason) => {
19307
+ const now = Date.now();
19308
+ if (now - lastUnhandledLogAt < 6e4) return;
19309
+ lastUnhandledLogAt = now;
19310
+ const r = reason;
19311
+ console.warn(`[agentmemory] unhandledRejection (suppressed):`, r?.code ? `${r.code} ${r.function_id ?? ""} ${r.message ?? ""}`.trim() : reason);
19312
+ });
19118
19313
  async function main() {
19119
19314
  const config = loadConfig();
19120
19315
  const embeddingConfig = loadEmbeddingConfig();