@agentmemory/agentmemory 0.9.22 → 0.9.24

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.
Files changed (84) hide show
  1. package/AGENTS.md +7 -2
  2. package/README.md +144 -32
  3. package/dist/cli.d.mts.map +1 -1
  4. package/dist/cli.mjs +42 -25
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{connect-BQQXpyDS.mjs → connect-Cf9bmBqO.mjs} +290 -33
  7. package/dist/connect-Cf9bmBqO.mjs.map +1 -0
  8. package/dist/hooks/notification.mjs +46 -21
  9. package/dist/hooks/notification.mjs.map +1 -1
  10. package/dist/hooks/post-tool-failure.mjs +47 -21
  11. package/dist/hooks/post-tool-failure.mjs.map +1 -1
  12. package/dist/hooks/post-tool-use.mjs +57 -22
  13. package/dist/hooks/post-tool-use.mjs.map +1 -1
  14. package/dist/hooks/pre-compact.mjs +26 -2
  15. package/dist/hooks/pre-compact.mjs.map +1 -1
  16. package/dist/hooks/pre-tool-use.mjs +19 -12
  17. package/dist/hooks/pre-tool-use.mjs.map +1 -1
  18. package/dist/hooks/prompt-submit.mjs +39 -16
  19. package/dist/hooks/prompt-submit.mjs.map +1 -1
  20. package/dist/hooks/session-end.mjs +26 -33
  21. package/dist/hooks/session-end.mjs.map +1 -1
  22. package/dist/hooks/session-start.mjs +28 -3
  23. package/dist/hooks/session-start.mjs.map +1 -1
  24. package/dist/hooks/stop.mjs +14 -17
  25. package/dist/hooks/stop.mjs.map +1 -1
  26. package/dist/hooks/subagent-start.mjs +31 -4
  27. package/dist/hooks/subagent-start.mjs.map +1 -1
  28. package/dist/hooks/subagent-stop.mjs +45 -20
  29. package/dist/hooks/subagent-stop.mjs.map +1 -1
  30. package/dist/hooks/task-completed.mjs +44 -21
  31. package/dist/hooks/task-completed.mjs.map +1 -1
  32. package/dist/iii-config.docker.yaml +3 -2
  33. package/dist/iii-config.yaml +11 -2
  34. package/dist/index.mjs +336 -57
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/{src-gpTAJuBy.mjs → src-B8J9Exum.mjs} +323 -58
  37. package/dist/src-B8J9Exum.mjs.map +1 -0
  38. package/dist/{standalone-C4i7ktpn.mjs → standalone-CPfsVTBA.mjs} +92 -11
  39. package/dist/standalone-CPfsVTBA.mjs.map +1 -0
  40. package/dist/standalone.mjs +94 -9
  41. package/dist/standalone.mjs.map +1 -1
  42. package/dist/{tools-registry-B7Y6nJsr.mjs → tools-registry-DJizX9Az.mjs} +16 -2
  43. package/dist/tools-registry-DJizX9Az.mjs.map +1 -0
  44. package/dist/version-BWEBnKAp.mjs +6 -0
  45. package/dist/version-BWEBnKAp.mjs.map +1 -0
  46. package/dist/viewer/index.html +9 -2
  47. package/iii-config.docker.yaml +3 -2
  48. package/iii-config.yaml +11 -2
  49. package/package.json +1 -1
  50. package/plugin/.claude-plugin/plugin.json +2 -2
  51. package/plugin/.codex-plugin/plugin.json +2 -2
  52. package/plugin/.mcp.copilot.json +15 -0
  53. package/plugin/hooks/hooks.copilot.json +72 -0
  54. package/plugin/plugin.json +15 -0
  55. package/plugin/scripts/notification.mjs +46 -21
  56. package/plugin/scripts/notification.mjs.map +1 -1
  57. package/plugin/scripts/post-tool-failure.mjs +47 -21
  58. package/plugin/scripts/post-tool-failure.mjs.map +1 -1
  59. package/plugin/scripts/post-tool-use.mjs +57 -22
  60. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  61. package/plugin/scripts/pre-compact.mjs +26 -2
  62. package/plugin/scripts/pre-compact.mjs.map +1 -1
  63. package/plugin/scripts/pre-tool-use.mjs +19 -12
  64. package/plugin/scripts/pre-tool-use.mjs.map +1 -1
  65. package/plugin/scripts/prompt-submit.mjs +39 -16
  66. package/plugin/scripts/prompt-submit.mjs.map +1 -1
  67. package/plugin/scripts/session-end.mjs +26 -33
  68. package/plugin/scripts/session-end.mjs.map +1 -1
  69. package/plugin/scripts/session-start.mjs +28 -3
  70. package/plugin/scripts/session-start.mjs.map +1 -1
  71. package/plugin/scripts/stop.mjs +14 -17
  72. package/plugin/scripts/stop.mjs.map +1 -1
  73. package/plugin/scripts/subagent-start.mjs +31 -4
  74. package/plugin/scripts/subagent-start.mjs.map +1 -1
  75. package/plugin/scripts/subagent-stop.mjs +45 -20
  76. package/plugin/scripts/subagent-stop.mjs.map +1 -1
  77. package/plugin/scripts/task-completed.mjs +44 -21
  78. package/plugin/scripts/task-completed.mjs.map +1 -1
  79. package/dist/connect-BQQXpyDS.mjs.map +0 -1
  80. package/dist/src-gpTAJuBy.mjs.map +0 -1
  81. package/dist/standalone-C4i7ktpn.mjs.map +0 -1
  82. package/dist/tools-registry-B7Y6nJsr.mjs.map +0 -1
  83. package/dist/version-DvQMNbEH.mjs +0 -6
  84. package/dist/version-DvQMNbEH.mjs.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./schema-BkALl7Z_.mjs";
