@agentmemory/agentmemory 0.9.23 → 0.9.25

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 (88) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/cli.d.mts.map +1 -1
  4. package/dist/cli.mjs +129 -66
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{connect-Cf9bmBqO.mjs → connect-bmZ5eqYN.mjs} +17 -56
  7. package/dist/{connect-Cf9bmBqO.mjs.map → connect-bmZ5eqYN.mjs.map} +1 -1
  8. package/dist/hooks/notification.mjs +2 -4
  9. package/dist/hooks/notification.mjs.map +1 -1
  10. package/dist/hooks/post-commit.mjs +2 -3
  11. package/dist/hooks/post-commit.mjs.map +1 -1
  12. package/dist/hooks/post-tool-failure.mjs +2 -4
  13. package/dist/hooks/post-tool-failure.mjs.map +1 -1
  14. package/dist/hooks/post-tool-use.mjs +2 -4
  15. package/dist/hooks/post-tool-use.mjs.map +1 -1
  16. package/dist/hooks/pre-compact.mjs +2 -4
  17. package/dist/hooks/pre-compact.mjs.map +1 -1
  18. package/dist/hooks/pre-tool-use.mjs +2 -2
  19. package/dist/hooks/pre-tool-use.mjs.map +1 -1
  20. package/dist/hooks/prompt-submit.mjs +2 -4
  21. package/dist/hooks/prompt-submit.mjs.map +1 -1
  22. package/dist/hooks/session-end.mjs +2 -2
  23. package/dist/hooks/session-start.mjs +2 -4
  24. package/dist/hooks/session-start.mjs.map +1 -1
  25. package/dist/hooks/stop.mjs +2 -2
  26. package/dist/hooks/subagent-start.mjs +2 -4
  27. package/dist/hooks/subagent-start.mjs.map +1 -1
  28. package/dist/hooks/subagent-stop.mjs +2 -4
  29. package/dist/hooks/subagent-stop.mjs.map +1 -1
  30. package/dist/hooks/task-completed.mjs +2 -4
  31. package/dist/hooks/task-completed.mjs.map +1 -1
  32. package/dist/image-refs-C7h9L5wx.mjs +52 -0
  33. package/dist/image-refs-C7h9L5wx.mjs.map +1 -0
  34. package/dist/{image-refs-CJS5B9Gq.mjs → image-store-Gpo2mgM9.mjs} +11 -42
  35. package/dist/image-store-Gpo2mgM9.mjs.map +1 -0
  36. package/dist/index.mjs +942 -493
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/{logger-xlVlvCWX.mjs → logger-yHTcEBAI.mjs} +2 -2
  39. package/dist/{logger-xlVlvCWX.mjs.map → logger-yHTcEBAI.mjs.map} +1 -1
  40. package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
  41. package/dist/{schema-BkALl7Z_.mjs → schema-Dsr_V2Wp.mjs} +4 -4
  42. package/dist/schema-Dsr_V2Wp.mjs.map +1 -0
  43. package/dist/{src-DvS3bhMe.mjs → src-fQOMXeCp.mjs} +937 -483
  44. package/dist/src-fQOMXeCp.mjs.map +1 -0
  45. package/dist/{standalone-DHQcPX_g.mjs → standalone-BzfA1zu8.mjs} +6 -10
  46. package/dist/{standalone-DHQcPX_g.mjs.map → standalone-BzfA1zu8.mjs.map} +1 -1
  47. package/dist/standalone.mjs +3 -14
  48. package/dist/standalone.mjs.map +1 -1
  49. package/dist/{tools-registry-DJizX9Az.mjs → tools-registry-Dzxv9iUu.mjs} +7 -5
  50. package/dist/tools-registry-Dzxv9iUu.mjs.map +1 -0
  51. package/dist/version-C3hZKw8n.mjs +6 -0
  52. package/dist/version-C3hZKw8n.mjs.map +1 -0
  53. package/dist/viewer/index.html +155 -9
  54. package/package.json +9 -4
  55. package/plugin/.claude-plugin/plugin.json +1 -1
  56. package/plugin/.codex-plugin/plugin.json +1 -1
  57. package/plugin/plugin.json +1 -1
  58. package/plugin/scripts/notification.mjs +2 -4
  59. package/plugin/scripts/notification.mjs.map +1 -1
  60. package/plugin/scripts/post-commit.mjs +2 -3
  61. package/plugin/scripts/post-commit.mjs.map +1 -1
  62. package/plugin/scripts/post-tool-failure.mjs +2 -4
  63. package/plugin/scripts/post-tool-failure.mjs.map +1 -1
  64. package/plugin/scripts/post-tool-use.mjs +2 -4
  65. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  66. package/plugin/scripts/pre-compact.mjs +2 -4
  67. package/plugin/scripts/pre-compact.mjs.map +1 -1
  68. package/plugin/scripts/pre-tool-use.mjs +2 -2
  69. package/plugin/scripts/pre-tool-use.mjs.map +1 -1
  70. package/plugin/scripts/prompt-submit.mjs +2 -4
  71. package/plugin/scripts/prompt-submit.mjs.map +1 -1
  72. package/plugin/scripts/session-end.mjs +2 -2
  73. package/plugin/scripts/session-start.mjs +2 -4
  74. package/plugin/scripts/session-start.mjs.map +1 -1
  75. package/plugin/scripts/stop.mjs +2 -2
  76. package/plugin/scripts/subagent-start.mjs +2 -4
  77. package/plugin/scripts/subagent-start.mjs.map +1 -1
  78. package/plugin/scripts/subagent-stop.mjs +2 -4
  79. package/plugin/scripts/subagent-stop.mjs.map +1 -1
  80. package/plugin/scripts/task-completed.mjs +2 -4
  81. package/plugin/scripts/task-completed.mjs.map +1 -1
  82. package/dist/image-refs-CJS5B9Gq.mjs.map +0 -1
  83. package/dist/image-store-CdE0amb1.mjs +0 -3
  84. package/dist/schema-BkALl7Z_.mjs.map +0 -1
  85. package/dist/src-DvS3bhMe.mjs.map +0 -1
  86. package/dist/tools-registry-DJizX9Az.mjs.map +0 -1
  87. package/dist/version-BPfyI4Kc.mjs +0 -6
  88. package/dist/version-BPfyI4Kc.mjs.map +0 -1
@@ -1,8 +1,9 @@
1
- import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./schema-BkALl7Z_.mjs";
2
- import { n as logger, t as bootLog } from "./logger-xlVlvCWX.mjs";
3
- import { t as VERSION } from "./version-BPfyI4Kc.mjs";
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-DJizX9Az.mjs";
1
+ import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./schema-Dsr_V2Wp.mjs";
2
+ import { n as logger, t as bootLog } from "./logger-yHTcEBAI.mjs";
3
+ import { t as VERSION } from "./version-C3hZKw8n.mjs";
4
+ import { a as isManagedImagePath, n as deleteImage, r as getMaxBytes, t as IMAGES_DIR } from "./image-store-Gpo2mgM9.mjs";
5
+ import { r as withKeyedLock, t as getImageRefCount } from "./image-refs-C7h9L5wx.mjs";
6
+ import { _ as loadConfig, a as getAgentId, b as loadSnapshotConfig, c as getFollowupWindowSeconds, d as isAutoCompressEnabled, f as isConsolidationEnabled, g as loadClaudeBridgeConfig, h as isGraphExtractionEnabled, i as detectLlmProviderKind, m as isDropStaleIndexEnabled, n as getVisibleTools, o as getConsolidationDecayDays, p as isContextInjectionEnabled, r as detectEmbeddingProvider, s as getEnvVar, t as getAllTools, u as isAgentScopeIsolated, v as loadEmbeddingConfig, x as loadTeamConfig, y as loadFallbackConfig } from "./tools-registry-Dzxv9iUu.mjs";
6
7
  import { createRequire } from "node:module";
7
8
  import { execFile } from "node:child_process";
8
9
  import { constants, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
@@ -12,16 +13,24 @@ import { homedir } from "node:os";
12
13
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
13
14
  import { lstat, mkdir, open, readFile, readdir, stat, writeFile } from "node:fs/promises";
14
15
  import { TriggerAction, registerWorker } from "iii-sdk";
16
+ import { AsyncLocalStorage } from "node:async_hooks";
15
17
  import Anthropic from "@anthropic-ai/sdk";
16
18
  import { z } from "zod";
17
19
  import { promisify } from "node:util";
18
20
  import { lookup } from "node:dns/promises";
19
21
  import { isIP } from "node:net";
20
22
  import { createServer } from "node:http";
21
-
22
23
  //#region src/providers/agent-sdk.ts
24
+ const sdkChildContext = new AsyncLocalStorage();
25
+ let sdkActiveCount = 0;
26
+ let sdkOriginalEnv;
23
27
  var AgentSDKProvider = class {
24
28
  name = "agent-sdk";
29
+ sdkPromise = null;
30
+ loadSdk() {
31
+ if (!this.sdkPromise) this.sdkPromise = import("@anthropic-ai/claude-agent-sdk");
32
+ return this.sdkPromise;
33
+ }
25
34
  async compress(systemPrompt, userPrompt) {
26
35
  return this.query(systemPrompt, userPrompt);
27
36
  }
@@ -29,29 +38,37 @@ var AgentSDKProvider = class {
29
38
  return this.query(systemPrompt, userPrompt);
30
39
  }
31
40
  async query(systemPrompt, userPrompt) {
32
- if (process.env.AGENTMEMORY_SDK_CHILD === "1") return "";
33
- const prev = process.env.AGENTMEMORY_SDK_CHILD;
34
- process.env.AGENTMEMORY_SDK_CHILD = "1";
35
- try {
36
- const { query } = await import("@anthropic-ai/claude-agent-sdk");
37
- const messages = query({
38
- prompt: userPrompt,
39
- options: {
40
- systemPrompt,
41
- maxTurns: 1,
42
- allowedTools: []
41
+ if (sdkChildContext.getStore()) return "";
42
+ return sdkChildContext.run(true, async () => {
43
+ if (sdkActiveCount === 0) {
44
+ sdkOriginalEnv = process.env.AGENTMEMORY_SDK_CHILD;
45
+ process.env.AGENTMEMORY_SDK_CHILD = "1";
46
+ }
47
+ sdkActiveCount++;
48
+ try {
49
+ const { query } = await this.loadSdk();
50
+ const messages = query({
51
+ prompt: userPrompt,
52
+ options: {
53
+ systemPrompt,
54
+ maxTurns: 1,
55
+ allowedTools: []
56
+ }
57
+ });
58
+ let result = "";
59
+ for await (const msg of messages) if (msg.type === "result") result = msg.result ?? "";
60
+ return result;
61
+ } finally {
62
+ sdkActiveCount--;
63
+ if (sdkActiveCount === 0) {
64
+ if (sdkOriginalEnv === void 0) delete process.env.AGENTMEMORY_SDK_CHILD;
65
+ else process.env.AGENTMEMORY_SDK_CHILD = sdkOriginalEnv;
66
+ sdkOriginalEnv = void 0;
43
67
  }
44
- });
45
- let result = "";
46
- for await (const msg of messages) if (msg.type === "result") result = msg.result ?? "";
47
- return result;
48
- } finally {
49
- if (prev === void 0) delete process.env.AGENTMEMORY_SDK_CHILD;
50
- else process.env.AGENTMEMORY_SDK_CHILD = prev;
51
- }
68
+ }
69
+ });
52
70
  }
53
71
  };
54
-
55
72
  //#endregion
56
73
  //#region src/providers/anthropic.ts
57
74
  var AnthropicProvider = class {
@@ -105,7 +122,6 @@ var AnthropicProvider = class {
105
122
  })).content.find((b) => b.type === "text")?.text ?? "";
106
123
  }
107
124
  };
108
-
109
125
  //#endregion
110
126
  //#region src/providers/_fetch.ts
111
127
  function fetchWithTimeout(url, init, timeoutMs) {
@@ -119,7 +135,6 @@ function fetchWithTimeout(url, init, timeoutMs) {
119
135
  signal
120
136
  }).finally(() => clearTimeout(t));
121
137
  }
122
-
123
138
  //#endregion
124
139
  //#region src/providers/minimax.ts
125
140
  /**
@@ -179,7 +194,6 @@ var MinimaxProvider = class {
179
194
  return ((await response.json()).content?.find((b) => b.type === "text"))?.text ?? "";
180
195
  }
181
196
  };
182
-
183
197
  //#endregion
184
198
  //#region src/providers/noop.ts
185
199
  /**
@@ -198,11 +212,6 @@ var NoopProvider = class {
198
212
  return "";
199
213
  }
200
214
  };
201
-
202
- //#endregion
203
- //#region src/providers/_openai-shared.ts
204
- const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
205
- const DEFAULT_AZURE_API_VERSION = "2024-08-01-preview";
206
215
  function detectAzure(baseUrl) {
207
216
  try {
208
217
  return new URL(baseUrl).hostname.endsWith(".openai.azure.com");
@@ -262,9 +271,8 @@ function buildAuthHeaders(apiKey, isAzure) {
262
271
  };
263
272
  }
264
273
  function normalizeBaseUrl(raw) {
265
- return (raw || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, "");
274
+ return (raw || "https://api.openai.com").replace(/\/+$/, "");
266
275
  }
267
-
268
276
  //#endregion
269
277
  //#region src/providers/openai.ts
270
278
  const DEFAULT_TIMEOUT_MS = 6e4;
@@ -317,7 +325,7 @@ var OpenAIProvider = class {
317
325
  this.baseUrl = normalizeBaseUrl(baseURL || getEnvVar("OPENAI_BASE_URL"));
318
326
  this.reasoningEffort = getEnvVar("OPENAI_REASONING_EFFORT") || void 0;
319
327
  this.timeoutMs = resolveTimeout();
320
- this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION;
328
+ this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || "2024-08-01-preview";
321
329
  this.isAzure = detectAzure(this.baseUrl);
322
330
  }
323
331
  async compress(systemPrompt, userPrompt) {
@@ -379,7 +387,6 @@ function parsePositiveInt(raw) {
379
387
  const n = Number(trimmed);
380
388
  return Number.isFinite(n) && n > 0 ? n : void 0;
381
389
  }
382
-
383
390
  //#endregion
384
391
  //#region src/providers/openrouter.ts
385
392
  var OpenRouterProvider = class {
@@ -431,7 +438,6 @@ var OpenRouterProvider = class {
431
438
  return content;
432
439
  }
433
440
  };
434
-
435
441
  //#endregion
436
442
  //#region src/providers/circuit-breaker.ts
437
443
  function positiveFinite(val, fallback) {
@@ -493,7 +499,6 @@ var CircuitBreaker = class {
493
499
  };
494
500
  }
495
501
  };
496
-
497
502
  //#endregion
498
503
  //#region src/providers/resilient.ts
499
504
  var ResilientProvider = class {
@@ -524,7 +529,6 @@ var ResilientProvider = class {
524
529
  return this.breaker.getState();
525
530
  }
526
531
  };
527
-
528
532
  //#endregion
529
533
  //#region src/providers/fallback-chain.ts
530
534
  var FallbackChainProvider = class {
@@ -549,7 +553,6 @@ var FallbackChainProvider = class {
549
553
  throw lastError || /* @__PURE__ */ new Error("No providers available");
550
554
  }
551
555
  };
552
-
553
556
  //#endregion
554
557
  //#region src/providers/embedding/gemini.ts
555
558
  const BATCH_LIMIT = 100;
@@ -605,7 +608,6 @@ function l2Normalize(vec) {
605
608
  for (let i = 0; i < vec.length; i++) vec[i] = vec[i] / norm;
606
609
  return vec;
607
610
  }
608
-
609
611
  //#endregion
610
612
  //#region src/providers/embedding/openai.ts
611
613
  const DEFAULT_MODEL$1 = "text-embedding-3-small";
@@ -679,7 +681,7 @@ var OpenAIEmbeddingProvider = class {
679
681
  this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
680
682
  this.dimensions = resolveDimensions(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
681
683
  this.isAzure = detectAzure(this.baseUrl);
682
- this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION;
684
+ this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || "2024-08-01-preview";
683
685
  }
684
686
  async embed(text) {
685
687
  const [result] = await this.embedBatch([text]);
@@ -701,7 +703,6 @@ var OpenAIEmbeddingProvider = class {
701
703
  return (await response.json()).data.map((d) => new Float32Array(d.embedding));
702
704
  }
703
705
  };
704
-
705
706
  //#endregion
706
707
  //#region src/providers/embedding/voyage.ts
707
708
  const API_URL$2 = "https://api.voyageai.com/v1/embeddings";
@@ -737,7 +738,6 @@ var VoyageEmbeddingProvider = class {
737
738
  return (await response.json()).data.map((d) => new Float32Array(d.embedding));
738
739
  }
739
740
  };
740
-
741
741
  //#endregion
742
742
  //#region src/providers/embedding/cohere.ts
743
743
  const API_URL$1 = "https://api.cohere.ai/v1/embed";
@@ -773,7 +773,6 @@ var CohereEmbeddingProvider = class {
773
773
  return (await response.json()).embeddings.map((e) => new Float32Array(e));
774
774
  }
775
775
  };
