@agentmemory/agentmemory 0.9.26 → 0.9.28

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 (125) hide show
  1. package/AGENTS.md +6 -6
  2. package/README.md +104 -42
  3. package/dist/cli.d.mts.map +1 -1
  4. package/dist/cli.mjs +239 -170
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/config-ChC9EtgV.mjs +259 -0
  7. package/dist/config-ChC9EtgV.mjs.map +1 -0
  8. package/dist/{connect-bmZ5eqYN.mjs → connect-JnsJXc68.mjs} +136 -42
  9. package/dist/connect-JnsJXc68.mjs.map +1 -0
  10. package/dist/hooks/notification.mjs +2 -1
  11. package/dist/hooks/notification.mjs.map +1 -1
  12. package/dist/hooks/post-commit.mjs +2 -1
  13. package/dist/hooks/post-commit.mjs.map +1 -1
  14. package/dist/hooks/post-tool-failure.mjs +2 -1
  15. package/dist/hooks/post-tool-failure.mjs.map +1 -1
  16. package/dist/hooks/post-tool-use.mjs +2 -1
  17. package/dist/hooks/post-tool-use.mjs.map +1 -1
  18. package/dist/hooks/pre-compact.mjs +2 -1
  19. package/dist/hooks/pre-compact.mjs.map +1 -1
  20. package/dist/hooks/pre-tool-use.mjs +2 -1
  21. package/dist/hooks/pre-tool-use.mjs.map +1 -1
  22. package/dist/hooks/prompt-submit.mjs +2 -1
  23. package/dist/hooks/prompt-submit.mjs.map +1 -1
  24. package/dist/hooks/session-end.mjs +2 -1
  25. package/dist/hooks/session-end.mjs.map +1 -1
  26. package/dist/hooks/session-start.mjs +2 -1
  27. package/dist/hooks/session-start.mjs.map +1 -1
  28. package/dist/hooks/stop.mjs +2 -1
  29. package/dist/hooks/stop.mjs.map +1 -1
  30. package/dist/hooks/subagent-start.mjs +2 -1
  31. package/dist/hooks/subagent-start.mjs.map +1 -1
  32. package/dist/hooks/subagent-stop.mjs +2 -1
  33. package/dist/hooks/subagent-stop.mjs.map +1 -1
  34. package/dist/hooks/task-completed.mjs +2 -1
  35. package/dist/hooks/task-completed.mjs.map +1 -1
  36. package/dist/{image-refs-C7h9L5wx.mjs → image-refs-BKLmp_sD.mjs} +4 -22
  37. package/dist/image-refs-BKLmp_sD.mjs.map +1 -0
  38. package/dist/image-refs-C2YU7BIm.mjs +34 -0
  39. package/dist/image-refs-C2YU7BIm.mjs.map +1 -0
  40. package/dist/index.mjs +559 -205
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/{image-store-Gpo2mgM9.mjs → keyed-mutex-DTHtcZrt.mjs} +14 -11
  43. package/dist/keyed-mutex-DTHtcZrt.mjs.map +1 -0
  44. package/dist/{logger-yHTcEBAI.mjs → logger-BEB7pCI9.mjs} +1 -1
  45. package/dist/{logger-yHTcEBAI.mjs.map → logger-BEB7pCI9.mjs.map} +1 -1
  46. package/dist/{schema-Dsr_V2Wp.mjs → schema-CNiCWzV9.mjs} +5 -1
  47. package/dist/schema-CNiCWzV9.mjs.map +1 -0
  48. package/dist/{src-u7kAEUC0.mjs → src-CzgoepGU.mjs} +482 -83
  49. package/dist/src-CzgoepGU.mjs.map +1 -0
  50. package/dist/{standalone-C1yPO519.mjs → standalone-YsZFFQBM.mjs} +8 -5
  51. package/dist/standalone-YsZFFQBM.mjs.map +1 -0
  52. package/dist/standalone.d.mts.map +1 -1
  53. package/dist/standalone.mjs +6 -2
  54. package/dist/standalone.mjs.map +1 -1
  55. package/dist/{tools-registry-Dzxv9iUu.mjs → tools-registry-D3NEoe5m.mjs} +4 -254
  56. package/dist/tools-registry-D3NEoe5m.mjs.map +1 -0
  57. package/package.json +4 -1
  58. package/plugin/.claude-plugin/plugin.json +1 -1
  59. package/plugin/.codex-plugin/plugin.json +1 -1
  60. package/plugin/plugin.json +2 -2
  61. package/plugin/scripts/notification.mjs +2 -1
  62. package/plugin/scripts/notification.mjs.map +1 -1
  63. package/plugin/scripts/post-commit.mjs +2 -1
  64. package/plugin/scripts/post-commit.mjs.map +1 -1
  65. package/plugin/scripts/post-tool-failure.mjs +2 -1
  66. package/plugin/scripts/post-tool-failure.mjs.map +1 -1
  67. package/plugin/scripts/post-tool-use.mjs +2 -1
  68. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  69. package/plugin/scripts/pre-compact.mjs +2 -1
  70. package/plugin/scripts/pre-compact.mjs.map +1 -1
  71. package/plugin/scripts/pre-tool-use.mjs +2 -1
  72. package/plugin/scripts/pre-tool-use.mjs.map +1 -1
  73. package/plugin/scripts/prompt-submit.mjs +2 -1
  74. package/plugin/scripts/prompt-submit.mjs.map +1 -1
  75. package/plugin/scripts/session-end.mjs +2 -1
  76. package/plugin/scripts/session-end.mjs.map +1 -1
  77. package/plugin/scripts/session-start.mjs +2 -1
  78. package/plugin/scripts/session-start.mjs.map +1 -1
  79. package/plugin/scripts/stop.mjs +2 -1
  80. package/plugin/scripts/stop.mjs.map +1 -1
  81. package/plugin/scripts/subagent-start.mjs +2 -1
  82. package/plugin/scripts/subagent-start.mjs.map +1 -1
  83. package/plugin/scripts/subagent-stop.mjs +2 -1
  84. package/plugin/scripts/subagent-stop.mjs.map +1 -1
  85. package/plugin/scripts/task-completed.mjs +2 -1
  86. package/plugin/scripts/task-completed.mjs.map +1 -1
  87. package/plugin/skills/_shared/TROUBLESHOOTING.md +38 -0
  88. package/plugin/skills/agentmemory-agents/REFERENCE.md +28 -0
  89. package/plugin/skills/agentmemory-agents/SKILL.md +34 -0
  90. package/plugin/skills/agentmemory-architecture/SKILL.md +33 -0
  91. package/plugin/skills/agentmemory-config/REFERENCE.md +42 -0
  92. package/plugin/skills/agentmemory-config/SKILL.md +37 -0
  93. package/plugin/skills/agentmemory-hooks/REFERENCE.md +20 -0
  94. package/plugin/skills/agentmemory-hooks/SKILL.md +39 -0
  95. package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +65 -0
  96. package/plugin/skills/agentmemory-mcp-tools/SKILL.md +39 -0
  97. package/plugin/skills/agentmemory-rest-api/REFERENCE.md +129 -0
  98. package/plugin/skills/agentmemory-rest-api/SKILL.md +43 -0
  99. package/plugin/skills/commit-context/EXAMPLES.md +71 -0
  100. package/plugin/skills/commit-context/SKILL.md +53 -8
  101. package/plugin/skills/commit-history/EXAMPLES.md +71 -0
  102. package/plugin/skills/commit-history/SKILL.md +51 -9
  103. package/plugin/skills/forget/EXAMPLES.md +75 -0
  104. package/plugin/skills/forget/SKILL.md +54 -14
  105. package/plugin/skills/handoff/EXAMPLES.md +56 -0
  106. package/plugin/skills/handoff/SKILL.md +56 -9
  107. package/plugin/skills/recall/EXAMPLES.md +74 -0
  108. package/plugin/skills/recall/SKILL.md +50 -11
  109. package/plugin/skills/recap/EXAMPLES.md +60 -0
  110. package/plugin/skills/recap/SKILL.md +51 -13
  111. package/plugin/skills/remember/EXAMPLES.md +67 -0
  112. package/plugin/skills/remember/SKILL.md +53 -17
  113. package/plugin/skills/session-history/EXAMPLES.md +59 -0
  114. package/plugin/skills/session-history/SKILL.md +54 -11
  115. package/plugin/skills/write-agentmemory-skill/SKILL.md +40 -0
  116. package/dist/connect-bmZ5eqYN.mjs.map +0 -1
  117. package/dist/image-refs-C7h9L5wx.mjs.map +0 -1
  118. package/dist/image-store-Gpo2mgM9.mjs.map +0 -1
  119. package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
  120. package/dist/schema-Dsr_V2Wp.mjs.map +0 -1
  121. package/dist/src-u7kAEUC0.mjs.map +0 -1
  122. package/dist/standalone-C1yPO519.mjs.map +0 -1
  123. package/dist/tools-registry-Dzxv9iUu.mjs.map +0 -1
  124. package/dist/version-BMFYFFut.mjs +0 -6
  125. package/dist/version-BMFYFFut.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@ import { TriggerAction, registerWorker } from "iii-sdk";
4
4
  import { constants, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, extname, join, resolve, sep } from "node:path";
6
6
  import { homedir } from "node:os";
