@agentmemory/agentmemory 0.9.22 → 0.9.23

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 +32 -18
  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 +335 -57
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/{src-gpTAJuBy.mjs → src-DvS3bhMe.mjs} +322 -58
  37. package/dist/src-DvS3bhMe.mjs.map +1 -0
  38. package/dist/{standalone-C4i7ktpn.mjs → standalone-DHQcPX_g.mjs} +92 -11
  39. package/dist/standalone-DHQcPX_g.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-BPfyI4Kc.mjs +6 -0
  45. package/dist/version-BPfyI4Kc.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-BPfyI4Kc.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,8 @@ 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"
6345
6449
  ]).has(importData.version)) return {
6346
6450
  success: false,
6347
6451
  error: `Unsupported export version: ${importData.version}`
@@ -6464,6 +6568,7 @@ function registerExportImportFunction(sdk, kv) {
6464
6568
  continue;
6465
6569
  }
6466
6570
  }
6571
+ if (!Array.isArray(memory.sessionIds)) memory.sessionIds = [];
6467
6572
  await kv.set(KV.memories, memory.id, memory);
6468
6573
  stats.memories++;
6469
6574
  }
@@ -6667,6 +6772,7 @@ function escapeXml(s) {
6667
6772
  }
6668
6773
  function registerEnrichFunction(sdk, kv) {
6669
6774
  sdk.registerFunction("mem::enrich", async (data) => {
6775
+ const project = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
6670
6776
  const parts = [];
6671
6777
  const fileContextPromise = sdk.trigger({
6672
6778
  function_id: "mem::file-context",
@@ -6680,10 +6786,11 @@ function registerEnrichFunction(sdk, kv) {
6680
6786
  function_id: "mem::search",
6681
6787
  payload: {
6682
6788
  query: searchQueries.join(" "),
6683
- limit: 5
6789
+ limit: 5,
6790
+ ...project !== void 0 && { project }
6684
6791
  }
6685
6792
  }).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(() => []);
6793
+ 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
6794
  const [fileContext, searchResult, bugMemories] = await Promise.all([
6688
6795
  fileContextPromise,
6689
6796
  searchPromise,
@@ -6706,6 +6813,7 @@ function registerEnrichFunction(sdk, kv) {
6706
6813
  }
6707
6814
  logger.info("Enrichment completed", {
6708
6815
  sessionId: data.sessionId,
6816
+ project,
6709
6817
  fileCount: data.files.length,
6710
6818
  contextLength: context.length,
6711
6819
  truncated
@@ -6863,16 +6971,24 @@ function buildGraphExtractionPrompt(observations) {
6863
6971
 
6864
6972
  //#endregion
6865
6973
  //#region src/functions/graph.ts
6974
+ function parseAttrs(raw) {
6975
+ const attrs = {};
6976
+ const attrRegex = /([A-Za-z_][\w:-]*)="([^"]*)"/g;
6977
+ let m;
6978
+ while ((m = attrRegex.exec(raw)) !== null) attrs[m[1]] = m[2];
6979
+ return attrs;
6980
+ }
6866
6981
  function parseGraphXml(xml, observationIds) {
6867
6982
  const nodes = [];
6868
6983
  const edges = [];
6869
6984
  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] ?? "";
6985
+ const entitySelfClose = /<entity\b([^>]*?)\/>/g;
6986
+ const entityWithBody = /<entity\b([^>]*[^/])>([\s\S]*?)<\/entity>/g;
6987
+ const addEntity = (rawAttrs, propsBlock = "") => {
6988
+ const attrs = parseAttrs(rawAttrs);
6989
+ const type = attrs["type"];
6990
+ const name = attrs["name"];
6991
+ if (!type || !name) return;
6876
6992
  const properties = {};
6877
6993
  const propRegex = /<property\s+key="([^"]+)">([^<]*)<\/property>/g;
6878
6994
  let propMatch;
@@ -6885,17 +7001,23 @@ function parseGraphXml(xml, observationIds) {
6885
7001
  sourceObservationIds: observationIds,
6886
7002
  createdAt: now
6887
7003
  });
6888
- }
6889
- const relRegex = /<relationship\s+type="([^"]+)"\s+source="([^"]+)"\s+target="([^"]+)"\s+weight="([^"]+)"\s*\/>/g;
7004
+ };
7005
+ let match;
7006
+ while ((match = entitySelfClose.exec(xml)) !== null) addEntity(match[1]);
7007
+ while ((match = entityWithBody.exec(xml)) !== null) addEntity(match[1], match[2]);
7008
+ const relRegex = /<relationship\b([^>]*?)\/>/g;
6890
7009
  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;
7010
+ const attrs = parseAttrs(match[1]);
7011
+ const type = attrs["type"];
7012
+ const sourceName = attrs["source"];
7013
+ const targetName = attrs["target"];
7014
+ if (!type || !sourceName || !targetName) continue;
7015
+ const parsedWeight = parseFloat(attrs["weight"] ?? "");
7016
+ const weight = Number.isFinite(parsedWeight) ? parsedWeight : .5;
6896
7017
  const sourceNode = nodes.find((n) => n.name === sourceName);
6897
7018
  const targetNode = nodes.find((n) => n.name === targetName);
6898
- if (sourceNode && targetNode) edges.push({
7019
+ if (!sourceNode || !targetNode) continue;
7020
+ edges.push({
6899
7021
  id: generateId("ge"),
6900
7022
  type,
6901
7023
  sourceNodeId: sourceNode.id,
@@ -7109,7 +7231,7 @@ function registerConsolidationPipelineFunction(sdk, kv, provider) {
7109
7231
  if (!data?.force && !isConsolidationEnabled()) return {
7110
7232
  success: false,
7111
7233
  skipped: true,
7112
- reason: "CONSOLIDATION_ENABLED is not set to true"
7234
+ 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
7235
  };
7114
7236
  const tier = data?.tier || "all";
7115
7237
  const decayDays = getConsolidationDecayDays();
@@ -10279,11 +10401,34 @@ function registerDiagnosticsFunction(sdk, kv) {
10279
10401
  });
10280
10402
  memoryIssues++;
10281
10403
  }
10404
+ const latestMemories = memories.filter((m) => m.isLatest);
10405
+ const unscopedCount = latestMemories.filter((m) => !m.project).length;
10406
+ if (unscopedCount === 0) checks.push({
10407
+ name: "memory-project-coverage",
10408
+ category: "memories",
10409
+ status: "pass",
10410
+ message: `All ${latestMemories.length} latest memories have a project scope`,
10411
+ fixable: false
10412
+ });
10413
+ else if (unscopedCount <= 10) checks.push({
10414
+ name: "memory-project-coverage",
10415
+ category: "memories",
10416
+ status: "warn",
10417
+ message: `${unscopedCount} of ${latestMemories.length} latest memories have no project scope — run POST /agentmemory/migrate {"step":"infer-memory-projects"} to backfill`,
10418
+ fixable: true
10419
+ });
10420
+ else checks.push({
10421
+ name: "memory-project-coverage",
10422
+ category: "memories",
10423
+ status: "fail",
10424
+ message: `${unscopedCount} of ${latestMemories.length} latest memories have no project scope — run POST /agentmemory/migrate {"step":"infer-memory-projects"} to backfill`,
10425
+ fixable: true
10426
+ });
10282
10427
  if (memoryIssues === 0) checks.push({
10283
10428
  name: "memories-ok",
10284
10429
  category: "memories",
10285
10430
  status: "pass",
10286
- message: `All ${memories.length} memories are consistent`,
10431
+ message: `All ${memories.length} memories are structurally consistent`,
10287
10432
  fixable: false
10288
10433
  });
10289
10434
  }
@@ -14056,6 +14201,22 @@ function consolidationDisabledResponse() {
14056
14201
  docsHref: "https://github.com/rohitg00/agentmemory#consolidation"
14057
14202
  });
14058
14203
  }
14204
+ function slotsDisabledResponse() {
14205
+ return flagDisabledResponse({
14206
+ error: "Memory slots not enabled",
14207
+ flag: "AGENTMEMORY_SLOTS",
14208
+ enableHow: "Set AGENTMEMORY_SLOTS=true (in ~/.agentmemory/.env or the shell) and restart.",
14209
+ docsHref: "https://github.com/rohitg00/agentmemory#memory-slots"
14210
+ });
14211
+ }
14212
+ function reflectDisabledResponse() {
14213
+ return flagDisabledResponse({
14214
+ error: "Slot reflection not enabled",
14215
+ flag: "AGENTMEMORY_REFLECT",
14216
+ enableHow: "Set AGENTMEMORY_REFLECT=true (in ~/.agentmemory/.env or the shell) and restart. Requires AGENTMEMORY_SLOTS=true.",
14217
+ docsHref: "https://github.com/rohitg00/agentmemory#memory-slots"
14218
+ });
14219
+ }
14059
14220
  function asNonEmptyString$1(value) {
14060
14221
  if (typeof value !== "string") return null;
14061
14222
  const trimmed = value.trim();
@@ -14500,6 +14661,14 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14500
14661
  path: "status",
14501
14662
  value: "completed"
14502
14663
  }]);
14664
+ try {
14665
+ sdk.triggerVoid("event::session::stopped", { sessionId });
14666
+ } catch (err) {
14667
+ logger.warn("event::session::stopped triggerVoid failed", {
14668
+ sessionId,
14669
+ error: err instanceof Error ? err.message : String(err)
14670
+ });
14671
+ }
14503
14672
  return {
14504
14673
  status_code: 200,
14505
14674
  body: { success: true }
@@ -14718,11 +14887,21 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14718
14887
  status_code: 400,
14719
14888
  body: { error: "terms must be an array of strings" }
14720
14889
  };
14890
+ if (req.body.project !== void 0 && (typeof req.body.project !== "string" || !req.body.project.trim())) return {
14891
+ status_code: 400,
14892
+ body: { error: "project must be a non-empty string" }
14893
+ };
14721
14894
  return {
14722
14895
  status_code: 200,
14723
14896
  body: await sdk.trigger({
14724
14897
  function_id: "mem::enrich",
14725
- payload: req.body
14898
+ payload: {
14899
+ sessionId: req.body.sessionId,
14900
+ files: req.body.files,
14901
+ ...req.body.terms !== void 0 && { terms: req.body.terms },
14902
+ ...req.body.toolName !== void 0 && { toolName: req.body.toolName },
14903
+ ...req.body.project !== void 0 && { project: req.body.project }
14904
+ }
14726
14905
  })
14727
14906
  };
14728
14907
  });