776
-
777
776
  //#endregion
778
777
  //#region src/providers/embedding/openrouter.ts
779
778
  const API_URL = "https://openrouter.ai/api/v1/embeddings";
@@ -810,7 +809,6 @@ var OpenRouterEmbeddingProvider = class {
810
809
  return (await response.json()).data.map((d) => new Float32Array(d.embedding));
811
810
  }
812
811
  };
813
-
814
812
  //#endregion
815
813
  //#region src/providers/embedding/local.ts
816
814
  var LocalEmbeddingProvider = class {
@@ -839,7 +837,6 @@ var LocalEmbeddingProvider = class {
839
837
  return this.extractor;
840
838
  }
841
839
  };
842
-
843
840
  //#endregion
844
841
  //#region src/providers/embedding/clip.ts
845
842
  const DEFAULT_MODEL = "Xenova/clip-vit-base-patch32";
@@ -880,12 +877,14 @@ var ClipEmbeddingProvider = class {
880
877
  }
881
878
  async getTextExtractor() {
882
879
  if (this.textExtractor) return this.textExtractor;
883
- this.textExtractor = await (await this.getTransformers()).pipeline("feature-extraction", this.modelId);
880
+ const t = await this.getTransformers();
881
+ this.textExtractor = await t.pipeline("feature-extraction", this.modelId);
884
882
  return this.textExtractor;
885
883
  }
886
884
  async getImageExtractor() {
887
885
  if (this.imageExtractor) return this.imageExtractor;
888
- this.imageExtractor = await (await this.getTransformers()).pipeline("image-feature-extraction", this.modelId);
886
+ const t = await this.getTransformers();
887
+ this.imageExtractor = await t.pipeline("image-feature-extraction", this.modelId);
889
888
  return this.imageExtractor;
890
889
  }
891
890
  };
@@ -910,7 +909,6 @@ function normalize(vec) {
910
909
  for (let i = 0; i < vec.length; i++) out[i] = vec[i] / norm;
911
910
  return out;
912
911
  }
913
-
914
912
  //#endregion
915
913
  //#region src/providers/embedding/index.ts
916
914
  let imageEmbeddingProvider = null;
@@ -949,7 +947,6 @@ function withDimensionGuard(provider) {
949
947
  if (provider.embedImage) wrapped.embedImage = async (s) => check(await provider.embedImage(s), "embedImage");
950
948
  return wrapped;
951
949
  }
952
-
953
950
  //#endregion
954
951
  //#region src/providers/index.ts
955
952
  function requireEnvVar(key) {
@@ -957,6 +954,17 @@ function requireEnvVar(key) {
957
954
  if (!value) throw new Error(`Missing required environment variable: ${key}. Set it in ~/.agentmemory/.env or as an environment variable.`);
958
955
  return value;
959
956
  }
957
+ function defaultModelFor(providerType) {
958
+ switch (providerType) {
959
+ case "openai": return getEnvVar("OPENAI_MODEL") || "gpt-4o-mini";
960
+ case "anthropic": return getEnvVar("ANTHROPIC_MODEL") || "claude-sonnet-4-20250514";
961
+ case "gemini": return getEnvVar("GEMINI_MODEL") || "gemini-2.5-flash";
962
+ case "openrouter": return getEnvVar("OPENROUTER_MODEL") || "anthropic/claude-sonnet-4-20250514";
963
+ case "minimax": return getEnvVar("MINIMAX_MODEL") || "MiniMax-M2.7";
964
+ case "agent-sdk": return "claude-sonnet-4-20250514";
965
+ default: return "noop";
966
+ }
967
+ }
960
968
  function createProvider(config) {
961
969
  return new ResilientProvider(createBaseProvider(config));
962
970
  }
@@ -968,7 +976,7 @@ function createFallbackProvider(config, fallbackConfig) {
968
976
  try {
969
977
  const fbConfig = {
970
978
  provider: providerType,
971
- model: config.model,
979
+ model: defaultModelFor(providerType),
972
980
  maxTokens: config.maxTokens
973
981
  };
974
982
  providers.push(createBaseProvider(fbConfig));
@@ -996,7 +1004,6 @@ function createBaseProvider(config) {
996
1004
  default: return new AgentSDKProvider();
997
1005
  }
998
1006
  }
999
-
1000
1007
  //#endregion
1001
1008
  //#region src/state/kv.ts
1002
1009
  var StateKV = class {
@@ -1048,7 +1055,6 @@ var StateKV = class {
1048
1055
  });
1049
1056
  }
1050
1057
  };
1051
-
1052
1058
  //#endregion
1053
1059
  //#region src/state/vector-index.ts
1054
1060
  function float32ToBase64(arr) {
@@ -1171,7 +1177,6 @@ var VectorIndex = class VectorIndex {
1171
1177
  return idx;
1172
1178
  }
1173
1179
  };
1174
-
1175
1180
  //#endregion
1176
1181
  //#region src/state/memory-utils.ts
1177
1182
  function memoryToObservation(memory) {
@@ -1188,7 +1193,6 @@ function memoryToObservation(memory) {
1188
1193
  importance: memory.strength
1189
1194
  };
1190
1195
  }
1191
-
1192
1196
  //#endregion
1193
1197
  //#region src/functions/graph-retrieval.ts
1194
1198
  function buildGraphContext(path) {
@@ -1433,7 +1437,6 @@ var MinHeap = class {
1433
1437
  }
1434
1438
  }
1435
1439
  };
1436
-
1437
1440
  //#endregion
1438
1441
  //#region src/functions/query-expansion.ts