2
2
  import { n as logger, t as bootLog } from "./logger-xlVlvCWX.mjs";
3
- import { t as VERSION } from "./version-DvQMNbEH.mjs";
3
+ import { t as VERSION } from "./version-BWEBnKAp.mjs";
4
4
  import { a as isManagedImagePath, getImageRefCount, i as getMaxBytes, n as IMAGES_DIR, r as deleteImage, t as withKeyedLock } from "./image-refs-CJS5B9Gq.mjs";
5
- import { _ as loadEmbeddingConfig, a as getAgentId, b as loadTeamConfig, d as isConsolidationEnabled, f as isContextInjectionEnabled, g as loadConfig, h as loadClaudeBridgeConfig, i as detectLlmProviderKind, l as isAgentScopeIsolated, m as isGraphExtractionEnabled, n as getVisibleTools, o as getConsolidationDecayDays, p as isDropStaleIndexEnabled, r as detectEmbeddingProvider, s as getEnvVar, t as getAllTools, u as isAutoCompressEnabled, v as loadFallbackConfig, y as loadSnapshotConfig } from "./tools-registry-B7Y6nJsr.mjs";
5
+ import { _ as loadEmbeddingConfig, a as getAgentId, b as loadTeamConfig, d as isConsolidationEnabled, f as isContextInjectionEnabled, g as loadConfig, h as loadClaudeBridgeConfig, i as detectLlmProviderKind, l as isAgentScopeIsolated, m as isGraphExtractionEnabled, n as getVisibleTools, o as getConsolidationDecayDays, p as isDropStaleIndexEnabled, r as detectEmbeddingProvider, s as getEnvVar, t as getAllTools, u as isAutoCompressEnabled, v as loadFallbackConfig, y as loadSnapshotConfig } from "./tools-registry-DJizX9Az.mjs";
6
6
  import { createRequire } from "node:module";
7
7
  import { execFile } from "node:child_process";