7
+ import pc from "picocolors";
7
8
  import { AsyncLocalStorage } from "node:async_hooks";
8
9
  import Anthropic from "@anthropic-ai/sdk";
9
10
  import { lstat, mkdir, open, readFile, readdir, stat, unlink, utimes, writeFile } from "node:fs/promises";
@@ -15,18 +16,6 @@ import { lookup } from "node:dns/promises";
15
16
  import { isIP } from "node:net";
16
17
  import { fileURLToPath } from "node:url";
17
18
  import { createServer } from "node:http";
18
- //#region \0rolldown/runtime.js
19
- var __defProp = Object.defineProperty;
20
- var __exportAll = (all, no_symbols) => {
21
- let target = {};
22
- for (var name in all) __defProp(target, name, {
23
- get: all[name],
24
- enumerable: true
25
- });
26
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
27
- return target;
28
- };
29
- //#endregion
30
19
  //#region src/config.ts
31
20
  function safeParseInt(value, fallback) {
32
21
  if (!value) return fallback;
@@ -102,14 +91,14 @@ function detectProvider(env) {
102
91
  };
103
92
  }
104
93
  if (!(env["AGENTMEMORY_ALLOW_AGENT_SDK"] === "true")) {
105
- process.stderr.write("[agentmemory] No LLM provider key found (ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY, MINIMAX_API_KEY, OPENAI_API_KEY). LLM-backed compression and summarization are DISABLED — using no-op provider. This is the safe default: the agent-sdk fallback used to spawn Claude Agent SDK child sessions which inherit Claude Code's plugin hooks and cause infinite Stop-hook recursion (#149 follow-up). To opt in to the agent-sdk fallback anyway, set both AGENTMEMORY_AUTO_COMPRESS=true AND AGENTMEMORY_ALLOW_AGENT_SDK=true — but be aware it will burn your Claude Pro allocation and may still recurse if you use it from inside Claude Code itself.\n");
94
+ process.stderr.write(pc.dim("[agentmemory] No LLM provider key set — running zero-LLM (BM25 + on-device embeddings). Set ANTHROPIC_API_KEY (or GEMINI/OPENAI/OPENROUTER/MINIMAX) in ~/.agentmemory/.env for LLM compression and summaries. Agent-SDK fallback stays off by default to avoid a Stop-hook recursion loop; opt in with AGENTMEMORY_AUTO_COMPRESS=true + AGENTMEMORY_ALLOW_AGENT_SDK=true.\n"));
106
95
  return {
107
96
  provider: "noop",
108
97
  model: "noop",
109
98
  maxTokens
110
99
  };
111
100
  }
