@agentmemory/agentmemory 0.9.3 → 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.3";
5653
+ const VERSION = "0.9.4";
5622
5654
 
5623
5655
  //#endregion
5624
5656
  //#region src/functions/export-import.ts
@@ -5739,7 +5771,8 @@ function registerExportImportFunction(sdk, kv) {
5739
5771
  "0.9.0",
5740
5772
  "0.9.1",
5741
5773
  "0.9.2",
5742
- "0.9.3"
5774
+ "0.9.3",
5775
+ "0.9.4"
5743
5776
  ]).has(importData.version)) return {
5744
5777
  success: false,
5745
5778
  error: `Unsupported export version: ${importData.version}`
@@ -12357,7 +12390,7 @@ function parseJsonlText(text, fallbackSessionId) {
12357
12390
  const parsed = JSON.parse(line);
12358
12391
  if (parsed && typeof parsed === "object") entries.push(parsed);
12359
12392
  } catch {}
12360
- let sessionId = fallbackSessionId || "";
12393
+ let sessionId = "";
12361
12394
  let cwd = "";
12362
12395
  let firstTs = "";
12363
12396
  let lastTs = "";
@@ -12418,7 +12451,7 @@ function parseJsonlText(text, fallbackSessionId) {
12418
12451
  });
12419
12452
  } else if (entry.type === "summary" || entry.type === "system") {}
12420
12453
  }
12421
- const effectiveSessionId = sessionId || generateId("sess");
12454
+ const effectiveSessionId = sessionId || fallbackSessionId || generateId("sess");
12422
12455
  for (const obs of observations) if (obs.sessionId === "imported") obs.sessionId = effectiveSessionId;
