@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.
@@ -1,5 +1,5 @@
1
1
  import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./cli.mjs";
2
- import { _ as loadTeamConfig, a as getConsolidationDecayDays, c as isAutoCompressEnabled, d as isGraphExtractionEnabled, f as loadClaudeBridgeConfig, g as loadSnapshotConfig, h as loadFallbackConfig, i as detectEmbeddingProvider, l as isConsolidationEnabled, m as loadEmbeddingConfig, n as getVisibleTools, o as getEnvVar, p as loadConfig, r as VERSION, t as getAllTools, u as isContextInjectionEnabled } from "./tools-registry-m8Ofn9vV.mjs";
2
+ import { _ as loadSnapshotConfig, a as detectLlmProviderKind, d as isContextInjectionEnabled, f as isGraphExtractionEnabled, g as loadFallbackConfig, h as loadEmbeddingConfig, i as detectEmbeddingProvider, l as isAutoCompressEnabled, m as loadConfig, n as getVisibleTools, o as getConsolidationDecayDays, p as loadClaudeBridgeConfig, r as VERSION, s as getEnvVar, t as getAllTools, u as isConsolidationEnabled, v as loadTeamConfig } from "./tools-registry-BWM0vWeA.mjs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, extname, join, resolve, sep } from "node:path";
@@ -787,22 +787,38 @@ let imageEmbeddingProvider = null;
787
787
  function createImageEmbeddingProvider() {
788
788
  if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] !== "true") return null;
789
789
  if (imageEmbeddingProvider) return imageEmbeddingProvider;
790
- imageEmbeddingProvider = new ClipEmbeddingProvider();
790
+ imageEmbeddingProvider = withDimensionGuard(new ClipEmbeddingProvider());
791
791
  return imageEmbeddingProvider;
792
792
  }
793
793
  function createEmbeddingProvider() {
794
794
  const detected = detectEmbeddingProvider();
795
795
  if (!detected) return null;
796
796
  switch (detected) {
797
- case "gemini": return new GeminiEmbeddingProvider(getEnvVar("GEMINI_API_KEY"));
798
- case "openai": return new OpenAIEmbeddingProvider(getEnvVar("OPENAI_API_KEY"));
799
- case "voyage": return new VoyageEmbeddingProvider(getEnvVar("VOYAGE_API_KEY"));
800
- case "cohere": return new CohereEmbeddingProvider(getEnvVar("COHERE_API_KEY"));
801
- case "openrouter": return new OpenRouterEmbeddingProvider(getEnvVar("OPENROUTER_API_KEY"));
802
- case "local": return new LocalEmbeddingProvider();
797
+ case "gemini": return withDimensionGuard(new GeminiEmbeddingProvider(getEnvVar("GEMINI_API_KEY")));
798
+ case "openai": return withDimensionGuard(new OpenAIEmbeddingProvider(getEnvVar("OPENAI_API_KEY")));
799
+ case "voyage": return withDimensionGuard(new VoyageEmbeddingProvider(getEnvVar("VOYAGE_API_KEY")));
800
+ case "cohere": return withDimensionGuard(new CohereEmbeddingProvider(getEnvVar("COHERE_API_KEY")));
801
+ case "openrouter": return withDimensionGuard(new OpenRouterEmbeddingProvider(getEnvVar("OPENROUTER_API_KEY")));
802
+ case "local": return withDimensionGuard(new LocalEmbeddingProvider());
803
803
  default: return null;
804
804
  }
805
805
  }
806
+ function withDimensionGuard(provider) {
807
+ const expected = provider.dimensions;
808
+ const check = (v, where) => {
809
+ if (v.length !== expected) throw new Error(`Embedding dimension mismatch in ${provider.name}.${where}: expected ${expected}, got ${v.length}`);
810
+ return v;
811
+ };
812
+ const wrapped = Object.create(provider);
813
+ wrapped.embed = async (t) => check(await provider.embed(t), "embed");
814
+ wrapped.embedBatch = async (ts) => {
815
+ const out = await provider.embedBatch(ts);
816
+ out.forEach((v, i) => check(v, `embedBatch[${i}]`));
817
+ return out;
818
+ };
819
+ if (provider.embedImage) wrapped.embedImage = async (s) => check(await provider.embedImage(s), "embedImage");
820
+ return wrapped;
821
+ }
806
822
 