112
- process.stderr.write("[agentmemory] WARNING: agent-sdk fallback enabled via AGENTMEMORY_ALLOW_AGENT_SDK=true. This spawns @anthropic-ai/claude-agent-sdk child sessions that can trigger the Stop-hook recursion loop (#149 follow-up). A SDK-child env marker is set to block re-entry, but prefer setting a real API key in ~/.agentmemory/.env instead.\n");
101
+ process.stderr.write("[agentmemory] WARNING: agent-sdk fallback enabled via AGENTMEMORY_ALLOW_AGENT_SDK=true. This spawns @anthropic-ai/claude-agent-sdk child sessions that can trigger the Stop-hook recursion loop. A SDK-child env marker is set to block re-entry, but prefer setting a real API key in ~/.agentmemory/.env instead.\n");
113
102
  return {
114
103
  provider: "agent-sdk",
115
104
  model: "claude-sonnet-4-20250514",
@@ -119,10 +108,12 @@ function detectProvider(env) {
119
108
  function loadConfig() {
120
109
  const env = getMergedEnv();
121
110
  const provider = detectProvider(env);
111
+ const restPort = parseInt(env["III_REST_PORT"] || "3111", 10) || 3111;
112
+ const streamsPort = parseInt(env["III_STREAM_PORT"] || env["III_STREAMS_PORT"] || "", 10) || restPort + 1;
122
113
  return {
123
- engineUrl: env["III_ENGINE_URL"] || "ws://localhost:49134",
124
- restPort: parseInt(env["III_REST_PORT"] || "3111", 10) || 3111,
125
- streamsPort: parseInt(env["III_STREAMS_PORT"] || "3112", 10) || 3112,
114
+ engineUrl: env["III_ENGINE_URL"] || `ws://localhost:${parseInt(env["III_ENGINE_PORT"] || "", 10) || restPort + 46023}`,
115
+ restPort,
116
+ streamsPort,
126
117
  provider,
127
118
  tokenBudget: safeParseInt(env["TOKEN_BUDGET"], 2e3),
128
119
  maxObservationsPerSession: safeParseInt(env["MAX_OBS_PER_SESSION"], 500),
@@ -267,7 +258,7 @@ function loadFallbackConfig() {
267
258
  const allowAgentSdk = env["AGENTMEMORY_ALLOW_AGENT_SDK"] === "true";
268
259
  return { providers: raw.split(",").map((p) => p.trim()).filter((p) => Boolean(p) && VALID_PROVIDERS.has(p)).filter((p) => {
269
260
  if (p === "agent-sdk" && !allowAgentSdk) {
270
- process.stderr.write("[agentmemory] Ignoring FALLBACK_PROVIDERS entry 'agent-sdk' (AGENTMEMORY_ALLOW_AGENT_SDK is not 'true'). The agent-sdk fallback can spawn Claude Agent SDK child sessions that trigger the Stop-hook recursion loop (#149 follow-up). Opt in explicitly with AGENTMEMORY_ALLOW_AGENT_SDK=true if this is intentional.\n");
261
+ process.stderr.write("[agentmemory] Ignoring FALLBACK_PROVIDERS entry 'agent-sdk' (AGENTMEMORY_ALLOW_AGENT_SDK is not 'true'). The agent-sdk fallback can spawn Claude Agent SDK child sessions that trigger the Stop-hook recursion loop. Opt in explicitly with AGENTMEMORY_ALLOW_AGENT_SDK=true if this is intentional.\n");
271
262
  return false;
272
263
  }
273
264
  return true;
@@ -1326,6 +1317,10 @@ const KV = {
1326
1317
  claudeBridge: "mem:claude-bridge",
1327
1318
  graphNodes: "mem:graph:nodes",
1328
1319
  graphEdges: "mem:graph:edges",
1320
+ graphSnapshot: "mem:graph:snapshot",
1321
+ graphNameIndex: "mem:graph:name-index",
1322
+ graphEdgeKey: "mem:graph:edge-key",
1323
+ graphNodeDegree: "mem:graph:node-degree",
1329
1324
  semantic: "mem:semantic",
1330
1325
  procedural: "mem:procedural",
1331
1326
  teamShared: (teamId) => `mem:team:${teamId}:shared`,
@@ -3482,6 +3477,12 @@ function registerSearchFunction(sdk, kv) {
3482
3477
  }
3483
3478
  const projectFilter = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
3484
3479
  const cwdFilter = typeof data.cwd === "string" && data.cwd.trim().length > 0 ? data.cwd.trim() : void 0;
3480
+ const isolated = isAgentScopeIsolated();
3481
+ const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
3482
+ const wildcardAgent = explicitAgentId === "*";
3483
+ const envAgentId = isolated ? getAgentId() : void 0;
3484
+ const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? envAgentId;
3485
+ if (isolated && !wildcardAgent && !explicitAgentId && !envAgentId) throw new Error("mem::search: AGENTMEMORY_AGENT_SCOPE=isolated is set but no agent id is available (env AGENT_ID unset and no explicit agentId in the call). Refusing to read cross-agent rows. Pass agentId: \"*\" to opt in to a wildcard read.");
3485
3486
  const format = typeof data.format === "string" ? data.format : "full";
3486
3487
  if (![
3487
3488
  "full",
@@ -3497,7 +3498,7 @@ function registerSearchFunction(sdk, kv) {
3497
3498
  const count = await rebuildIndex(kv);
3498
3499
  logger.info("Search index rebuilt", { entries: count });
3499
3500
  }
3500
- const filtering = !!(projectFilter || cwdFilter);
3501
+ const filtering = !!(projectFilter || cwdFilter || filterAgentId);
3501
3502
  const fetchLimit = filtering ? Math.max(effectiveLimit * 10, 100) : effectiveLimit;
3502
3503
  const results = idx.search(query, fetchLimit);
3503
3504
  const sessionCache = /* @__PURE__ */ new Map();
@@ -3514,9 +3515,10 @@ function registerSearchFunction(sdk, kv) {
3514
3515
  memoryProjectCache.set(obsId, proj);
3515
3516
  return proj;
3516
3517
  };
3518
+ const earlyCap = filterAgentId ? fetchLimit : effectiveLimit;
3517
3519
  const candidates = [];
3518
3520
  for (const r of results) {
3519
- if (candidates.length >= effectiveLimit) break;
3521
+ if (candidates.length >= earlyCap) break;
3520
3522
  if (filtering) {
3521
3523
  const s = await loadSession(r.sessionId);
3522
3524
  if (s) {
@@ -3538,7 +3540,10 @@ function registerSearchFunction(sdk, kv) {
3538
3540
  const enriched = [];
3539
3541
  for (let i = 0; i < candidates.length; i++) {
3540
3542
  const obs = obsResults[i];
3541
- if (obs) enriched.push({
3543
+ if (!obs) continue;
3544
+ if (filterAgentId !== void 0 && obs.agentId !== filterAgentId) continue;
3545
+ if (enriched.length >= effectiveLimit) break;
3546
+ enriched.push({
3542
3547
  observation: obs,
3543
3548
  score: candidates[i].score,
3544
3549
  sessionId: candidates[i].sessionId
@@ -3623,6 +3628,74 @@ function registerSearchFunction(sdk, kv) {
3623
3628
  });
3624
3629
  }
3625
3630
  //#endregion
3631
+ //#region src/utils/image-store.ts
3632
+ const IMAGES_DIR = join(homedir(), ".agentmemory", "images");
3633
+ const DEFAULT_MAX_BYTES = 500 * 1024 * 1024;
3634
+ function getMaxBytes() {
3635
+ return Number(process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES) || DEFAULT_MAX_BYTES;
3636
+ }
3637
+ function isManagedImagePath(filePath) {
3638
+ const resolved = resolve(filePath);
3639
+ const normalizedImagesDir = resolve(IMAGES_DIR);
3640
+ return resolved.startsWith(normalizedImagesDir + sep) || resolved === normalizedImagesDir;
3641
+ }
3642
+ function contentHash(data) {
3643
+ return createHash("sha256").update(data).digest("hex");
3644
+ }
3645
+ async function saveImageToDisk(base64Data) {
3646
+ if (!base64Data) return {
3647
+ filePath: "",
3648
+ bytesWritten: 0
3649
+ };
3650
+ if (!existsSync(IMAGES_DIR)) await mkdir(IMAGES_DIR, { recursive: true });
3651
+ let cleanBase64 = base64Data;
3652
+ let ext = "png";
3653
+ if (base64Data.startsWith("data:image/")) {
3654
+ const commaIdx = base64Data.indexOf(",");
3655
+ if (commaIdx !== -1) {
3656
+ const meta = base64Data.substring(0, commaIdx);
3657
+ if (meta.includes("jpeg") || meta.includes("jpg")) ext = "jpg";
3658
+ else if (meta.includes("webp")) ext = "webp";
3659
+ else if (meta.includes("gif")) ext = "gif";
3660
+ cleanBase64 = base64Data.substring(commaIdx + 1);
3661
+ }
3662
+ } else if (base64Data.startsWith("/9j/")) ext = "jpg";
3663
+ const filePath = join(IMAGES_DIR, `${contentHash(cleanBase64)}.${ext}`);
3664
+ if (existsSync(filePath)) return {
3665
+ filePath,
3666
+ bytesWritten: 0
3667
+ };
3668
+ await writeFile(filePath, Buffer.from(cleanBase64, "base64"));
3669
+ return {
3670
+ filePath,
3671
+ bytesWritten: (await stat(filePath)).size
3672
+ };
3673
+ }
3674
+ async function deleteImage(filePath) {
3675
+ if (!filePath) return { deletedBytes: 0 };
3676
+ if (!isManagedImagePath(filePath)) return { deletedBytes: 0 };
3677
+ try {
3678
+ if (existsSync(filePath)) {
3679
+ const size = (await stat(filePath)).size;
3680
+ await unlink(filePath);
3681
+ return { deletedBytes: size };
3682
+ }
3683
+ } catch (err) {
3684
+ console.error("[agentmemory] Failed to delete image context:", err);
3685
+ }
3686
+ return { deletedBytes: 0 };
3687
+ }
3688
+ /** Touch an image file to update its mtime (marking it as recently used for LRU eviction) */
3689
+ async function touchImage(filePath) {
3690
+ if (!filePath || !isManagedImagePath(filePath)) return;
3691
+ try {
3692
+ if (existsSync(filePath)) {
3693
+ const now = /* @__PURE__ */ new Date();
3694
+ await utimes(filePath, now, now);
3695
+ }
3696
+ } catch (err) {}
3697
+ }
3698
+ //#endregion
3626
3699
  //#region src/functions/observe.ts
3627
3700
  function extractImage(d) {
3628
3701
  if (!d) return void 0;
@@ -3700,10 +3773,9 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
3700
3773
  const inheritedAgentId = existingSession ? existingSession.agentId : getAgentId();
3701
3774
  if (inheritedAgentId) raw.agentId = inheritedAgentId;
3702
3775
  if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) {
3703
- const { saveImageToDisk } = await Promise.resolve().then(() => image_store_exports);
3704
3776
  const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
3705
3777
  raw.imageData = filePath;
3706
- const { incrementImageRef } = await Promise.resolve().then(() => image_refs_exports);
3778
+ const { incrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
3707
3779
  await incrementImageRef(kv, filePath);
3708
3780
  sdk.trigger({
3709
3781
  function_id: "mem::disk-size-delta",
@@ -3723,13 +3795,13 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
3723
3795
  try {
3724
3796
  await kv.set(KV.observations(payload.sessionId), obsId, raw);
3725
3797
  } catch (error) {
3726
- if (raw.imageData) {
3727
- const { deleteImage } = await Promise.resolve().then(() => image_store_exports);
3728
- const { deletedBytes } = await deleteImage(raw.imageData);
3729
- if (deletedBytes > 0) sdk.trigger({
3730
- function_id: "mem::disk-size-delta",
3731
- payload: { deltaBytes: -deletedBytes },
3732
- action: TriggerAction.Void()
3798
+ if (raw.imageData) try {
3799
+ const { decrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
3800
+ await decrementImageRef(kv, sdk, raw.imageData);
3801
+ } catch (rollbackError) {
3802
+ logger.error("Failed to roll back image ref after observation write failure", {
3803
+ imageRef: raw.imageData,
3804
+ error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
3733
3805
  });
3734
3806
  }
3735
3807
  throw error;
@@ -3851,115 +3923,6 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
3851
3923
  });
3852
3924
  }
3853
3925
  //#endregion
3854
- //#region src/utils/image-store.ts
3855
- var image_store_exports = /* @__PURE__ */ __exportAll({
3856
- IMAGES_DIR: () => IMAGES_DIR,
3857
- deleteImage: () => deleteImage,
3858
- getMaxBytes: () => getMaxBytes,
3859
- isManagedImagePath: () => isManagedImagePath,
3860
- saveImageToDisk: () => saveImageToDisk,
3861
- touchImage: () => touchImage
3862
- });
3863
- const IMAGES_DIR = join(homedir(), ".agentmemory", "images");
3864
- const DEFAULT_MAX_BYTES = 500 * 1024 * 1024;
3865
- function getMaxBytes() {
3866
- return Number(process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES) || DEFAULT_MAX_BYTES;
3867
- }
3868
- function isManagedImagePath(filePath) {
3869
- const resolved = resolve(filePath);
3870
- const normalizedImagesDir = resolve(IMAGES_DIR);
3871
- return resolved.startsWith(normalizedImagesDir + sep) || resolved === normalizedImagesDir;
3872
- }
3873
- function contentHash(data) {
3874
- return createHash("sha256").update(data).digest("hex");
3875
- }
3876
- async function saveImageToDisk(base64Data) {
3877
- if (!base64Data) return {
3878
- filePath: "",
3879
- bytesWritten: 0
3880
- };
3881
- if (!existsSync(IMAGES_DIR)) await mkdir(IMAGES_DIR, { recursive: true });
3882
- let cleanBase64 = base64Data;
3883
- let ext = "png";
3884
- if (base64Data.startsWith("data:image/")) {
3885
- const commaIdx = base64Data.indexOf(",");
3886
- if (commaIdx !== -1) {
3887
- const meta = base64Data.substring(0, commaIdx);
3888
- if (meta.includes("jpeg") || meta.includes("jpg")) ext = "jpg";
3889
- else if (meta.includes("webp")) ext = "webp";
3890
- else if (meta.includes("gif")) ext = "gif";
3891
- cleanBase64 = base64Data.substring(commaIdx + 1);
3892
- }
3893
- } else if (base64Data.startsWith("/9j/")) ext = "jpg";
3894
- const filePath = join(IMAGES_DIR, `${contentHash(cleanBase64)}.${ext}`);
3895
- if (existsSync(filePath)) return {
3896
- filePath,
3897
- bytesWritten: 0
3898
- };
3899
- await writeFile(filePath, Buffer.from(cleanBase64, "base64"));
3900
- return {
3901
- filePath,
3902
- bytesWritten: (await stat(filePath)).size
3903
- };
3904
- }
3905
- async function deleteImage(filePath) {
3906
- if (!filePath) return { deletedBytes: 0 };
3907
- if (!isManagedImagePath(filePath)) return { deletedBytes: 0 };
3908
- try {
3909
- if (existsSync(filePath)) {
3910
- const size = (await stat(filePath)).size;
3911
- await unlink(filePath);
3912
- return { deletedBytes: size };
3913
- }
3914
- } catch (err) {
3915
- console.error("[agentmemory] Failed to delete image context:", err);
3916
- }
3917
- return { deletedBytes: 0 };
3918
- }
3919
- /** Touch an image file to update its mtime (marking it as recently used for LRU eviction) */
3920
- async function touchImage(filePath) {
3921
- if (!filePath || !isManagedImagePath(filePath)) return;
3922
- try {
3923
- if (existsSync(filePath)) {
3924
- const now = /* @__PURE__ */ new Date();
3925
- await utimes(filePath, now, now);
3926
- }
3927
- } catch (err) {}
3928
- }
3929
- //#endregion
3930
- //#region src/functions/image-refs.ts
3931
- var image_refs_exports = /* @__PURE__ */ __exportAll({
3932
- decrementImageRef: () => decrementImageRef,
3933
- getImageRefCount: () => getImageRefCount,
3934
- incrementImageRef: () => incrementImageRef
3935
- });
3936
- async function getImageRefCount(kv, filePath) {
3937
- const count = await kv.get(KV.imageRefs, filePath);
3938
- return count ? Number(count) : 0;
3939
- }
3940
- async function incrementImageRef(kv, filePath) {
3941
- return withKeyedLock(`imgRef:${filePath}`, async () => {
3942
- const current = await getImageRefCount(kv, filePath);
3943
- await kv.set(KV.imageRefs, filePath, current + 1);
3944
- await touchImage(filePath);
3945
- });
3946
- }
3947
- async function decrementImageRef(kv, sdk, filePath) {
3948
- return withKeyedLock(`imgRef:${filePath}`, async () => {
3949
- const current = await getImageRefCount(kv, filePath);
3950
- if (current <= 1) {
3951
- await kv.delete(KV.imageEmbeddings, filePath);
3952
- await kv.delete(KV.imageRefs, filePath);
3953
- const { deletedBytes } = await deleteImage(filePath);
3954
- if (deletedBytes > 0) sdk.trigger({
3955
- function_id: "mem::disk-size-delta",
3956
- payload: { deltaBytes: -deletedBytes },
3957
- action: TriggerAction.Void()
3958
- });
3959
- } else await kv.set(KV.imageRefs, filePath, current - 1);
3960
- });
3961
- }
3962
- //#endregion
3963
3926
  //#region src/functions/image-quota-cleanup.ts
3964
3927
  const GRACE_PERIOD_MS = 3e4;
3965
3928
  function registerImageQuotaCleanup(sdk, kv) {
@@ -4007,6 +3970,7 @@ function registerImageQuotaCleanup(sdk, kv) {
4007
3970
  await withKeyedLock(`imgRef:${f.filePath}`, async () => {
4008
3971
  let refCount;
4009
3972
  try {
3973
+ const { getImageRefCount } = await import("./image-refs-BKLmp_sD.mjs");
4010
3974
  refCount = await getImageRefCount(kv, f.filePath);
4011
3975
  } catch (err) {
4012
3976
  logger.error("Failed to read refCount; skipping eviction", {
@@ -5097,6 +5061,12 @@ function registerContextFunction(sdk, kv, tokenBudget) {
5097
5061
  sdk.registerFunction("mem::context", async (data) => {
5098
5062
  const budget = data.budget || tokenBudget;
5099
5063
  const blocks = [];
5064
+ const isolated = isAgentScopeIsolated();
5065
+ const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
5066
+ const wildcardAgent = explicitAgentId === "*";
5067
+ const envAgentId = isolated ? getAgentId() : void 0;
5068
+ const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? envAgentId;
5069
+ if (isolated && !wildcardAgent && !explicitAgentId && !envAgentId) throw new Error("mem::context: AGENTMEMORY_AGENT_SCOPE=isolated is set but no agent id is available (env AGENT_ID unset and no explicit agentId in the call). Refusing to read cross-agent rows. Pass agentId: \"*\" to opt in to a wildcard read.");
5100
5070
  const [pinnedSlots, profile, lessons] = await Promise.all([
5101
5071
  isSlotsEnabled() ? listPinnedSlots(kv).catch(() => []) : Promise.resolve([]),
5102
5072
  kv.get(KV.profiles, data.project).catch(() => null),
@@ -5143,7 +5113,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
5143
5113
  sourceIds: relevantLessons.map((l) => l.id)
5144
5114
  });
5145
5115
  }
5146
- const sessions = (await kv.list(KV.sessions)).filter((s) => s.project === data.project && s.id !== data.sessionId).sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).slice(0, 10);
5116
+ const sessions = (await kv.list(KV.sessions)).filter((s) => s.project === data.project && s.id !== data.sessionId && (filterAgentId === void 0 || s.agentId === filterAgentId)).sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).slice(0, 10);
5147
5117
  const summariesPerSession = await Promise.all(sessions.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
5148
5118
  const sessionsNeedingObs = [];
5149
5119
  for (let i = 0; i < sessions.length; i++) {
@@ -6133,7 +6103,7 @@ function registerRememberFunction(sdk, kv) {
6133
6103
  const deletedMemoryIds = [];
6134
6104
  const deletedObservationIds = [];
6135
6105
  let deletedSession = false;
6136
- const { decrementImageRef } = await Promise.resolve().then(() => image_refs_exports);
6106
+ const { decrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
6137
6107
  if (data.memoryId) {
6138
6108
  const mem = await kv.get(KV.memories, data.memoryId);
6139
6109
  await kv.delete(KV.memories, data.memoryId);
@@ -6240,7 +6210,7 @@ async function runRecoveredSessionConsolidation(sdk) {
6240
6210
  function registerEvictFunction(sdk, kv) {
6241
6211
  sdk.registerFunction("mem::evict", async (data) => {
6242
6212
  const dryRun = data?.dryRun ?? false;
6243
- const { decrementImageRef } = await Promise.resolve().then(() => image_refs_exports);
6213
+ const { decrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
6244
6214
  const configOverride = await kv.get(KV.config, "eviction").catch(() => null);
6245
6215
  const cfg = {
6246
6216
  ...DEFAULTS$1,
@@ -6688,7 +6658,7 @@ async function findByKeyword(kv, keyword, project) {
6688
6658
  }
6689
6659
  //#endregion
6690
6660
  //#region src/version.ts
6691
- const VERSION = "0.9.26";
6661
+ const VERSION = "0.9.28";
6692
6662
  //#endregion
6693
6663
  //#region src/telemetry/setup.ts
6694
6664
  const OTEL_CONFIG = {
@@ -6763,7 +6733,10 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
6763
6733
  sdk.registerFunction("mem::smart-search", async (data) => {
6764
6734
  const isolated = isAgentScopeIsolated();
6765
6735
  const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
6766
- const filterAgentId = explicitAgentId === "*" ? void 0 : explicitAgentId ?? (isolated ? getAgentId() : void 0);
6736
+ const wildcardAgent = explicitAgentId === "*";
6737
+ const envAgentId = isolated ? getAgentId() : void 0;
6738
+ const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? envAgentId;
6739
+ if (isolated && !wildcardAgent && !explicitAgentId && !envAgentId) throw new Error("mem::smart-search: AGENTMEMORY_AGENT_SCOPE=isolated is set but no agent id is available (env AGENT_ID unset and no explicit agentId in the call). Refusing to read cross-agent rows. Pass agentId: \"*\" to opt in to a wildcard read.");
6767
6740
  if (data.expandIds && data.expandIds.length > 0) {
6768
6741
  const raw = data.expandIds.slice(0, 20);
6769
6742
  const items = raw.map((entry) => {
@@ -7055,7 +7028,7 @@ function registerAutoForgetFunction(sdk, kv) {
7055
7028
  sdk.registerFunction("mem::auto-forget", async (data) => {
7056
7029
  const dryRun = data?.dryRun ?? false;
7057
7030
  const now = Date.now();
7058
- const { decrementImageRef } = await Promise.resolve().then(() => image_refs_exports);
7031
+ const { decrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
7059
7032
  const result = {
7060
7033
  ttlExpired: [],
7061
7034
  contradictions: [],
@@ -7314,7 +7287,9 @@ function registerExportImportFunction(sdk, kv) {
7314
7287
  "0.9.23",
7315
7288
  "0.9.24",
7316
7289
  "0.9.25",
7317
- "0.9.26"
7290
+ "0.9.26",
7291
+ "0.9.27",
7292
+ "0.9.28"
7318
7293
  ]).has(importData.version)) return {
7319
7294
  success: false,
7320
7295
  error: `Unsupported export version: ${importData.version}`
@@ -7838,19 +7813,167 @@ function buildGraphExtractionPrompt(observations) {
7838
7813
  //#region src/functions/graph.ts
7839
7814
  const DEFAULT_GRAPH_QUERY_LIMIT = 500;
7840
7815
  const MAX_GRAPH_QUERY_LIMIT = 5e3;
7841
- function resolvePagination(rawLimit, rawOffset) {
7816
+ const SNAPSHOT_TOP_NODES = DEFAULT_GRAPH_QUERY_LIMIT;
7817
+ const SNAPSHOT_KEY = "current";
7818
+ const LIVE_ENUMERATION_BUDGET_MS = 6e3;
7819
+ function withTimeout(p, ms, label) {
7820
+ return new Promise((resolve, reject) => {
7821
+ const t = setTimeout(() => reject(/* @__PURE__ */ new Error(`${label}: exceeded ${ms}ms budget`)), ms);
7822
+ p.then((v) => {
7823
+ clearTimeout(t);
7824
+ resolve(v);
7825
+ }, (err) => {
7826
+ clearTimeout(t);
7827
+ reject(err);
7828
+ });
7829
+ });
7830
+ }
7831
+ function emptySnapshot() {
7842
7832
  return {
7843
- limit: Math.max(1, Math.min(typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.floor(rawLimit) : DEFAULT_GRAPH_QUERY_LIMIT, MAX_GRAPH_QUERY_LIMIT)),
7844
- offset: Math.max(0, typeof rawOffset === "number" && Number.isFinite(rawOffset) ? Math.floor(rawOffset) : 0)
7833
+ version: 1,
7834
+ topNodes: [],
7835
+ topEdges: [],
7836
+ topDegrees: {},
7837
+ stats: {
7838
+ totalNodes: 0,
7839
+ totalEdges: 0,
7840
+ nodesByType: {},
7841
+ edgesByType: {}
7842
+ },
7843
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
7844
+ dirty: true
7845
7845
  };
7846
7846
  }
7847
- function rankByDegree(nodes, edges) {
7847
+ async function readSnapshot(kv) {
7848
+ try {
7849
+ const snap = await kv.get(KV.graphSnapshot, SNAPSHOT_KEY);
7850
+ if (snap && typeof snap === "object" && snap.version === 1) return snap;
7851
+ return null;
7852
+ } catch (err) {
7853
+ logger.warn("Graph snapshot read failed", { error: err instanceof Error ? err.message : String(err) });
7854
+ return null;
7855
+ }
7856
+ }
7857
+ function buildSnapshotFromArrays(nodes, edges) {
7858
+ const liveNodes = nodes.filter((n) => !n.stale);
7859
+ const liveEdges = edges.filter((e) => !e.stale);
7848
7860
  const degree = /* @__PURE__ */ new Map();
7849
- for (const edge of edges) {
7850
- degree.set(edge.sourceNodeId, (degree.get(edge.sourceNodeId) ?? 0) + 1);
7851
- degree.set(edge.targetNodeId, (degree.get(edge.targetNodeId) ?? 0) + 1);
7861
+ for (const e of liveEdges) {
7862
+ degree.set(e.sourceNodeId, (degree.get(e.sourceNodeId) ?? 0) + 1);
7863
+ degree.set(e.targetNodeId, (degree.get(e.targetNodeId) ?? 0) + 1);
7864
+ }
7865
+ const ranked = [...liveNodes].sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0)).slice(0, SNAPSHOT_TOP_NODES);
7866
+ const rankedIds = new Set(ranked.map((n) => n.id));
7867
+ const topEdges = liveEdges.filter((e) => rankedIds.has(e.sourceNodeId) && rankedIds.has(e.targetNodeId));
7868
+ const topDegrees = {};
7869
+ for (const n of ranked) topDegrees[n.id] = degree.get(n.id) ?? 0;
7870
+ const nodesByType = {};
7871
+ for (const n of liveNodes) nodesByType[n.type] = (nodesByType[n.type] || 0) + 1;
7872
+ const edgesByType = {};
7873
+ for (const e of liveEdges) edgesByType[e.type] = (edgesByType[e.type] || 0) + 1;
7874
+ return {
7875
+ version: 1,
7876
+ topNodes: ranked,
7877
+ topEdges,
7878
+ topDegrees,
7879
+ stats: {
7880
+ totalNodes: liveNodes.length,
7881
+ totalEdges: liveEdges.length,
7882
+ nodesByType,
7883
+ edgesByType
7884
+ },
7885
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7886
+ dirty: false
7887
+ };
7888
+ }
7889
+ function paginateFromSnapshot(snap, filterType, limit, offset) {
7890
+ const filteredNodes = filterType ? snap.topNodes.filter((n) => n.type === filterType) : snap.topNodes;
7891
+ const total = filterType ? snap.stats.nodesByType[filterType] ?? 0 : snap.stats.totalNodes;
7892
+ const pageNodes = filteredNodes.slice(offset, offset + limit);
7893
+ const pageIds = new Set(pageNodes.map((n) => n.id));
7894
+ return {
7895
+ nodes: pageNodes,
7896
+ edges: snap.topEdges.filter((e) => pageIds.has(e.sourceNodeId) && pageIds.has(e.targetNodeId)),
7897
+ depth: 0,
7898
+ totalNodes: total,
7899
+ totalEdges: snap.stats.totalEdges,
7900
+ truncated: total > pageNodes.length,
7901
+ limit,
7902
+ offset,
7903
+ fromSnapshot: true
7904
+ };
7905
+ }
7906
+ const REBUILD_SAFE_NODE_CEILING = 25e3;
7907
+ function nameIndexKey(type, name) {
7908
+ return `${type}|${name}`;
7909
+ }
7910
+ function edgeIndexKey(sourceNodeId, targetNodeId, type) {
7911
+ return `${sourceNodeId}|${targetNodeId}|${type}`;
7912
+ }
7913
+ async function applyDegreeDelta(kv, snap, nodeId, delta) {
7914
+ const prev = await kv.get(KV.graphNodeDegree, nodeId) ?? 0;
7915
+ const next = Math.max(0, prev + delta);
7916
+ await kv.set(KV.graphNodeDegree, nodeId, next);
7917
+ if (snap.topNodes.findIndex((n) => n.id === nodeId) !== -1) {
7918
+ snap.topDegrees[nodeId] = next;
7919
+ snap.topNodes.sort((a, b) => (snap.topDegrees[b.id] ?? 0) - (snap.topDegrees[a.id] ?? 0));
7920
+ return next;
7921
+ }
7922
+ if (snap.topNodes.length < SNAPSHOT_TOP_NODES) {
7923
+ const node = await kv.get(KV.graphNodes, nodeId);
7924
+ if (node && !node.stale) {
7925
+ snap.topNodes.push(node);
7926
+ snap.topDegrees[node.id] = next;
7927
+ snap.topNodes.sort((a, b) => (snap.topDegrees[b.id] ?? 0) - (snap.topDegrees[a.id] ?? 0));
7928
+ }
7929
+ return next;
7930
+ }
7931
+ const tailEntry = snap.topNodes[snap.topNodes.length - 1];
7932
+ if (!tailEntry) return next;
7933
+ if (next > (snap.topDegrees[tailEntry.id] ?? 0)) {
7934
+ const node = await kv.get(KV.graphNodes, nodeId);
7935
+ if (node && !node.stale) {
7936
+ const evicted = snap.topNodes.pop();
7937
+ if (evicted) delete snap.topDegrees[evicted.id];
7938
+ snap.topNodes.push(node);
7939
+ snap.topDegrees[node.id] = next;
7940
+ snap.topNodes.sort((a, b) => (snap.topDegrees[b.id] ?? 0) - (snap.topDegrees[a.id] ?? 0));
7941
+ }
7942
+ }
7943
+ return next;
7944
+ }
7945
+ function snapshotPushEdgeIfBothInTop(snap, edge) {
7946
+ const topIds = new Set(snap.topNodes.map((n) => n.id));
7947
+ if (topIds.has(edge.sourceNodeId) && topIds.has(edge.targetNodeId)) {
7948
+ if (!snap.topEdges.find((e) => e.id === edge.id)) snap.topEdges.push(edge);
7852
7949
  }
7853
- return [...nodes].sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0));
7950
+ }
7951
+ function mergeNode(existing, incoming, obsIds, capturedAt) {
7952
+ return {
7953
+ ...existing,
7954
+ sourceObservationIds: [...new Set([
7955
+ ...existing.sourceObservationIds,
7956
+ ...incoming.sourceObservationIds,
7957
+ ...obsIds
7958
+ ])],
7959
+ properties: {
7960
+ ...existing.properties,
7961
+ ...incoming.properties
7962
+ },
7963
+ updatedAt: capturedAt
7964
+ };
7965
+ }
7966
+ function mergeEdge(existing, obsIds) {
7967
+ return {
7968
+ ...existing,
7969
+ sourceObservationIds: [...new Set([...existing.sourceObservationIds, ...obsIds])]
7970
+ };
7971
+ }
7972
+ function resolvePagination(rawLimit, rawOffset) {
7973
+ return {
7974
+ limit: Math.max(1, Math.min(typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.floor(rawLimit) : DEFAULT_GRAPH_QUERY_LIMIT, MAX_GRAPH_QUERY_LIMIT)),
7975
+ offset: Math.max(0, typeof rawOffset === "number" && Number.isFinite(rawOffset) ? Math.floor(rawOffset) : 0)
7976
+ };
7854
7977
  }
7855
7978
  function paginate(nodes, allEdges, depth, limit, offset) {
7856
7979
  const totalNodes = nodes.length;
@@ -7947,45 +8070,76 @@ function registerGraphFunction(sdk, kv, provider) {
7947
8070
  const response = await provider.compress(GRAPH_EXTRACTION_SYSTEM, prompt);
7948
8071
  const obsIds = data.observations.map((o) => o.id);
7949
8072
  const { nodes, edges } = parseGraphXml(response, obsIds);
7950
- const existingNodes = await kv.list(KV.graphNodes);
7951
- const existingEdges = await kv.list(KV.graphEdges);
8073
+ const snap = await readSnapshot(kv) ?? emptySnapshot();
8074
+ const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
8075
+ let newNodeCount = 0;
8076
+ let newEdgeCount = 0;
8077
+ const newEdgesForTopCheck = [];
7952
8078
  for (const node of nodes) {
7953
- const existing = existingNodes.find((n) => n.name === node.name && n.type === node.type);
8079
+ const indexKey = nameIndexKey(node.type, node.name);
8080
+ const existingId = await kv.get(KV.graphNameIndex, indexKey);
8081
+ let existing = null;
8082
+ if (existingId) {
8083
+ existing = await kv.get(KV.graphNodes, existingId);
8084
+ if (existing && snap.resetAt && typeof existing.createdAt === "string" && existing.createdAt < snap.resetAt) existing = null;
8085
+ }
7954
8086
  if (existing) {
7955
- const merged = {
7956
- ...existing,
7957
- sourceObservationIds: [...new Set([...existing.sourceObservationIds, ...obsIds])],
7958
- properties: {
7959
- ...existing.properties,
7960
- ...node.properties
7961
- }
7962
- };
8087
+ const merged = mergeNode(existing, node, obsIds, capturedAt);
7963
8088
  await kv.set(KV.graphNodes, existing.id, merged);
7964
- const idx = existingNodes.findIndex((n) => n.id === existing.id);
7965
- if (idx !== -1) existingNodes[idx] = merged;
8089
+ const topIdx = snap.topNodes.findIndex((n) => n.id === existing.id);
8090
+ if (topIdx !== -1) snap.topNodes[topIdx] = merged;
7966
8091
  } else {
7967
8092
  await kv.set(KV.graphNodes, node.id, node);
7968
- existingNodes.push(node);
8093
+ await kv.set(KV.graphNameIndex, indexKey, node.id);
8094
+ await kv.set(KV.graphNodeDegree, node.id, 0);
8095
+ snap.stats.totalNodes += 1;
8096
+ snap.stats.nodesByType[node.type] = (snap.stats.nodesByType[node.type] ?? 0) + 1;
8097
+ newNodeCount += 1;
8098
+ if (snap.topNodes.length < SNAPSHOT_TOP_NODES) {
8099
+ snap.topNodes.push(node);
8100
+ snap.topDegrees[node.id] = 0;
8101
+ }
7969
8102
  }
7970
8103
  }
7971
8104
  for (const edge of edges) {
7972
- const edgeKey = `${edge.sourceNodeId}|${edge.targetNodeId}|${edge.type}`;
7973
- const existingEdge = existingEdges.find((e) => `${e.sourceNodeId}|${e.targetNodeId}|${e.type}` === edgeKey);
7974
- if (existingEdge) {
7975
- existingEdge.sourceObservationIds = [...new Set([...existingEdge.sourceObservationIds, ...obsIds])];
7976
- await kv.set(KV.graphEdges, existingEdge.id, existingEdge);
8105
+ const eKey = edgeIndexKey(edge.sourceNodeId, edge.targetNodeId, edge.type);
8106
+ const existingId = await kv.get(KV.graphEdgeKey, eKey);
8107
+ let existing = null;
8108
+ if (existingId) {
8109
+ existing = await kv.get(KV.graphEdges, existingId);
8110
+ if (existing && snap.resetAt && typeof existing.createdAt === "string" && existing.createdAt < snap.resetAt) existing = null;
8111
+ }
8112
+ if (existing) {
8113
+ const merged = mergeEdge(existing, obsIds);
8114
+ await kv.set(KV.graphEdges, existing.id, merged);
8115
+ const topIdx = snap.topEdges.findIndex((e) => e.id === existing.id);
8116
+ if (topIdx !== -1) snap.topEdges[topIdx] = merged;
7977
8117
  } else {
7978
8118
  await kv.set(KV.graphEdges, edge.id, edge);
7979
- existingEdges.push(edge);
8119
+ await kv.set(KV.graphEdgeKey, eKey, edge.id);
8120
+ snap.stats.totalEdges += 1;
8121
+ snap.stats.edgesByType[edge.type] = (snap.stats.edgesByType[edge.type] ?? 0) + 1;
8122
+ newEdgeCount += 1;
8123
+ await applyDegreeDelta(kv, snap, edge.sourceNodeId, 1);
8124
+ await applyDegreeDelta(kv, snap, edge.targetNodeId, 1);
8125
+ newEdgesForTopCheck.push(edge);
7980
8126
  }
7981
8127
  }
8128
+ for (const edge of newEdgesForTopCheck) snapshotPushEdgeIfBothInTop(snap, edge);
8129
+ if (newNodeCount > 0 || newEdgeCount > 0) {
8130
+ snap.updatedAt = capturedAt;
8131
+ snap.dirty = false;
8132
+ await kv.set(KV.graphSnapshot, SNAPSHOT_KEY, snap);
8133
+ }
7982
8134
  await recordAudit(kv, "observe", "mem::graph-extract", obsIds, {
7983
8135
  nodesExtracted: nodes.length,
7984
8136
  edgesExtracted: edges.length
7985
8137
  });
7986
8138
  logger.info("Graph extraction complete", {
7987
8139
  nodes: nodes.length,
7988
- edges: edges.length
8140
+ edges: edges.length,
8141
+ newNodes: newNodeCount,
8142
+ newEdges: newEdgeCount
7989
8143
  });
7990
8144
  return {
7991
8145
  success: true,
@@ -8002,10 +8156,49 @@ function registerGraphFunction(sdk, kv, provider) {
8002
8156
  }
8003
8157
  });
8004
8158
  sdk.registerFunction("mem::graph-query", async (data) => {
8005
- const allNodes = (await kv.list(KV.graphNodes)).filter((n) => !n.stale);
8006
- const allEdges = (await kv.list(KV.graphEdges)).filter((e) => !e.stale);
8007
8159
  const maxDepth = Math.min(data.maxDepth || 3, 5);
8008
8160
  const { limit, offset } = resolvePagination(data.limit, data.offset);
8161
+ if (!data.query && !data.startNodeId) {
8162
+ const snap = await readSnapshot(kv);
8163
+ if (snap && snap.stats.totalNodes > 0) return paginateFromSnapshot(snap, data.nodeType, limit, offset);
8164
+ return {
8165
+ nodes: [],
8166
+ edges: [],
8167
+ depth: 0,
8168
+ totalNodes: 0,
8169
+ totalEdges: 0,
8170
+ truncated: false,
8171
+ limit,
8172
+ offset,
8173
+ warning: "No graph snapshot available. Either no graph has been extracted yet, or you are on a legacy corpus from a pre-#814 agentmemory build. Run POST /agentmemory/graph/snapshot-rebuild (safe up to ~25K nodes) or POST /agentmemory/graph/reset to wipe and let future extracts repopulate."
8174
+ };
8175
+ }
8176
+ let allNodes;
8177
+ let allEdges;
8178
+ try {
8179
+ const [rawNodes, rawEdges] = await withTimeout(Promise.all([kv.list(KV.graphNodes), kv.list(KV.graphEdges)]), LIVE_ENUMERATION_BUDGET_MS, "graph-query enumeration");
8180
+ allNodes = rawNodes.filter((n) => !n.stale);
8181
+ allEdges = rawEdges.filter((e) => !e.stale);
8182
+ } catch (err) {
8183
+ const msg = err instanceof Error ? err.message : String(err);
8184
+ logger.warn("Graph query enumeration timed out, using snapshot", { error: msg });
8185
+ const snap = await readSnapshot(kv);
8186
+ if (snap) return {
8187
+ ...paginateFromSnapshot(snap, data.nodeType, limit, offset),
8188
+ warning: "Live graph enumeration exceeded budget. Query / startNodeId paths degrade on >25K-node corpora until a per-node edge index lands. Result reflects top-degree snapshot, not the requested walk."
8189
+ };
8190
+ return {
8191
+ nodes: [],
8192
+ edges: [],
8193
+ depth: 0,
8194
+ totalNodes: 0,
8195
+ totalEdges: 0,
8196
+ truncated: false,
8197
+ limit,
8198
+ offset,
8199
+ warning: "Graph enumeration exceeded budget and no snapshot is available."
8200
+ };
8201
+ }
8009
8202
  if (data.query) {
8010
8203
  const lower = data.query.toLowerCase();
8011
8204
  return paginate(allNodes.filter((n) => n.name.toLowerCase().includes(lower) || Object.values(n.properties).some((v) => typeof v === "string" && v.toLowerCase().includes(lower))), allEdges, 0, limit, offset);
@@ -8042,22 +8235,116 @@ function registerGraphFunction(sdk, kv, provider) {
8042
8235
  }
8043
8236
  return paginate(resultNodes, resultEdges, maxDepth, limit, offset);
8044
8237
  }
8045
- let filtered = allNodes;
8046
- if (data.nodeType) filtered = allNodes.filter((n) => n.type === data.nodeType);
8047
- return paginate(rankByDegree(filtered, allEdges), allEdges, 0, limit, offset);
8238
+ return paginate([], [], 0, limit, offset);
8048
8239
  });
8049
8240
  sdk.registerFunction("mem::graph-stats", async () => {
8050
- const nodes = await kv.list(KV.graphNodes);
8051
- const edges = await kv.list(KV.graphEdges);
8052
- const nodesByType = {};
8053
- for (const n of nodes) nodesByType[n.type] = (nodesByType[n.type] || 0) + 1;
8054
- const edgesByType = {};
8055
- for (const e of edges) edgesByType[e.type] = (edgesByType[e.type] || 0) + 1;
8241
+ const snap = await readSnapshot(kv);
8242
+ if (snap) return {
8243
+ ...snap.stats,
8244
+ fromSnapshot: true,
8245
+ updatedAt: snap.updatedAt,
8246
+ ...snap.dirty ? { warning: "Snapshot is marked dirty (write was in-flight when read). Counts are eventually consistent." } : {}
8247
+ };
8056
8248
  return {
8057
- totalNodes: nodes.length,
8058
- totalEdges: edges.length,
8059
- nodesByType,
8060
- edgesByType
8249
+ totalNodes: 0,
8250
+ totalEdges: 0,
8251
+ nodesByType: {},
8252
+ edgesByType: {},
8253
+ fromSnapshot: false,
8254
+ warning: "No graph snapshot available. Run POST /agentmemory/graph/snapshot-rebuild (safe up to ~25K nodes) or POST /agentmemory/graph/reset to wipe and let future extracts repopulate."
8255
+ };
8256
+ });
8257
+ sdk.registerFunction("mem::graph-snapshot-rebuild", async (data) => {
8258
+ const started = Date.now();
8259
+ const forceRebuild = data?.force === true;
8260
+ try {
8261
+ if (!await readSnapshot(kv) && !forceRebuild) {
8262
+ logger.warn("Graph snapshot rebuild refused: no prior snapshot", { hint: "legacy corpus or empty store" });
8263
+ return {
8264
+ success: false,
8265
+ legacyCorpus: true,
8266
+ error: "No prior snapshot found. Rebuild would call kv.list on KV.graphNodes/Edges, which heartbeat-crashes the worker on corpora past the iii state response budget (~25K nodes). Either (a) call POST /agentmemory/graph/reset to drop into incremental-only mode and rebuild from new extracts, or (b) re-send with `force: true` if you're certain the corpus is small."
8267
+ };
8268
+ }
8269
+ } catch (err) {
8270
+ const msg = err instanceof Error ? err.message : String(err);
8271
+ logger.warn("Graph snapshot pre-flight read failed", { error: msg });
8272
+ }
8273
+ try {
8274
+ const [nodes, edges] = await withTimeout(Promise.all([kv.list(KV.graphNodes), kv.list(KV.graphEdges)]), LIVE_ENUMERATION_BUDGET_MS, "graph-snapshot-rebuild enumeration");
8275
+ if (nodes.length > REBUILD_SAFE_NODE_CEILING) {
8276
+ logger.warn("Graph snapshot rebuild aborted: corpus too large", {
8277
+ totalNodes: nodes.length,
8278
+ ceiling: REBUILD_SAFE_NODE_CEILING
8279
+ });
8280
+ return {
8281
+ success: false,
8282
+ tooLarge: true,
8283
+ totalNodes: nodes.length,
8284
+ ceiling: REBUILD_SAFE_NODE_CEILING,
8285
+ error: `Corpus has ${nodes.length} graph nodes; safe-rebuild ceiling is ${REBUILD_SAFE_NODE_CEILING}. Run POST /agentmemory/graph/reset to wipe and let future extracts rebuild incrementally.`
8286
+ };
8287
+ }
8288
+ const liveNodes = nodes.filter((n) => !n.stale);
8289
+ const liveEdges = edges.filter((e) => !e.stale);
8290
+ const degree = /* @__PURE__ */ new Map();
8291
+ for (const e of liveEdges) {
8292
+ degree.set(e.sourceNodeId, (degree.get(e.sourceNodeId) ?? 0) + 1);
8293
+ degree.set(e.targetNodeId, (degree.get(e.targetNodeId) ?? 0) + 1);
8294
+ }
8295
+ const BATCH_SIZE = 100;
8296
+ for (let i = 0; i < liveNodes.length; i += BATCH_SIZE) {
8297
+ const batch = liveNodes.slice(i, i + BATCH_SIZE);
8298
+ await Promise.all(batch.flatMap((n) => [kv.set(KV.graphNameIndex, nameIndexKey(n.type, n.name), n.id), kv.set(KV.graphNodeDegree, n.id, degree.get(n.id) ?? 0)]));
8299
+ }
8300
+ for (let i = 0; i < liveEdges.length; i += BATCH_SIZE) {
8301
+ const batch = liveEdges.slice(i, i + BATCH_SIZE);
8302
+ await Promise.all(batch.map((e) => kv.set(KV.graphEdgeKey, edgeIndexKey(e.sourceNodeId, e.targetNodeId, e.type), e.id)));
8303
+ }
8304
+ const snap = buildSnapshotFromArrays(nodes, edges);
8305
+ await kv.set(KV.graphSnapshot, SNAPSHOT_KEY, snap);
8306
+ const tookMs = Date.now() - started;
8307
+ logger.info("Graph snapshot rebuilt", {
8308
+ totalNodes: snap.stats.totalNodes,
8309
+ totalEdges: snap.stats.totalEdges,
8310
+ topNodes: snap.topNodes.length,
8311
+ topEdges: snap.topEdges.length,
8312
+ tookMs
8313
+ });
8314
+ return {
8315
+ success: true,
8316
+ ...snap.stats,
8317
+ topNodes: snap.topNodes.length,
8318
+ topEdges: snap.topEdges.length,
8319
+ updatedAt: snap.updatedAt,
8320
+ tookMs
8321
+ };
8322
+ } catch (err) {
8323
+ const msg = err instanceof Error ? err.message : String(err);
8324
+ logger.error("Graph snapshot rebuild failed", { error: msg });
8325
+ return {
8326
+ success: false,
8327
+ error: msg
8328
+ };
8329
+ }
8330
+ });
8331
+ sdk.registerFunction("mem::graph-reset", async () => {
8332
+ const started = Date.now();
8333
+ const resetSnapshot = {
8334
+ ...emptySnapshot(),
8335
+ resetAt: (/* @__PURE__ */ new Date()).toISOString()
8336
+ };
8337
+ await kv.set(KV.graphSnapshot, SNAPSHOT_KEY, resetSnapshot);
8338
+ const counts = { [KV.graphSnapshot]: 1 };
8339
+ const tookMs = Date.now() - started;
8340
+ logger.info("Graph state reset", {
8341
+ counts,
8342
+ tookMs
8343
+ });
8344
+ return {
8345
+ success: true,
8346
+ cleared: counts,
8347
+ tookMs
8061
8348
  };
8062
8349
  });
8063
8350
  }
@@ -13880,7 +14167,7 @@ function registerRetentionFunctions(sdk, kv) {
13880
14167
  const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
13881
14168
  const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
13882
14169
  const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
13883
- const { decrementImageRef } = await Promise.resolve().then(() => image_refs_exports);
14170
+ const { decrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
13884
14171
  const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
13885
14172
  if (data?.dryRun) return {
13886
14173
  success: true,
@@ -15348,11 +15635,15 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15348
15635
  status_code: 400,
15349
15636
  body: { error: "budget must be a positive integer" }
15350
15637
  };
15638
+ const queryAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"] : void 0;
15639
+ const bodyAgentId = typeof body.agentId === "string" && body.agentId.trim().length > 0 ? body.agentId.trim() : void 0;
15351
15640
  const payload = {
15352
15641
  sessionId,
15353
15642
  project
15354
15643
  };
15355
15644
  if (budget !== void 0) payload.budget = budget;
15645
+ const agentId = bodyAgentId ?? queryAgentId;
15646
+ if (agentId !== void 0) payload.agentId = agentId;
15356
15647
  return {
15357
15648
  status_code: 200,
15358
15649
  body: await sdk.trigger({
@@ -15372,6 +15663,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15372
15663
  });
15373
15664
  sdk.registerFunction("api::search", async (req) => {
15374
15665
  const body = req.body ?? {};
15666
+ const queryAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"] : void 0;
15375
15667
  if (typeof body.query !== "string" || !body.query.trim()) return {
15376
15668
  status_code: 400,
15377
15669
  body: { error: "query is required and must be a non-empty string" }
@@ -15400,13 +15692,15 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15400
15692
  status_code: 400,
15401
15693
  body: { error: "token_budget must be a positive integer" }
15402
15694
  };
15695
+ const bodyAgentId = typeof body.agentId === "string" && body.agentId.trim().length > 0 ? body.agentId.trim() : void 0;
15403
15696
  const payload = {
15404
15697
  query: body.query.trim(),
15405
15698
  limit: body.limit,
15406
15699
  project: body.project,
15407
15700
  cwd: body.cwd,
15408
15701
  format: typeof body.format === "string" ? body.format.trim().toLowerCase() : void 0,
15409
- token_budget: body.token_budget
15702
+ token_budget: body.token_budget,
15703
+ agentId: bodyAgentId ?? queryAgentId
15410
15704
  };
15411
15705
  return {
15412
15706
  status_code: 200,
@@ -15561,7 +15855,8 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15561
15855
  function_id: "mem::context",
15562
15856
  payload: {
15563
15857
  sessionId,
15564
- project
15858
+ project,
15859
+ ...agentId ? { agentId } : {}
15565
15860
  }
15566
15861
  })).context
15567
15862
  }
@@ -15753,9 +16048,14 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15753
16048
  const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
15754
16049
  const wildcardAgent = normalizedAgentId === "*";
15755
16050
  const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
16051
+ const filtered = filterAgentId ? sessions.filter((s) => s.agentId === filterAgentId) : sessions;
16052
+ const summaries = await Promise.all(filtered.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
15756
16053
  return {
15757
16054
  status_code: 200,
15758
- body: { sessions: filterAgentId ? sessions.filter((s) => s.agentId === filterAgentId) : sessions }
16055
+ body: { sessions: filtered.map((s, i) => summaries[i] ? {
16056
+ ...s,
16057
+ summary: summaries[i]
16058
+ } : s) }
15759
16059
  };
15760
16060
  });
15761
16061
  sdk.registerTrigger({
@@ -16342,6 +16642,52 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
16342
16642
  http_method: "GET"
16343
16643
  }
16344
16644
  });
16645
+ sdk.registerFunction("api::graph-snapshot-rebuild", async (req) => {
16646
+ const authErr = checkAuth(req, secret);
16647
+ if (authErr) return authErr;
16648
+ try {
16649
+ return {
16650
+ status_code: 200,
16651
+ body: await sdk.trigger({
16652
+ function_id: "mem::graph-snapshot-rebuild",
16653
+ payload: {}
16654
+ })
16655
+ };
16656
+ } catch {
16657
+ return graphDisabledResponse();
16658
+ }
16659
+ });
16660
+ sdk.registerTrigger({
16661
+ type: "http",
16662
+ function_id: "api::graph-snapshot-rebuild",
16663
+ config: {
16664
+ api_path: "/agentmemory/graph/snapshot-rebuild",
16665
+ http_method: "POST"
16666
+ }
16667
+ });
16668
+ sdk.registerFunction("api::graph-reset", async (req) => {
16669
+ const authErr = checkAuth(req, secret);
16670
+ if (authErr) return authErr;
16671
+ try {
16672
+ return {
16673
+ status_code: 200,
16674
+ body: await sdk.trigger({
16675
+ function_id: "mem::graph-reset",
16676
+ payload: {}
16677
+ })
16678
+ };
16679
+ } catch {
16680
+ return graphDisabledResponse();
16681
+ }
16682
+ });
16683
+ sdk.registerTrigger({
16684
+ type: "http",
16685
+ function_id: "api::graph-reset",
16686
+ config: {
16687
+ api_path: "/agentmemory/graph/reset",
16688
+ http_method: "POST"
16689
+ }
16690
+ });
16345
16691
  sdk.registerFunction("api::graph-extract", async (req) => {
16346
16692
  const authErr = checkAuth(req, secret);
16347
16693
  if (authErr) return authErr;
@@ -18589,13 +18935,15 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
18589
18935
  //#region src/triggers/events.ts
18590
18936
  function registerEventTriggers(sdk, kv) {
18591
18937
  sdk.registerFunction("event::session::started", async (data) => {
18938
+ const agentId = (typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim().slice(0, 128) : void 0) ?? getAgentId();
18592
18939
  const session = {
18593
18940
  id: data.sessionId,
18594
18941
  project: data.project,
18595
18942
  cwd: data.cwd,
18596
18943
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
18597
18944
  status: "active",
18598
- observationCount: 0
18945
+ observationCount: 0,
18946
+ ...agentId ? { agentId } : {}
18599
18947
  };
18600
18948
  await kv.set(KV.sessions, data.sessionId, session);
18601
18949
  return {
@@ -18604,7 +18952,8 @@ function registerEventTriggers(sdk, kv) {
18604
18952
  function_id: "mem::context",
18605
18953
  payload: {
18606
18954
  sessionId: data.sessionId,
18607
- project: data.project
18955
+ project: data.project,
18956
+ ...agentId ? { agentId } : {}
18608
18957
  }
18609
18958
  })).context
18610
18959
  };
@@ -19885,13 +20234,15 @@ function registerMcpEndpoints(sdk, kv, secret) {
19885
20234
  status_code: 400,
19886
20235
  body: { error: "token_budget must be a positive integer" }
19887
20236
  };
20237
+ const recallAgentId = typeof args.agentId === "string" && args.agentId.trim().length > 0 ? args.agentId.trim() : void 0;
19888
20238
  const result = await sdk.trigger({
19889
20239
  function_id: "mem::search",
19890
20240
  payload: {
19891
20241
  query: args.query,
19892
20242
  limit: typeof args.limit === "number" ? args.limit : 10,
19893
20243
  format,
19894
- token_budget: tokenBudget
20244
+ token_budget: tokenBudget,
20245
+ agentId: recallAgentId
19895
20246
  }
19896
20247
  });
19897
20248
  return {
@@ -21379,7 +21730,10 @@ function registerMcpEndpoints(sdk, kv, secret) {
21379
21730
  limit: 10
21380
21731
  }
21381
21732
  }).catch(() => ({ results: [] }));
21382
- const relevant = (await kv.list(KV.memories)).filter((m) => m.isLatest).slice(0, 5);
21733
+ const memories = await kv.list(KV.memories);
21734
+ const isolated = isAgentScopeIsolated();
21735
+ const activeAgentId = isolated ? getAgentId() : void 0;
21736
+ const relevant = isolated && activeAgentId === void 0 ? [] : memories.filter((m) => m.isLatest).filter((m) => activeAgentId === void 0 || m.agentId === activeAgentId).slice(0, 5);
21383
21737
  return {
21384
21738
  status_code: 200,
21385
21739
  body: { messages: [{
@@ -21633,10 +21987,10 @@ async function main() {
21633
21987
  }
21634
21988
  registerConsolidationPipelineFunction(sdk, kv, provider);
21635
21989
  bootLog(`Consolidation pipeline: registered (CONSOLIDATION_ENABLED=${isConsolidationEnabled() ? "true" : "false"})`);
21636
- if (isAutoCompressEnabled()) bootLog(`WARNING: AGENTMEMORY_AUTO_COMPRESS=true — every PostToolUse observation will be sent to your LLM provider for compression. This spends API tokens proportional to your session tool-use frequency (see #138). Set AGENTMEMORY_AUTO_COMPRESS=false to disable.`);
21637
- else bootLog(`Auto-compress: OFF (default, #138) — observations indexed via zero-LLM synthetic compression. Set AGENTMEMORY_AUTO_COMPRESS=true to opt-in to LLM-powered summaries (uses your API key).`);
21638
- if (isContextInjectionEnabled()) bootLog(`WARNING: AGENTMEMORY_INJECT_CONTEXT=true — the PreToolUse and SessionStart hooks will inject up to ~4000 chars of memory context into every tool turn. On Claude Pro this burns session tokens proportional to your tool-call frequency (see #143). Set AGENTMEMORY_INJECT_CONTEXT=false to disable.`);
21639
- else bootLog(`Context injection: OFF (default, #143) — hooks capture observations but do not inject context into Claude Code's conversation. Set AGENTMEMORY_INJECT_CONTEXT=true to opt-in (warning: expect your Claude Pro allocation to drain faster).`);
21990
+ if (isAutoCompressEnabled()) bootLog(`WARNING: AGENTMEMORY_AUTO_COMPRESS=true — every PostToolUse observation will be sent to your LLM provider for compression. This spends API tokens proportional to your session tool-use frequency. Set AGENTMEMORY_AUTO_COMPRESS=false to disable.`);
21991
+ else bootLog(`Auto-compress: OFF (default) — observations indexed via zero-LLM synthetic compression. Set AGENTMEMORY_AUTO_COMPRESS=true to opt-in to LLM-powered summaries (uses your API key).`);
21992
+ if (isContextInjectionEnabled()) bootLog(`WARNING: AGENTMEMORY_INJECT_CONTEXT=true — the PreToolUse and SessionStart hooks will inject up to ~4000 chars of memory context into every tool turn. On Claude Pro this burns session tokens proportional to your tool-call frequency. Set AGENTMEMORY_INJECT_CONTEXT=false to disable.`);
21993
+ else bootLog(`Context injection: OFF (default) — hooks capture observations but do not inject context into Claude Code's conversation. Set AGENTMEMORY_INJECT_CONTEXT=true to opt-in (warning: expect your Claude Pro allocation to drain faster).`);
21640
21994
  const teamConfig = loadTeamConfig();
21641
21995
  if (teamConfig) {
21642
21996
  registerTeamFunction(sdk, kv, teamConfig);
@@ -21743,14 +22097,14 @@ async function main() {
21743
22097
  backfilled++;
21744
22098
  }
21745
22099
  if (backfilled > 0) {
21746
- bootLog(`Backfilled ${backfilled} memories into BM25 (legacy gap before #257)`);
22100
+ bootLog(`Backfilled ${backfilled} memories into BM25 (legacy index gap)`);
21747
22101
  indexPersistence.scheduleSave();
21748
22102
  }
21749
22103
  } catch (err) {
21750
22104
  console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
21751
22105
  }
21752
22106
  bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
21753
- bootLog(`REST API: 126 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
22107
+ bootLog(`REST API: 128 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
21754
22108
  bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
21755
22109
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
21756
22110
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
@@ -21825,6 +22179,6 @@ main().catch((err) => {
21825
22179
  process.exit(1);
21826
22180
  });
21827
22181
  //#endregion
21828
- export {};
22182
+ export { KV as i, touchImage as n, withKeyedLock as r, deleteImage as t };
21829
22183
 
21830
22184
  //# sourceMappingURL=index.mjs.map