1439
1442
  const QUERY_EXPANSION_SYSTEM = `You are a query expansion engine for a memory retrieval system. Given a user query, generate diverse reformulations to maximize recall.
@@ -1590,7 +1593,6 @@ function extractEntitiesFromQuery(query) {
1590
1593
  }
1591
1594
  return [...new Set(entities)];
1592
1595
  }
1593
-
1594
1596
  //#endregion
1595
1597
  //#region src/state/reranker.ts
1596
1598
  let pipeline = null;
@@ -1644,7 +1646,6 @@ async function rerank(query, results, topK = 20) {
1644
1646
  rerankPosition: i + 1
1645
1647
  }));
1646
1648
  }
1647
-
1648
1649
  //#endregion
1649
1650
  //#region src/state/hybrid-search.ts
1650
1651
  const RRF_K = 60;
@@ -1815,7 +1816,6 @@ var HybridSearch = class {
1815
1816
  return enriched;
1816
1817
  }
1817
1818
  };
1818
-
1819
1819
  //#endregion
1820
1820
  //#region src/state/stemmer.ts
1821
1821
  const step2map = {
@@ -1911,7 +1911,6 @@ function stem(word) {
1911
1911
  if (endsDoubleConsonant(w) && w.endsWith("l") && measure(w.slice(0, -1)) > 1) w = w.slice(0, -1);
1912
1912
  return w;
1913
1913
  }
1914
-
1915
1914
  //#endregion
1916
1915
  //#region src/state/synonyms.ts
1917
1916
  const SYNONYM_GROUPS = [
@@ -2073,7 +2072,6 @@ function getSynonyms(stemmedTerm) {
2073
2072
  const syns = synonymMap.get(stemmedTerm);
2074
2073
  return syns ? [...syns] : [];
2075
2074
  }
2076
-
2077
2075
  //#endregion
2078
2076
  //#region src/state/cjk-segmenter.ts
2079
2077
  const cjkRequire = createRequire(import.meta.url);
@@ -2177,7 +2175,6 @@ function segmentCjk(text) {
2177
2175
  }
2178
2176
  return out;
2179
2177
  }
2180
-
2181
2178
  //#endregion
2182
2179
  //#region src/state/search-index.ts
2183
2180
  var SearchIndex = class SearchIndex {
@@ -2377,18 +2374,92 @@ var SearchIndex = class SearchIndex {
2377
2374
  return lo;
2378
2375
  }
2379
2376
  };
2380
-
2377
+ //#endregion
2378
+ //#region src/functions/audit.ts
2379
+ async function recordAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
2380
+ const entry = {
2381
+ id: generateId("aud"),
2382
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2383
+ operation,
2384
+ userId,
2385
+ functionId,
2386
+ targetIds,
2387
+ details,
2388
+ qualityScore
2389
+ };
2390
+ await kv.set(KV.audit, entry.id, entry);
2391
+ return entry;
2392
+ }
2393
+ async function safeAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
2394
+ try {
2395
+ await recordAudit(kv, operation, functionId, targetIds, details, qualityScore, userId);
2396
+ } catch (err) {
2397
+ try {
2398
+ logger.warn("audit write failed", {
2399
+ functionId,
2400
+ operation,
2401
+ targetIds,
2402
+ error: err instanceof Error ? err.message : String(err)
2403
+ });
2404
+ } catch {}
2405
+ }
2406
+ }
2407
+ async function queryAudit(kv, filter) {
2408
+ let entries = [...await kv.list(KV.audit)].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
2409
+ if (filter?.operation) entries = entries.filter((e) => e.operation === filter.operation);
2410
+ if (filter?.dateFrom) {
2411
+ const from = new Date(filter.dateFrom).getTime();
2412
+ if (Number.isNaN(from)) throw new Error(`Invalid dateFrom: ${filter.dateFrom}`);
2413
+ entries = entries.filter((e) => new Date(e.timestamp).getTime() >= from);
2414
+ }
2415
+ if (filter?.dateTo) {
2416
+ const to = new Date(filter.dateTo).getTime();
2417
+ if (Number.isNaN(to)) throw new Error(`Invalid dateTo: ${filter.dateTo}`);
2418
+ entries = entries.filter((e) => new Date(e.timestamp).getTime() <= to);
2419
+ }
2420
+ return entries.slice(0, filter?.limit || 100);
2421
+ }
2381
2422
  //#endregion
2382
2423
  //#region src/state/index-persistence.ts
2383
2424
  const DEBOUNCE_MS = 5e3;
2384
2425
  const FAILURE_LOG_THROTTLE_MS = 6e4;
2426
+ const INDEX_PERSISTENCE_FUNCTION_ID = "mem::index-persistence";
2427
+ const BM25_KEY = "data";
2428
+ const BM25_MANIFEST_KEY = "data:manifest";
2429
+ const BM25_SHARD_SCOPE_PREFIX = `${KV.bm25Index}:bm25:`;
2430
+ const VECTOR_KEY = "vectors";
2431
+ const VECTOR_MANIFEST_KEY = "vectors:manifest";
2432
+ const VECTOR_SHARD_SCOPE_PREFIX = `${KV.bm25Index}:vectors:`;
2433
+ const INDEX_SHARD_KEY = "data";
2434
+ const DEFAULT_INDEX_SHARD_CHARS = 2e6;
2435
+ function shardChars(options) {
2436
+ const configured = options.shardChars;
2437
+ if (typeof configured !== "number" || !Number.isFinite(configured)) return DEFAULT_INDEX_SHARD_CHARS;
2438
+ const wholeChars = Math.floor(configured);
2439
+ return wholeChars >= 1 ? wholeChars : DEFAULT_INDEX_SHARD_CHARS;
2440
+ }
2441
+ function createIndexGeneration() {
2442
+ return generateId("idx");
2443
+ }
2444
+ function statePath(scope, key) {
2445
+ return `${scope}/${key}`;
2446
+ }
2447
+ function errorMessage(err) {
2448
+ return err instanceof Error ? err.message : String(err);
2449
+ }
2450
+ function isValidShardDescriptor(shard) {
2451
+ if (!shard || typeof shard !== "object") return false;
2452
+ const candidate = shard;
2453
+ return typeof candidate.scope === "string" && candidate.scope.length > 0 && typeof candidate.key === "string" && candidate.key.length > 0 && Number.isInteger(candidate.chars) && candidate.chars >= 0;
2454
+ }
2385
2455
  var IndexPersistence = class {
2386
2456
  timer = null;
2387
2457
  lastFailureLogAt = 0;
2388
- constructor(kv, bm25, vector) {
2458
+ constructor(kv, bm25, vector, options = {}) {
2389
2459
  this.kv = kv;
2390
2460
  this.bm25 = bm25;
2391
2461
  this.vector = vector;
2462
+ this.options = options;
2392
2463
  }
2393
2464
  scheduleSave() {
2394
2465
  if (this.timer) clearTimeout(this.timer);
@@ -2402,8 +2473,8 @@ var IndexPersistence = class {
2402
2473
  this.timer = null;
2403
2474
  }
2404
2475
  try {
2405
- await this.kv.set(KV.bm25Index, "data", this.bm25.serialize());
2406
- if (this.vector && this.vector.size > 0) await this.kv.set(KV.bm25Index, "vectors", this.vector.serialize());
2476
+ await this.saveBm25Index(this.bm25.serialize());
2477
+ if (this.vector) await this.saveVectorIndex(this.vector.serialize());
2407
2478
  } catch (err) {
2408
2479
  this.logFailure(err);
2409
2480
  }
@@ -2411,9 +2482,9 @@ var IndexPersistence = class {
2411
2482
  async load() {
2412
2483
  let bm25 = null;
2413
2484
  let vector = null;
2414
- const bm25Data = await this.kv.get(KV.bm25Index, "data").catch(() => null);
2485
+ const bm25Data = await this.loadBm25Data();
2415
2486
  if (bm25Data && typeof bm25Data === "string") bm25 = SearchIndex.deserialize(bm25Data);
2416
- const vecData = await this.kv.get(KV.bm25Index, "vectors").catch(() => null);
2487
+ const vecData = await this.loadVectorData();
2417
2488
  if (vecData && typeof vecData === "string") vector = VectorIndex.deserialize(vecData);
2418
2489
  return {
2419
2490
  bm25,
@@ -2438,8 +2509,190 @@ var IndexPersistence = class {
2438
2509
  hint: code === "TIMEOUT" ? "iii-engine state::set timed out; recent index updates remain in memory and will retry on the next debounce flush" : void 0
2439
2510
  });
2440
2511
  }
2512
+ async saveBm25Index(serialized) {
2513
+ await this.saveShardedIndex(serialized, BM25_MANIFEST_KEY, BM25_KEY, BM25_SHARD_SCOPE_PREFIX);
2514
+ }
2515
+ async saveVectorIndex(serialized) {
2516
+ await this.saveShardedIndex(serialized, VECTOR_MANIFEST_KEY, VECTOR_KEY, VECTOR_SHARD_SCOPE_PREFIX);
2517
+ }
2518
+ async saveShardedIndex(serialized, manifestKey, legacyKey, scopePrefix) {
2519
+ const previous = await this.kv.get(KV.bm25Index, manifestKey).catch(() => null);
2520
+ const generation = this.options.createGeneration?.() ?? createIndexGeneration();
2521
+ const chunkChars = shardChars(this.options);
2522
+ const shards = [];
2523
+ const chunks = [];
2524
+ for (let offset = 0; offset < serialized.length; offset += chunkChars) {
2525
+ const shardIndex = shards.length;
2526
+ const scope = `${scopePrefix}${generation}:${String(shardIndex).padStart(5, "0")}`;
2527
+ const chunk = serialized.slice(offset, offset + chunkChars);
2528
+ shards.push({
2529
+ scope,
2530
+ key: INDEX_SHARD_KEY,
2531
+ chars: chunk.length
2532
+ });
2533
+ chunks.push(chunk);
2534
+ }
2535
+ const failedWrite = (await Promise.allSettled(shards.map(async (shard, index) => {
2536
+ const chunk = chunks[index] ?? "";
2537
+ await this.kv.set(shard.scope, shard.key, chunk);
2538
+ await this.auditIndexPersistence("shard_write", [statePath(shard.scope, shard.key)], {
2539
+ scope: shard.scope,
2540
+ key: shard.key,
2541
+ manifestKey,
2542
+ generation,
2543
+ chars: chunk.length
2544
+ });
2545
+ }))).find((result) => result.status === "rejected");
2546
+ if (failedWrite) {
2547
+ await this.deleteShards(shards, "shard_write_rollback");
2548
+ throw failedWrite.reason;
2549
+ }
2550
+ const nextManifest = {
2551
+ v: 1,
2552
+ generation,
2553
+ shards,
2554
+ chars: serialized.length
2555
+ };
2556
+ try {
2557
+ await this.kv.set(KV.bm25Index, manifestKey, nextManifest);
2558
+ await this.auditIndexPersistence("manifest_publish", [statePath(KV.bm25Index, manifestKey)], {
2559
+ manifestKey,
2560
+ generation,
2561
+ chars: serialized.length,
2562
+ shards: shards.length,
2563
+ result: "committed"
2564
+ });
2565
+ } catch (err) {
2566
+ if (await this.isManifestPublished(manifestKey, nextManifest)) await this.auditIndexPersistence("manifest_publish", [statePath(KV.bm25Index, manifestKey)], {
2567
+ manifestKey,
2568
+ generation,
2569
+ chars: serialized.length,
2570
+ shards: shards.length,
2571
+ result: "committed_after_error",
2572
+ error: errorMessage(err)
2573
+ });
2574
+ else await this.deleteShards(shards, "manifest_publish_rollback");
2575
+ throw err;
2576
+ }
2577
+ await this.deleteKey(KV.bm25Index, legacyKey, "legacy_cleanup");
2578
+ if (previous?.v === 1 && Array.isArray(previous.shards)) {
2579
+ const currentShardIds = new Set(shards.map((shard) => `${shard.scope}\0${shard.key}`));
2580
+ for (const shard of previous.shards) {
2581
+ if (currentShardIds.has(`${shard.scope}\0${shard.key}`)) continue;
2582
+ await this.deleteShards([shard], "previous_generation_cleanup");
2583
+ }
2584
+ }
2585
+ }
2586
+ async auditIndexPersistence(action, targetIds, details) {
2587
+ await safeAudit(this.kv, "index_persist", INDEX_PERSISTENCE_FUNCTION_ID, targetIds, {
2588
+ action,
2589
+ ...details
2590
+ });
2591
+ }
2592
+ async deleteKey(scope, key, reason) {
2593
+ let result = "deleted";
2594
+ let error;
2595
+ try {
2596
+ await this.kv.delete(scope, key);
2597
+ } catch (err) {
2598
+ result = "failed";
2599
+ error = errorMessage(err);
2600
+ }
2601
+ await this.auditIndexPersistence("delete", [statePath(scope, key)], {
2602
+ scope,
2603
+ key,
2604
+ reason,
2605
+ result,
2606
+ error
2607
+ });
2608
+ }
2609
+ async deleteShards(shards, reason) {
2610
+ for (const shard of shards) await this.deleteKey(shard.scope, shard.key, reason);
2611
+ }
2612
+ async isManifestPublished(manifestKey, expected) {
2613
+ const published = await this.kv.get(KV.bm25Index, manifestKey).catch(() => null);
2614
+ if (published?.v !== 1 || published.generation !== expected.generation || published.chars !== expected.chars || !Array.isArray(published.shards) || published.shards.length !== expected.shards.length) return false;
2615
+ return published.shards.every((shard, index) => {
2616
+ const expectedShard = expected.shards[index];
2617
+ if (!expectedShard) return false;
2618
+ return shard.scope === expectedShard.scope && shard.key === expectedShard.key && shard.chars === expectedShard.chars;
2619
+ });
2620
+ }
2621
+ async loadBm25Data() {
2622
+ return this.loadShardedData(BM25_KEY, BM25_MANIFEST_KEY, "BM25");
2623
+ }
2624
+ async loadVectorData() {
2625
+ return this.loadShardedData(VECTOR_KEY, VECTOR_MANIFEST_KEY, "vector");
2626
+ }
2627
+ async loadShardedData(legacyKey, manifestKey, label) {
2628
+ const manifest = await this.readIndexValue(KV.bm25Index, manifestKey, label, "manifest");
2629
+ if (!manifest.ok) return null;
2630
+ if (manifest.value !== null) return this.loadManifestData(manifest.value, label);
2631
+ const legacy = await this.readIndexValue(KV.bm25Index, legacyKey, label, "legacy");
2632
+ if (!legacy.ok) return null;
2633
+ if (legacy.value && typeof legacy.value === "string") return legacy.value;
2634
+ return null;
2635
+ }
2636
+ async readIndexValue(scope, key, label, source) {
2637
+ try {
2638
+ return {
2639
+ ok: true,
2640
+ value: await this.kv.get(scope, key)
2641
+ };
2642
+ } catch (err) {
2643
+ logger.warn(`index persistence: ${label} ${source} read failed`, {
2644
+ scope,
2645
+ key,
2646
+ message: errorMessage(err)
2647
+ });
2648
+ return { ok: false };
2649
+ }
2650
+ }
2651
+ async loadManifestData(manifest, label) {
2652
+ if (manifest.v !== 1 || !Array.isArray(manifest.shards) || manifest.shards.length === 0 || !Number.isInteger(manifest.chars) || manifest.chars < 0) {
2653
+ logger.warn(`index persistence: ${label} shard manifest invalid`);
2654
+ return null;
2655
+ }
2656
+ for (const shard of manifest.shards) if (!isValidShardDescriptor(shard)) {
2657
+ logger.warn(`index persistence: ${label} shard manifest invalid`);
2658
+ return null;
2659
+ }
2660
+ const loadedShards = await Promise.all(manifest.shards.map(async (shard) => ({
2661
+ shard,
2662
+ chunk: await this.kv.get(shard.scope, shard.key).catch(() => null)
2663
+ })));
2664
+ const chunks = [];
2665
+ let chars = 0;
2666
+ for (const { shard, chunk } of loadedShards) {
2667
+ if (typeof chunk !== "string") {
2668
+ logger.warn(`index persistence: ${label} shard missing`, {
2669
+ scope: shard.scope,
2670
+ key: shard.key
2671
+ });
2672
+ return null;
2673
+ }
2674
+ if (chunk.length !== shard.chars) {
2675
+ logger.warn(`index persistence: ${label} shard length mismatch`, {
2676
+ scope: shard.scope,
2677
+ key: shard.key,
2678
+ expected: shard.chars,
2679
+ actual: chunk.length
2680
+ });
2681
+ return null;
2682
+ }
2683
+ chunks.push(chunk);
2684
+ chars += chunk.length;
2685
+ }
2686
+ if (chars !== manifest.chars) {
2687
+ logger.warn(`index persistence: ${label} total length mismatch`, {
2688
+ expected: manifest.chars,
2689
+ actual: chars
2690
+ });
2691
+ return null;
2692
+ }
2693
+ return chunks.join("");
2694
+ }
2441
2695
  };
2442
-
2443
2696
  //#endregion
2444
2697
  //#region src/functions/privacy.ts
2445
2698
  const PRIVATE_TAG_RE = /<private>[\s\S]*?<\/private>/gi;
@@ -2476,7 +2729,6 @@ function registerPrivacyFunction(sdk) {
2476
2729
  return { output: stripPrivateData(data.input) };
2477
2730
  });
2478
2731
  }
2479
-
2480
2732
  //#endregion
2481
2733
  //#region src/functions/compress-synthetic.ts
2482
2734
  function inferType(toolName, hookType) {
@@ -2572,7 +2824,6 @@ function buildSyntheticCompression(raw) {
2572
2824
  if (raw.agentId) result.agentId = raw.agentId;
2573
2825
  return result;
2574
2826
  }
2575
-
2576
2827
  //#endregion
2577
2828
  //#region src/functions/access-tracker.ts
2578
2829
  const RECENT_CAP = 20;
@@ -2643,7 +2894,6 @@ async function deleteAccessLog(kv, memoryId) {
2643
2894
  });
2644
2895
  } catch {}
2645
2896
  }
2646
-
2647
2897
  //#endregion
2648
2898
  //#region src/functions/search.ts
2649
2899
  let index = null;
@@ -2999,7 +3249,6 @@ function registerSearchFunction(sdk, kv) {
2999
3249
  };
3000
3250
  });
3001
3251
  }
3002
-
3003
3252
  //#endregion
3004
3253
  //#region src/functions/observe.ts
3005
3254
  function extractImage(d) {
@@ -3078,25 +3327,37 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
3078
3327
  const inheritedAgentId = existingSession ? existingSession.agentId : getAgentId();
3079
3328
  if (inheritedAgentId) raw.agentId = inheritedAgentId;
3080
3329
  if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) {
3081
- const { saveImageToDisk } = await import("./image-store-CdE0amb1.mjs");
3330
+ const { saveImageToDisk } = await import("./image-store-Gpo2mgM9.mjs").then((n) => n.i);
3082
3331
  const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
3083
3332
  raw.imageData = filePath;
3084
- const { incrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
3333
+ const { incrementImageRef } = await import("./image-refs-C7h9L5wx.mjs").then((n) => n.n);
3085
3334
  await incrementImageRef(kv, filePath);
3086
- sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: bytesWritten });
3087
- if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] === "true") sdk.triggerVoid("mem::vision-embed", {
3088
- imageRef: filePath,
3089
- sessionId: payload.sessionId,
3090
- observationId: obsId
3335
+ sdk.trigger({
3336
+ function_id: "mem::disk-size-delta",
3337
+ payload: { deltaBytes: bytesWritten },
3338
+ action: TriggerAction.Void()
3339
+ });
3340
+ if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] === "true") sdk.trigger({
3341
+ function_id: "mem::vision-embed",
3342
+ payload: {
3343
+ imageRef: filePath,
3344
+ sessionId: payload.sessionId,
3345
+ observationId: obsId
3346
+ },
3347
+ action: TriggerAction.Void()
3091
3348
  });
3092
3349
  }
3093
3350
  try {
3094
3351
  await kv.set(KV.observations(payload.sessionId), obsId, raw);
3095
3352
  } catch (error) {
3096
3353
  if (raw.imageData) {
3097
- const { deleteImage } = await import("./image-store-CdE0amb1.mjs");
3354
+ const { deleteImage } = await import("./image-store-Gpo2mgM9.mjs").then((n) => n.i);
3098
3355
  const { deletedBytes } = await deleteImage(raw.imageData);
3099
- if (deletedBytes > 0) sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
3356
+ if (deletedBytes > 0) sdk.trigger({
3357
+ function_id: "mem::disk-size-delta",
3358
+ payload: { deltaBytes: -deletedBytes },
3359
+ action: TriggerAction.Void()
3360
+ });
3100
3361
  }
3101
3362
  throw error;
3102
3363
  }
@@ -3216,7 +3477,6 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
3216
3477
  });
3217
3478
  });
3218
3479
  }
3219
-
3220
3480
  //#endregion
3221
3481
  //#region src/functions/image-quota-cleanup.ts
3222
3482
  const GRACE_PERIOD_MS = 3e4;
@@ -3276,7 +3536,11 @@ function registerImageQuotaCleanup(sdk, kv) {
3276
3536
  if (refCount > 0) return;
3277
3537
  const { deletedBytes } = await deleteImage(f.filePath);
3278
3538
  if (deletedBytes > 0) {
3279
- sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
3539
+ sdk.trigger({
3540
+ function_id: "mem::disk-size-delta",
3541
+ payload: { deltaBytes: -deletedBytes },
3542
+ action: TriggerAction.Void()
3543
+ });
3280
3544
  totalToFree -= deletedBytes;
3281
3545
  freedBytes += deletedBytes;
3282
3546
  evicted++;
@@ -3298,53 +3562,6 @@ function registerImageQuotaCleanup(sdk, kv) {
3298
3562
  });
3299
3563
  });
3300
3564
  }
3301
-
3302
- //#endregion
3303
- //#region src/functions/audit.ts
3304
- async function recordAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
3305
- const entry = {
3306
- id: generateId("aud"),
3307
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3308
- operation,
3309
- userId,
3310
- functionId,
3311
- targetIds,
3312
- details,
3313
- qualityScore
3314
- };
3315
- await kv.set(KV.audit, entry.id, entry);
3316
- return entry;
3317
- }
3318
- async function safeAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
3319
- try {
3320
- await recordAudit(kv, operation, functionId, targetIds, details, qualityScore, userId);
3321
- } catch (err) {
3322
- try {
3323
- logger.warn("audit write failed", {
3324
- functionId,
3325
- operation,
3326
- targetIds,
3327
- error: err instanceof Error ? err.message : String(err)
3328
- });
3329
- } catch {}
3330
- }
3331
- }
3332
- async function queryAudit(kv, filter) {
3333
- let entries = [...await kv.list(KV.audit)].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
3334
- if (filter?.operation) entries = entries.filter((e) => e.operation === filter.operation);
3335
- if (filter?.dateFrom) {
3336
- const from = new Date(filter.dateFrom).getTime();
3337
- if (Number.isNaN(from)) throw new Error(`Invalid dateFrom: ${filter.dateFrom}`);
3338
- entries = entries.filter((e) => new Date(e.timestamp).getTime() >= from);
3339
- }
3340
- if (filter?.dateTo) {
3341
- const to = new Date(filter.dateTo).getTime();
3342
- if (Number.isNaN(to)) throw new Error(`Invalid dateTo: ${filter.dateTo}`);
3343
- entries = entries.filter((e) => new Date(e.timestamp).getTime() <= to);
3344
- }
3345
- return entries.slice(0, filter?.limit || 100);
3346
- }
3347
-
3348
3565
  //#endregion
3349
3566
  //#region src/functions/vision-search.ts
3350
3567
  function registerVisionSearchFunctions(sdk, kv, imageProvider) {
@@ -3468,7 +3685,6 @@ function cosine(a, b) {
3468
3685
  const denom = Math.sqrt(normA) * Math.sqrt(normB);
3469
3686
  return denom === 0 ? 0 : dot / denom;
3470
3687
  }
3471
-
3472
3688
  //#endregion
3473
3689
  //#region src/functions/slots.ts
3474
3690
  const DEFAULT_SIZE_LIMIT = 2e3;
@@ -3907,7 +4123,6 @@ function registerSlotsFunctions(sdk, kv) {
3907
4123
  };
3908
4124
  });
3909
4125
  }
3910
-
3911
4126
  //#endregion
3912
4127
  //#region src/functions/disk-size-manager.ts
3913
4128
  const DISK_SIZE_KEY = "system:currentDiskSize";
@@ -3922,7 +4137,11 @@ function registerDiskSizeManager(sdk, kv) {
3922
4137
  if (newTotal < 0) newTotal = 0;
3923
4138
  await kv.set(KV.state, DISK_SIZE_KEY, newTotal);
3924
4139
  if (data.deltaBytes > 0 && newTotal > getMaxBytes()) {
3925
- sdk.triggerVoid("mem::image-quota-cleanup", {});
4140
+ sdk.trigger({
4141
+ function_id: "mem::image-quota-cleanup",
4142
+ payload: {},
4143
+ action: TriggerAction.Void()
4144
+ });
3926
4145
  logger.info("Disk quota exceeded, cleanup triggered", {
3927
4146
  currentBytes: newTotal,
3928
4147
  maxBytes: getMaxBytes()
@@ -3935,7 +4154,6 @@ function registerDiskSizeManager(sdk, kv) {
3935
4154
  });
3936
4155
  });
3937
4156
  }
3938
-
3939
4157
  //#endregion
3940
4158
  //#region src/prompts/compression.ts
3941
4159
  const COMPRESSION_SYSTEM = `You are a memory compression engine for an AI coding agent. Your job is to extract the essential information from a tool usage observation and compress it into structured data.
@@ -3983,7 +4201,6 @@ function buildCompressionPrompt(observation) {
3983
4201
  function truncate$1(s, max) {
3984
4202
  return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s;
3985
4203
  }
3986
-
3987
4204
  //#endregion
3988
4205
  //#region src/prompts/vision.ts
3989
4206
  const VISION_DESCRIPTION_PROMPT = `Describe what this image shows in the context of software development. Extract:
@@ -3994,7 +4211,6 @@ const VISION_DESCRIPTION_PROMPT = `Describe what this image shows in the context
3994
4211
  - Text content visible in the image
3995
4212
 
3996
4213
  Be concise but preserve all technically relevant details. Output plain text, no XML.`;
3997
-
3998
4214
  //#endregion
3999
4215
  //#region src/prompts/xml.ts
4000
4216
  const VALID_TAG = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
@@ -4013,7 +4229,6 @@ function getXmlChildren(xml, parentTag, childTag) {
4013
4229
  while ((m = re.exec(parentMatch[1])) !== null) items.push(m[1].trim());
4014
4230
  return items;
4015
4231
  }
4016
-
4017
4232
  //#endregion
4018
4233
  //#region src/eval/schemas.ts
4019
4234
  const HookTypeEnum = z.enum([
@@ -4046,7 +4261,7 @@ const ObservationTypeEnum = z.enum([
4046
4261
  "task",
4047
4262
  "other"
4048
4263
  ]);
4049
- const ObserveInputSchema = z.object({
4264
+ z.object({
4050
4265
  hookType: HookTypeEnum,
4051
4266
  sessionId: z.string().min(1),
4052
4267
  project: z.string().min(1),
@@ -4071,16 +4286,16 @@ const SummaryOutputSchema = z.object({
4071
4286
  filesModified: z.array(z.string()),
4072
4287
  concepts: z.array(z.string())
4073
4288
  });
4074
- const SearchInputSchema = z.object({
4289
+ z.object({
4075
4290
  query: z.string().min(1),
4076
4291
  limit: z.number().int().positive().optional()
4077
4292
  });
4078
- const ContextInputSchema = z.object({
4293
+ z.object({
4079
4294
  sessionId: z.string().min(1),
4080
4295
  project: z.string().min(1),
4081
4296
  budget: z.number().positive().optional()
4082
4297
  });
4083
- const RememberInputSchema = z.object({
4298
+ z.object({
4084
4299
  content: z.string().min(1),
4085
4300
  type: z.enum([
4086
4301
  "pattern",
@@ -4093,22 +4308,22 @@ const RememberInputSchema = z.object({
4093
4308
  concepts: z.array(z.string()).optional(),
4094
4309
  files: z.array(z.string()).optional()
4095
4310
  });
4096
- const SmartSearchInputSchema = z.object({
4311
+ z.object({
4097
4312
  query: z.string().optional(),
4098
4313
  expandIds: z.array(z.string()).optional(),
4099
4314
  limit: z.number().int().positive().optional()
4100
4315
  });
4101
- const TimelineInputSchema = z.object({
4316
+ z.object({
4102
4317
  anchor: z.string().min(1),
4103
4318
  project: z.string().optional(),
4104
4319
  before: z.number().int().nonnegative().optional(),
4105
4320
  after: z.number().int().nonnegative().optional()
4106
4321
  });
4107
- const ProfileInputSchema = z.object({
4322
+ z.object({
4108
4323
  project: z.string().min(1),
4109
4324
  refresh: z.boolean().optional()
4110
4325
  });
4111
- const RelateInputSchema = z.object({
4326
+ z.object({
4112
4327
  sourceId: z.string().min(1),
4113
4328
  targetId: z.string().min(1),
4114
4329
  type: z.enum([
@@ -4119,12 +4334,12 @@ const RelateInputSchema = z.object({
4119
4334
  "related"
4120
4335
  ])
4121
4336
  });
4122
- const EvolveInputSchema = z.object({
4337
+ z.object({
4123
4338
  memoryId: z.string().min(1),
4124
4339
  newContent: z.string().min(1),
4125
4340
  newTitle: z.string().optional()
4126
4341
  });
4127
- const ExportImportInputSchema = z.object({
4342
+ z.object({
4128
4343
  exportData: z.object({
4129
4344
  version: z.union([z.literal("0.3.0"), z.literal("0.4.0")]),
4130
4345
  exportedAt: z.string(),
@@ -4140,7 +4355,6 @@ const ExportImportInputSchema = z.object({
4140
4355
  "skip"
4141
4356
  ]).optional()
4142
4357
  });
4143
-
4144
4358
  //#endregion
4145
4359
  //#region src/eval/validator.ts
4146
4360
  function validateInput(schema, data, functionId) {
@@ -4163,7 +4377,6 @@ function validateInput(schema, data, functionId) {
4163
4377
  function validateOutput(schema, data, functionId) {
4164
4378
  return validateInput(schema, data, functionId);
4165
4379
  }
4166
-
4167
4380
  //#endregion
4168
4381
  //#region src/eval/quality.ts
4169
4382
  function scoreCompression(obs) {
@@ -4187,7 +4400,6 @@ function scoreSummary(summary) {
4187
4400
  if (summary.concepts && summary.concepts.length > 0) score += 15;
4188
4401
  return Math.min(100, score);
4189
4402
  }
4190
-
4191
4403
  //#endregion
4192
4404
  //#region src/eval/self-correct.ts
4193
4405
  const STRICTER_SUFFIX = `
@@ -4211,7 +4423,6 @@ async function compressWithRetry(provider, systemPrompt, userPrompt, validator,
4211
4423
  retried: true
4212
4424
  };
4213
4425
  }