@@ -14741,11 +14920,23 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14741
14920
  status_code: 400,
14742
14921
  body: { error: "content is required" }
14743
14922
  };
14923
+ if (req.body.project !== void 0 && (typeof req.body.project !== "string" || !req.body.project.trim())) return {
14924
+ status_code: 400,
14925
+ body: { error: "project must be a non-empty string" }
14926
+ };
14744
14927
  return {
14745
14928
  status_code: 201,
14746
14929
  body: await sdk.trigger({
14747
14930
  function_id: "mem::remember",
14748
- payload: req.body
14931
+ payload: {
14932
+ content: req.body.content,
14933
+ ...req.body.type !== void 0 && { type: req.body.type },
14934
+ ...req.body.concepts !== void 0 && { concepts: req.body.concepts },
14935
+ ...req.body.files !== void 0 && { files: req.body.files },
14936
+ ...req.body.ttlDays !== void 0 && { ttlDays: req.body.ttlDays },
14937
+ ...req.body.sourceObservationIds !== void 0 && { sourceObservationIds: req.body.sourceObservationIds },
14938
+ ...req.body.project !== void 0 && { project: req.body.project }
14939
+ }
14749
14940
  })
14750
14941
  };
14751
14942
  });
@@ -14840,15 +15031,21 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14840
15031
  sdk.registerFunction("api::migrate", async (req) => {
14841
15032
  const authErr = checkAuth(req, secret);
14842
15033
  if (authErr) return authErr;
14843
- if (!req.body?.dbPath || typeof req.body.dbPath !== "string") return {
15034
+ const hasStep = typeof req.body?.step === "string" && req.body.step.trim().length > 0;
15035
+ const hasDbPath = typeof req.body?.dbPath === "string" && req.body.dbPath.trim().length > 0;
15036
+ if (!hasStep && !hasDbPath) return {
14844
15037
  status_code: 400,
14845
- body: { error: "dbPath is required" }
15038
+ body: { error: "Either step (string) or dbPath (string) is required" }
14846
15039
  };
14847
15040
  return {
14848
15041
  status_code: 200,
14849
15042
  body: await sdk.trigger({
14850
15043
  function_id: "mem::migrate",
14851
- payload: req.body
15044
+ payload: {
15045
+ ...req.body.step !== void 0 && { step: req.body.step },
15046
+ ...req.body.dbPath !== void 0 && { dbPath: req.body.dbPath },
15047
+ ...req.body.dryRun !== void 0 && { dryRun: req.body.dryRun }
15048
+ }
14852
15049
  })
14853
15050
  };
14854
15051
  });
@@ -15194,6 +15391,63 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15194
15391
  http_method: "POST"
15195
15392
  }