12423
12456
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
12424
12457
  return {
@@ -12528,6 +12561,8 @@ function projectTimeline(observations) {
12528
12561
 
12529
12562
  //#endregion
12530
12563
  //#region src/functions/replay.ts
12564
+ const MAX_FILES_DEFAULT = 200;
12565
+ const MAX_FILES_UPPER_BOUND = 1e3;
12531
12566
  const SENSITIVE_PATH_PATTERNS = [
12532
12567
  /(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
12533
12568
  /(^|[\\/_.-])credentials?([\\/_.-]|$)/i,
@@ -12662,8 +12697,11 @@ async function loadObservations(kv, sessionId) {
12662
12697
  }
12663
12698
  async function findJsonlFiles(root, limit = 200) {
12664
12699
  const out = [];
12700
+ let discovered = 0;
12701
+ let walked = 0;
12702
+ const traversalCap = Math.max(limit * 50, 5e4);
12665
12703
  async function walk(dir) {
12666
- if (out.length >= limit) return;
12704
+ if (walked >= traversalCap) return;
12667
12705
  let names;
12668
12706
  try {
12669
12707
  names = await readdir(dir);
@@ -12671,7 +12709,8 @@ async function findJsonlFiles(root, limit = 200) {
12671
12709
  return;
12672
12710
  }
12673
12711
  for (const name of names) {
12674
- if (out.length >= limit) return;
12712
+ if (walked >= traversalCap) return;
12713
+ walked++;
12675
12714
  const full = join(dir, name);
12676
12715
  let st;
12677
12716
  try {
@@ -12681,11 +12720,20 @@ async function findJsonlFiles(root, limit = 200) {
12681
12720
  }
12682
12721
  if (st.isSymbolicLink()) continue;
12683
12722
  if (st.isDirectory()) await walk(full);
12684
- 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
+ }
12685
12727
  }
12686
12728
  }
12687
12729
  await walk(root);
12688
- return out;
12730
+ const traversalCapped = walked >= traversalCap;
12731
+ return {
12732
+ files: out,
12733
+ truncated: discovered > out.length || traversalCapped,
12734
+ discovered,
12735
+ traversalCapped
12736
+ };
12689
12737
  }
12690
12738
  function registerReplayFunctions(sdk, kv) {
12691
12739
  sdk.registerFunction("mem::replay::load", async (data) => {
@@ -12733,10 +12781,21 @@ function registerReplayFunctions(sdk, kv) {
12733
12781
  error: "path not found"
12734
12782
  };
12735
12783
  }
12784
+ const maxFiles = Number.isInteger(data.maxFiles) && data.maxFiles > 0 ? Math.min(data.maxFiles, MAX_FILES_UPPER_BOUND) : MAX_FILES_DEFAULT;
12736
12785
  let files = [];
12737
- if (stat.isDirectory()) files = await findJsonlFiles(abs, data.maxFiles || 200);
12738
- else if (stat.isFile() && abs.endsWith(".jsonl")) files = [abs];
12739
- 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 {
12740
12799
  success: false,
12741
12800
  error: "path must be a .jsonl file or directory"
12742
12801
  };
@@ -12744,7 +12803,12 @@ function registerReplayFunctions(sdk, kv) {
12744
12803
  success: true,
12745
12804
  imported: 0,
12746
12805
  sessionIds: [],
12747
- observations: 0
12806
+ observations: 0,
12807
+ discovered,
12808
+ truncated,
12809
+ traversalCapped,
12810
+ maxFiles,
12811
+ maxFilesUpperBound: MAX_FILES_UPPER_BOUND
12748
12812
  };
12749
12813
  const sessionIds = [];
12750
12814
  let observationCount = 0;
@@ -12810,7 +12874,12 @@ function registerReplayFunctions(sdk, kv) {
12810
12874
  success: true,
12811
12875
  imported: files.length,
12812
12876
  sessionIds,
12813
- observations: observationCount
12877
+ observations: observationCount,
12878
+ discovered,
12879
+ truncated,
12880
+ traversalCapped,
12881
+ maxFiles,
12882
+ maxFilesUpperBound: MAX_FILES_UPPER_BOUND
12814
12883
  };
12815
12884
  });
12816
12885
  }
@@ -13113,12 +13182,11 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13113
13182
  sdk.registerFunction("api::config-flags", async (req) => {
13114
13183
  const authErr = checkAuth(req, secret);
13115
13184
  if (authErr) return authErr;
13116
- const env = process.env;
13117
13185
  return {
13118
13186
  status_code: 200,
13119
13187
  body: {
13120
13188
  version: VERSION,
13121
- provider: env["ANTHROPIC_API_KEY"] || env["GEMINI_API_KEY"] || env["OPENROUTER_API_KEY"] || env["MINIMAX_API_KEY"] ? "llm" : "noop",
13189
+ provider: detectLlmProviderKind(),
13122
13190
  embeddingProvider: detectEmbeddingProvider() ? "embeddings" : "none",
13123
13191
  flags: [
13124
13192
  {
@@ -13416,11 +13484,12 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13416
13484
  payload.path = body.path.trim();
13417
13485
  }
13418
13486
  if (body.maxFiles !== void 0) {
13419
- 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 {
13420
13489
  status_code: 400,
13421
- body: { error: "maxFiles must be a positive integer" }
13490
+ body: { error: `maxFiles must be an integer between 1 and ${MAX_FILES_UPPER_BOUND}` }
13422
13491
  };
13423
- payload.maxFiles = body.maxFiles;
13492
+ payload.maxFiles = n;
13424
13493
  }
13425
13494
  return {
13426
13495
  status_code: 202,
@@ -14336,6 +14405,32 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14336
14405
  http_method: "GET"
14337
14406
  }
14338
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
+ });
14339
14434
  sdk.registerFunction("api::semantic-list", async (req) => {
14340
14435
  const authErr = checkAuth(req, secret);
14341
14436
  if (authErr) return authErr;
@@ -16214,6 +16309,15 @@ function registerEventTriggers(sdk, kv) {
16214
16309
  error: err instanceof Error ? err.message : String(err)
16215
16310
  });
16216
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
+ }
16217
16321
  return summary;
16218
16322
  });
16219
16323
  sdk.registerTrigger({
@@ -19198,6 +19302,14 @@ function initMetrics(getMeter) {
19198
19302
  function hasGetMeter(sdk) {
19199
19303
  return typeof sdk === "object" && sdk !== null && "getMeter" in sdk && typeof sdk.getMeter === "function";
19200
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
+ });
19201
19313
  async function main() {
19202
19314
  const config = loadConfig();
19203
19315
  const embeddingConfig = loadEmbeddingConfig();