4214
-
4215
4426
  //#endregion
4216
4427
  //#region src/functions/compress.ts
4217
4428
  const VALID_TYPES$1 = new Set([
@@ -4392,7 +4603,6 @@ function registerCompressFunction(sdk, kv, provider, metricsStore) {
4392
4603
  }
4393
4604
  });
4394
4605
  }
4395
-
4396
4606
  //#endregion
4397
4607
  //#region src/functions/context.ts
4398
4608
  function estimateTokens$1(text) {
@@ -4517,7 +4727,6 @@ function registerContextFunction(sdk, kv, tokenBudget) {
4517
4727
  };
4518
4728
  });
4519
4729
  }
4520
-
4521
4730
  //#endregion
4522
4731
  //#region src/prompts/summary.ts
4523
4732
  const SUMMARY_SYSTEM = `You are a session summarizer for an AI coding agent's memory system. Given all compressed observations from a coding session, produce a concise session summary.
@@ -4589,7 +4798,6 @@ Concepts: ${concepts}`;
4589
4798
  });
4590
4799
  return `Partial summaries (${partials.length} chunks of one session, chronological):\n\n${sections.join("\n\n---\n\n")}`;
4591
4800
  }
4592
-
4593
4801
  //#endregion
4594
4802
  //#region src/functions/summarize.ts
4595
4803
  const CHUNK_SIZE_DEFAULT = 400;
@@ -4678,18 +4886,29 @@ async function produceSummaryXml(provider, compressed, sessionId, project) {
4678
4886
  skipped
4679
4887
  };
4680
4888
  }
4889
+ function stripXmlWrappers(raw) {
4890
+ if (!raw) return "";
4891
+ let cleaned = raw.trim();
4892
+ cleaned = cleaned.replace(/```\s*xml\s*\n?/gi, "");
4893
+ cleaned = cleaned.replace(/```/g, "");
4894
+ cleaned = cleaned.trim();
4895
+ const rootMatch = cleaned.match(/(<[a-zA-Z_][a-zA-Z0-9_-]*>[\s\S]*<\/[a-zA-Z_][a-zA-Z0-9_-]*>)/);
4896
+ if (rootMatch && rootMatch[1]) return rootMatch[1].trim();
4897
+ return cleaned;
4898
+ }
4681
4899
  function parseSummaryXml(xml, sessionId, project, obsCount) {
4682
- const title = getXmlTag(xml, "title");
4900
+ const cleaned = stripXmlWrappers(xml);
4901
+ const title = getXmlTag(cleaned, "title");
4683
4902
  if (!title) return null;
4684
4903
  return {
4685
4904
  sessionId,
4686
4905
  project,
4687
4906
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4688
4907
  title,
4689
- narrative: getXmlTag(xml, "narrative"),
4690
- keyDecisions: getXmlChildren(xml, "decisions", "decision"),
4691
- filesModified: getXmlChildren(xml, "files", "file"),
4692
- concepts: getXmlChildren(xml, "concepts", "concept"),
4908
+ narrative: getXmlTag(cleaned, "narrative"),
4909
+ keyDecisions: getXmlChildren(cleaned, "decisions", "decision"),
4910
+ filesModified: getXmlChildren(cleaned, "files", "file"),
4911
+ concepts: getXmlChildren(cleaned, "concepts", "concept"),
4693
4912
  observationCount: obsCount
4694
4913
  };
4695
4914
  }
@@ -4726,27 +4945,44 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
4726
4945
  };
4727
4946
  }
4728
4947
  try {
4729
- const { response, mode, chunks } = await produceSummaryXml(provider, compressed, sessionId, session.project);
4948
+ let summary = null;
4949
+ let response = "";
4950
+ let mode = "single";
4951
+ let chunks = 1;
4952
+ for (let attempt = 1; attempt <= 2; attempt++) {
4953
+ const produced = await produceSummaryXml(provider, compressed, sessionId, session.project);
4954
+ response = produced.response;
4955
+ mode = produced.mode;
4956
+ chunks = produced.chunks;
4957
+ if (!response || !response.trim()) {
4958
+ logger.warn("Empty provider response on summarize", {
4959
+ sessionId,
4960
+ provider: provider.name,
4961
+ mode,
4962
+ chunks,
4963
+ observationCount: compressed.length,
4964
+ attempt
4965
+ });
4966
+ continue;
4967
+ }
4968
+ summary = parseSummaryXml(response, sessionId, session.project, compressed.length);
4969
+ if (summary) break;
4970
+ logger.warn("Failed to parse summary XML", {
4971
+ sessionId,
4972
+ attempt
4973
+ });
4974
+ }
4730
4975
  if (!response || !response.trim()) {
4731
4976
  const latencyMs = Date.now() - startMs;
4732
4977
  if (metricsStore) await metricsStore.record("mem::summarize", latencyMs, false);
4733
- logger.warn("Empty provider response on summarize", {
4734
- sessionId,
4735
- provider: provider.name,
4736
- mode,
4737
- chunks,
4738
- observationCount: compressed.length
4739
- });
4740
4978
  return {
4741
4979
  success: false,
4742
4980
  error: "empty_provider_response"
4743
4981
  };
4744
4982
  }
4745
- const summary = parseSummaryXml(response, sessionId, session.project, compressed.length);
4746
4983
  if (!summary) {
4747
4984
  const latencyMs = Date.now() - startMs;
4748
4985
  if (metricsStore) await metricsStore.record("mem::summarize", latencyMs, false);
4749
- logger.warn("Failed to parse summary XML", { sessionId });
4750
4986
  return {
4751
4987
  success: false,
4752
4988
  error: "parse_failed"
@@ -4807,7 +5043,6 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
4807
5043
  }
4808
5044
  });
4809
5045
  }
4810
-
4811
5046
  //#endregion
4812
5047
  //#region src/functions/migrate.ts
4813
5048
  const ALLOWED_DIRS = [resolve(homedir(), ".agentmemory")];
@@ -5008,7 +5243,6 @@ function safeJsonParse(value, fallback) {
5008
5243
  }
5009
5244
  return fallback;
5010
5245
  }
5011
-
5012
5246
  //#endregion
5013
5247
  //#region src/functions/file-index.ts
5014
5248
  function registerFileIndexFunction(sdk, kv) {
@@ -5078,7 +5312,6 @@ function registerFileIndexFunction(sdk, kv) {
5078
5312
  return { context };
5079
5313
  });
5080
5314
  }
5081
-
5082
5315
  //#endregion
5083
5316
  //#region src/functions/consolidate.ts
5084
5317
  const CONSOLIDATION_SYSTEM = `You are a memory consolidation engine. Given a set of related observations from coding sessions, synthesize them into a single long-term memory.
@@ -5230,7 +5463,6 @@ function registerConsolidateFunction(sdk, kv, provider) {
5230
5463
  };
5231
5464
  });
5232
5465
  }
5233
-
5234
5466
  //#endregion
5235
5467
  //#region src/functions/patterns.ts
5236
5468
  function registerPatternsFunction(sdk, kv) {
@@ -5314,7 +5546,6 @@ function registerPatternsFunction(sdk, kv) {
5314
5546
  return { rules };
5315
5547
  });
5316
5548
  }
5317
-
5318
5549
  //#endregion
5319
5550
  //#region src/functions/remember.ts
5320
5551
  function registerRememberFunction(sdk, kv) {
@@ -5420,7 +5651,7 @@ function registerRememberFunction(sdk, kv) {
5420
5651
  const deletedMemoryIds = [];
5421
5652
  const deletedObservationIds = [];
5422
5653
  let deletedSession = false;
5423
- const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
5654
+ const { decrementImageRef } = await import("./image-refs-C7h9L5wx.mjs").then((n) => n.n);
5424
5655
  if (data.memoryId) {
5425
5656
  const mem = await kv.get(KV.memories, data.memoryId);
5426
5657
  await kv.delete(KV.memories, data.memoryId);
@@ -5475,7 +5706,6 @@ function registerRememberFunction(sdk, kv) {
5475
5706
  };
5476
5707
  });
5477
5708
  }
5478
-
5479
5709
  //#endregion
5480
5710
  //#region src/functions/evict.ts
5481
5711
  const MS_PER_DAY$1 = 1440 * 60 * 1e3;
@@ -5528,7 +5758,7 @@ async function runRecoveredSessionConsolidation(sdk) {
5528
5758
  function registerEvictFunction(sdk, kv) {
5529
5759
  sdk.registerFunction("mem::evict", async (data) => {
5530
5760
  const dryRun = data?.dryRun ?? false;
5531
- const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
5761
+ const { decrementImageRef } = await import("./image-refs-C7h9L5wx.mjs").then((n) => n.n);
5532
5762
  const configOverride = await kv.get(KV.config, "eviction").catch(() => null);
5533
5763
  const cfg = {
5534
5764
  ...DEFAULTS$1,
@@ -5706,7 +5936,6 @@ function registerEvictFunction(sdk, kv) {
5706
5936
  return stats;
5707
5937
  });
5708
5938
  }
5709
-
5710
5939
  //#endregion
5711
5940
  //#region src/functions/relations.ts
5712
5941
  function computeConfidence(source, target, relationType) {
@@ -5894,7 +6123,6 @@ function registerRelationsFunction(sdk, kv) {
5894
6123
  return { results: result };
5895
6124
  });
5896
6125
  }
5897
-
5898
6126
  //#endregion
5899
6127
  //#region src/functions/timeline.ts
5900
6128
  function registerTimelineFunction(sdk, kv) {
@@ -5976,9 +6204,75 @@ async function findByKeyword(kv, keyword, project) {
5976
6204
  }
5977
6205
  return matches.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
5978
6206
  }
5979
-
6207
+ //#endregion
6208
+ //#region src/telemetry/setup.ts
6209
+ const OTEL_CONFIG = {
6210
+ serviceName: "agentmemory",
6211
+ serviceVersion: VERSION,
6212
+ metricsExportIntervalMs: 3e4
6213
+ };
6214
+ let counters = null;
6215
+ let histograms = null;
6216
+ const NOOP_COUNTER = { add: () => {} };
6217
+ const NOOP_HISTOGRAM = { record: () => {} };
6218
+ const COUNTER_NAMES = [
6219
+ ["observationsTotal", "observations.total"],
6220
+ ["compressionSuccess", "compression.success"],
6221
+ ["compressionFailure", "compression.failure"],
6222
+ ["searchTotal", "search.total"],
6223
+ ["dedupSkipped", "dedup.skipped"],
6224
+ ["evictionTotal", "eviction.total"],
6225
+ ["circuitBreakerOpen", "circuit_breaker.open"],
6226
+ ["embeddingSuccess", "embedding.success"],
6227
+ ["embeddingFailure", "embedding.failure"],
6228
+ ["vectorSearchTotal", "vector_search.total"],
6229
+ ["autoForgetTotal", "auto_forget.total"],
6230
+ ["profileGenerated", "profile.generated"],
6231
+ ["claudeBridgeSync", "claude_bridge.sync"],
6232
+ ["graphExtraction", "graph.extraction"],
6233
+ ["consolidationRun", "consolidation.run"],
6234
+ ["teamShare", "team.share"],
6235
+ ["auditLog", "audit.log"],
6236
+ ["snapshotCreate", "snapshot.create"],
6237
+ ["governanceDelete", "governance.delete"],
6238
+ ["smartSearchFollowupWithinWindow", "smart_search.followup_within_window_total"],
6239
+ ["readerFailureWithEvidence", "reader_failure_with_evidence_total"]
6240
+ ];
6241
+ const HISTOGRAM_NAMES = [
6242
+ ["compressionLatency", "compression.latency_ms"],
6243
+ ["searchLatency", "search.latency_ms"],
6244
+ ["contextTokens", "context.tokens"],
6245
+ ["qualityScore", "quality.score"],
6246
+ ["embeddingLatency", "embedding.latency_ms"],
6247
+ ["vectorSearchLatency", "vector_search.latency_ms"]
6248
+ ];
6249
+ function getCounters() {
6250
+ if (counters) return counters;
6251
+ return Object.fromEntries(COUNTER_NAMES.map(([key]) => [key, NOOP_COUNTER]));
6252
+ }
6253
+ function initMetrics(getMeter) {
6254
+ const meter = getMeter?.("agentmemory");
6255
+ counters = Object.fromEntries(COUNTER_NAMES.map(([key, name]) => [key, meter ? meter.createCounter(name) : NOOP_COUNTER]));
6256
+ histograms = Object.fromEntries(HISTOGRAM_NAMES.map(([key, name]) => [key, meter ? meter.createHistogram(name) : NOOP_HISTOGRAM]));
6257
+ return {
6258
+ counters,
6259
+ histograms
6260
+ };
6261
+ }
5980
6262
  //#endregion
5981
6263
  //#region src/functions/smart-search.ts
6264
+ const followupStats = {
6265
+ followupWithinWindow: 0,
6266
+ agentInitiatedSearches: 0
6267
+ };
6268
+ const pendingFollowups = /* @__PURE__ */ new Set();
6269
+ function getFollowupStats() {
6270
+ const total = followupStats.agentInitiatedSearches;
6271
+ return {
6272
+ ...followupStats,
6273
+ rate: total > 0 ? followupStats.followupWithinWindow / total : 0
6274
+ };
6275
+ }
5982
6276
  const LESSON_CONTENT_PREVIEW_CHARS = 240;
5983
6277
  function registerSmartSearchFunction(sdk, kv, searchFn) {
5984
6278
  sdk.registerFunction("mem::smart-search", async (data) => {
@@ -6040,6 +6334,21 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
6040
6334
  timestamp: r.observation.timestamp
6041
6335
  }));
6042
6336
  recordAccessBatch(kv, compact.map((r) => r.obsId));
6337
+ if (data.sessionId && typeof data.sessionId === "string" && data.source !== "viewer" && compact.length > 0) {
6338
+ followupStats.agentInitiatedSearches++;
6339
+ const sessionIdForFollowup = data.sessionId;
6340
+ const queryForFollowup = data.query;
6341
+ const compactForFollowup = compact;
6342
+ const detection = withKeyedLock(`recent-searches:${sessionIdForFollowup}`, () => detectFollowup(kv, sessionIdForFollowup, queryForFollowup, compactForFollowup)).catch((err) => {
6343
+ logger.warn("Smart search followup detection failed", {
6344
+ sessionId: sessionIdForFollowup,
6345
+ error: err instanceof Error ? err.message : String(err)
6346
+ });
6347
+ }).finally(() => {
6348
+ pendingFollowups.delete(detection);
6349
+ });
6350
+ pendingFollowups.add(detection);
6351
+ }
6043
6352
  logger.info("Smart search compact", {
6044
6353
  query: data.query,
6045
6354
  results: compact.length,
@@ -6078,6 +6387,35 @@ async function recallLessons(sdk, query, limit, project) {
6078
6387
  return [];
6079
6388
  }
6080
6389
  }
6390
+ async function detectFollowup(kv, sessionId, query, compact) {
6391
+ const now = Date.now();
6392
+ const windowMs = Math.max(1, getFollowupWindowSeconds()) * 1e3;
6393
+ const currentIds = compact.map((r) => r.obsId);
6394
+ const current = {
6395
+ sessionId,
6396
+ query,
6397
+ resultIds: currentIds,
6398
+ at: now
6399
+ };
6400
+ const prior = await kv.get(KV.recentSearches, sessionId).catch(() => null);
6401
+ await kv.set(KV.recentSearches, sessionId, current);
6402
+ if (!prior || typeof prior.at !== "number") return;
6403
+ if (now - prior.at > windowMs) return;
6404
+ if (typeof prior.query === "string" && prior.query === query) return;
6405
+ const priorIds = Array.isArray(prior.resultIds) ? prior.resultIds : [];
6406
+ const priorSet = new Set(priorIds);
6407
+ if (currentIds.some((id) => priorSet.has(id))) return;
6408
+ getCounters().smartSearchFollowupWithinWindow.add(1);
6409
+ followupStats.followupWithinWindow++;
6410
+ logger.info("Smart search followup detected", {
6411
+ sessionId,
6412
+ windowSeconds: Math.round(windowMs / 1e3),
6413
+ priorQuery: prior.query,
6414
+ nextQuery: query,
6415
+ priorResultCount: priorIds.length,
6416
+ nextResultCount: currentIds.length
6417
+ });
6418
+ }
6081
6419
  async function findObservation$1(kv, obsId, sessionIdHint) {
6082
6420
  if (sessionIdHint) {
6083
6421
  const obs = await kv.get(KV.observations(sessionIdHint), obsId).catch(() => null);
@@ -6091,7 +6429,52 @@ async function findObservation$1(kv, obsId, sessionIdHint) {
6091
6429
  }
6092
6430
  return null;
6093
6431
  }
6094
-
6432
+ //#endregion
6433
+ //#region src/functions/recent-searches-sweep.ts
6434
+ const RETENTION_MS = 1440 * 60 * 1e3;
6435
+ function registerRecentSearchesSweepFunction(sdk, kv) {
6436
+ sdk.registerFunction("mem::diagnostic::recent-searches-sweep", async () => {
6437
+ const cutoff = Date.now() - RETENTION_MS;
6438
+ const rows = await kv.list(KV.recentSearches).catch(() => []);
6439
+ let swept = 0;
6440
+ let skipped = 0;
6441
+ for (const row of rows) {
6442
+ if (!row || typeof row.sessionId !== "string" || !row.sessionId) {
6443
+ skipped++;
6444
+ continue;
6445
+ }
6446
+ if ((typeof row.at === "number" ? row.at : 0) >= cutoff) continue;
6447
+ try {
6448
+ await kv.delete(KV.recentSearches, row.sessionId);
6449
+ swept++;
6450
+ } catch (err) {
6451
+ logger.warn("recent-searches sweep delete failed", {
6452
+ sessionId: row.sessionId,
6453
+ error: err instanceof Error ? err.message : String(err)
6454
+ });
6455
+ }
6456
+ }
6457
+ if (swept > 0 || skipped > 0) logger.info("Recent-searches sweep complete", {
6458
+ swept,
6459
+ skipped
6460
+ });
6461
+ return {
6462
+ success: true,
6463
+ swept,
6464
+ skipped
6465
+ };
6466
+ });
6467
+ sdk.registerFunction("mem::diagnostic::followup-stats", async () => {
6468
+ const stats = getFollowupStats();
6469
+ return {
6470
+ success: true,
6471
+ windowSeconds: getFollowupWindowSeconds(),
6472
+ agentInitiatedSearches: stats.agentInitiatedSearches,
6473
+ followupWithinWindow: stats.followupWithinWindow,
6474
+ rate: stats.rate
6475
+ };
6476
+ });
6477
+ }
6095
6478
  //#endregion
6096
6479
  //#region src/functions/profile.ts
6097
6480
  function registerProfileFunction(sdk, kv) {
@@ -6179,7 +6562,6 @@ function extractConventions(concepts, files) {
6179
6562
  for (const { concept, frequency } of concepts.slice(0, 5)) if (frequency >= 3) conventions.push(`Frequently uses: ${concept}`);
6180
6563
  return conventions;
6181
6564
  }
6182
-
6183
6565
  //#endregion
6184
6566
  //#region src/functions/auto-forget.ts
6185
6567
  const MS_PER_DAY = 1440 * 60 * 1e3;
@@ -6188,7 +6570,7 @@ function registerAutoForgetFunction(sdk, kv) {
6188
6570
  sdk.registerFunction("mem::auto-forget", async (data) => {
6189
6571
  const dryRun = data?.dryRun ?? false;
6190
6572
  const now = Date.now();
6191
- const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
6573
+ const { decrementImageRef } = await import("./image-refs-C7h9L5wx.mjs").then((n) => n.n);
6192
6574
  const result = {
6193
6575
  ttlExpired: [],
6194
6576
  contradictions: [],
@@ -6305,7 +6687,6 @@ function registerAutoForgetFunction(sdk, kv) {
6305
6687
  return result;
6306
6688
  });
6307
6689
  }
6308
-
6309
6690
  //#endregion
6310
6691
  //#region src/functions/export-import.ts
6311
6692
  function registerExportImportFunction(sdk, kv) {
@@ -6445,7 +6826,9 @@ function registerExportImportFunction(sdk, kv) {
6445
6826
  "0.9.20",
6446
6827
  "0.9.21",
6447
6828
  "0.9.22",
6448
- "0.9.23"
6829
+ "0.9.23",
6830
+ "0.9.24",
6831
+ "0.9.25"
6449
6832
  ]).has(importData.version)) return {
6450
6833
  success: false,
6451
6834
  error: `Unsupported export version: ${importData.version}`
@@ -6763,7 +7146,6 @@ function registerExportImportFunction(sdk, kv) {
6763
7146
  };
6764
7147
  });
6765
7148
  }
6766
-
6767
7149
  //#endregion
6768
7150
  //#region src/functions/enrich.ts
6769
7151
  const MAX_CONTEXT_LENGTH = 4e3;
@@ -6824,7 +7206,6 @@ function registerEnrichFunction(sdk, kv) {
6824
7206
  };
6825
7207
  });
6826
7208
  }
6827
-
6828
7209
  //#endregion
6829
7210
  //#region src/functions/claude-bridge.ts
6830
7211
  function parseMemoryMd(content) {
@@ -6945,7 +7326,6 @@ function registerClaudeBridgeFunction(sdk, kv, config) {
6945
7326
  }
6946
7327
  });
6947
7328
  }
6948
-
6949
7329
  //#endregion
6950
7330
  //#region src/prompts/graph-extraction.ts
6951
7331
  const GRAPH_EXTRACTION_SYSTEM = `You are a knowledge graph extraction engine. Given a compressed observation from a coding session, extract entities and relationships.