15196
15393
  });
15394
+ sdk.registerFunction("api::graph-build", async (req) => {
15395
+ const authErr = checkAuth(req, secret);
15396
+ if (authErr) return authErr;
15397
+ const batchSize = Math.max(1, Math.min(100, Number(req.body?.batchSize) || 25));
15398
+ try {
15399
+ const sessions = await kv.list(KV.sessions);
15400
+ let totalNodes = 0;
15401
+ let totalEdges = 0;
15402
+ let batchesRun = 0;
15403
+ for (const session of sessions) {
15404
+ const sid = session?.id;
15405
+ if (typeof sid !== "string" || sid.length === 0) continue;
15406
+ const compressed = (await kv.list(KV.observations(sid))).filter((o) => o && typeof o.title === "string" && o.title.length > 0);
15407
+ if (compressed.length === 0) continue;
15408
+ for (let i = 0; i < compressed.length; i += batchSize) {
15409
+ const batch = compressed.slice(i, i + batchSize);
15410
+ try {
15411
+ const result = await sdk.trigger({
15412
+ function_id: "mem::graph-extract",
15413
+ payload: { observations: batch }
15414
+ });
15415
+ if (result?.success) {
15416
+ totalNodes += Number(result.nodesAdded) || 0;
15417
+ totalEdges += Number(result.edgesAdded) || 0;
15418
+ }
15419
+ batchesRun++;
15420
+ } catch (err) {
15421
+ logger.warn("graph-build batch failed", {
15422
+ sessionId: sid,
15423
+ batchIndex: Math.floor(i / batchSize),
15424
+ error: err instanceof Error ? err.message : String(err)
15425
+ });
15426
+ }
15427
+ }
15428
+ }
15429
+ return {
15430
+ status_code: 200,
15431
+ body: {
15432
+ success: true,
15433
+ sessions: sessions.length,
15434
+ batches: batchesRun,
15435
+ nodes: totalNodes,
15436
+ edges: totalEdges
15437
+ }
15438
+ };
15439
+ } catch {
15440
+ return graphDisabledResponse();
15441
+ }
15442
+ });
15443
+ sdk.registerTrigger({
15444
+ type: "http",
15445
+ function_id: "api::graph-build",
15446
+ config: {
15447
+ api_path: "/agentmemory/graph/build",
15448
+ http_method: "POST"
15449
+ }
15450
+ });
15197
15451
  sdk.registerFunction("api::consolidate-pipeline", async (req) => {
15198
15452
  const authErr = checkAuth(req, secret);
15199
15453
  if (authErr) return authErr;
@@ -15651,6 +15905,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15651
15905
  sdk.registerFunction("api::slot-list", async (req) => {
15652
15906
  const authErr = checkAuth(req, secret);
15653
15907
  if (authErr) return authErr;
15908
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15654
15909
  return {
15655
15910
  status_code: 200,
15656
15911
  body: await sdk.trigger({
@@ -15670,6 +15925,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15670
15925
  sdk.registerFunction("api::slot-get", async (req) => {
15671
15926
  const authErr = checkAuth(req, secret);
15672
15927
  if (authErr) return authErr;
15928
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15673
15929
  const label = asNonEmptyString$1(req.query_params?.["label"]);
15674
15930
  if (!label) return {
15675
15931
  status_code: 400,
@@ -15700,6 +15956,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15700
15956
  sdk.registerFunction("api::slot-create", async (req) => {
15701
15957
  const authErr = checkAuth(req, secret);
15702
15958
  if (authErr) return authErr;
15959
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15703
15960
  const body = req.body ?? {};
15704
15961
  const label = asNonEmptyString$1(body["label"]);
15705
15962
  if (!label) return {
@@ -15762,6 +16019,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15762
16019
  sdk.registerFunction("api::slot-append", async (req) => {
15763
16020
  const authErr = checkAuth(req, secret);
15764
16021
  if (authErr) return authErr;
16022
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15765
16023
  const body = req.body ?? {};
15766
16024
  const label = asNonEmptyString$1(body["label"]);
15767
16025
  const text = typeof body["text"] === "string" ? body["text"] : null;
@@ -15801,6 +16059,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15801
16059
  sdk.registerFunction("api::slot-replace", async (req) => {
15802
16060
  const authErr = checkAuth(req, secret);
15803
16061
  if (authErr) return authErr;
16062
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15804
16063
  const body = req.body ?? {};
15805
16064
  const label = asNonEmptyString$1(body["label"]);
15806
16065
  const content = body["content"];
@@ -15840,6 +16099,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15840
16099
  sdk.registerFunction("api::slot-delete", async (req) => {
15841
16100
  const authErr = checkAuth(req, secret);
15842
16101
  if (authErr) return authErr;
16102
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
15843
16103
  const label = asNonEmptyString$1(req.query_params?.["label"]);
15844
16104
  if (!label) return {
15845
16105
  status_code: 400,
@@ -15870,6 +16130,8 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15870
16130
  sdk.registerFunction("api::slot-reflect", async (req) => {
15871
16131
  const authErr = checkAuth(req, secret);
15872
16132
  if (authErr) return authErr;
16133
+ if (!isSlotsEnabled()) return slotsDisabledResponse();
16134
+ if (!isReflectEnabled()) return reflectDisabledResponse();
15873
16135
  const body = req.body ?? {};
15874
16136
  const sessionId = asNonEmptyString$1(body["sessionId"]);
15875
16137
  if (!sessionId) return {
@@ -17572,13 +17834,15 @@ function registerMcpEndpoints(sdk, kv, secret) {
17572
17834
  const type = args.type || "fact";
17573
17835
  const concepts = typeof args.concepts === "string" ? args.concepts.split(",").map((c) => c.trim()).filter(Boolean) : [];
17574
17836
  const files = typeof args.files === "string" ? args.files.split(",").map((f) => f.trim()).filter(Boolean) : [];
17837
+ const project = typeof args.project === "string" && args.project.trim().length > 0 ? args.project.trim() : void 0;
17575
17838
  const result = await sdk.trigger({
17576
17839
  function_id: "mem::remember",
17577
17840
  payload: {
17578
17841
  content: args.content,
17579
17842
  type,
17580
17843
  concepts,
17581
- files
17844
+ files,
17845
+ ...project !== void 0 && { project }
17582
17846
  }
17583
17847
  });
17584
17848
  return {
@@ -19425,7 +19689,7 @@ async function main() {
19425
19689
  if (bm25Index.has(memory.id)) continue;
19426
19690
  bm25Index.add({
19427
19691
  id: memory.id,
19428
- sessionId: memory.sessionIds[0] ?? "memory",
19692
+ sessionId: memory.sessionIds?.[0] ?? "memory",
19429
19693
  timestamp: memory.createdAt,
19430
19694
  type: "decision",
19431
19695
  title: memory.title,
@@ -19445,7 +19709,7 @@ async function main() {
19445
19709
  console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
19446
19710
  }
19447
19711
  bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
19448
- bootLog(`REST API: 124 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
19712
+ bootLog(`REST API: 125 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
19449
19713
  bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
19450
19714
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
19451
19715
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
@@ -19514,4 +19778,4 @@ main().catch((err) => {
19514
19778
 
19515
19779
  //#endregion
19516
19780
  export { };
19517
- //# sourceMappingURL=src-gpTAJuBy.mjs.map
19781
+ //# sourceMappingURL=src-DvS3bhMe.mjs.map