8
8
  import { constants, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
@@ -639,16 +639,30 @@ function resolveDimensions(model, override) {
639
639
  * `api-key` header instead of `Authorization: Bearer`.
640
640
  *
641
641
  * Required env vars:
642
- * OPENAI_API_KEY — API key
642
+ * OPENAI_API_KEY — API key (fallback for OPENAI_EMBEDDING_API_KEY)
643
643
  *
644
644
  * Optional:
645
- * OPENAI_BASE_URL — base URL without path (default: https://api.openai.com).
646
- * Azure: https://<resource>.openai.azure.com/openai/deployments/<deployment>
647
- * OPENAI_API_VERSION Azure api-version query param (default: 2024-08-01-preview)
648
- * OPENAI_EMBEDDING_MODEL — model name (default: text-embedding-3-small)
649
- * OPENAI_EMBEDDING_DIMENSIONS override reported dimensions (required for
650
- * custom / self-hosted models not in the
651
- * MODEL_DIMENSIONS table above)
645
+ * OPENAI_BASE_URL — base URL without path (default: https://api.openai.com).
646
+ * Azure: https://<resource>.openai.azure.com/openai/deployments/<deployment>
647
+ * OPENAI_EMBEDDING_BASE_URL embedding-specific base URL override (defaults
648
+ * to OPENAI_BASE_URL). Lets operators run
649
+ * embeddings on a separate endpoint from chat —
650
+ * e.g. local Ollama / LM Studio / llama.cpp /
651
+ * vLLM at http://localhost:1234 for unlimited
652
+ * free embeddings, while keeping chat
653
+ * completions on a rate-limited but high-quality
654
+ * hosted provider. Azure detection runs on
655
+ * whichever URL ends up selected.
656
+ * OPENAI_EMBEDDING_API_KEY — separate API key for the embedding endpoint
657
+ * (defaults to OPENAI_API_KEY). Useful when the
658
+ * embedding endpoint requires a different key
659
+ * or no key at all (set to e.g. "local" for
660
+ * endpoints that ignore Authorization).
661
+ * OPENAI_API_VERSION — Azure api-version query param (default: 2024-08-01-preview)
662
+ * OPENAI_EMBEDDING_MODEL — model name (default: text-embedding-3-small)
663
+ * OPENAI_EMBEDDING_DIMENSIONS — override reported dimensions (required for
664
+ * custom / self-hosted models not in the
665
+ * MODEL_DIMENSIONS table above)
652
666
  */
653
667
  var OpenAIEmbeddingProvider = class {
654
668
  name = "openai";
@@ -659,9 +673,9 @@ var OpenAIEmbeddingProvider = class {
659
673
  isAzure;
660
674
  azureApiVersion;
661
675
  constructor(apiKey) {
662
- this.apiKey = apiKey || getEnvVar("OPENAI_API_KEY") || "";
663
- if (!this.apiKey) throw new Error("OPENAI_API_KEY is required");
664
- this.baseUrl = normalizeBaseUrl(getEnvVar("OPENAI_BASE_URL"));
676
+ this.apiKey = apiKey || getEnvVar("OPENAI_EMBEDDING_API_KEY") || getEnvVar("OPENAI_API_KEY") || "";
677
+ if (!this.apiKey) throw new Error("API key is required (via constructor, OPENAI_EMBEDDING_API_KEY, or OPENAI_API_KEY)");
678
+ this.baseUrl = normalizeBaseUrl(getEnvVar("OPENAI_EMBEDDING_BASE_URL") || getEnvVar("OPENAI_BASE_URL"));
665
679
  this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
666
680
  this.dimensions = resolveDimensions(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
667
681
  this.isAzure = detectAzure(this.baseUrl);
@@ -1038,10 +1052,11 @@ var StateKV = class {
1038
1052
  //#endregion
1039
1053
  //#region src/state/vector-index.ts
1040
1054
  function float32ToBase64(arr) {
1041
- return Buffer.from(arr.buffer).toString("base64");
1055
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
1042
1056
  }
1043
1057
  function base64ToFloat32(b64) {
1044
- return new Float32Array(Buffer.from(b64, "base64").buffer);
1058
+ const buf = Buffer.from(b64, "base64");
1059
+ return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / Float32Array.BYTES_PER_ELEMENT);
1045
1060
  }
1046
1061
  function cosineSimilarity(a, b) {
1047
1062
  if (a.length !== b.length) return 0;
@@ -1162,7 +1177,7 @@ var VectorIndex = class VectorIndex {
1162
1177
  function memoryToObservation(memory) {
1163
1178
  return {
1164
1179
  id: memory.id,
1165
- sessionId: memory.sessionIds[0] ?? "memory",
1180
+ sessionId: memory.sessionIds?.[0] ?? "memory",
1166
1181
  timestamp: memory.createdAt,
1167
1182
  type: "decision",
1168
1183
  title: memory.title,
@@ -2783,7 +2798,7 @@ async function rebuildIndex(kv) {
2783
2798
  idx.add(memoryToObservation(memory));
2784
2799
  await enqueue({
2785
2800
  id: memory.id,
2786
- sessionId: memory.sessionIds[0] ?? "memory",
2801
+ sessionId: memory.sessionIds?.[0] ?? "memory",
2787
2802
  text: memory.title + " " + memory.content,
2788
2803
  context: {
2789
2804
  kind: "memory",
@@ -2842,8 +2857,8 @@ function registerSearchFunction(sdk, kv) {
2842
2857
  if (!Number.isInteger(data.limit) || data.limit < 1) throw new Error("mem::search: limit must be a positive integer");
2843
2858
  effectiveLimit = Math.min(data.limit, MAX_LIMIT);
2844
2859
  }
2845
- const projectFilter = typeof data.project === "string" && data.project.length > 0 ? data.project : void 0;
2846
- const cwdFilter = typeof data.cwd === "string" && data.cwd.length > 0 ? data.cwd : void 0;
2860
+ const projectFilter = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
2861
+ const cwdFilter = typeof data.cwd === "string" && data.cwd.trim().length > 0 ? data.cwd.trim() : void 0;
2847
2862
  const format = typeof data.format === "string" ? data.format : "full";
2848
2863
  if (![
2849
2864
  "full",
@@ -2869,14 +2884,25 @@ function registerSearchFunction(sdk, kv) {
2869
2884
  sessionCache.set(sessionId, s ?? null);
2870
2885
  return s ?? null;
2871
2886
  };
2887
+ const memoryProjectCache = /* @__PURE__ */ new Map();
2888
+ const loadMemoryProject = async (obsId) => {
2889
+ if (memoryProjectCache.has(obsId)) return memoryProjectCache.get(obsId);
2890
+ const proj = (await kv.get(KV.memories, obsId).catch(() => null))?.project ?? null;
2891
+ memoryProjectCache.set(obsId, proj);
2892
+ return proj;
2893
+ };
2872
2894
  const candidates = [];
2873
2895
  for (const r of results) {
2874
2896
  if (candidates.length >= effectiveLimit) break;
2875
2897
  if (filtering) {
2876
2898
  const s = await loadSession(r.sessionId);
2877
- if (!s) continue;
2878
- if (projectFilter && s.project !== projectFilter) continue;
2879
- if (cwdFilter && s.cwd !== cwdFilter) continue;
2899
+ if (s) {
2900
+ if (projectFilter && s.project !== projectFilter) continue;
2901
+ if (cwdFilter && s.cwd !== cwdFilter) continue;
2902
+ } else if (projectFilter) {
2903
+ const memProject = await loadMemoryProject(r.obsId);
2904
+ if (memProject !== null && memProject !== projectFilter) continue;
2905
+ }
2880
2906
  }
2881
2907
  candidates.push(r);
2882
2908
  }
@@ -3521,10 +3547,10 @@ const DEFAULT_SLOTS = [
3521
3547
  }
3522
3548
  ];
3523
3549
  function isSlotsEnabled() {
3524
- return process.env["AGENTMEMORY_SLOTS"] === "true";
3550
+ return getEnvVar("AGENTMEMORY_SLOTS") === "true";
3525
3551
  }
3526
3552
  function isReflectEnabled() {
3527
- return process.env["AGENTMEMORY_REFLECT"] === "true";
3553
+ return getEnvVar("AGENTMEMORY_REFLECT") === "true";
3528
3554
  }
3529
3555
  function scopeKv(scope) {
3530
3556
  return scope === "global" ? KV.globalSlots : KV.slots;
@@ -4789,8 +4815,78 @@ function isAllowedPath(dbPath) {
4789
4815
  const resolved = resolve(dbPath);
4790
4816
  return ALLOWED_DIRS.some((dir) => resolved.startsWith(dir + "/"));
4791
4817
  }
4818
+ async function inferMemoryProjects(kv, dryRun = false) {
4819
+ const memories = await kv.list(KV.memories);
4820
+ const sessionCache = /* @__PURE__ */ new Map();
4821
+ const loadSession = async (sid) => {
4822
+ if (sessionCache.has(sid)) return sessionCache.get(sid);
4823
+ const s = await kv.get(KV.sessions, sid).catch(() => null);
4824
+ sessionCache.set(sid, s);
4825
+ return s;
4826
+ };
4827
+ let updated = 0;
4828
+ let skipped = 0;
4829
+ let ambiguous = 0;
4830
+ for (const memory of memories) {
4831
+ if (memory.project) {
4832
+ skipped++;
4833
+ continue;
4834
+ }
4835
+ const sessionIds = memory.sessionIds ?? [];
4836
+ if (sessionIds.length === 0) {
4837
+ ambiguous++;
4838
+ continue;
4839
+ }
4840
+ const projects = [];
4841
+ for (const sid of sessionIds) {
4842
+ const session = await loadSession(sid);
4843
+ if (session?.project) projects.push(session.project);
4844
+ }
4845
+ if (projects.length === 0) {
4846
+ ambiguous++;
4847
+ continue;
4848
+ }
4849
+ const freq = /* @__PURE__ */ new Map();
4850
+ for (const p of projects) freq.set(p, (freq.get(p) ?? 0) + 1);
4851
+ const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1]);
4852
+ const [topProject, topCount] = sorted[0];
4853
+ if (topCount <= projects.length / 2 && sorted.length > 1) {
4854
+ ambiguous++;
4855
+ continue;
4856
+ }
4857
+ if (!dryRun) {
4858
+ memory.project = topProject;
4859
+ await kv.set(KV.memories, memory.id, memory);
4860
+ }
4861
+ updated++;
4862
+ }
4863
+ logger.info("inferMemoryProjects complete", {
4864
+ updated,
4865
+ skipped,
4866
+ ambiguous,
4867
+ dryRun
4868
+ });
4869
+ return {
4870
+ updated,
4871
+ skipped,
4872
+ ambiguous
4873
+ };
4874
+ }
4792
4875
  function registerMigrateFunction(sdk, kv) {
4793
4876
  sdk.registerFunction("mem::migrate", async (data) => {
4877
+ if (data.step === "infer-memory-projects") {
4878
+ const dryRun = data.dryRun ?? false;
4879
+ logger.info("Migration step: infer-memory-projects", { dryRun });
4880
+ return {
4881
+ success: true,
4882
+ step: "infer-memory-projects",
4883
+ ...await inferMemoryProjects(kv, dryRun)
4884
+ };
4885
+ }
4886
+ if (!data.dbPath) return {
4887
+ success: false,
4888
+ error: "Either step or dbPath is required"
4889
+ };
4794
4890
  logger.info("Migration started", { dbPath: data.dbPath });
4795
4891
  if (!isAllowedPath(data.dbPath)) return {
4796
4892
  success: false,
@@ -5066,9 +5162,10 @@ function registerConsolidateFunction(sdk, kv, provider) {
5066
5162
  llmCallCount++;
5067
5163
  const parsed = parseMemoryXml(response, sessionIds);
5068
5164
  if (!parsed) continue;
5069
- const existingMatch = existingMemories.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase());
5070
5165
  const now = (/* @__PURE__ */ new Date()).toISOString();
5071
5166
  const obsIds = [...new Set(top.map((o) => o.id))];
5167
+ const scopedProject = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
5168
+ const existingMatch = existingMemories.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase() && (!scopedProject || !m.project || m.project === scopedProject));
5072
5169
  if (existingMatch) {
5073
5170
  existingMatch.isLatest = false;
5074
5171
  await kv.set(KV.memories, existingMatch.id, existingMatch);
@@ -5085,7 +5182,8 @@ function registerConsolidateFunction(sdk, kv, provider) {
5085
5182
  parentId: existingMatch.id,
5086
5183
  supersedes: [existingMatch.id, ...existingMatch.supersedes || []],
5087
5184
  sourceObservationIds: obsIds,
5088
- isLatest: true
5185
+ isLatest: true,
5186
+ ...scopedProject !== void 0 && { project: scopedProject }
5089
5187
  };
5090
5188
  await kv.set(KV.memories, evolved.id, evolved);
5091
5189
  await recordAudit(kv, "evolve", "mem::consolidate", [evolved.id], {
@@ -5104,7 +5202,8 @@ function registerConsolidateFunction(sdk, kv, provider) {
5104
5202
  ...parsed,
5105
5203
  sourceObservationIds: obsIds,
5106
5204
  version: 1,
5107
- isLatest: true
5205
+ isLatest: true,
5206
+ ...scopedProject !== void 0 && { project: scopedProject }
5108
5207
  };
5109
5208
  await kv.set(KV.memories, memory.id, memory);
5110
5209
  await recordAudit(kv, "remember", "mem::consolidate", [memory.id], {
@@ -5245,6 +5344,7 @@ function registerRememberFunction(sdk, kv) {
5245
5344
  "fact"
5246
5345
  ]).has(data.type || "") ? data.type : "fact";
5247
5346
  const now = (/* @__PURE__ */ new Date()).toISOString();
5347
+ const project = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
5248
5348
  return withKeyedLock("mem:remember", async () => {
5249
5349
  const existingMemories = await kv.list(KV.memories);
5250
5350
  let supersededId;
@@ -5253,6 +5353,7 @@ function registerRememberFunction(sdk, kv) {
5253
5353
  const lowerContent = data.content.toLowerCase();
5254
5354
  for (const existing of existingMemories) {
5255
5355
  if (existing.isLatest === false) continue;
5356
+ if (project && existing.project && existing.project !== project) continue;
5256
5357
  if (jaccardSimilarity(lowerContent, existing.content.toLowerCase()) > .7) {
5257
5358
  supersededId = existing.id;
5258
5359
  supersededVersion = existing.version ?? 1;
@@ -5277,7 +5378,8 @@ function registerRememberFunction(sdk, kv) {
5277
5378
  supersedes: supersededId ? [supersededId] : [],
5278
5379
  sourceObservationIds: (data.sourceObservationIds || []).filter((id) => typeof id === "string" && id.length > 0),
5279
5380
  isLatest: true,
5280
- ...callAgentId ? { agentId: callAgentId } : {}
5381
+ ...callAgentId ? { agentId: callAgentId } : {},
5382
+ ...project !== void 0 && { project }
5281
5383
  };
5282
5384
  if (data.ttlDays && typeof data.ttlDays === "number" && data.ttlDays > 0) memory.forgetAfter = new Date(Date.now() + data.ttlDays * 864e5).toISOString();
5283
5385
  if (supersededMemory) {
@@ -5293,7 +5395,7 @@ function registerRememberFunction(sdk, kv) {
5293
5395
  error: err instanceof Error ? err.message : String(err)
5294
5396
  });
5295
5397
  }
5296
- await vectorIndexAddGuarded(memory.id, memory.sessionIds[0] ?? "memory", memory.title + " " + memory.content, {
5398
+ await vectorIndexAddGuarded(memory.id, memory.sessionIds?.[0] ?? "memory", memory.title + " " + memory.content, {
5297
5399
  kind: "memory",
5298
5400
  logId: memory.id
5299
5401
  });
@@ -5304,7 +5406,8 @@ function registerRememberFunction(sdk, kv) {
5304
5406
  });
5305
5407
  logger.info("Memory saved", {
5306
5408
  memId: memory.id,
5307
- type: memory.type
5409
+ type: memory.type,
5410
+ project: memory.project
5308
5411
  });
5309
5412
  return {
5310
5413
  success: true,
@@ -6341,7 +6444,9 @@ function registerExportImportFunction(sdk, kv) {
6341
6444
  "0.9.19",
6342
6445
  "0.9.20",
6343
6446
  "0.9.21",
6344
- "0.9.22"
6447
+ "0.9.22",
6448
+ "0.9.23",
6449
+ "0.9.24"
6345
6450
  ]).has(importData.version)) return {
6346
6451
  success: false,
6347
6452
  error: `Unsupported export version: ${importData.version}`
@@ -6464,6 +6569,7 @@ function registerExportImportFunction(sdk, kv) {
6464
6569
  continue;
6465
6570
  }
6466
6571
  }
6572
+ if (!Array.isArray(memory.sessionIds)) memory.sessionIds = [];
6467
6573
  await kv.set(KV.memories, memory.id, memory);
6468
6574
  stats.memories++;
6469
6575
  }
@@ -6667,6 +6773,7 @@ function escapeXml(s) {
6667
6773
  }
6668
6774
  function registerEnrichFunction(sdk, kv) {
6669
6775
  sdk.registerFunction("mem::enrich", async (data) => {
6776
+ const project = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
6670
6777
  const parts = [];
6671
6778
  const fileContextPromise = sdk.trigger({
6672
6779
  function_id: "mem::file-context",
@@ -6680,10 +6787,11 @@ function registerEnrichFunction(sdk, kv) {
6680
6787
  function_id: "mem::search",
6681
6788
  payload: {
6682
6789
  query: searchQueries.join(" "),
6683
- limit: 5
6790
+ limit: 5,
6791
+ ...project !== void 0 && { project }
6684
6792
  }
6685
6793
  }).catch(() => ({ results: [] })) : Promise.resolve({ results: [] });
6686
- const bugMemoriesPromise = kv.list(KV.memories).then((memories) => memories.filter((m) => m.type === "bug" && m.isLatest && m.files.some((f) => data.files.some((df) => f.includes(df) || df.includes(f)))).sort((a, b) => new Date(b.updatedAt || b.createdAt).getTime() - new Date(a.updatedAt || a.createdAt).getTime())).catch(() => []);
6794
+ const bugMemoriesPromise = kv.list(KV.memories).then((memories) => memories.filter((m) => m.type === "bug" && m.isLatest && (!project || !m.project || m.project === project) && m.files.some((f) => data.files.some((df) => f.includes(df) || df.includes(f)))).sort((a, b) => new Date(b.updatedAt || b.createdAt).getTime() - new Date(a.updatedAt || a.createdAt).getTime())).catch(() => []);
6687
6795
  const [fileContext, searchResult, bugMemories] = await Promise.all([
6688
6796
  fileContextPromise,
6689
6797
  searchPromise,
@@ -6706,6 +6814,7 @@ function registerEnrichFunction(sdk, kv) {
6706
6814
  }
6707
6815
  logger.info("Enrichment completed", {
6708
6816
  sessionId: data.sessionId,
6817
+ project,
6709
6818
  fileCount: data.files.length,
6710
6819
  contextLength: context.length,
6711
6820
  truncated
@@ -6863,16 +6972,24 @@ function buildGraphExtractionPrompt(observations) {
6863
6972
 
6864
6973
  //#endregion
6865
6974
  //#region src/functions/graph.ts
6975
+ function parseAttrs(raw) {
6976
+ const attrs = {};
6977
+ const attrRegex = /([A-Za-z_][\w:-]*)="([^"]*)"/g;
6978
+ let m;
6979
+ while ((m = attrRegex.exec(raw)) !== null) attrs[m[1]] = m[2];
6980
+ return attrs;
6981
+ }
6866
6982
  function parseGraphXml(xml, observationIds) {
6867
6983
  const nodes = [];
6868
6984
  const edges = [];
6869
6985
  const now = (/* @__PURE__ */ new Date()).toISOString();
6870
- const entityRegex = /<entity\s+type="([^"]+)"\s+name="([^"]+)"[^>]*?(?:\/>|>([\s\S]*?)<\/entity>)/g;
6871
- let match;
6872
- while ((match = entityRegex.exec(xml)) !== null) {
6873
- const type = match[1];
6874
- const name = match[2];
6875
- const propsBlock = match[3] ?? "";
6986
+ const entitySelfClose = /<entity\b([^>]*?)\/>/g;
6987
+ const entityWithBody = /<entity\b([^>]*[^/])>([\s\S]*?)<\/entity>/g;
6988
+ const addEntity = (rawAttrs, propsBlock = "") => {
6989
+ const attrs = parseAttrs(rawAttrs);
6990
+ const type = attrs["type"];
6991
+ const name = attrs["name"];
6992
+ if (!type || !name) return;
6876
6993
  const properties = {};
6877
6994
  const propRegex = /<property\s+key="([^"]+)">([^<]*)<\/property>/g;
6878
6995
  let propMatch;
@@ -6885,17 +7002,23 @@ function parseGraphXml(xml, observationIds) {
6885
7002
  sourceObservationIds: observationIds,
6886
7003
  createdAt: now
6887
7004
  });
6888
- }
6889
- const relRegex = /<relationship\s+type="([^"]+)"\s+source="([^"]+)"\s+target="([^"]+)"\s+weight="([^"]+)"\s*\/>/g;
7005
+ };
7006
+ let match;
7007
+ while ((match = entitySelfClose.exec(xml)) !== null) addEntity(match[1]);
7008
+ while ((match = entityWithBody.exec(xml)) !== null) addEntity(match[1], match[2]);
7009
+ const relRegex = /<relationship\b([^>]*?)\/>/g;
6890
7010
  while ((match = relRegex.exec(xml)) !== null) {
6891
- const type = match[1];
6892
- const sourceName = match[2];
6893
- const targetName = match[3];
6894
- const parsedWeight = parseFloat(match[4]);
6895
- const weight = Number.isNaN(parsedWeight) ? .5 : parsedWeight;
7011
+ const attrs = parseAttrs(match[1]);
7012
+ const type = attrs["type"];
7013
+ const sourceName = attrs["source"];
7014
+ const targetName = attrs["target"];
7015
+ if (!type || !sourceName || !targetName) continue;
7016
+ const parsedWeight = parseFloat(attrs["weight"] ?? "");
7017
+ const weight = Number.isFinite(parsedWeight) ? parsedWeight : .5;
6896
7018
  const sourceNode = nodes.find((n) => n.name === sourceName);
6897
7019
  const targetNode = nodes.find((n) => n.name === targetName);
6898
- if (sourceNode && targetNode) edges.push({
7020
+ if (!sourceNode || !targetNode) continue;
7021
+ edges.push({
6899
7022
  id: generateId("ge"),
6900
7023
  type,
6901
7024
  sourceNodeId: sourceNode.id,
@@ -7109,7 +7232,7 @@ function registerConsolidationPipelineFunction(sdk, kv, provider) {
7109
7232
  if (!data?.force && !isConsolidationEnabled()) return {
7110
7233
  success: false,
7111
7234
  skipped: true,
7112
- reason: "CONSOLIDATION_ENABLED is not set to true"
7235
+ reason: "Consolidation disabled: set CONSOLIDATION_ENABLED=true or configure an LLM provider (ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY / GEMINI_API_KEY / GOOGLE_API_KEY / MINIMAX_API_KEY / OPENAI_BASE_URL / AGENTMEMORY_PROVIDER=agent-sdk)"
7113
7236
  };
7114
7237
  const tier = data?.tier || "all";
7115
7238
  const decayDays = getConsolidationDecayDays();
@@ -10279,11 +10402,34 @@ function registerDiagnosticsFunction(sdk, kv) {
10279
10402
  });
10280
10403
  memoryIssues++;
10281
10404
  }
10405
+ const latestMemories = memories.filter((m) => m.isLatest);
10406
+ const unscopedCount = latestMemories.filter((m) => !m.project).length;
10407
+ if (unscopedCount === 0) checks.push({
10408
+ name: "memory-project-coverage",
10409
+ category: "memories",
10410
+ status: "pass",
10411
+ message: `All ${latestMemories.length} latest memories have a project scope`,
10412
+ fixable: false
10413
+ });
10414
+ else if (unscopedCount <= 10) checks.push({
10415
+ name: "memory-project-coverage",
10416
+ category: "memories",
10417
+ status: "warn",
10418
+ message: `${unscopedCount} of ${latestMemories.length} latest memories have no project scope — run POST /agentmemory/migrate {"step":"infer-memory-projects"} to backfill`,
10419
+ fixable: true
10420
+ });
10421
+ else checks.push({
10422
+ name: "memory-project-coverage",
10423
+ category: "memories",
10424
+ status: "fail",
10425
+ message: `${unscopedCount} of ${latestMemories.length} latest memories have no project scope — run POST /agentmemory/migrate {"step":"infer-memory-projects"} to backfill`,
10426
+ fixable: true
10427
+ });
10282
10428
  if (memoryIssues === 0) checks.push({
10283
10429
  name: "memories-ok",
10284
10430
  category: "memories",
10285
10431
  status: "pass",
10286
- message: `All ${memories.length} memories are consistent`,
10432
+ message: `All ${memories.length} memories are structurally consistent`,
10287
10433
  fixable: false
10288
10434
  });
10289
10435
  }
@@ -14056,6 +14202,22 @@ function consolidationDisabledResponse() {
14056
14202
  docsHref: "https://github.com/rohitg00/agentmemory#consolidation"
14057
14203
  });
14058
14204
  }
14205
+ function slotsDisabledResponse() {
14206
+ return flagDisabledResponse({
14207
+ error: "Memory slots not enabled",
14208
+ flag: "AGENTMEMORY_SLOTS",
14209
+ enableHow: "Set AGENTMEMORY_SLOTS=true (in ~/.agentmemory/.env or the shell) and restart.",
14210
+ docsHref: "https://github.com/rohitg00/agentmemory#memory-slots"
14211
+ });
14212
+ }
14213
+ function reflectDisabledResponse() {
14214
+ return flagDisabledResponse({
14215
+ error: "Slot reflection not enabled",
14216
+ flag: "AGENTMEMORY_REFLECT",
14217
+ enableHow: "Set AGENTMEMORY_REFLECT=true (in ~/.agentmemory/.env or the shell) and restart. Requires AGENTMEMORY_SLOTS=true.",
14218
+ docsHref: "https://github.com/rohitg00/agentmemory#memory-slots"
14219
+ });
14220
+ }
14059
14221
  function asNonEmptyString$1(value) {
14060
14222
  if (typeof value !== "string") return null;
14061
14223
  const trimmed = value.trim();
@@ -14500,6 +14662,14 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14500
14662
  path: "status",
14501
14663
  value: "completed"
14502
14664
  }]);
14665
+ try {
14666
+ sdk.triggerVoid("event::session::stopped", { sessionId });
14667
+ } catch (err) {
14668
+ logger.warn("event::session::stopped triggerVoid failed", {
14669
+ sessionId,
14670
+ error: err instanceof Error ? err.message : String(err)
14671
+ });
14672
+ }
14503
14673
  return {
14504
14674
  status_code: 200,
14505
14675
  body: { success: true }
@@ -14718,11 +14888,21 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14718
14888
  status_code: 400,
14719
14889
  body: { error: "terms must be an array of strings" }
14720
14890
  };
14891
+ if (req.body.project !== void 0 && (typeof req.body.project !== "string" || !req.body.project.trim())) return {
14892
+ status_code: 400,
14893
+ body: { error: "project must be a non-empty string" }
14894
+ };
14721
14895
  return {
14722
14896
  status_code: 200,
14723
14897
  body: await sdk.trigger({
14724
14898
  function_id: "mem::enrich",
14725
- payload: req.body
14899
+ payload: {
14900
+ sessionId: req.body.sessionId,
14901
+ files: req.body.files,
14902
+ ...req.body.terms !== void 0 && { terms: req.body.terms },
14903
+ ...req.body.toolName !== void 0 && { toolName: req.body.toolName },
14904
+ ...req.body.project !== void 0 && { project: req.body.project }
14905
+ }
14726
14906
  })
14727
14907
  };
14728
14908
  });
@@ -14741,11 +14921,23 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14741
14921
  status_code: 400,
14742
14922
  body: { error: "content is required" }
14743
14923
  };
14924
+ if (req.body.project !== void 0 && (typeof req.body.project !== "string" || !req.body.project.trim())) return {
14925
+ status_code: 400,
14926
+ body: { error: "project must be a non-empty string" }
14927
+ };
14744
14928
  return {
14745
14929
  status_code: 201,
14746
14930
  body: await sdk.trigger({
14747
14931
  function_id: "mem::remember",
14748
- payload: req.body
14932
+ payload: {
14933
+ content: req.body.content,
14934
+ ...req.body.type !== void 0 && { type: req.body.type },
14935
+ ...req.body.concepts !== void 0 && { concepts: req.body.concepts },
14936
+ ...req.body.files !== void 0 && { files: req.body.files },
14937
+ ...req.body.ttlDays !== void 0 && { ttlDays: req.body.ttlDays },
14938
+ ...req.body.sourceObservationIds !== void 0 && { sourceObservationIds: req.body.sourceObservationIds },
14939
+ ...req.body.project !== void 0 && { project: req.body.project }
14940
+ }
14749
14941
  })
14750
14942
  };
14751
14943
  });
@@ -14840,15 +15032,21 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14840
15032
  sdk.registerFunction("api::migrate", async (req) => {
14841
15033
  const authErr = checkAuth(req, secret);
14842
15034
  if (authErr) return authErr;
14843
- if (!req.body?.dbPath || typeof req.body.dbPath !== "string") return {
15035
+ const hasStep = typeof req.body?.step === "string" && req.body.step.trim().length > 0;
15036
+ const hasDbPath = typeof req.body?.dbPath === "string" && req.body.dbPath.trim().length > 0;
15037
+ if (!hasStep && !hasDbPath) return {
14844
15038
  status_code: 400,
14845
- body: { error: "dbPath is required" }
15039
+ body: { error: "Either step (string) or dbPath (string) is required" }
14846
15040
  };
14847
15041
  return {
14848
15042
  status_code: 200,
14849
15043
  body: await sdk.trigger({
14850
15044
  function_id: "mem::migrate",
14851
- payload: req.body
15045
+ payload: {
15046
+ ...req.body.step !== void 0 && { step: req.body.step },
15047
+ ...req.body.dbPath !== void 0 && { dbPath: req.body.dbPath },
15048
+ ...req.body.dryRun !== void 0 && { dryRun: req.body.dryRun }
15049
+ }
14852
15050
  })
14853
15051
  };
14854
15052
  });
@@ -15194,6 +15392,63 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15194
15392
  http_method: "POST"
15195
15393
  }
15196
15394
  });
15395
+ sdk.registerFunction("api::graph-build", async (req) => {
15396
+ const authErr = checkAuth(req, secret);
15397
+ if (authErr) return authErr;
15398
+ const batchSize = Math.max(1, Math.min(100, Number(req.body?.batchSize) || 25));
15399
+ try {
15400
+ const sessions = await kv.list(KV.sessions);
15401
+ let totalNodes = 0;
15402
+ let totalEdges = 0;
15403
+ let batchesRun = 0;
15404
+ for (const session of sessions) {
15405
+ const sid = session?.id;
15406
+ if (typeof sid !== "string" || sid.length === 0) continue;
15407
+ const compressed = (await kv.list(KV.observations(sid))).filter((o) => o && typeof o.title === "string" && o.title.length > 0);
15408
+ if (compressed.length === 0) continue;
15409
+ for (let i = 0; i < compressed.length; i += batchSize) {
15410
+ const batch = compressed.slice(i, i + batchSize);
15411
+ try {
15412
+ const result = await sdk.trigger({
15413
+ function_id: "mem::graph-extract",
15414
+ payload: { observations: batch }
15415
+ });
15416
+ if (result?.success) {
15417
+ totalNodes += Number(result.nodesAdded) || 0;
15418
+ totalEdges += Number(result.edgesAdded) || 0;
15419
+ }
15420
+ batchesRun++;
15421
+ } catch (err) {
15422
+ logger.warn("graph-build batch failed", {
15423
+ sessionId: sid,
15424
+ batchIndex: Math.floor(i / batchSize),
15425
+ error: err instanceof Error ? err.message : String(err)
15426
+ });
15427
+ }
15428
+ }
15429
+ }
15430
+ return {
15431
+ status_code: 200,
15432
+ body: {
15433
+ success: true,
15434
+ sessions: sessions.length,
15435
+ batches: batchesRun,
15436
+ nodes: totalNodes,
15437
+ edges: totalEdges
15438
+ }
15439
+ };
15440
+ } catch {
15441
+ return graphDisabledResponse();
15442
+ }
15443
+ });
15444
+ sdk.registerTrigger({
15445
+ type: "http",
15446
+ function_id: "api::graph-build",
15447
+ config: {
15448
+ api_path: "/agentmemory/graph/build",
15449
+ http_method: "POST"
15450
+ }
15451
+ });
15197
15452
  sdk.registerFunction("api::consolidate-pipeline", async (req) => {
15198
15453
  const authErr = checkAuth(req, secret);
15199
15454
  if (authErr) return authErr;
@@ -15651,6 +15906,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15651
15906
  sdk.registerFunction("api::slot-list", async (req) => {
15652
15907
  const authErr = checkAuth(req, secret);
15653
15908
  if (authErr) return authErr;
15909
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15654
15910
  return {
15655
15911
  status_code: 200,
15656
15912
  body: await sdk.trigger({
@@ -15670,6 +15926,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15670
15926
  sdk.registerFunction("api::slot-get", async (req) => {
15671
15927
  const authErr = checkAuth(req, secret);
15672
15928
  if (authErr) return authErr;
15929
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15673
15930
  const label = asNonEmptyString$1(req.query_params?.["label"]);
15674
15931
  if (!label) return {
15675
15932
  status_code: 400,
@@ -15700,6 +15957,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15700
15957
  sdk.registerFunction("api::slot-create", async (req) => {
15701
15958
  const authErr = checkAuth(req, secret);
15702
15959
  if (authErr) return authErr;
15960
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15703
15961
  const body = req.body ?? {};
15704
15962
  const label = asNonEmptyString$1(body["label"]);
15705
15963
  if (!label) return {
@@ -15762,6 +16020,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15762
16020
  sdk.registerFunction("api::slot-append", async (req) => {
15763
16021
  const authErr = checkAuth(req, secret);
15764
16022
  if (authErr) return authErr;
16023
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15765
16024
  const body = req.body ?? {};
15766
16025
  const label = asNonEmptyString$1(body["label"]);
15767
16026
  const text = typeof body["text"] === "string" ? body["text"] : null;
@@ -15801,6 +16060,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15801
16060
  sdk.registerFunction("api::slot-replace", async (req) => {
15802
16061
  const authErr = checkAuth(req, secret);
15803
16062
  if (authErr) return authErr;
16063
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15804
16064
  const body = req.body ?? {};
15805
16065
  const label = asNonEmptyString$1(body["label"]);
15806
16066
  const content = body["content"];
@@ -15840,6 +16100,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15840
16100
  sdk.registerFunction("api::slot-delete", async (req) => {
15841
16101
  const authErr = checkAuth(req, secret);
15842
16102
  if (authErr) return authErr;
16103
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15843
16104
  const label = asNonEmptyString$1(req.query_params?.["label"]);
15844
16105
  if (!label) return {
15845
16106
  status_code: 400,
@@ -15870,6 +16131,8 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15870
16131
  sdk.registerFunction("api::slot-reflect", async (req) => {
15871
16132
  const authErr = checkAuth(req, secret);
15872
16133
  if (authErr) return authErr;
16134
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
16135
+ if (!isReflectEnabled()) return reflectDisabledResponse();
15873
16136
  const body = req.body ?? {};
15874
16137
  const sessionId = asNonEmptyString$1(body["sessionId"]);
15875
16138
  if (!sessionId) return {
@@ -17572,13 +17835,15 @@ function registerMcpEndpoints(sdk, kv, secret) {
17572
17835
  const type = args.type || "fact";
17573
17836
  const concepts = typeof args.concepts === "string" ? args.concepts.split(",").map((c) => c.trim()).filter(Boolean) : [];
17574
17837
  const files = typeof args.files === "string" ? args.files.split(",").map((f) => f.trim()).filter(Boolean) : [];
17838
+ const project = typeof args.project === "string" && args.project.trim().length > 0 ? args.project.trim() : void 0;
17575
17839
  const result = await sdk.trigger({
17576
17840
  function_id: "mem::remember",
17577
17841
  payload: {
17578
17842
  content: args.content,
17579
17843
  type,
17580
17844
  concepts,
17581
- files
17845
+ files,
17846
+ ...project !== void 0 && { project }
17582
17847
  }
17583
17848
  });
17584
17849
  return {
@@ -19425,7 +19690,7 @@ async function main() {
19425
19690
  if (bm25Index.has(memory.id)) continue;
19426
19691
  bm25Index.add({
19427
19692
  id: memory.id,
19428
- sessionId: memory.sessionIds[0] ?? "memory",
19693
+ sessionId: memory.sessionIds?.[0] ?? "memory",
19429
19694
  timestamp: memory.createdAt,
19430
19695
  type: "decision",
19431
19696
  title: memory.title,
@@ -19445,7 +19710,7 @@ async function main() {
19445
19710
  console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
19446
19711
  }
19447
19712
  bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
19448
- bootLog(`REST API: 124 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
19713
+ bootLog(`REST API: 125 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
19449
19714
  bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
19450
19715
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
19451
19716
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
@@ -19514,4 +19779,4 @@ main().catch((err) => {
19514
19779
 
19515
19780
  //#endregion
19516
19781
  export { };
19517
- //# sourceMappingURL=src-gpTAJuBy.mjs.map
19782
+ //# sourceMappingURL=src-B8J9Exum.mjs.map