@@ -6968,9 +7348,41 @@ Rules:
6968
7348
  function buildGraphExtractionPrompt(observations) {
6969
7349
  return `Extract entities and relationships from these observations:\n\n${observations.map((o, i) => `[${i + 1}] Type: ${o.type}\nTitle: ${o.title}\nNarrative: ${o.narrative}\nConcepts: ${(o.concepts ?? []).join(", ")}\nFiles: ${(o.files ?? []).join(", ")}`).join("\n\n")}`;
6970
7350
  }
6971
-
6972
7351
  //#endregion
6973
7352
  //#region src/functions/graph.ts
7353
+ const DEFAULT_GRAPH_QUERY_LIMIT = 500;
7354
+ const MAX_GRAPH_QUERY_LIMIT = 5e3;
7355
+ function resolvePagination(rawLimit, rawOffset) {
7356
+ return {
7357
+ limit: Math.max(1, Math.min(typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.floor(rawLimit) : DEFAULT_GRAPH_QUERY_LIMIT, MAX_GRAPH_QUERY_LIMIT)),
7358
+ offset: Math.max(0, typeof rawOffset === "number" && Number.isFinite(rawOffset) ? Math.floor(rawOffset) : 0)
7359
+ };
7360
+ }
7361
+ function rankByDegree(nodes, edges) {
7362
+ const degree = /* @__PURE__ */ new Map();
7363
+ for (const edge of edges) {
7364
+ degree.set(edge.sourceNodeId, (degree.get(edge.sourceNodeId) ?? 0) + 1);
7365
+ degree.set(edge.targetNodeId, (degree.get(edge.targetNodeId) ?? 0) + 1);
7366
+ }
7367
+ return [...nodes].sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0));
7368
+ }
7369
+ function paginate(nodes, allEdges, depth, limit, offset) {
7370
+ const totalNodes = nodes.length;
7371
+ const pageNodes = nodes.slice(offset, offset + limit);
7372
+ const pageNodeIds = new Set(pageNodes.map((n) => n.id));
7373
+ const pageEdges = allEdges.filter((e) => pageNodeIds.has(e.sourceNodeId) && pageNodeIds.has(e.targetNodeId));
7374
+ const universeIds = new Set(nodes.map((n) => n.id));
7375
+ return {
7376
+ nodes: pageNodes,
7377
+ edges: pageEdges,
7378
+ depth,
7379
+ totalNodes,
7380
+ totalEdges: allEdges.reduce((count, e) => universeIds.has(e.sourceNodeId) && universeIds.has(e.targetNodeId) ? count + 1 : count, 0),
7381
+ truncated: totalNodes > pageNodes.length,
7382
+ limit,
7383
+ offset
7384
+ };
7385
+ }
6974
7386
  function parseAttrs(raw) {
6975
7387
  const attrs = {};
6976
7388
  const attrRegex = /([A-Za-z_][\w:-]*)="([^"]*)"/g;
@@ -7107,15 +7519,10 @@ function registerGraphFunction(sdk, kv, provider) {
7107
7519
  const allNodes = (await kv.list(KV.graphNodes)).filter((n) => !n.stale);
7108
7520
  const allEdges = (await kv.list(KV.graphEdges)).filter((e) => !e.stale);
7109
7521
  const maxDepth = Math.min(data.maxDepth || 3, 5);
7522
+ const { limit, offset } = resolvePagination(data.limit, data.offset);
7110
7523
  if (data.query) {
7111
7524
  const lower = data.query.toLowerCase();
7112
- const matchingNodes = allNodes.filter((n) => n.name.toLowerCase().includes(lower) || Object.values(n.properties).some((v) => typeof v === "string" && v.toLowerCase().includes(lower)));
7113
- const nodeIds = new Set(matchingNodes.map((n) => n.id));
7114
- return {
7115
- nodes: matchingNodes,
7116
- edges: allEdges.filter((e) => nodeIds.has(e.sourceNodeId) || nodeIds.has(e.targetNodeId)),
7117
- depth: 0
7118
- };
7525
+ 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);
7119
7526
  }
7120
7527
  if (data.startNodeId) {
7121
7528
  const visited = /* @__PURE__ */ new Set();
@@ -7147,19 +7554,11 @@ function registerGraphFunction(sdk, kv, provider) {
7147
7554
  });
7148
7555
  }
7149
7556
  }
7150
- return {
7151
- nodes: resultNodes,
7152
- edges: resultEdges,
7153
- depth: maxDepth
7154
- };
7557
+ return paginate(resultNodes, resultEdges, maxDepth, limit, offset);
7155
7558
  }
7156
7559
  let filtered = allNodes;
7157
7560
  if (data.nodeType) filtered = allNodes.filter((n) => n.type === data.nodeType);
7158
- return {
7159
- nodes: filtered,
7160
- edges: allEdges,
7161
- depth: 0
7162
- };
7561
+ return paginate(rankByDegree(filtered, allEdges), allEdges, 0, limit, offset);
7163
7562
  });
7164
7563
  sdk.registerFunction("mem::graph-stats", async () => {
7165
7564
  const nodes = await kv.list(KV.graphNodes);
@@ -7176,7 +7575,6 @@ function registerGraphFunction(sdk, kv, provider) {
7176
7575
  };
7177
7576
  });
7178
7577
  }
7179
-
7180
7578
  //#endregion
7181
7579
  //#region src/prompts/consolidation.ts
7182
7580
  const SEMANTIC_MERGE_SYSTEM = `You are a memory consolidation engine. Given overlapping episodic memories (session summaries), extract stable factual knowledge.
@@ -7211,7 +7609,6 @@ Rules:
7211
7609
  function buildProceduralExtractionPrompt(patterns) {
7212
7610
  return `Extract reusable procedures from these recurring patterns:\n\n${patterns.map((p, i) => `[Pattern ${i + 1}] (seen ${p.frequency}x)\n${p.content}`).join("\n\n")}`;
7213
7611
  }
7214
-
7215
7612
  //#endregion
7216
7613
  //#region src/functions/consolidation-pipeline.ts
7217
7614
  function applyDecay(items, decayDays) {
@@ -7405,7 +7802,6 @@ function registerConsolidationPipelineFunction(sdk, kv, provider) {
7405
7802
  };
7406
7803
  });
7407
7804
  }
7408
-
7409
7805
  //#endregion
7410
7806
  //#region src/functions/team.ts
7411
7807
  const VALID_ITEM_TYPES = new Set([
@@ -7509,7 +7905,6 @@ function registerTeamFunction(sdk, kv, config) {
7509
7905
  return profile;
7510
7906
  });
7511
7907
  }
7512
-
7513
7908
  //#endregion
7514
7909
  //#region src/functions/governance.ts
7515
7910
  function registerGovernanceFunction(sdk, kv) {
@@ -7618,7 +8013,6 @@ function registerGovernanceFunction(sdk, kv) {
7618
8013
  return queryAudit(kv, data);
7619
8014
  });
7620
8015
  }
7621
-
7622
8016
  //#endregion
7623
8017
  //#region src/functions/snapshot.ts
7624
8018
  const COMMIT_HASH_RE = /^[0-9a-f]{7,40}$/i;
@@ -7783,7 +8177,6 @@ function registerSnapshotFunction(sdk, kv, snapshotDir) {
7783
8177
  }
7784
8178
  });
7785
8179
  }
7786
-
7787
8180
  //#endregion
7788
8181
  //#region src/functions/actions.ts
7789
8182
  function registerActionsFunction(sdk, kv) {
@@ -7985,7 +8378,6 @@ async function propagateCompletion(kv, completedActionId) {
7985
8378
  });
7986
8379
  }
7987
8380
  }
7988
-
7989
8381
  //#endregion
7990
8382
  //#region src/functions/frontier.ts
7991
8383
  function registerFrontierFunction(sdk, kv) {
@@ -8090,7 +8482,6 @@ function computeScore(action, edges, now) {
8090
8482
  if (action.status === "active") score += 15;
8091
8483
  return Math.round(score * 100) / 100;
8092
8484
  }
8093
-
8094
8485
  //#endregion
8095
8486
  //#region src/functions/leases.ts
8096
8487
  const DEFAULT_LEASE_TTL_MS = 600 * 1e3;
@@ -8271,7 +8662,6 @@ function registerLeasesFunction(sdk, kv) {
8271
8662
  };
8272
8663
  });
8273
8664
  }
8274
-
8275
8665
  //#endregion
8276
8666
  //#region src/functions/routines.ts
8277
8667
  function registerRoutinesFunction(sdk, kv) {
@@ -8518,7 +8908,6 @@ function registerRoutinesFunction(sdk, kv) {
8518
8908
  });
8519
8909
  });
8520
8910
  }
8521
-
8522
8911
  //#endregion
8523
8912
  //#region src/functions/signals.ts
8524
8913
  function registerSignalsFunction(sdk, kv) {
@@ -8650,7 +9039,6 @@ function registerSignalsFunction(sdk, kv) {
8650
9039
  };
8651
9040
  });
8652
9041
  }
8653
-
8654
9042
  //#endregion
8655
9043
  //#region src/functions/checkpoints.ts
8656
9044
  function registerCheckpointsFunction(sdk, kv) {
@@ -8823,7 +9211,6 @@ function registerCheckpointsFunction(sdk, kv) {
8823
9211
  };
8824
9212
  });
8825
9213
  }
8826
-
8827
9214
  //#endregion
8828
9215
  //#region src/functions/flow-compress.ts
8829
9216
  const FLOW_COMPRESS_SYSTEM = `You are a workflow summarizer. Given a completed action chain, produce a concise summary capturing:
@@ -8968,7 +9355,6 @@ function extractFiles(actions) {
8968
9355
  }
8969
9356
  return Array.from(files);
8970
9357
  }
8971
-
8972
9358
  //#endregion
8973
9359
  //#region src/functions/mesh.ts
8974
9360
  function isPrivateIP(ip) {
@@ -9285,7 +9671,6 @@ async function applySyncData(kv, data, scopes) {
9285
9671
  if (scopes.includes("graph:edges")) applied += await lwwMergeList(kv, KV.graphEdges, data.graphEdges, "mem:gedge", "createdAt");
9286
9672
  return applied;
9287
9673
  }
9288
-
9289
9674
  //#endregion
9290
9675
  //#region src/functions/branch-aware.ts
9291
9676
  function execAsync(cmd, args, cwd) {
@@ -9400,7 +9785,6 @@ function registerBranchAwareFunction(sdk, kv) {
9400
9785
  };
9401
9786
  });