807
823
  //#endregion
808
824
  //#region src/providers/index.ts
@@ -961,6 +977,22 @@ var VectorIndex = class VectorIndex {
961
977
  get size() {
962
978
  return this.vectors.size;
963
979
  }
980
+ validateDimensions(expected) {
981
+ const mismatches = [];
982
+ const seenDimensions = /* @__PURE__ */ new Set();
983
+ for (const [obsId, entry] of this.vectors) {
984
+ const dim = entry.embedding.length;
985
+ seenDimensions.add(dim);
986
+ if (dim !== expected) mismatches.push({
987
+ obsId,
988
+ dim
989
+ });
990
+ }
991
+ return {
992
+ mismatches,
993
+ seenDimensions
994
+ };
995
+ }
964
996
  clear() {
965
997
  this.vectors.clear();
966
998
  }
@@ -1875,6 +1907,9 @@ var SearchIndex = class SearchIndex {
1875
1907
  }
1876
1908
  this.sortedTerms = null;
1877
1909
  }
1910
+ has(id) {
1911
+ return this.entries.has(id);
1912
+ }
1878
1913
  search(query, limit = 20) {
1879
1914
  const rawTerms = this.tokenize(query.toLowerCase());
1880
1915
  if (rawTerms.length === 0) return [];
@@ -2018,8 +2053,10 @@ var SearchIndex = class SearchIndex {
2018
2053
  //#endregion
2019
2054
  //#region src/state/index-persistence.ts
2020
2055
  const DEBOUNCE_MS = 5e3;
2056
+ const FAILURE_LOG_THROTTLE_MS = 6e4;
2021
2057
  var IndexPersistence = class {
2022
2058
  timer = null;
2059
+ lastFailureLogAt = 0;
2023
2060
  constructor(kv, bm25, vector) {
2024
2061
  this.kv = kv;
2025
2062
  this.bm25 = bm25;
@@ -2027,15 +2064,21 @@ var IndexPersistence = class {
2027
2064
  }
2028
2065
  scheduleSave() {
2029
2066
  if (this.timer) clearTimeout(this.timer);
2030
- this.timer = setTimeout(() => this.save(), DEBOUNCE_MS);
2067
+ this.timer = setTimeout(() => {
2068
+ this.save().catch((err) => this.logFailure(err));
2069
+ }, DEBOUNCE_MS);
2031
2070
  }
2032
2071
  async save() {
2033
2072
  if (this.timer) {
2034
2073
  clearTimeout(this.timer);
2035
2074
  this.timer = null;
2036
2075
  }
2037
- await this.kv.set(KV.bm25Index, "data", this.bm25.serialize());
2038
- if (this.vector && this.vector.size > 0) await this.kv.set(KV.bm25Index, "vectors", this.vector.serialize());
2076
+ try {
2077
+ await this.kv.set(KV.bm25Index, "data", this.bm25.serialize());
2078
+ if (this.vector && this.vector.size > 0) await this.kv.set(KV.bm25Index, "vectors", this.vector.serialize());
2079
+ } catch (err) {
2080
+ this.logFailure(err);
2081
+ }
2039
2082
  }
2040
2083
  async load() {
2041
2084
  let bm25 = null;
@@ -2055,6 +2098,18 @@ var IndexPersistence = class {
2055
2098
  this.timer = null;
2056
2099
  }
2057
2100
  }
2101
+ logFailure(err) {
2102
+ const now = Date.now();
2103
+ if (now - this.lastFailureLogAt < FAILURE_LOG_THROTTLE_MS) return;
2104
+ this.lastFailureLogAt = now;
2105
+ const code = err?.code;
2106
+ const message = err instanceof Error ? err.message : String(err);
2107
+ logger.warn("index persistence: failed to save BM25/vector index", {
2108
+ code,
2109
+ message,
2110
+ 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
2111
+ });
2112
+ }
2058
2113
  };
2059
2114
 
2060
2115
  //#endregion
@@ -2262,6 +2317,20 @@ async function deleteAccessLog(kv, memoryId) {
2262
2317
 
2263
2318
  //#endregion
2264
2319
  //#region src/functions/search.ts
2320
+ function memoryAsIndexable$1(memory) {
2321
+ return {
2322
+ id: memory.id,
2323
+ sessionId: memory.sessionIds[0] ?? "memory",
2324
+ timestamp: memory.createdAt,
2325
+ type: "decision",
2326
+ title: memory.title,
2327
+ facts: [memory.content],
2328
+ narrative: memory.content,
2329
+ concepts: memory.concepts,
2330
+ files: memory.files,
2331
+ importance: memory.strength
2332
+ };
2333
+ }
2265
2334
  let index = null;
2266
2335
  function getSearchIndex() {
2267
2336
  if (!index) index = new SearchIndex();
@@ -2270,9 +2339,20 @@ function getSearchIndex() {
2270
2339
  async function rebuildIndex(kv) {
2271
2340
  const idx = getSearchIndex();
2272
2341
  idx.clear();
2273
- const sessions = await kv.list(KV.sessions);
2274
- if (!sessions.length) return 0;
2275
2342
  let count = 0;
2343
+ try {
2344
+ const memories = await kv.list(KV.memories);
2345
+ for (const memory of memories) {
2346
+ if (memory.isLatest === false) continue;
2347
+ if (!memory.title || !memory.content) continue;
2348
+ idx.add(memoryAsIndexable$1(memory));
2349
+ count++;
2350
+ }
2351
+ } catch (err) {
2352
+ logger.warn("rebuildIndex: failed to load memories", { error: err instanceof Error ? err.message : String(err) });
2353
+ }
2354
+ const sessions = await kv.list(KV.sessions);
2355
+ if (!sessions.length) return count;
2276
2356
  const obsPerSession = [];
2277
2357
  const failedSessions = [];
2278
2358
  for (let batch = 0; batch < sessions.length; batch += 10) {
@@ -2507,10 +2587,10 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
2507
2587
  };
2508
2588
  }
2509
2589
  if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) {
2510
- const { saveImageToDisk } = await import("./image-store-DGvZMMrI.mjs");
2590
+ const { saveImageToDisk } = await import("./image-store-DnuCI2RB.mjs");
2511
2591
  const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
2512
2592
  raw.imageData = filePath;
2513
- const { incrementImageRef } = await import("./image-refs-CESf9ndJ.mjs");
2593
+ const { incrementImageRef } = await import("./image-refs-DRse_ePx.mjs");
2514
2594
  await incrementImageRef(kv, filePath);
2515
2595
  sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: bytesWritten });
2516
2596
  if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] === "true") sdk.triggerVoid("mem::vision-embed", {
@@ -2523,7 +2603,7 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
2523
2603
  await kv.set(KV.observations(payload.sessionId), obsId, raw);
2524
2604
  } catch (error) {
2525
2605
  if (raw.imageData) {
2526
- const { deleteImage } = await import("./image-store-DGvZMMrI.mjs");
2606
+ const { deleteImage } = await import("./image-store-DnuCI2RB.mjs");
2527
2607
  const { deletedBytes } = await deleteImage(raw.imageData);
2528
2608
  if (deletedBytes > 0) sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
2529
2609
  }
@@ -4471,6 +4551,20 @@ function registerPatternsFunction(sdk, kv) {
4471
4551
 
4472
4552
  //#endregion
4473
4553
  //#region src/functions/remember.ts
4554
+ function memoryAsIndexable(memory) {
4555
+ return {
4556
+ id: memory.id,
4557
+ sessionId: memory.sessionIds[0] ?? "memory",
4558
+ timestamp: memory.createdAt,
4559
+ type: "decision",
4560
+ title: memory.title,
4561
+ facts: [memory.content],
4562
+ narrative: memory.content,
4563
+ concepts: memory.concepts,
4564
+ files: memory.files,
4565
+ importance: memory.strength
4566
+ };
4567
+ }
4474
4568
  function registerRememberFunction(sdk, kv) {
4475
4569
  sdk.registerFunction("mem::remember", async (data) => {
4476
4570
  if (!data.content || typeof data.content !== "string" || !data.content.trim()) return {
@@ -4536,6 +4630,14 @@ function registerRememberFunction(sdk, kv) {
4536
4630
  await kv.set(KV.memories, supersededMemory.id, supersededMemory);
4537
4631
  }
4538
4632
  await kv.set(KV.memories, memory.id, memory);
4633
+ try {
4634
+ getSearchIndex().add(memoryAsIndexable(memory));
4635
+ } catch (err) {
4636
+ logger.warn("Failed to index saved memory into BM25", {
4637
+ memId: memory.id,
4638
+ error: err instanceof Error ? err.message : String(err)
4639
+ });
4640
+ }
4539
4641
  if (supersededId) await sdk.trigger({
4540
4642
  function_id: "mem::cascade-update",
4541
4643
  payload: { supersededMemoryId: supersededId },
@@ -4556,7 +4658,7 @@ function registerRememberFunction(sdk, kv) {
4556
4658
  const deletedMemoryIds = [];
4557
4659
  const deletedObservationIds = [];
4558
4660
  let deletedSession = false;
4559
- const { decrementImageRef } = await import("./image-refs-CESf9ndJ.mjs");
4661
+ const { decrementImageRef } = await import("./image-refs-DRse_ePx.mjs");
4560
4662
  if (data.memoryId) {
4561
4663
  const mem = await kv.get(KV.memories, data.memoryId);
4562
4664
  await kv.delete(KV.memories, data.memoryId);
@@ -4615,7 +4717,7 @@ const DEFAULTS$1 = {
4615
4717
  function registerEvictFunction(sdk, kv) {
4616
4718
  sdk.registerFunction("mem::evict", async (data) => {
4617
4719
  const dryRun = data?.dryRun ?? false;
4618
- const { decrementImageRef } = await import("./image-refs-CESf9ndJ.mjs");
4720
+ const { decrementImageRef } = await import("./image-refs-DRse_ePx.mjs");
4619
4721
  const configOverride = await kv.get(KV.config, "eviction").catch(() => null);
4620
4722
  const cfg = {
4621
4723
  ...DEFAULTS$1,
@@ -5218,7 +5320,7 @@ function registerAutoForgetFunction(sdk, kv) {
5218
5320
  sdk.registerFunction("mem::auto-forget", async (data) => {
5219
5321
  const dryRun = data?.dryRun ?? false;
5220
5322
  const now = Date.now();
5221
- const { decrementImageRef } = await import("./image-refs-CESf9ndJ.mjs");
5323
+ const { decrementImageRef } = await import("./image-refs-DRse_ePx.mjs");
5222
5324
  const result = {
5223
5325
  ttlExpired: [],
5224
5326
  contradictions: [],
@@ -5450,7 +5552,9 @@ function registerExportImportFunction(sdk, kv) {
5450
5552
  "0.9.0",
5451
5553
  "0.9.1",
5452
5554
  "0.9.2",
5453
- "0.9.3"
5555
+ "0.9.3",
5556
+ "0.9.4",
5557
+ "0.9.5"
5454
5558
  ]).has(importData.version)) return {
5455
5559
  success: false,
5456
5560
  error: `Unsupported export version: ${importData.version}`
@@ -11807,7 +11911,7 @@ function registerRetentionFunctions(sdk, kv) {
11807
11911
  const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
11808
11912
  const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
11809
11913
  const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
11810
- const { decrementImageRef } = await import("./image-refs-CESf9ndJ.mjs");
11914
+ const { decrementImageRef } = await import("./image-refs-DRse_ePx.mjs");
11811
11915
  const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
11812
11916
  if (data?.dryRun) return {
11813
11917
  success: true,
@@ -12068,7 +12172,7 @@ function parseJsonlText(text, fallbackSessionId) {
12068
12172
  const parsed = JSON.parse(line);
12069
12173
  if (parsed && typeof parsed === "object") entries.push(parsed);
12070
12174
  } catch {}
12071
- let sessionId = fallbackSessionId || "";
12175
+ let sessionId = "";
12072
12176
  let cwd = "";
12073
12177
  let firstTs = "";
12074
12178
  let lastTs = "";
@@ -12129,7 +12233,7 @@ function parseJsonlText(text, fallbackSessionId) {
12129
12233
  });
12130
12234
  } else if (entry.type === "summary" || entry.type === "system") {}
12131
12235
  }
12132
- const effectiveSessionId = sessionId || generateId("sess");
12236
+ const effectiveSessionId = sessionId || fallbackSessionId || generateId("sess");
12133
12237
  for (const obs of observations) if (obs.sessionId === "imported") obs.sessionId = effectiveSessionId;
12134
12238
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
12135
12239
  return {
@@ -12239,6 +12343,8 @@ function projectTimeline(observations) {
12239
12343
 
12240
12344
  //#endregion
12241
12345
  //#region src/functions/replay.ts
12346
+ const MAX_FILES_DEFAULT = 200;
12347
+ const MAX_FILES_UPPER_BOUND = 1e3;
12242
12348
  const SENSITIVE_PATH_PATTERNS = [
12243
12349
  /(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
12244
12350
  /(^|[\\/_.-])credentials?([\\/_.-]|$)/i,
@@ -12373,8 +12479,11 @@ async function loadObservations(kv, sessionId) {
12373
12479
  }
12374
12480
  async function findJsonlFiles(root, limit = 200) {
12375
12481
  const out = [];
12482
+ let discovered = 0;
12483
+ let walked = 0;
12484
+ const traversalCap = Math.max(limit * 50, 5e4);
12376
12485
  async function walk(dir) {
12377
- if (out.length >= limit) return;
12486
+ if (walked >= traversalCap) return;
12378
12487
  let names;
12379
12488
  try {
12380
12489
  names = await readdir(dir);
@@ -12382,7 +12491,8 @@ async function findJsonlFiles(root, limit = 200) {
12382
12491
  return;
12383
12492
  }
12384
12493
  for (const name of names) {
12385
- if (out.length >= limit) return;
12494
+ if (walked >= traversalCap) return;
12495
+ walked++;
12386
12496
  const full = join(dir, name);
12387
12497
  let st;
12388
12498
  try {
@@ -12392,11 +12502,20 @@ async function findJsonlFiles(root, limit = 200) {
12392
12502
  }
12393
12503
  if (st.isSymbolicLink()) continue;
12394
12504
  if (st.isDirectory()) await walk(full);
12395
- else if (st.isFile() && name.endsWith(".jsonl")) out.push(full);
12505
+ else if (st.isFile() && name.endsWith(".jsonl")) {
12506
+ discovered++;
12507
+ if (out.length < limit) out.push(full);
12508
+ }
12396
12509
  }
12397
12510
  }
12398
12511
  await walk(root);
12399
- return out;
12512
+ const traversalCapped = walked >= traversalCap;
12513
+ return {
12514
+ files: out,
12515
+ truncated: discovered > out.length || traversalCapped,
12516
+ discovered,
12517
+ traversalCapped
12518
+ };
12400
12519
  }
12401
12520
  function registerReplayFunctions(sdk, kv) {
12402
12521
  sdk.registerFunction("mem::replay::load", async (data) => {
@@ -12444,10 +12563,21 @@ function registerReplayFunctions(sdk, kv) {
12444
12563
  error: "path not found"
12445
12564
  };
12446
12565
  }
12566
+ const maxFiles = Number.isInteger(data.maxFiles) && data.maxFiles > 0 ? Math.min(data.maxFiles, MAX_FILES_UPPER_BOUND) : MAX_FILES_DEFAULT;
12447
12567
  let files = [];
12448
- if (stat.isDirectory()) files = await findJsonlFiles(abs, data.maxFiles || 200);
12449
- else if (stat.isFile() && abs.endsWith(".jsonl")) files = [abs];
12450
- else return {
12568
+ let truncated = false;
12569
+ let discovered = 0;
12570
+ let traversalCapped = false;
12571
+ if (stat.isDirectory()) {
12572
+ const found = await findJsonlFiles(abs, maxFiles);
12573
+ files = found.files;
12574
+ truncated = found.truncated;
12575
+ discovered = found.discovered;
12576
+ traversalCapped = found.traversalCapped;
12577
+ } else if (stat.isFile() && abs.endsWith(".jsonl")) {
12578
+ files = [abs];
12579
+ discovered = 1;
12580
+ } else return {
12451
12581
  success: false,
12452
12582
  error: "path must be a .jsonl file or directory"
12453
12583
  };
@@ -12455,7 +12585,12 @@ function registerReplayFunctions(sdk, kv) {
12455
12585
  success: true,
12456
12586
  imported: 0,
12457
12587
  sessionIds: [],
12458
- observations: 0
12588
+ observations: 0,
12589
+ discovered,
12590
+ truncated,
12591
+ traversalCapped,
12592
+ maxFiles,
12593
+ maxFilesUpperBound: MAX_FILES_UPPER_BOUND
12459
12594
  };
12460
12595
  const sessionIds = [];
12461
12596
  let observationCount = 0;
@@ -12521,7 +12656,12 @@ function registerReplayFunctions(sdk, kv) {
12521
12656
  success: true,
12522
12657
  imported: files.length,
12523
12658
  sessionIds,
12524
- observations: observationCount
12659
+ observations: observationCount,
12660
+ discovered,
12661
+ truncated,
12662
+ traversalCapped,
12663
+ maxFiles,
12664
+ maxFilesUpperBound: MAX_FILES_UPPER_BOUND
12525
12665
  };
12526
12666
  });
12527
12667
  }
@@ -12824,12 +12964,11 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
12824
12964
  sdk.registerFunction("api::config-flags", async (req) => {
12825
12965
  const authErr = checkAuth(req, secret);
12826
12966
  if (authErr) return authErr;
12827
- const env = process.env;
12828
12967
  return {
12829
12968
  status_code: 200,
12830
12969
  body: {
12831
12970
  version: VERSION,
12832
- provider: env["ANTHROPIC_API_KEY"] || env["GEMINI_API_KEY"] || env["OPENROUTER_API_KEY"] || env["MINIMAX_API_KEY"] ? "llm" : "noop",
12971
+ provider: detectLlmProviderKind(),
12833
12972
  embeddingProvider: detectEmbeddingProvider() ? "embeddings" : "none",
12834
12973
  flags: [
12835
12974
  {
@@ -13127,11 +13266,12 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13127
13266
  payload.path = body.path.trim();
13128
13267
  }
13129
13268
  if (body.maxFiles !== void 0) {
13130
- if (!Number.isInteger(body.maxFiles) || body.maxFiles < 1) return {
13269
+ const n = body.maxFiles;
13270
+ if (!Number.isInteger(n) || n < 1 || n > MAX_FILES_UPPER_BOUND) return {
13131
13271
  status_code: 400,
13132
- body: { error: "maxFiles must be a positive integer" }
13272
+ body: { error: `maxFiles must be an integer between 1 and ${MAX_FILES_UPPER_BOUND}` }
13133
13273
  };
13134
- payload.maxFiles = body.maxFiles;
13274
+ payload.maxFiles = n;
13135
13275
  }
13136
13276
  return {
13137
13277
  status_code: 202,
@@ -14047,6 +14187,32 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14047
14187
  http_method: "GET"
14048
14188
  }
14049
14189
  });
14190
+ sdk.registerFunction("api::memory-by-id", async (req) => {
14191
+ const authErr = checkAuth(req, secret);
14192
+ if (authErr) return authErr;
14193
+ const id = req.path_params?.["id"];
14194
+ if (!id || typeof id !== "string") return {
14195
+ status_code: 400,
14196
+ body: { error: "id path parameter is required" }
14197
+ };
14198
+ const memory = await kv.get(KV.memories, id);
14199
+ if (!memory) return {
14200
+ status_code: 404,
14201
+ body: { error: `memory not found: ${id}` }
14202
+ };
14203
+ return {
14204
+ status_code: 200,
14205
+ body: { memory }
14206
+ };
14207
+ });
14208
+ sdk.registerTrigger({
14209
+ type: "http",
14210
+ function_id: "api::memory-by-id",
14211
+ config: {
14212
+ api_path: "/agentmemory/memories/:id",
14213
+ http_method: "GET"
14214
+ }
14215
+ });
14050
14216
  sdk.registerFunction("api::semantic-list", async (req) => {
14051
14217
  const authErr = checkAuth(req, secret);
14052
14218
  if (authErr) return authErr;
@@ -15925,6 +16091,15 @@ function registerEventTriggers(sdk, kv) {
15925
16091
  error: err instanceof Error ? err.message : String(err)
15926
16092
  });
15927
16093
  }
16094
+ if (isGraphExtractionEnabled()) try {
16095
+ const compressed = (await kv.list(KV.observations(data.sessionId))).filter((o) => o.title);
16096
+ if (compressed.length > 0) sdk.triggerVoid("mem::graph-extract", { observations: compressed });
16097
+ } catch (err) {
16098
+ logger.warn("graph-extract triggerVoid failed", {
16099
+ sessionId: data.sessionId,
16100
+ error: err instanceof Error ? err.message : String(err)
16101
+ });
16102
+ }
15928
16103
  return summary;
15929
16104
  });
15930
16105
  sdk.registerTrigger({
@@ -17836,6 +18011,14 @@ function initMetrics(getMeter) {
17836
18011
  function hasGetMeter(sdk) {
17837
18012
  return typeof sdk === "object" && sdk !== null && "getMeter" in sdk && typeof sdk.getMeter === "function";
17838
18013
  }
18014
+ let lastUnhandledLogAt = 0;
18015
+ process.on("unhandledRejection", (reason) => {
18016
+ const now = Date.now();
18017
+ if (now - lastUnhandledLogAt < 6e4) return;
18018
+ lastUnhandledLogAt = now;
18019
+ const r = reason;
18020
+ console.warn(`[agentmemory] unhandledRejection (suppressed):`, r?.code ? `${r.code} ${r.function_id ?? ""} ${r.message ?? ""}`.trim() : reason);
18021
+ });
17839
18022
  async function main() {
17840
18023
  const config = loadConfig();
17841
18024
  const embeddingConfig = loadEmbeddingConfig();
@@ -17853,6 +18036,7 @@ async function main() {
17853
18036
  console.log(`[agentmemory] Streams: ws://localhost:${config.streamsPort}`);
17854
18037
  const sdk = registerWorker(config.engineUrl, {
17855
18038
  workerName: "agentmemory",
18039
+ invocationTimeoutMs: 18e4,
17856
18040
  otel: {
17857
18041
  serviceName: OTEL_CONFIG.serviceName,
17858
18042
  serviceVersion: OTEL_CONFIG.serviceVersion,
@@ -17961,8 +18145,20 @@ async function main() {
17961
18145
  console.log(`[agentmemory] Loaded persisted BM25 index (${bm25Index.size} docs)`);
17962
18146
  }
17963
18147
  if (loaded?.vector && vectorIndex && loaded.vector.size > 0) {
17964
- vectorIndex.restoreFrom(loaded.vector);
17965
- console.log(`[agentmemory] Loaded persisted vector index (${vectorIndex.size} vectors)`);
18148
+ const activeDim = embeddingProvider?.dimensions ?? 0;
18149
+ const { mismatches, seenDimensions } = activeDim > 0 ? loaded.vector.validateDimensions(activeDim) : {
18150
+ mismatches: [],
18151
+ seenDimensions: /* @__PURE__ */ new Set()
18152
+ };
18153
+ if (mismatches.length > 0) {
18154
+ const sample = mismatches.slice(0, 5).map((m) => `${m.obsId} (dim=${m.dim})`).join(", ");
18155
+ const distinct = Array.from(seenDimensions).sort((a, b) => a - b).join(", ");
18156
+ 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.`);
18157
+ 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.`);
18158
+ } else {
18159
+ vectorIndex.restoreFrom(loaded.vector);
18160
+ console.log(`[agentmemory] Loaded persisted vector index (${vectorIndex.size} vectors)`);
18161
+ }
17966
18162
  }
17967
18163
  if (bm25Index.size === 0) {
17968
18164
  const indexCount = await rebuildIndex(kv).catch((err) => {
@@ -17970,9 +18166,36 @@ async function main() {
17970
18166
  return 0;
17971
18167
  });
17972
18168
  if (indexCount > 0) {
17973
- console.log(`[agentmemory] Search index rebuilt: ${indexCount} observations`);
18169
+ console.log(`[agentmemory] Search index rebuilt: ${indexCount} entries`);
17974
18170
  indexPersistence.scheduleSave();
17975
18171
  }
18172
+ } else try {
18173
+ const memories = await kv.list(KV.memories);
18174
+ let backfilled = 0;
18175
+ for (const memory of memories) {
18176
+ if (memory.isLatest === false) continue;
18177
+ if (!memory.title || !memory.content) continue;
18178
+ if (bm25Index.has(memory.id)) continue;
18179
+ bm25Index.add({
18180
+ id: memory.id,
18181
+ sessionId: memory.sessionIds[0] ?? "memory",
18182
+ timestamp: memory.createdAt,
18183
+ type: "decision",
18184
+ title: memory.title,
18185
+ facts: [memory.content],
18186
+ narrative: memory.content,
18187
+ concepts: memory.concepts,
18188
+ files: memory.files,
18189
+ importance: memory.strength
18190
+ });
18191
+ backfilled++;
18192
+ }
18193
+ if (backfilled > 0) {
18194
+ console.log(`[agentmemory] Backfilled ${backfilled} memories into BM25 (legacy gap before #257)`);
18195
+ indexPersistence.scheduleSave();
18196
+ }
18197
+ } catch (err) {
18198
+ console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
17976
18199
  }
17977
18200
  console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
17978
18201
  console.log(`[agentmemory] Endpoints: 107 REST + ${getAllTools().length} MCP tools + 6 MCP resources + 3 MCP prompts`);
@@ -18042,4 +18265,4 @@ main().catch((err) => {
18042
18265
 
18043
18266
  //#endregion
18044
18267
  export { deleteImage as a, saveImageToDisk as c, IMAGES_DIR as i, touchImage as l, getImageRefCount as n, getMaxBytes as o, incrementImageRef as r, isManagedImagePath as s, decrementImageRef as t };
18045
- //# sourceMappingURL=src-3Snd7D3T.mjs.map
18268
+ //# sourceMappingURL=src-xYHSzz5S.mjs.map