9402
9787
  }
9403
-
9404
9788
  //#endregion
9405
9789
  //#region src/functions/sentinels.ts
9406
9790
  const VALID_TYPES = [
@@ -9721,7 +10105,6 @@ async function unblockLinkedActions(kv, sentinel) {
9721
10105
  });
9722
10106
  return unblockedCount;
9723
10107
  }
9724
-
9725
10108
  //#endregion
9726
10109
  //#region src/functions/sketches.ts
9727
10110
  function registerSketchesFunction(sdk, kv) {
@@ -9963,7 +10346,6 @@ function registerSketchesFunction(sdk, kv) {
9963
10346
  };
9964
10347
  });
9965
10348
  }
9966
-
9967
10349
  //#endregion
9968
10350
  //#region src/functions/crystallize.ts
9969
10351
  const CRYSTALLIZE_SYSTEM = `You are summarizing a completed chain of agent actions into a compact digest.
@@ -10160,7 +10542,6 @@ function parseDigest(response) {
10160
10542
  };
10161
10543
  }
10162
10544
  }
10163
-
10164
10545
  //#endregion
10165
10546
  //#region src/functions/diagnostics.ts
10166
10547
  const ALL_CATEGORIES = [
@@ -10874,7 +11255,6 @@ function registerDiagnosticsFunction(sdk, kv) {
10874
11255
  };
10875
11256
  });
10876
11257
  }
10877
-
10878
11258
  //#endregion
10879
11259
  //#region src/functions/facets.ts
10880
11260
  function registerFacetsFunction(sdk, kv) {
@@ -11046,7 +11426,6 @@ function registerFacetsFunction(sdk, kv) {
11046
11426
  };
11047
11427
  });
11048
11428
  }
11049
-
11050
11429
  //#endregion
11051
11430
  //#region src/functions/verify.ts
11052
11431
  function registerVerifyFunction(sdk, kv) {
@@ -11141,7 +11520,6 @@ async function findObservation(kv, obsId, hintSessionIds) {
11141
11520
  }
11142
11521
  return null;
11143
11522
  }
11144
-
11145
11523
  //#endregion
11146
11524
  //#region src/functions/cascade.ts
11147
11525
  function registerCascadeFunction(sdk, kv) {
@@ -11211,7 +11589,6 @@ function registerCascadeFunction(sdk, kv) {
11211
11589
  };
11212
11590
  });
11213
11591
  }
11214
-
11215
11592
  //#endregion
11216
11593
  //#region src/functions/lessons.ts
11217
11594
  function reinforceLesson(lesson) {
@@ -11397,7 +11774,6 @@ function registerLessonsFunctions(sdk, kv) {
11397
11774
  };
11398
11775
  });
11399
11776
  }
11400
-
11401
11777
  //#endregion
11402
11778
  //#region src/functions/obsidian-export.ts
11403
11779
  const DEFAULT_EXPORT_ROOT = join(homedir(), ".agentmemory");
@@ -11413,6 +11789,20 @@ function resolveVaultDir(vaultDir) {
11413
11789
  function sanitize(name) {
11414
11790
  return name.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_").slice(0, 100);
11415
11791
  }
11792
+ function hasExportId(item) {
11793
+ return !!item && typeof item.id === "string" && item.id.length > 0;
11794
+ }
11795
+ function safeArray(value) {
11796
+ return Array.isArray(value) ? value : [];
11797
+ }
11798
+ function safeString(value, fallback = "") {
11799
+ return typeof value === "string" ? value : fallback;
11800
+ }
11801
+ function safeTimestamp(value) {
11802
+ if (typeof value !== "string") return 0;
11803
+ const time = new Date(value).getTime();
11804
+ return Number.isFinite(time) ? time : 0;
11805
+ }
11416
11806
  function toFrontmatter(obj) {
11417
11807
  const lines = ["---"];
11418
11808
  for (const [key, value] of Object.entries(obj)) {
@@ -11424,6 +11814,11 @@ function toFrontmatter(obj) {
11424
11814
  return lines.join("\n");
11425
11815
  }
11426
11816
  function memoryToMd(m) {
11817
+ const concepts = safeArray(m.concepts);
11818
+ const files = safeArray(m.files);
11819
+ const relatedIds = safeArray(m.relatedIds);
11820
+ const supersedes = safeArray(m.supersedes);
11821
+ const title = safeString(m.title, m.id);
11427
11822
  const fm = toFrontmatter({
11428
11823
  id: m.id,
11429
11824
  type: m.type,
@@ -11431,24 +11826,28 @@ function memoryToMd(m) {
11431
11826
  updated: m.updatedAt,
11432
11827
  strength: m.strength,
11433
11828
  version: m.version,
11434
- concepts: m.concepts,
11435
- files: m.files
11829
+ concepts,
11830
+ files
11436
11831
  });
11437
- const related = (m.relatedIds || []).map((id) => `- [[${id}]]`).join("\n");
11438
- const supersedes = (m.supersedes || []).map((id) => `- [[${id}]] (superseded)`).join("\n");
11832
+ const relatedLines = relatedIds.map((id) => `- [[${id}]]`).join("\n");
11833
+ const supersedesLines = supersedes.map((id) => `- [[${id}]] (superseded)`).join("\n");
11439
11834
  const sections = [
11440
11835
  fm,
11441
11836
  "",
11442
- `# ${m.title}`,
11837
+ `# ${title}`,
11443
11838
  "",
11444
- m.content
11839
+ safeString(m.content)
11445
11840
  ];
11446
- if (m.concepts.length > 0) sections.push("", "## Concepts", m.concepts.map((c) => `#${c.replace(/\s+/g, "-")}`).join(" "));
11447
- if (related) sections.push("", "## Related", related);
11448
- if (supersedes) sections.push("", "## Supersedes", supersedes);
11841
+ if (concepts.length > 0) sections.push("", "## Concepts", concepts.map((c) => `#${c.replace(/\s+/g, "-")}`).join(" "));
11842
+ if (relatedLines) sections.push("", "## Related", relatedLines);
11843
+ if (supersedesLines) sections.push("", "## Supersedes", supersedesLines);
11449
11844
  return sections.join("\n");
11450
11845
  }
11451
11846
  function lessonToMd(l) {
11847
+ const tags = safeArray(l.tags);
11848
+ const sourceIds = safeArray(l.sourceIds);
11849
+ const content = safeString(l.content);
11850
+ const headline = content ? content.slice(0, 80) : l.id;
11452
11851
  const fm = toFrontmatter({
11453
11852
  id: l.id,
11454
11853
  type: "lesson",
@@ -11458,66 +11857,76 @@ function lessonToMd(l) {
11458
11857
  created: l.createdAt,
11459
11858
  updated: l.updatedAt,
11460
11859
  project: l.project,
11461
- tags: l.tags,
11860
+ tags,
11462
11861
  decayRate: l.decayRate
11463
11862
  });
11464
- const sourceLinks = l.sourceIds.map((id) => `- [[${id}]]`).join("\n");
11863
+ const sourceLinks = sourceIds.map((id) => `- [[${id}]]`).join("\n");
11465
11864
  const sections = [
11466
11865
  fm,
11467
11866
  "",
11468
- `# Lesson: ${l.content.slice(0, 80)}`,
11867
+ `# Lesson: ${headline}`,
11469
11868
  "",
11470
- l.content
11869
+ content
11471
11870
  ];
11472
11871
  if (l.context) sections.push("", "## Context", l.context);
11473
- if (l.tags.length > 0) sections.push("", "## Tags", l.tags.map((t) => `#${t.replace(/\s+/g, "-")}`).join(" "));
11872
+ if (tags.length > 0) sections.push("", "## Tags", tags.map((t) => `#${t.replace(/\s+/g, "-")}`).join(" "));
11474
11873
  if (sourceLinks) sections.push("", "## Sources", sourceLinks);
11475
11874
  return sections.join("\n");
11476
11875
  }
11477
11876
  function crystalToMd(c) {
11877
+ const keyOutcomes = safeArray(c.keyOutcomes);
11878
+ const lessons = safeArray(c.lessons);
11879
+ const filesAffected = safeArray(c.filesAffected);
11880
+ const sourceActionIds = safeArray(c.sourceActionIds);
11881
+ const narrative = safeString(c.narrative);
11882
+ const headline = narrative ? narrative.slice(0, 80) : c.id;
11478
11883
  const fm = toFrontmatter({
11479
11884
  id: c.id,
11480
11885
  type: "crystal",
11481
11886
  created: c.createdAt,
11482
11887
  project: c.project,
11483
11888
  sessionId: c.sessionId,
11484
- filesAffected: c.filesAffected
11889
+ filesAffected
11485
11890
  });
11486
- const actionLinks = c.sourceActionIds.map((id) => `- [[${id}]]`).join("\n");
11891
+ const actionLinks = sourceActionIds.map((id) => `- [[${id}]]`).join("\n");
11487
11892
  const sections = [
11488
11893
  fm,
11489
11894
  "",
11490
- `# Crystal: ${c.narrative.slice(0, 80)}`,
11895
+ `# Crystal: ${headline}`,
11491
11896
  "",
11492
- c.narrative,
11897
+ narrative,
11493
11898
  "",
11494
11899
  "## Key Outcomes",
11495
- ...c.keyOutcomes.map((o) => `- ${o}`)
11900
+ ...keyOutcomes.map((o) => `- ${o}`)
11496
11901
  ];
11497
- if (c.lessons.length > 0) sections.push("", "## Lessons", ...c.lessons.map((l) => `- ${l}`));
11498
- if (c.filesAffected.length > 0) sections.push("", "## Files", ...c.filesAffected.map((f) => `- \`${f}\``));
11902
+ if (lessons.length > 0) sections.push("", "## Lessons", ...lessons.map((l) => `- ${l}`));
11903
+ if (filesAffected.length > 0) sections.push("", "## Files", ...filesAffected.map((f) => `- \`${f}\``));
11499
11904
  if (actionLinks) sections.push("", "## Source Actions", actionLinks);
11500
11905
  return sections.join("\n");
11501
11906
  }
11502
11907
  function sessionToMd(s) {
11908
+ const project = safeString(s.project, "unknown");
11909
+ const status = safeString(s.status, "unknown");
11910
+ const startedAt = safeString(s.startedAt, "");
11911
+ const cwd = safeString(s.cwd, "");
11503
11912
  return [
11504
11913
  toFrontmatter({
11505
11914
  id: s.id,
11506
11915
  type: "session",
11507
- project: s.project,
11508
- status: s.status,
11509
- started: s.startedAt,
11916
+ project,
11917
+ status,
11918
+ started: startedAt || void 0,
11510
11919
  ended: s.endedAt,
11511
11920
  observations: s.observationCount
11512
11921
  }),
11513
11922
  "",
11514
- `# Session: ${s.project}`,
11923
+ `# Session: ${project}`,
11515
11924
  "",
11516
- `**Status:** ${s.status}`,
11517
- `**Started:** ${s.startedAt}`,
11925
+ `**Status:** ${status}`,
11926
+ startedAt ? `**Started:** ${startedAt}` : "",
11518
11927
  s.endedAt ? `**Ended:** ${s.endedAt}` : "",
11519
- `**Observations:** ${s.observationCount}`,
11520
- `**CWD:** \`${s.cwd}\``
11928
+ `**Observations:** ${s.observationCount ?? 0}`,
11929
+ cwd ? `**CWD:** \`${cwd}\`` : ""
11521
11930
  ].filter(Boolean).join("\n");
11522
11931
  }
11523
11932
  function registerObsidianExportFunction(sdk, kv) {
@@ -11553,122 +11962,131 @@ function registerObsidianExportFunction(sdk, kv) {
11553
11962
  crystals: join(vaultDir, "crystals"),
11554
11963
  sessions: join(vaultDir, "sessions")
11555
11964
  };
11556
- await Promise.all(Object.values(dirs).map((dir) => mkdir(dir, { recursive: true })));
11557
- const stats = {
11558
- memories: 0,
11559
- lessons: 0,
11560
- crystals: 0,
11561
- sessions: 0
11562
- };
11563
- const errors = [];
11564
- const memoryMoc = [];
11565
- const lessonMoc = [];
11566
- const crystalMoc = [];
11567
- const sessionMoc = [];
11568
- const [memories, lessons, crystals, sessions] = await Promise.all([
11569
- exportTypes.has("memories") ? kv.list(KV.memories) : Promise.resolve([]),
11570
- exportTypes.has("lessons") ? kv.list(KV.lessons) : Promise.resolve([]),
11571
- exportTypes.has("crystals") ? kv.list(KV.crystals) : Promise.resolve([]),
11572
- exportTypes.has("sessions") ? kv.list(KV.sessions) : Promise.resolve([])
11573
- ]);
11574
- for (const m of memories.filter((m) => m.isLatest)) {
11575
- const filename = `${sanitize(m.id)}.md`;
11576
- const filepath = join(dirs.memories, filename);
11577
- try {
11578
- await writeFile(filepath, memoryToMd(m));
11579
- stats.memories++;
11580
- memoryMoc.push(`- [[memories/${sanitize(m.id)}|${m.title}]] (${m.type}, strength: ${m.strength})`);
11581
- } catch (err) {
11582
- errors.push({
11583
- id: m.id,
11584
- path: filepath,
11585
- error: err instanceof Error ? err.message : String(err)
11586
- });
11965
+ try {
11966
+ await Promise.all(Object.values(dirs).map((dir) => mkdir(dir, { recursive: true })));
11967
+ const stats = {
11968
+ memories: 0,
11969
+ lessons: 0,
11970
+ crystals: 0,
11971
+ sessions: 0
11972
+ };
11973
+ const errors = [];
11974
+ const memoryMoc = [];
11975
+ const lessonMoc = [];
11976
+ const crystalMoc = [];
11977
+ const sessionMoc = [];
11978
+ const [memories, lessons, crystals, sessions] = await Promise.all([
11979
+ exportTypes.has("memories") ? kv.list(KV.memories) : Promise.resolve([]),
11980
+ exportTypes.has("lessons") ? kv.list(KV.lessons) : Promise.resolve([]),
11981
+ exportTypes.has("crystals") ? kv.list(KV.crystals) : Promise.resolve([]),
11982
+ exportTypes.has("sessions") ? kv.list(KV.sessions) : Promise.resolve([])
11983
+ ]);
11984
+ for (const m of memories.filter((m) => hasExportId(m) && m.isLatest === true)) {
11985
+ const filename = `${sanitize(m.id)}.md`;
11986
+ const filepath = join(dirs.memories, filename);
11987
+ try {
11988
+ await writeFile(filepath, memoryToMd(m));
11989
+ stats.memories++;
11990
+ memoryMoc.push(`- [[memories/${sanitize(m.id)}|${safeString(m.title, m.id)}]] (${m.type}, strength: ${m.strength ?? 0})`);
11991
+ } catch (err) {
11992
+ errors.push({
11993
+ id: m.id,
11994
+ path: filepath,
11995
+ error: err instanceof Error ? err.message : String(err)
11996
+ });
11997
+ }
11587
11998
  }
11588
- }
11589
- for (const l of lessons.filter((l) => !l.deleted)) {
11590
- const filename = `${sanitize(l.id)}.md`;
11591
- const filepath = join(dirs.lessons, filename);
11592
- try {
11593
- await writeFile(filepath, lessonToMd(l));
11594
- stats.lessons++;
11595
- lessonMoc.push(`- [[lessons/${sanitize(l.id)}|${l.content.slice(0, 60)}]] (confidence: ${l.confidence})`);
11596
- } catch (err) {
11597
- errors.push({
11598
- id: l.id,
11599
- path: filepath,
11600
- error: err instanceof Error ? err.message : String(err)
11601
- });
11999
+ for (const l of lessons.filter((l) => hasExportId(l) && !l.deleted)) {
12000
+ const filename = `${sanitize(l.id)}.md`;
12001
+ const filepath = join(dirs.lessons, filename);
12002
+ try {
12003
+ await writeFile(filepath, lessonToMd(l));
12004
+ stats.lessons++;
12005
+ const headline = safeString(l.content).slice(0, 60) || l.id;
12006
+ lessonMoc.push(`- [[lessons/${sanitize(l.id)}|${headline}]] (confidence: ${l.confidence ?? 0})`);
12007
+ } catch (err) {
12008
+ errors.push({
12009
+ id: l.id,
12010
+ path: filepath,
12011
+ error: err instanceof Error ? err.message : String(err)
12012
+ });
12013
+ }
11602
12014
  }
11603
- }
11604
- for (const c of crystals) {
11605
- const filename = `${sanitize(c.id)}.md`;
11606
- const filepath = join(dirs.crystals, filename);
11607
- try {
11608
- await writeFile(filepath, crystalToMd(c));
11609
- stats.crystals++;
11610
- crystalMoc.push(`- [[crystals/${sanitize(c.id)}|${c.narrative.slice(0, 60)}]]`);
11611
- } catch (err) {
11612
- errors.push({
11613
- id: c.id,
11614
- path: filepath,
11615
- error: err instanceof Error ? err.message : String(err)
11616
- });
12015
+ for (const c of crystals.filter(hasExportId)) {
12016
+ const filename = `${sanitize(c.id)}.md`;
12017
+ const filepath = join(dirs.crystals, filename);
12018
+ try {
12019
+ await writeFile(filepath, crystalToMd(c));
12020
+ stats.crystals++;
12021
+ const headline = safeString(c.narrative).slice(0, 60) || c.id;
12022
+ crystalMoc.push(`- [[crystals/${sanitize(c.id)}|${headline}]]`);
12023
+ } catch (err) {
12024
+ errors.push({
12025
+ id: c.id,
12026
+ path: filepath,
12027
+ error: err instanceof Error ? err.message : String(err)
12028
+ });
12029
+ }
11617
12030
  }
11618
- }
11619
- const recent = sessions.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).slice(0, 50);
11620
- for (const s of recent) {
11621
- const filename = `${sanitize(s.id)}.md`;
11622
- const filepath = join(dirs.sessions, filename);
11623
- try {
11624
- await writeFile(filepath, sessionToMd(s));
11625
- stats.sessions++;
11626
- sessionMoc.push(`- [[sessions/${sanitize(s.id)}|${s.project} (${s.status})]]`);
11627
- } catch (err) {
11628
- errors.push({
11629
- id: s.id,
11630
- path: filepath,
11631
- error: err instanceof Error ? err.message : String(err)
11632
- });
12031
+ const recent = sessions.filter(hasExportId).sort((a, b) => safeTimestamp(b.startedAt) - safeTimestamp(a.startedAt)).slice(0, 50);
12032
+ for (const s of recent) {
12033
+ const filename = `${sanitize(s.id)}.md`;
12034
+ const filepath = join(dirs.sessions, filename);
12035
+ try {
12036
+ await writeFile(filepath, sessionToMd(s));
12037
+ stats.sessions++;
12038
+ sessionMoc.push(`- [[sessions/${sanitize(s.id)}|${safeString(s.project, "unknown")} (${safeString(s.status, "unknown")})]]`);
12039
+ } catch (err) {
12040
+ errors.push({
12041
+ id: s.id,
12042
+ path: filepath,
12043
+ error: err instanceof Error ? err.message : String(err)
12044
+ });
12045
+ }
11633
12046
  }
12047
+ const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
12048
+ const moc = [
12049
+ "---",
12050
+ "type: moc",
12051
+ `exported: ${exportedAt}`,
12052
+ "---",
12053
+ "",
12054
+ "# agentmemory vault",
12055
+ "",
12056
+ `Exported: ${exportedAt}`,
12057
+ "",
12058
+ `## Memories (${stats.memories})`,
12059
+ ...memoryMoc,
12060
+ "",
12061
+ `## Lessons (${stats.lessons})`,
12062
+ ...lessonMoc,
12063
+ "",
12064
+ `## Crystals (${stats.crystals})`,
12065
+ ...crystalMoc,
12066
+ "",
12067
+ `## Sessions (${stats.sessions})`,
12068
+ ...sessionMoc
12069
+ ].join("\n");
12070
+ await writeFile(join(vaultDir, "MOC.md"), moc);
12071
+ await recordAudit(kv, "obsidian_export", "mem::obsidian-export", [], {
12072
+ vaultDir,
12073
+ stats
12074
+ });
12075
+ return {
12076
+ success: true,
12077
+ exported: stats,
12078
+ errors: errors.length > 0 ? errors : void 0,
12079
+ vaultDir
12080
+ };
12081
+ } catch (err) {
12082
+ return {
12083
+ success: false,
12084
+ error: err instanceof Error ? err.message : String(err),
12085
+ vaultDir
12086
+ };
11634
12087
  }
11635
- const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
11636
- const moc = [
11637
- "---",
11638
- "type: moc",
11639
- `exported: ${exportedAt}`,
11640
- "---",
11641
- "",
11642
- "# agentmemory vault",
11643
- "",
11644
- `Exported: ${exportedAt}`,
11645
- "",
11646
- `## Memories (${stats.memories})`,
11647
- ...memoryMoc,
11648
- "",
11649
- `## Lessons (${stats.lessons})`,
11650
- ...lessonMoc,
11651
- "",
11652
- `## Crystals (${stats.crystals})`,
11653
- ...crystalMoc,
11654
- "",
11655
- `## Sessions (${stats.sessions})`,
11656
- ...sessionMoc
11657
- ].join("\n");
11658
- await writeFile(join(vaultDir, "MOC.md"), moc);
11659
- await recordAudit(kv, "obsidian_export", "mem::obsidian-export", [], {
11660
- vaultDir,
11661
- stats
11662
- });
11663
- return {
11664
- success: true,
11665
- exported: stats,
11666
- errors: errors.length > 0 ? errors : void 0,
11667
- vaultDir
11668
- };
11669
12088
  });
11670
12089
  }
11671
-
11672
12090
  //#endregion
11673
12091
  //#region src/prompts/reflect.ts
11674
12092
  const REFLECT_SYSTEM = `You are a higher-order reasoning engine. Given a cluster of related concepts, facts, lessons, and action outcomes, synthesize cross-cutting insights that span multiple individual memories.
@@ -11696,7 +12114,6 @@ function buildReflectPrompt(cluster) {
11696
12114
  if (cluster.crystalNarratives.length > 0) sections.push("\n## Completed Work Summaries", ...cluster.crystalNarratives.map((n) => `- ${n}`));
11697
12115
  return `Synthesize higher-order insights from this cluster of related memories:\n\n${sections.join("\n")}`;
11698
12116
  }
11699
-
11700
12117
  //#endregion
11701
12118
  //#region src/functions/reflect.ts
11702
12119
  function reinforceInsight(insight) {
@@ -11989,7 +12406,6 @@ function registerReflectFunctions(sdk, kv, provider) {
11989
12406
  };
11990
12407
  });
11991
12408
  }
11992
-
11993
12409
  //#endregion
11994
12410
  //#region src/functions/working-memory.ts
11995
12411
  const CORE_SCOPE = "mem:core-memory";
@@ -12164,7 +12580,6 @@ function registerWorkingMemoryFunctions(sdk, kv, tokenBudget) {
12164
12580
  };
12165
12581
  });
12166
12582
  }
12167
-
12168
12583
  //#endregion
12169
12584
  //#region src/functions/skill-extract.ts
12170
12585
  const SKILL_EXTRACT_SYSTEM = `You are a skill extraction engine. Given a completed multi-step task session, extract a reusable procedural skill document.
@@ -12367,7 +12782,6 @@ function registerSkillExtractFunctions(sdk, kv, provider) {
12367
12782
  };
12368
12783
  });
12369
12784
  }
12370
-
12371
12785
  //#endregion
12372
12786
  //#region src/functions/sliding-window.ts
12373
12787
  const SLIDING_WINDOW_SYSTEM = `You are a contextual enrichment engine. Given a primary observation and its surrounding context window (previous and next observations from the same session), produce an enriched version.
@@ -12554,7 +12968,6 @@ function registerSlidingWindowFunction(sdk, kv, provider) {
12554
12968
  };
12555
12969
  });
12556
12970
  }
12557
-
12558
12971
  //#endregion
12559
12972
  //#region src/functions/temporal-graph.ts
12560
12973
  const TEMPORAL_EXTRACTION_SYSTEM = `You are a temporal knowledge extraction engine. Given observations, extract entities AND their temporal relationships with full context metadata.
@@ -12818,7 +13231,6 @@ function buildTimeline(edges) {
12818
13231
  context: e.context
12819
13232
  }));
12820
13233
  }
12821
-
12822
13234
  //#endregion
12823
13235
  //#region src/functions/retention.ts
12824
13236
  const DEFAULT_DECAY = {
@@ -12982,7 +13394,7 @@ function registerRetentionFunctions(sdk, kv) {
12982
13394
  const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
12983
13395
  const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
12984
13396
  const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
12985
- const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
13397
+ const { decrementImageRef } = await import("./image-refs-C7h9L5wx.mjs").then((n) => n.n);
12986
13398
  const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
12987
13399
  if (data?.dryRun) return {
12988
13400
  success: true,
@@ -13052,7 +13464,6 @@ function registerRetentionFunctions(sdk, kv) {
13052
13464
  };
13053
13465
  });
13054
13466
  }
13055
-
13056
13467
  //#endregion
13057
13468
  //#region src/functions/compress-file.ts
13058
13469
  const SENSITIVE_PATH_TERMS = [
@@ -13194,7 +13605,6 @@ function registerCompressFileFunction(sdk, kv, provider) {
13194
13605
  };
13195
13606
  });
13196
13607
  }
13197
-
13198
13608
  //#endregion
13199
13609
  //#region src/replay/jsonl-parser.ts
13200
13610
  function deriveProject(cwd) {
@@ -13321,7 +13731,6 @@ function parseJsonlText(text, fallbackSessionId) {
13321
13731
  observations
13322
13732
  };
13323
13733
  }
13324
-
13325
13734
  //#endregion
13326
13735
  //#region src/replay/timeline.ts
13327
13736
  const DEFAULT_CHARS_PER_SEC = 40;
@@ -13416,10 +13825,6 @@ function projectTimeline(observations) {
13416
13825
  events
13417
13826
  };
13418
13827
  }
13419
-
13420
- //#endregion
13421
- //#region src/functions/replay.ts
13422
- const MAX_FILES_DEFAULT = 200;
13423
13828
  const MAX_FILES_UPPER_BOUND = 1e3;
13424
13829
  const SENSITIVE_PATH_PATTERNS = [
13425
13830
  /(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
@@ -13639,7 +14044,7 @@ function registerReplayFunctions(sdk, kv) {
13639
14044
  error: "path not found"
13640
14045
  };
13641
14046
  }
13642
- const maxFiles = Number.isInteger(data.maxFiles) && data.maxFiles > 0 ? Math.min(data.maxFiles, MAX_FILES_UPPER_BOUND) : MAX_FILES_DEFAULT;
14047
+ const maxFiles = Number.isInteger(data.maxFiles) && data.maxFiles > 0 ? Math.min(data.maxFiles, MAX_FILES_UPPER_BOUND) : 200;
13643
14048
  let files = [];
13644
14049
  let truncated = false;
13645
14050
  let discovered = 0;
@@ -13695,7 +14100,8 @@ function registerReplayFunctions(sdk, kv) {
13695
14100
  const existingTags = existing.tags || [];
13696
14101
  if (!existingTags.includes("jsonl-import")) existing.tags = [...existingTags, "jsonl-import"];
13697
14102
  if (!existing.firstPrompt && firstPrompt) existing.firstPrompt = firstPrompt;
13698
- await kv.set(KV.sessions, existing.id, existing);
14103
+ if (!existing.id) existing.id = parsed.sessionId;
14104
+ await kv.set(KV.sessions, parsed.sessionId, existing);
13699
14105
  } else {
13700
14106
  const session = {
13701
14107
  id: parsed.sessionId,
@@ -13741,7 +14147,6 @@ function registerReplayFunctions(sdk, kv) {
13741
14147
  };
13742
14148
  });
13743
14149
  }
13744
-
13745
14150
  //#endregion
13746
14151
  //#region src/health/thresholds.ts
13747
14152
  const DEFAULTS = {
@@ -13800,7 +14205,6 @@ function evaluateHealth(snapshot, config = {}) {
13800
14205
  notes
13801
14206
  };
13802
14207
  }
13803
-
13804
14208
  //#endregion
13805
14209
  //#region src/health/monitor.ts
13806
14210
  function registerHealthMonitor(sdk, kv) {
@@ -13888,7 +14292,6 @@ function registerHealthMonitor(sdk, kv) {
13888
14292
  async function getLatestHealth(kv) {
13889
14293
  return kv.get(KV.health, "latest");
13890
14294
  }
13891
-
13892
14295
  //#endregion
13893
14296
  //#region src/auth.ts
13894
14297
  const hmacKey = randomBytes(32);
@@ -13914,7 +14317,6 @@ function buildViewerCsp(nonce) {
13914
14317
  "font-src 'self'"
13915
14318
  ].join("; ");
13916
14319
  }
13917
-
13918
14320
  //#endregion
13919
14321
  //#region src/viewer/document.ts
13920
14322
  const VIEWER_VERSION_PLACEHOLDER = "__AGENTMEMORY_VERSION__";
@@ -13940,7 +14342,6 @@ function renderViewerDocument() {
13940
14342
  csp: buildViewerCsp(nonce)
13941
14343
  };
13942
14344
  }
13943
-
13944
14345
  //#endregion
13945
14346
  //#region src/viewer/server.ts
13946
14347
  function loadViewerFavicon() {
@@ -13955,18 +14356,30 @@ function loadViewerFavicon() {
13955
14356
  } catch {}
13956
14357
  return null;
13957
14358
  }
14359
+ const VIEWER_FAVICON = loadViewerFavicon();
13958
14360
  const ALLOWED_ORIGINS = (process.env.VIEWER_ALLOWED_ORIGINS || "http://localhost:3111,http://localhost:3113,http://127.0.0.1:3111,http://127.0.0.1:3113").split(",").map((o) => o.trim());
13959
- const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
13960
- function buildAllowedHosts(origins, listenPort) {
14361
+ function readAllowedHostsOverride() {
14362
+ return (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
14363
+ }
14364
+ function resolveViewerHost() {
14365
+ return process.env.AGENTMEMORY_VIEWER_HOST?.trim() || "127.0.0.1";
14366
+ }
14367
+ function isLoopbackHost(host) {
14368
+ const h = host.trim().toLowerCase();
14369
+ return h === "127.0.0.1" || h === "::1" || h === "localhost";
14370
+ }
14371
+ function buildAllowedHosts(origins, listenPort, bindHost = "127.0.0.1") {
13961
14372
  const hosts = /* @__PURE__ */ new Set();
13962
- for (const o of origins) try {
13963
- const parsed = new URL(o);
13964
- if (parsed.host) hosts.add(parsed.host.toLowerCase());
13965
- } catch {}
13966
- hosts.add(`localhost:${listenPort}`);
13967
- hosts.add(`127.0.0.1:${listenPort}`);
13968
- hosts.add(`[::1]:${listenPort}`);
13969
- for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h);
14373
+ if (isLoopbackHost(bindHost)) {
14374
+ for (const o of origins) try {
14375
+ const parsed = new URL(o);
14376
+ if (parsed.host) hosts.add(parsed.host.toLowerCase());
14377
+ } catch {}
14378
+ hosts.add(`localhost:${listenPort}`);
14379
+ hosts.add(`127.0.0.1:${listenPort}`);
14380
+ hosts.add(`[::1]:${listenPort}`);
14381
+ }
14382
+ for (const h of readAllowedHostsOverride()) hosts.add(h);
13970
14383
  return hosts;
13971
14384
  }
13972
14385
  function isHostAllowed(headerHost, allowed) {
@@ -13975,11 +14388,16 @@ function isHostAllowed(headerHost, allowed) {
13975
14388
  if (!lower) return false;
13976
14389
  return allowed.has(lower);
13977
14390
  }
14391
+ function requireInboundBearer(authHeader, secret) {
14392
+ if (typeof authHeader !== "string") return false;
14393
+ const match = /^Bearer\s+(\S+)\s*$/i.exec(authHeader);
14394
+ if (!match) return false;
14395
+ return timingSafeCompare(match[1], secret);
14396
+ }
13978
14397
  function corsHeaders(req) {
13979
14398
  const origin = req.headers.origin || "";
13980
- const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
13981
14399
  return {
13982
- "Access-Control-Allow-Origin": allowed,
14400
+ "Access-Control-Allow-Origin": ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0],
13983
14401
  "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
13984
14402
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
13985
14403
  Vary: "Origin"
@@ -14023,16 +14441,29 @@ function getBoundViewerPort() {
14023
14441
  function getViewerSkipped() {
14024
14442
  return viewerSkipped;
14025
14443
  }
14444
+ var ViewerConfigError = class extends Error {
14445
+ constructor(message) {
14446
+ super(message);
14447
+ this.name = "ViewerConfigError";
14448
+ }
14449
+ };
14026
14450
  function startViewerServer(port, _kv, _sdk, secret, restPort) {
14027
14451
  boundViewerPort = null;
14028
14452
  viewerSkipped = false;
14029
14453
  const resolvedRestPort = restPort ?? port - 2;
14030
14454
  const requestedPort = port;
14455
+ const host = resolveViewerHost();
14456
+ let inboundSecret = null;
14457
+ if (!isLoopbackHost(host)) {
14458
+ if (!secret) throw new ViewerConfigError(`AGENTMEMORY_VIEWER_HOST=${host} requires AGENTMEMORY_SECRET to be set so the viewer can validate inbound bearer tokens. To fix: unset AGENTMEMORY_VIEWER_HOST to keep the safe loopback bind, or set AGENTMEMORY_SECRET. For Fly images, it is printed on first boot; see deploy/fly/README.md.`);
14459
+ if (readAllowedHostsOverride().length === 0) throw new ViewerConfigError(`AGENTMEMORY_VIEWER_HOST=${host} requires VIEWER_ALLOWED_HOSTS because non-loopback viewer binds only trust explicit Host headers. To fix: set VIEWER_ALLOWED_HOSTS to a comma-separated list of trusted Host header values (e.g. "localhost:3113" for fly proxy), or unset AGENTMEMORY_VIEWER_HOST to keep the safe loopback bind.`);
14460
+ inboundSecret = secret;
14461
+ }
14031
14462
  let allowedHosts = null;
14032
14463
  const server = createServer(async (req, res) => {
14033
14464
  if (!allowedHosts) {
14034
14465
  const addr = server.address();
14035
- allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port);
14466
+ allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port, host);
14036
14467
  }
14037
14468
  if (!isHostAllowed(req.headers.host, allowedHosts)) {
14038
14469
  res.writeHead(403, { "Content-Type": "text/plain" });
@@ -14068,19 +14499,26 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
14068
14499
  return;
14069
14500
  }
14070
14501
  if (method === "GET" && pathname === "/favicon.svg") {
14071
- const favicon = loadViewerFavicon();
14072
- if (favicon) {
14502
+ if (VIEWER_FAVICON) {
14073
14503
  res.writeHead(200, {
14074
14504
  "Content-Type": "image/svg+xml",
14075
14505
  "Cache-Control": "public, max-age=3600"
14076
14506
  });
14077
- res.end(favicon);
14507
+ res.end(VIEWER_FAVICON);
14078
14508
  return;
14079
14509
  }
14080
14510
  res.writeHead(404, { "Content-Type": "text/plain" });
14081
14511
  res.end("favicon not found");
14082
14512
  return;
14083
14513
  }
14514
+ if (inboundSecret !== null && !requireInboundBearer(req.headers.authorization, inboundSecret)) {
14515
+ res.writeHead(401, {
14516
+ "Content-Type": "text/plain",
14517
+ "WWW-Authenticate": "Bearer realm=\"agentmemory-viewer\""
14518
+ });
14519
+ res.end("unauthorized");
14520
+ return;
14521
+ }
14084
14522
  try {
14085
14523
  await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret);
14086
14524
  } catch (err) {
@@ -14091,17 +14529,23 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
14091
14529
  let attempt = 0;
14092
14530
  let currentPort = requestedPort;
14093
14531
  const tryListen = () => {
14094
- server.listen(currentPort, "127.0.0.1");
14532
+ server.listen(currentPort, host);
14095
14533
  };
14096
14534
  server.on("listening", () => {
14097
14535
  const addr = server.address();
14098
- boundViewerPort = addr && typeof addr === "object" && "port" in addr ? addr.port : currentPort;
14536
+ const actualPort = addr && typeof addr === "object" && "port" in addr ? addr.port : currentPort;
14537
+ boundViewerPort = actualPort;
14099
14538
  viewerSkipped = false;
14100
- if (currentPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${currentPort}`);
14101
- else console.log(`[agentmemory] Viewer started on http://localhost:${currentPort} (fallback from ${requestedPort})`);
14539
+ if (inboundSecret !== null) {
14540
+ const allowedHosts = readAllowedHostsOverride().join(", ");
14541
+ console.log(`[agentmemory] Viewer: http://localhost:${actualPort} (bound to ${host}; inbound Bearer required; allowed Host headers: ${allowedHosts})`);
14542
+ return;
14543
+ }
14544
+ if (actualPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${actualPort}`);
14545
+ else console.log(`[agentmemory] Viewer started on http://localhost:${actualPort} (fallback from ${requestedPort})`);
14102
14546
  });
14103
14547
  server.on("error", (err) => {
14104
- if (err.code === "EADDRINUSE" && attempt < MAX_VIEWER_PORT_RETRIES) {
14548
+ if (err.code === "EADDRINUSE" && inboundSecret === null && attempt < MAX_VIEWER_PORT_RETRIES) {
14105
14549
  attempt++;
14106
14550
  currentPort = requestedPort + attempt;
14107
14551
  setImmediate(tryListen);
@@ -14110,7 +14554,8 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
14110
14554
  if (err.code === "EADDRINUSE") {
14111
14555
  boundViewerPort = null;
14112
14556
  viewerSkipped = true;
14113
- console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
14557
+ if (inboundSecret !== null) console.warn(`[agentmemory] Viewer port ${requestedPort} is in use while bound to ${host}; not retrying because non-loopback viewer binds require VIEWER_ALLOWED_HOSTS to match the exact port. Free the port, choose another viewer port, or unset AGENTMEMORY_VIEWER_HOST to keep the safe loopback bind.`);
14558
+ else console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
14114
14559
  } else {
14115
14560
  boundViewerPort = null;
14116
14561
  viewerSkipped = true;
@@ -14155,7 +14600,6 @@ async function proxyToRestApi(restPort, pathname, qs, method, req, res, secret)
14155
14600
  res.writeHead(upstream.status, responseHeaders);
14156
14601
  res.end(responseBody);
14157
14602
  }
14158
-
14159
14603
  //#endregion
14160
14604
  //#region src/triggers/api.ts
14161
14605
  function parseOptionalInt(raw) {
@@ -14578,7 +15022,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14578
15022
  }
14579
15023
  if (body.maxFiles !== void 0) {
14580
15024
  const n = body.maxFiles;
14581
- if (!Number.isInteger(n) || n < 1 || n > MAX_FILES_UPPER_BOUND) return {
15025
+ if (!Number.isInteger(n) || n < 1 || n > 1e3) return {
14582
15026
  status_code: 400,
14583
15027
  body: { error: `maxFiles must be an integer between 1 and ${MAX_FILES_UPPER_BOUND}` }
14584
15028
  };
@@ -14662,9 +15106,13 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14662
15106
  value: "completed"
14663
15107
  }]);
14664
15108
  try {
14665
- sdk.triggerVoid("event::session::stopped", { sessionId });
15109
+ sdk.trigger({
15110
+ function_id: "event::session::stopped",
15111
+ payload: { sessionId },
15112
+ action: TriggerAction.Void()
15113
+ });
14666
15114
  } catch (err) {
14667
- logger.warn("event::session::stopped triggerVoid failed", {
15115
+ logger.warn("event::session::stopped trigger failed", {
14668
15116
  sessionId,
14669
15117
  error: err instanceof Error ? err.message : String(err)
14670
15118
  });
@@ -15084,11 +15532,24 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15084
15532
  status_code: 400,
15085
15533
  body: { error: "query or expandIds is required" }
15086
15534
  };
15535
+ const headers = req.headers || {};
15536
+ const sourceHeader = headers["x-agentmemory-source"] ?? headers["X-Agentmemory-Source"];
15537
+ const sourceFromHeader = Array.isArray(sourceHeader) ? sourceHeader[0] : sourceHeader;
15538
+ const payload = {
15539
+ query: req.body?.query,
15540
+ expandIds: req.body?.expandIds,
15541
+ limit: req.body?.limit,
15542
+ project: req.body?.project,
15543
+ includeLessons: req.body?.includeLessons,
15544
+ agentId: req.body?.agentId,
15545
+ sessionId: req.body?.sessionId,
15546
+ source: req.body?.source ?? sourceFromHeader
15547
+ };
15087
15548
  return {
15088
15549
  status_code: 200,
15089
15550
  body: await sdk.trigger({
15090
15551
  function_id: "mem::smart-search",
15091
- payload: req.body
15552
+ payload
15092
15553
  })
15093
15554
  };
15094
15555
  });
@@ -15100,6 +15561,29 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15100
15561
  http_method: "POST"
15101
15562
  }
15102
15563
  });
15564
+ sdk.registerFunction("api::diagnostic-followup", async (req) => {
15565
+ const authErr = checkAuth(req, secret);
15566
+ if (authErr) return authErr;
15567
+ return {
15568
+ status_code: 200,
15569
+ body: {
15570
+ ...await sdk.trigger({
15571
+ function_id: "mem::diagnostic::followup-stats",
15572
+ payload: {}
15573
+ }),
15574
+ caveat: "Directional signal: overcounts on legitimate query refinement. Tune via AGENTMEMORY_FOLLOWUP_WINDOW_SECONDS."
15575
+ }
15576
+ };
15577
+ });
15578
+ sdk.registerTrigger({
15579
+ type: "http",
15580
+ function_id: "api::diagnostic-followup",
15581
+ config: {
15582
+ api_path: "/agentmemory/diagnostics/followup",
15583
+ http_method: "GET",
15584
+ middleware_function_ids: ["middleware::api-auth"]
15585
+ }
15586
+ });
15103
15587
  sdk.registerFunction("api::timeline", async (req) => {
15104
15588
  const authErr = checkAuth(req, secret);
15105
15589
  if (authErr) return authErr;
@@ -15321,12 +15805,20 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15321
15805
  sdk.registerFunction("api::graph-query", async (req) => {
15322
15806
  const authErr = checkAuth(req, secret);
15323
15807
  if (authErr) return authErr;
15808
+ const payload = {
15809
+ startNodeId: req.body?.startNodeId,
15810
+ nodeType: req.body?.nodeType,
15811
+ maxDepth: req.body?.maxDepth,
15812
+ query: req.body?.query,
15813
+ limit: req.body?.limit,
15814
+ offset: req.body?.offset
15815
+ };
15324
15816
  try {
15325
15817
  return {
15326
15818
  status_code: 200,
15327
15819
  body: await sdk.trigger({
15328
15820
  function_id: "mem::graph-query",
15329
- payload: req.body || {}
15821
+ payload
15330
15822
  })
15331
15823
  };
15332
15824
  } catch {
@@ -17607,7 +18099,6 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
17607
18099
  }
17608
18100
  });
17609
18101
  }
17610
-
17611
18102
  //#endregion
17612
18103
  //#region src/triggers/events.ts
17613
18104
  function registerEventTriggers(sdk, kv) {
@@ -17652,18 +18143,26 @@ function registerEventTriggers(sdk, kv) {
17652
18143
  payload: data
17653
18144
  });
17654
18145
  if (isReflectEnabled()) try {
17655
- sdk.triggerVoid("mem::slot-reflect", { sessionId: data.sessionId });
18146
+ sdk.trigger({
18147
+ function_id: "mem::slot-reflect",
18148
+ payload: { sessionId: data.sessionId },
18149
+ action: TriggerAction.Void()
18150
+ });
17656
18151
  } catch (err) {
17657
- logger.warn("slot-reflect triggerVoid failed", {
18152
+ logger.warn("slot-reflect trigger failed", {
17658
18153
  sessionId: data.sessionId,
17659
18154
  error: err instanceof Error ? err.message : String(err)
17660
18155
  });
17661
18156
  }
17662
18157
  if (isGraphExtractionEnabled()) try {
17663
18158
  const compressed = (await kv.list(KV.observations(data.sessionId))).filter((o) => o.title);
17664
- if (compressed.length > 0) sdk.triggerVoid("mem::graph-extract", { observations: compressed });
18159
+ if (compressed.length > 0) sdk.trigger({
18160
+ function_id: "mem::graph-extract",
18161
+ payload: { observations: compressed },
18162
+ action: TriggerAction.Void()
18163
+ });
17665
18164
  } catch (err) {
17666
- logger.warn("graph-extract triggerVoid failed", {
18165
+ logger.warn("graph-extract trigger failed", {
17667
18166
  sessionId: data.sessionId,
17668
18167
  error: err instanceof Error ? err.message : String(err)
17669
18168
  });
@@ -17721,7 +18220,6 @@ function registerEventTriggers(sdk, kv) {
17721
18220
  config: { scope: KV.sessions }
17722
18221
  });
17723
18222
  }
17724
-
17725
18223
  //#endregion
17726
18224
  //#region src/mcp/server.ts
17727
18225
  function asNonEmptyString(value) {
@@ -19358,7 +19856,6 @@ function registerMcpEndpoints(sdk, kv, secret) {
19358
19856
  }
19359
19857
  });
19360
19858
  }
19361
-
19362
19859
  //#endregion
19363
19860
  //#region src/eval/metrics-store.ts
19364
19861
  var MetricsStore = class {
@@ -19401,7 +19898,6 @@ var MetricsStore = class {
19401
19898
  return Array.from(merged.values());
19402
19899
  }
19403
19900
  };
19404
-
19405
19901
  //#endregion
19406
19902
  //#region src/functions/dedup.ts
19407
19903
  const TTL_MS = 300 * 1e3;
@@ -19443,57 +19939,6 @@ var DedupMap = class {
19443
19939
  return this.entries.size;
19444
19940
  }
19445
19941
  };
19446
-
19447
- //#endregion
19448
- //#region src/telemetry/setup.ts
19449
- const OTEL_CONFIG = {
19450
- serviceName: "agentmemory",
19451
- serviceVersion: VERSION,
19452
- metricsExportIntervalMs: 3e4
19453
- };
19454
- let counters = null;
19455
- let histograms = null;
19456
- const NOOP_COUNTER = { add: () => {} };
19457
- const NOOP_HISTOGRAM = { record: () => {} };
19458
- const COUNTER_NAMES = [
19459
- ["observationsTotal", "observations.total"],
19460
- ["compressionSuccess", "compression.success"],
19461
- ["compressionFailure", "compression.failure"],
19462
- ["searchTotal", "search.total"],
19463
- ["dedupSkipped", "dedup.skipped"],
19464
- ["evictionTotal", "eviction.total"],
19465
- ["circuitBreakerOpen", "circuit_breaker.open"],
19466
- ["embeddingSuccess", "embedding.success"],
19467
- ["embeddingFailure", "embedding.failure"],
19468
- ["vectorSearchTotal", "vector_search.total"],
19469
- ["autoForgetTotal", "auto_forget.total"],
19470
- ["profileGenerated", "profile.generated"],
19471
- ["claudeBridgeSync", "claude_bridge.sync"],
19472
- ["graphExtraction", "graph.extraction"],
19473
- ["consolidationRun", "consolidation.run"],
19474
- ["teamShare", "team.share"],
19475
- ["auditLog", "audit.log"],
19476
- ["snapshotCreate", "snapshot.create"],
19477
- ["governanceDelete", "governance.delete"]
19478
- ];
19479
- const HISTOGRAM_NAMES = [
19480
- ["compressionLatency", "compression.latency_ms"],
19481
- ["searchLatency", "search.latency_ms"],
19482
- ["contextTokens", "context.tokens"],
19483
- ["qualityScore", "quality.score"],
19484
- ["embeddingLatency", "embedding.latency_ms"],
19485
- ["vectorSearchLatency", "vector_search.latency_ms"]
19486
- ];
19487
- function initMetrics(getMeter) {
19488
- const meter = getMeter?.("agentmemory");
19489
- counters = Object.fromEntries(COUNTER_NAMES.map(([key, name]) => [key, meter ? meter.createCounter(name) : NOOP_COUNTER]));
19490
- histograms = Object.fromEntries(HISTOGRAM_NAMES.map(([key, name]) => [key, meter ? meter.createHistogram(name) : NOOP_HISTOGRAM]));
19491
- return {
19492
- counters,
19493
- histograms
19494
- };
19495
- }
19496
-
19497
19942
  //#endregion
19498
19943
  //#region src/index.ts
19499
19944
  function workerPidfilePath() {
@@ -19642,6 +20087,7 @@ async function main() {
19642
20087
  const graphWeight = parseFloat(getEnvVar("AGENTMEMORY_GRAPH_WEIGHT") || "0.3");
19643
20088
  const hybridSearch = new HybridSearch(bm25Index, vectorIndex, embeddingProvider, kv, embeddingConfig.bm25Weight, embeddingConfig.vectorWeight, graphWeight);
19644
20089
  registerSmartSearchFunction(sdk, kv, (query, limit) => hybridSearch.search(query, limit));
20090
+ registerRecentSearchesSweepFunction(sdk, kv);
19645
20091
  registerApiTriggers(sdk, kv, secret, metricsStore, provider);
19646
20092
  registerEventTriggers(sdk, kv);
19647
20093
  registerMcpEndpoints(sdk, kv, secret);
@@ -19709,7 +20155,7 @@ async function main() {
19709
20155
  console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
19710
20156
  }
19711
20157
  bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
19712
- bootLog(`REST API: 125 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
20158
+ bootLog(`REST API: 126 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
19713
20159
  bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
19714
20160
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
19715
20161
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
@@ -19744,6 +20190,14 @@ async function main() {
19744
20190
  });
19745
20191
  } catch {}
19746
20192
  }, 864e5).unref();
20193
+ setInterval(async () => {
20194
+ try {
20195
+ await sdk.trigger({
20196
+ function_id: "mem::diagnostic::recent-searches-sweep",
20197
+ payload: {}
20198
+ });
20199
+ } catch {}
20200
+ }, 3600 * 1e3).unref();
19747
20201
  if (isConsolidationEnabled()) {
19748
20202
  setInterval(async () => {
19749
20203
  try {
@@ -19775,7 +20229,7 @@ main().catch((err) => {
19775
20229
  console.error(`[agentmemory] Fatal:`, err);
19776
20230
  process.exit(1);
19777
20231
  });
19778
-
19779
20232
  //#endregion
19780
- export { };
19781
- //# sourceMappingURL=src-DvS3bhMe.mjs.map
20233
+ export {};
20234
+
20235
+ //# sourceMappingURL=src-fQOMXeCp.mjs.map