@agentmemory/agentmemory 0.9.24 → 0.9.26

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 +125 -65
  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-B8J9Exum.mjs → src-u7kAEUC0.mjs} +937 -483
  44. package/dist/src-u7kAEUC0.mjs.map +1 -0
  45. package/dist/{standalone-CPfsVTBA.mjs → standalone-C1yPO519.mjs} +6 -10
  46. package/dist/{standalone-CPfsVTBA.mjs.map → standalone-C1yPO519.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-BMFYFFut.mjs +6 -0
  52. package/dist/version-BMFYFFut.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-B8J9Exum.mjs.map +0 -1
  86. package/dist/tools-registry-DJizX9Az.mjs.map +0 -1
  87. package/dist/version-BWEBnKAp.mjs +0 -6
  88. package/dist/version-BWEBnKAp.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-BWEBnKAp.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-BMFYFFut.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 && typeof manifest.value === "object") 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) {
@@ -6446,7 +6827,9 @@ function registerExportImportFunction(sdk, kv) {
6446
6827
  "0.9.21",
6447
6828
  "0.9.22",
6448
6829
  "0.9.23",
6449
- "0.9.24"
6830
+ "0.9.24",
6831
+ "0.9.25",
6832
+ "0.9.26"
6450
6833
  ]).has(importData.version)) return {
6451
6834
  success: false,
6452
6835
  error: `Unsupported export version: ${importData.version}`
@@ -6764,7 +7147,6 @@ function registerExportImportFunction(sdk, kv) {
6764
7147
  };
6765
7148
  });
6766
7149
  }
6767
-
6768
7150
  //#endregion
6769
7151
  //#region src/functions/enrich.ts
6770
7152
  const MAX_CONTEXT_LENGTH = 4e3;
@@ -6825,7 +7207,6 @@ function registerEnrichFunction(sdk, kv) {
6825
7207
  };
6826
7208
  });
6827
7209
  }
6828
-
6829
7210
  //#endregion
6830
7211
  //#region src/functions/claude-bridge.ts
6831
7212
  function parseMemoryMd(content) {
@@ -6946,7 +7327,6 @@ function registerClaudeBridgeFunction(sdk, kv, config) {
6946
7327
  }
6947
7328
  });
6948
7329
  }
6949
-
6950
7330
  //#endregion
6951
7331
  //#region src/prompts/graph-extraction.ts
6952
7332
  const GRAPH_EXTRACTION_SYSTEM = `You are a knowledge graph extraction engine. Given a compressed observation from a coding session, extract entities and relationships.
@@ -6969,9 +7349,41 @@ Rules:
6969
7349
  function buildGraphExtractionPrompt(observations) {
6970
7350
  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")}`;
6971
7351
  }
6972
-
6973
7352
  //#endregion
6974
7353
  //#region src/functions/graph.ts
7354
+ const DEFAULT_GRAPH_QUERY_LIMIT = 500;
7355
+ const MAX_GRAPH_QUERY_LIMIT = 5e3;
7356
+ function resolvePagination(rawLimit, rawOffset) {
7357
+ return {
7358
+ limit: Math.max(1, Math.min(typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.floor(rawLimit) : DEFAULT_GRAPH_QUERY_LIMIT, MAX_GRAPH_QUERY_LIMIT)),
7359
+ offset: Math.max(0, typeof rawOffset === "number" && Number.isFinite(rawOffset) ? Math.floor(rawOffset) : 0)
7360
+ };
7361
+ }
7362
+ function rankByDegree(nodes, edges) {
7363
+ const degree = /* @__PURE__ */ new Map();
7364
+ for (const edge of edges) {
7365
+ degree.set(edge.sourceNodeId, (degree.get(edge.sourceNodeId) ?? 0) + 1);
7366
+ degree.set(edge.targetNodeId, (degree.get(edge.targetNodeId) ?? 0) + 1);
7367
+ }
7368
+ return [...nodes].sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0));
7369
+ }
7370
+ function paginate(nodes, allEdges, depth, limit, offset) {
7371
+ const totalNodes = nodes.length;
7372
+ const pageNodes = nodes.slice(offset, offset + limit);
7373
+ const pageNodeIds = new Set(pageNodes.map((n) => n.id));
7374
+ const pageEdges = allEdges.filter((e) => pageNodeIds.has(e.sourceNodeId) && pageNodeIds.has(e.targetNodeId));
7375
+ const universeIds = new Set(nodes.map((n) => n.id));
7376
+ return {
7377
+ nodes: pageNodes,
7378
+ edges: pageEdges,
7379
+ depth,
7380
+ totalNodes,
7381
+ totalEdges: allEdges.reduce((count, e) => universeIds.has(e.sourceNodeId) && universeIds.has(e.targetNodeId) ? count + 1 : count, 0),
7382
+ truncated: totalNodes > pageNodes.length,
7383
+ limit,
7384
+ offset
7385
+ };
7386
+ }
6975
7387
  function parseAttrs(raw) {
6976
7388
  const attrs = {};
6977
7389
  const attrRegex = /([A-Za-z_][\w:-]*)="([^"]*)"/g;
@@ -7108,15 +7520,10 @@ function registerGraphFunction(sdk, kv, provider) {
7108
7520
  const allNodes = (await kv.list(KV.graphNodes)).filter((n) => !n.stale);
7109
7521
  const allEdges = (await kv.list(KV.graphEdges)).filter((e) => !e.stale);
7110
7522
  const maxDepth = Math.min(data.maxDepth || 3, 5);
7523
+ const { limit, offset } = resolvePagination(data.limit, data.offset);
7111
7524
  if (data.query) {
7112
7525
  const lower = data.query.toLowerCase();
7113
- const matchingNodes = allNodes.filter((n) => n.name.toLowerCase().includes(lower) || Object.values(n.properties).some((v) => typeof v === "string" && v.toLowerCase().includes(lower)));
7114
- const nodeIds = new Set(matchingNodes.map((n) => n.id));
7115
- return {
7116
- nodes: matchingNodes,
7117
- edges: allEdges.filter((e) => nodeIds.has(e.sourceNodeId) || nodeIds.has(e.targetNodeId)),
7118
- depth: 0
7119
- };
7526
+ 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);
7120
7527
  }
7121
7528
  if (data.startNodeId) {
7122
7529
  const visited = /* @__PURE__ */ new Set();
@@ -7148,19 +7555,11 @@ function registerGraphFunction(sdk, kv, provider) {
7148
7555
  });
7149
7556
  }
7150
7557
  }
7151
- return {
7152
- nodes: resultNodes,
7153
- edges: resultEdges,
7154
- depth: maxDepth
7155
- };
7558
+ return paginate(resultNodes, resultEdges, maxDepth, limit, offset);
7156
7559
  }
7157
7560
  let filtered = allNodes;
7158
7561
  if (data.nodeType) filtered = allNodes.filter((n) => n.type === data.nodeType);
7159
- return {
7160
- nodes: filtered,
7161
- edges: allEdges,
7162
- depth: 0
7163
- };
7562
+ return paginate(rankByDegree(filtered, allEdges), allEdges, 0, limit, offset);
7164
7563
  });
7165
7564
  sdk.registerFunction("mem::graph-stats", async () => {
7166
7565
  const nodes = await kv.list(KV.graphNodes);
@@ -7177,7 +7576,6 @@ function registerGraphFunction(sdk, kv, provider) {
7177
7576
  };
7178
7577
  });
7179
7578
  }
7180
-
7181
7579
  //#endregion
7182
7580
  //#region src/prompts/consolidation.ts
7183
7581
  const SEMANTIC_MERGE_SYSTEM = `You are a memory consolidation engine. Given overlapping episodic memories (session summaries), extract stable factual knowledge.
@@ -7212,7 +7610,6 @@ Rules:
7212
7610
  function buildProceduralExtractionPrompt(patterns) {
7213
7611
  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")}`;
7214
7612
  }
7215
-
7216
7613
  //#endregion
7217
7614
  //#region src/functions/consolidation-pipeline.ts
7218
7615
  function applyDecay(items, decayDays) {
@@ -7406,7 +7803,6 @@ function registerConsolidationPipelineFunction(sdk, kv, provider) {
7406
7803
  };
7407
7804
  });
7408
7805
  }
7409
-
7410
7806
  //#endregion
7411
7807
  //#region src/functions/team.ts
7412
7808
  const VALID_ITEM_TYPES = new Set([
@@ -7510,7 +7906,6 @@ function registerTeamFunction(sdk, kv, config) {
7510
7906
  return profile;
7511
7907
  });
7512
7908
  }
7513
-
7514
7909
  //#endregion
7515
7910
  //#region src/functions/governance.ts
7516
7911
  function registerGovernanceFunction(sdk, kv) {
@@ -7619,7 +8014,6 @@ function registerGovernanceFunction(sdk, kv) {
7619
8014
  return queryAudit(kv, data);
7620
8015
  });
7621
8016
  }
7622
-
7623
8017
  //#endregion
7624
8018
  //#region src/functions/snapshot.ts
7625
8019
  const COMMIT_HASH_RE = /^[0-9a-f]{7,40}$/i;
@@ -7784,7 +8178,6 @@ function registerSnapshotFunction(sdk, kv, snapshotDir) {
7784
8178
  }
7785
8179
  });
7786
8180
  }
7787
-
7788
8181
  //#endregion
7789
8182
  //#region src/functions/actions.ts
7790
8183
  function registerActionsFunction(sdk, kv) {
@@ -7986,7 +8379,6 @@ async function propagateCompletion(kv, completedActionId) {
7986
8379
  });
7987
8380
  }
7988
8381
  }
7989
-
7990
8382
  //#endregion
7991
8383
  //#region src/functions/frontier.ts
7992
8384
  function registerFrontierFunction(sdk, kv) {
@@ -8091,7 +8483,6 @@ function computeScore(action, edges, now) {
8091
8483
  if (action.status === "active") score += 15;
8092
8484
  return Math.round(score * 100) / 100;
8093
8485
  }
8094
-
8095
8486
  //#endregion
8096
8487
  //#region src/functions/leases.ts
8097
8488
  const DEFAULT_LEASE_TTL_MS = 600 * 1e3;
@@ -8272,7 +8663,6 @@ function registerLeasesFunction(sdk, kv) {
8272
8663
  };
8273
8664
  });
8274
8665
  }
8275
-
8276
8666
  //#endregion
8277
8667
  //#region src/functions/routines.ts
8278
8668
  function registerRoutinesFunction(sdk, kv) {
@@ -8519,7 +8909,6 @@ function registerRoutinesFunction(sdk, kv) {
8519
8909
  });
8520
8910
  });
8521
8911
  }
8522
-
8523
8912
  //#endregion
8524
8913
  //#region src/functions/signals.ts
8525
8914
  function registerSignalsFunction(sdk, kv) {
@@ -8651,7 +9040,6 @@ function registerSignalsFunction(sdk, kv) {
8651
9040
  };
8652
9041
  });
8653
9042
  }
8654
-
8655
9043
  //#endregion
8656
9044
  //#region src/functions/checkpoints.ts
8657
9045
  function registerCheckpointsFunction(sdk, kv) {
@@ -8824,7 +9212,6 @@ function registerCheckpointsFunction(sdk, kv) {
8824
9212
  };
8825
9213
  });
8826
9214
  }
8827
-
8828
9215
  //#endregion
8829
9216
  //#region src/functions/flow-compress.ts
8830
9217
  const FLOW_COMPRESS_SYSTEM = `You are a workflow summarizer. Given a completed action chain, produce a concise summary capturing:
@@ -8969,7 +9356,6 @@ function extractFiles(actions) {
8969
9356
  }
8970
9357
  return Array.from(files);
8971
9358
  }
8972
-
8973
9359
  //#endregion
8974
9360
  //#region src/functions/mesh.ts
8975
9361
  function isPrivateIP(ip) {
@@ -9286,7 +9672,6 @@ async function applySyncData(kv, data, scopes) {
9286
9672
  if (scopes.includes("graph:edges")) applied += await lwwMergeList(kv, KV.graphEdges, data.graphEdges, "mem:gedge", "createdAt");
9287
9673
  return applied;
9288
9674
  }
9289
-
9290
9675
  //#endregion
9291
9676
  //#region src/functions/branch-aware.ts
9292
9677
  function execAsync(cmd, args, cwd) {
@@ -9401,7 +9786,6 @@ function registerBranchAwareFunction(sdk, kv) {
9401
9786
  };
9402
9787
  });
9403
9788
  }
9404
-
9405
9789
  //#endregion
9406
9790
  //#region src/functions/sentinels.ts
9407
9791
  const VALID_TYPES = [
@@ -9722,7 +10106,6 @@ async function unblockLinkedActions(kv, sentinel) {
9722
10106
  });
9723
10107
  return unblockedCount;
9724
10108
  }
9725
-
9726
10109
  //#endregion
9727
10110
  //#region src/functions/sketches.ts
9728
10111
  function registerSketchesFunction(sdk, kv) {
@@ -9964,7 +10347,6 @@ function registerSketchesFunction(sdk, kv) {
9964
10347
  };
9965
10348
  });
9966
10349
  }
9967
-
9968
10350
  //#endregion
9969
10351
  //#region src/functions/crystallize.ts
9970
10352
  const CRYSTALLIZE_SYSTEM = `You are summarizing a completed chain of agent actions into a compact digest.
@@ -10161,7 +10543,6 @@ function parseDigest(response) {
10161
10543
  };
10162
10544
  }
10163
10545
  }
10164
-
10165
10546
  //#endregion
10166
10547
  //#region src/functions/diagnostics.ts
10167
10548
  const ALL_CATEGORIES = [
@@ -10875,7 +11256,6 @@ function registerDiagnosticsFunction(sdk, kv) {
10875
11256
  };
10876
11257
  });
10877
11258
  }
10878
-
10879
11259
  //#endregion
10880
11260
  //#region src/functions/facets.ts
10881
11261
  function registerFacetsFunction(sdk, kv) {
@@ -11047,7 +11427,6 @@ function registerFacetsFunction(sdk, kv) {
11047
11427
  };
11048
11428
  });
11049
11429
  }
11050
-
11051
11430
  //#endregion
11052
11431
  //#region src/functions/verify.ts
11053
11432
  function registerVerifyFunction(sdk, kv) {
@@ -11142,7 +11521,6 @@ async function findObservation(kv, obsId, hintSessionIds) {
11142
11521
  }
11143
11522
  return null;
11144
11523
  }
11145
-
11146
11524
  //#endregion
11147
11525
  //#region src/functions/cascade.ts
11148
11526
  function registerCascadeFunction(sdk, kv) {
@@ -11212,7 +11590,6 @@ function registerCascadeFunction(sdk, kv) {
11212
11590
  };
11213
11591
  });
11214
11592
  }
11215
-
11216
11593
  //#endregion
11217
11594
  //#region src/functions/lessons.ts
11218
11595
  function reinforceLesson(lesson) {
@@ -11398,7 +11775,6 @@ function registerLessonsFunctions(sdk, kv) {
11398
11775
  };
11399
11776
  });
11400
11777
  }
11401
-
11402
11778
  //#endregion
11403
11779
  //#region src/functions/obsidian-export.ts
11404
11780
  const DEFAULT_EXPORT_ROOT = join(homedir(), ".agentmemory");
@@ -11414,6 +11790,20 @@ function resolveVaultDir(vaultDir) {
11414
11790
  function sanitize(name) {
11415
11791
  return name.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_").slice(0, 100);
11416
11792
  }
11793
+ function hasExportId(item) {
11794
+ return !!item && typeof item.id === "string" && item.id.length > 0;
11795
+ }
11796
+ function safeArray(value) {
11797
+ return Array.isArray(value) ? value : [];
11798
+ }
11799
+ function safeString(value, fallback = "") {
11800
+ return typeof value === "string" ? value : fallback;
11801
+ }
11802
+ function safeTimestamp(value) {
11803
+ if (typeof value !== "string") return 0;
11804
+ const time = new Date(value).getTime();
11805
+ return Number.isFinite(time) ? time : 0;
11806
+ }
11417
11807
  function toFrontmatter(obj) {
11418
11808
  const lines = ["---"];
11419
11809
  for (const [key, value] of Object.entries(obj)) {
@@ -11425,6 +11815,11 @@ function toFrontmatter(obj) {
11425
11815
  return lines.join("\n");
11426
11816
  }
11427
11817
  function memoryToMd(m) {
11818
+ const concepts = safeArray(m.concepts);
11819
+ const files = safeArray(m.files);
11820
+ const relatedIds = safeArray(m.relatedIds);
11821
+ const supersedes = safeArray(m.supersedes);
11822
+ const title = safeString(m.title, m.id);
11428
11823
  const fm = toFrontmatter({
11429
11824
  id: m.id,
11430
11825
  type: m.type,
@@ -11432,24 +11827,28 @@ function memoryToMd(m) {
11432
11827
  updated: m.updatedAt,
11433
11828
  strength: m.strength,
11434
11829
  version: m.version,
11435
- concepts: m.concepts,
11436
- files: m.files
11830
+ concepts,
11831
+ files
11437
11832
  });
11438
- const related = (m.relatedIds || []).map((id) => `- [[${id}]]`).join("\n");
11439
- const supersedes = (m.supersedes || []).map((id) => `- [[${id}]] (superseded)`).join("\n");
11833
+ const relatedLines = relatedIds.map((id) => `- [[${id}]]`).join("\n");
11834
+ const supersedesLines = supersedes.map((id) => `- [[${id}]] (superseded)`).join("\n");
11440
11835
  const sections = [
11441
11836
  fm,
11442
11837
  "",
11443
- `# ${m.title}`,
11838
+ `# ${title}`,
11444
11839
  "",
11445
- m.content
11840
+ safeString(m.content)
11446
11841
  ];
11447
- if (m.concepts.length > 0) sections.push("", "## Concepts", m.concepts.map((c) => `#${c.replace(/\s+/g, "-")}`).join(" "));
11448
- if (related) sections.push("", "## Related", related);
11449
- if (supersedes) sections.push("", "## Supersedes", supersedes);
11842
+ if (concepts.length > 0) sections.push("", "## Concepts", concepts.map((c) => `#${c.replace(/\s+/g, "-")}`).join(" "));
11843
+ if (relatedLines) sections.push("", "## Related", relatedLines);
11844
+ if (supersedesLines) sections.push("", "## Supersedes", supersedesLines);
11450
11845
  return sections.join("\n");
11451
11846
  }
11452
11847
  function lessonToMd(l) {
11848
+ const tags = safeArray(l.tags);
11849
+ const sourceIds = safeArray(l.sourceIds);
11850
+ const content = safeString(l.content);
11851
+ const headline = content ? content.slice(0, 80) : l.id;
11453
11852
  const fm = toFrontmatter({
11454
11853
  id: l.id,
11455
11854
  type: "lesson",
@@ -11459,66 +11858,76 @@ function lessonToMd(l) {
11459
11858
  created: l.createdAt,
11460
11859
  updated: l.updatedAt,
11461
11860
  project: l.project,
11462
- tags: l.tags,
11861
+ tags,
11463
11862
  decayRate: l.decayRate
11464
11863
  });
11465
- const sourceLinks = l.sourceIds.map((id) => `- [[${id}]]`).join("\n");
11864
+ const sourceLinks = sourceIds.map((id) => `- [[${id}]]`).join("\n");
11466
11865
  const sections = [
11467
11866
  fm,
11468
11867
  "",
11469
- `# Lesson: ${l.content.slice(0, 80)}`,
11868
+ `# Lesson: ${headline}`,
11470
11869
  "",
11471
- l.content
11870
+ content
11472
11871
  ];
11473
11872
  if (l.context) sections.push("", "## Context", l.context);
11474
- if (l.tags.length > 0) sections.push("", "## Tags", l.tags.map((t) => `#${t.replace(/\s+/g, "-")}`).join(" "));
11873
+ if (tags.length > 0) sections.push("", "## Tags", tags.map((t) => `#${t.replace(/\s+/g, "-")}`).join(" "));
11475
11874
  if (sourceLinks) sections.push("", "## Sources", sourceLinks);
11476
11875
  return sections.join("\n");
11477
11876
  }
11478
11877
  function crystalToMd(c) {
11878
+ const keyOutcomes = safeArray(c.keyOutcomes);
11879
+ const lessons = safeArray(c.lessons);
11880
+ const filesAffected = safeArray(c.filesAffected);
11881
+ const sourceActionIds = safeArray(c.sourceActionIds);
11882
+ const narrative = safeString(c.narrative);
11883
+ const headline = narrative ? narrative.slice(0, 80) : c.id;
11479
11884
  const fm = toFrontmatter({
11480
11885
  id: c.id,
11481
11886
  type: "crystal",
11482
11887
  created: c.createdAt,
11483
11888
  project: c.project,
11484
11889
  sessionId: c.sessionId,
11485
- filesAffected: c.filesAffected
11890
+ filesAffected
11486
11891
  });
11487
- const actionLinks = c.sourceActionIds.map((id) => `- [[${id}]]`).join("\n");
11892
+ const actionLinks = sourceActionIds.map((id) => `- [[${id}]]`).join("\n");
11488
11893
  const sections = [
11489
11894
  fm,
11490
11895
  "",
11491
- `# Crystal: ${c.narrative.slice(0, 80)}`,
11896
+ `# Crystal: ${headline}`,
11492
11897
  "",
11493
- c.narrative,
11898
+ narrative,
11494
11899
  "",
11495
11900
  "## Key Outcomes",
11496
- ...c.keyOutcomes.map((o) => `- ${o}`)
11901
+ ...keyOutcomes.map((o) => `- ${o}`)
11497
11902
  ];
11498
- if (c.lessons.length > 0) sections.push("", "## Lessons", ...c.lessons.map((l) => `- ${l}`));
11499
- if (c.filesAffected.length > 0) sections.push("", "## Files", ...c.filesAffected.map((f) => `- \`${f}\``));
11903
+ if (lessons.length > 0) sections.push("", "## Lessons", ...lessons.map((l) => `- ${l}`));
11904
+ if (filesAffected.length > 0) sections.push("", "## Files", ...filesAffected.map((f) => `- \`${f}\``));
11500
11905
  if (actionLinks) sections.push("", "## Source Actions", actionLinks);
11501
11906
  return sections.join("\n");
11502
11907
  }
11503
11908
  function sessionToMd(s) {
11909
+ const project = safeString(s.project, "unknown");
11910
+ const status = safeString(s.status, "unknown");
11911
+ const startedAt = safeString(s.startedAt, "");
11912
+ const cwd = safeString(s.cwd, "");
11504
11913
  return [
11505
11914
  toFrontmatter({
11506
11915
  id: s.id,
11507
11916
  type: "session",
11508
- project: s.project,
11509
- status: s.status,
11510
- started: s.startedAt,
11917
+ project,
11918
+ status,
11919
+ started: startedAt || void 0,
11511
11920
  ended: s.endedAt,
11512
11921
  observations: s.observationCount
11513
11922
  }),
11514
11923
  "",
11515
- `# Session: ${s.project}`,
11924
+ `# Session: ${project}`,
11516
11925
  "",
11517
- `**Status:** ${s.status}`,
11518
- `**Started:** ${s.startedAt}`,
11926
+ `**Status:** ${status}`,
11927
+ startedAt ? `**Started:** ${startedAt}` : "",
11519
11928
  s.endedAt ? `**Ended:** ${s.endedAt}` : "",
11520
- `**Observations:** ${s.observationCount}`,
11521
- `**CWD:** \`${s.cwd}\``
11929
+ `**Observations:** ${s.observationCount ?? 0}`,
11930
+ cwd ? `**CWD:** \`${cwd}\`` : ""
11522
11931
  ].filter(Boolean).join("\n");
11523
11932
  }
11524
11933
  function registerObsidianExportFunction(sdk, kv) {
@@ -11554,122 +11963,131 @@ function registerObsidianExportFunction(sdk, kv) {
11554
11963
  crystals: join(vaultDir, "crystals"),
11555
11964
  sessions: join(vaultDir, "sessions")
11556
11965
  };
11557
- await Promise.all(Object.values(dirs).map((dir) => mkdir(dir, { recursive: true })));
11558
- const stats = {
11559
- memories: 0,
11560
- lessons: 0,
11561
- crystals: 0,
11562
- sessions: 0
11563
- };
11564
- const errors = [];
11565
- const memoryMoc = [];
11566
- const lessonMoc = [];
11567
- const crystalMoc = [];
11568
- const sessionMoc = [];
11569
- const [memories, lessons, crystals, sessions] = await Promise.all([
11570
- exportTypes.has("memories") ? kv.list(KV.memories) : Promise.resolve([]),
11571
- exportTypes.has("lessons") ? kv.list(KV.lessons) : Promise.resolve([]),
11572
- exportTypes.has("crystals") ? kv.list(KV.crystals) : Promise.resolve([]),
11573
- exportTypes.has("sessions") ? kv.list(KV.sessions) : Promise.resolve([])
11574
- ]);
11575
- for (const m of memories.filter((m) => m.isLatest)) {
11576
- const filename = `${sanitize(m.id)}.md`;
11577
- const filepath = join(dirs.memories, filename);
11578
- try {
11579
- await writeFile(filepath, memoryToMd(m));
11580
- stats.memories++;
11581
- memoryMoc.push(`- [[memories/${sanitize(m.id)}|${m.title}]] (${m.type}, strength: ${m.strength})`);
11582
- } catch (err) {
11583
- errors.push({
11584
- id: m.id,
11585
- path: filepath,
11586
- error: err instanceof Error ? err.message : String(err)
11587
- });
11966
+ try {
11967
+ await Promise.all(Object.values(dirs).map((dir) => mkdir(dir, { recursive: true })));
11968
+ const stats = {
11969
+ memories: 0,
11970
+ lessons: 0,
11971
+ crystals: 0,
11972
+ sessions: 0
11973
+ };
11974
+ const errors = [];
11975
+ const memoryMoc = [];
11976
+ const lessonMoc = [];
11977
+ const crystalMoc = [];
11978
+ const sessionMoc = [];
11979
+ const [memories, lessons, crystals, sessions] = await Promise.all([
11980
+ exportTypes.has("memories") ? kv.list(KV.memories) : Promise.resolve([]),
11981
+ exportTypes.has("lessons") ? kv.list(KV.lessons) : Promise.resolve([]),
11982
+ exportTypes.has("crystals") ? kv.list(KV.crystals) : Promise.resolve([]),
11983
+ exportTypes.has("sessions") ? kv.list(KV.sessions) : Promise.resolve([])
11984
+ ]);
11985
+ for (const m of memories.filter((m) => hasExportId(m) && m.isLatest === true)) {
11986
+ const filename = `${sanitize(m.id)}.md`;
11987
+ const filepath = join(dirs.memories, filename);
11988
+ try {
11989
+ await writeFile(filepath, memoryToMd(m));
11990
+ stats.memories++;
11991
+ memoryMoc.push(`- [[memories/${sanitize(m.id)}|${safeString(m.title, m.id)}]] (${m.type}, strength: ${m.strength ?? 0})`);
11992
+ } catch (err) {
11993
+ errors.push({
11994
+ id: m.id,
11995
+ path: filepath,
11996
+ error: err instanceof Error ? err.message : String(err)
11997
+ });
11998
+ }
11588
11999
  }
11589
- }
11590
- for (const l of lessons.filter((l) => !l.deleted)) {
11591
- const filename = `${sanitize(l.id)}.md`;
11592
- const filepath = join(dirs.lessons, filename);
11593
- try {
11594
- await writeFile(filepath, lessonToMd(l));
11595
- stats.lessons++;
11596
- lessonMoc.push(`- [[lessons/${sanitize(l.id)}|${l.content.slice(0, 60)}]] (confidence: ${l.confidence})`);
11597
- } catch (err) {
11598
- errors.push({
11599
- id: l.id,
11600
- path: filepath,
11601
- error: err instanceof Error ? err.message : String(err)
11602
- });
12000
+ for (const l of lessons.filter((l) => hasExportId(l) && !l.deleted)) {
12001
+ const filename = `${sanitize(l.id)}.md`;
12002
+ const filepath = join(dirs.lessons, filename);
12003
+ try {
12004
+ await writeFile(filepath, lessonToMd(l));
12005
+ stats.lessons++;
12006
+ const headline = safeString(l.content).slice(0, 60) || l.id;
12007
+ lessonMoc.push(`- [[lessons/${sanitize(l.id)}|${headline}]] (confidence: ${l.confidence ?? 0})`);
12008
+ } catch (err) {
12009
+ errors.push({
12010
+ id: l.id,
12011
+ path: filepath,
12012
+ error: err instanceof Error ? err.message : String(err)
12013
+ });
12014
+ }
11603
12015
  }
11604
- }
11605
- for (const c of crystals) {
11606
- const filename = `${sanitize(c.id)}.md`;
11607
- const filepath = join(dirs.crystals, filename);
11608
- try {
11609
- await writeFile(filepath, crystalToMd(c));
11610
- stats.crystals++;
11611
- crystalMoc.push(`- [[crystals/${sanitize(c.id)}|${c.narrative.slice(0, 60)}]]`);
11612
- } catch (err) {
11613
- errors.push({
11614
- id: c.id,
11615
- path: filepath,
11616
- error: err instanceof Error ? err.message : String(err)
11617
- });
12016
+ for (const c of crystals.filter(hasExportId)) {
12017
+ const filename = `${sanitize(c.id)}.md`;
12018
+ const filepath = join(dirs.crystals, filename);
12019
+ try {
12020
+ await writeFile(filepath, crystalToMd(c));
12021
+ stats.crystals++;
12022
+ const headline = safeString(c.narrative).slice(0, 60) || c.id;
12023
+ crystalMoc.push(`- [[crystals/${sanitize(c.id)}|${headline}]]`);
12024
+ } catch (err) {
12025
+ errors.push({
12026
+ id: c.id,
12027
+ path: filepath,
12028
+ error: err instanceof Error ? err.message : String(err)
12029
+ });
12030
+ }
11618
12031
  }
11619
- }
11620
- const recent = sessions.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).slice(0, 50);
11621
- for (const s of recent) {
11622
- const filename = `${sanitize(s.id)}.md`;
11623
- const filepath = join(dirs.sessions, filename);
11624
- try {
11625
- await writeFile(filepath, sessionToMd(s));
11626
- stats.sessions++;
11627
- sessionMoc.push(`- [[sessions/${sanitize(s.id)}|${s.project} (${s.status})]]`);
11628
- } catch (err) {
11629
- errors.push({
11630
- id: s.id,
11631
- path: filepath,
11632
- error: err instanceof Error ? err.message : String(err)
11633
- });
12032
+ const recent = sessions.filter(hasExportId).sort((a, b) => safeTimestamp(b.startedAt) - safeTimestamp(a.startedAt)).slice(0, 50);
12033
+ for (const s of recent) {
12034
+ const filename = `${sanitize(s.id)}.md`;
12035
+ const filepath = join(dirs.sessions, filename);
12036
+ try {
12037
+ await writeFile(filepath, sessionToMd(s));
12038
+ stats.sessions++;
12039
+ sessionMoc.push(`- [[sessions/${sanitize(s.id)}|${safeString(s.project, "unknown")} (${safeString(s.status, "unknown")})]]`);
12040
+ } catch (err) {
12041
+ errors.push({
12042
+ id: s.id,
12043
+ path: filepath,
12044
+ error: err instanceof Error ? err.message : String(err)
12045
+ });
12046
+ }
11634
12047
  }
12048
+ const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
12049
+ const moc = [
12050
+ "---",
12051
+ "type: moc",
12052
+ `exported: ${exportedAt}`,
12053
+ "---",
12054
+ "",
12055
+ "# agentmemory vault",
12056
+ "",
12057
+ `Exported: ${exportedAt}`,
12058
+ "",
12059
+ `## Memories (${stats.memories})`,
12060
+ ...memoryMoc,
12061
+ "",
12062
+ `## Lessons (${stats.lessons})`,
12063
+ ...lessonMoc,
12064
+ "",
12065
+ `## Crystals (${stats.crystals})`,
12066
+ ...crystalMoc,
12067
+ "",
12068
+ `## Sessions (${stats.sessions})`,
12069
+ ...sessionMoc
12070
+ ].join("\n");
12071
+ await writeFile(join(vaultDir, "MOC.md"), moc);
12072
+ await recordAudit(kv, "obsidian_export", "mem::obsidian-export", [], {
12073
+ vaultDir,
12074
+ stats
12075
+ });
12076
+ return {
12077
+ success: true,
12078
+ exported: stats,
12079
+ errors: errors.length > 0 ? errors : void 0,
12080
+ vaultDir
12081
+ };
12082
+ } catch (err) {
12083
+ return {
12084
+ success: false,
12085
+ error: err instanceof Error ? err.message : String(err),
12086
+ vaultDir
12087
+ };
11635
12088
  }
11636
- const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
11637
- const moc = [
11638
- "---",
11639
- "type: moc",
11640
- `exported: ${exportedAt}`,
11641
- "---",
11642
- "",
11643
- "# agentmemory vault",
11644
- "",
11645
- `Exported: ${exportedAt}`,
11646
- "",
11647
- `## Memories (${stats.memories})`,
11648
- ...memoryMoc,
11649
- "",
11650
- `## Lessons (${stats.lessons})`,
11651
- ...lessonMoc,
11652
- "",
11653
- `## Crystals (${stats.crystals})`,
11654
- ...crystalMoc,
11655
- "",
11656
- `## Sessions (${stats.sessions})`,
11657
- ...sessionMoc
11658
- ].join("\n");
11659
- await writeFile(join(vaultDir, "MOC.md"), moc);
11660
- await recordAudit(kv, "obsidian_export", "mem::obsidian-export", [], {
11661
- vaultDir,
11662
- stats
11663
- });
11664
- return {
11665
- success: true,
11666
- exported: stats,
11667
- errors: errors.length > 0 ? errors : void 0,
11668
- vaultDir
11669
- };
11670
12089
  });
11671
12090
  }
11672
-
11673
12091
  //#endregion
11674
12092
  //#region src/prompts/reflect.ts
11675
12093
  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.
@@ -11697,7 +12115,6 @@ function buildReflectPrompt(cluster) {
11697
12115
  if (cluster.crystalNarratives.length > 0) sections.push("\n## Completed Work Summaries", ...cluster.crystalNarratives.map((n) => `- ${n}`));
11698
12116
  return `Synthesize higher-order insights from this cluster of related memories:\n\n${sections.join("\n")}`;
11699
12117
  }
11700
-
11701
12118
  //#endregion
11702
12119
  //#region src/functions/reflect.ts
11703
12120
  function reinforceInsight(insight) {
@@ -11990,7 +12407,6 @@ function registerReflectFunctions(sdk, kv, provider) {
11990
12407
  };
11991
12408
  });
11992
12409
  }
11993
-
11994
12410
  //#endregion
11995
12411
  //#region src/functions/working-memory.ts
11996
12412
  const CORE_SCOPE = "mem:core-memory";
@@ -12165,7 +12581,6 @@ function registerWorkingMemoryFunctions(sdk, kv, tokenBudget) {
12165
12581
  };
12166
12582
  });
12167
12583
  }
12168
-
12169
12584
  //#endregion
12170
12585
  //#region src/functions/skill-extract.ts
12171
12586
  const SKILL_EXTRACT_SYSTEM = `You are a skill extraction engine. Given a completed multi-step task session, extract a reusable procedural skill document.
@@ -12368,7 +12783,6 @@ function registerSkillExtractFunctions(sdk, kv, provider) {
12368
12783
  };
12369
12784
  });
12370
12785
  }
12371
-
12372
12786
  //#endregion
12373
12787
  //#region src/functions/sliding-window.ts
12374
12788
  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.
@@ -12555,7 +12969,6 @@ function registerSlidingWindowFunction(sdk, kv, provider) {
12555
12969
  };
12556
12970
  });
12557
12971
  }
12558
-
12559
12972
  //#endregion
12560
12973
  //#region src/functions/temporal-graph.ts
12561
12974
  const TEMPORAL_EXTRACTION_SYSTEM = `You are a temporal knowledge extraction engine. Given observations, extract entities AND their temporal relationships with full context metadata.
@@ -12819,7 +13232,6 @@ function buildTimeline(edges) {
12819
13232
  context: e.context
12820
13233
  }));
12821
13234
  }
12822
-
12823
13235
  //#endregion
12824
13236
  //#region src/functions/retention.ts
12825
13237
  const DEFAULT_DECAY = {
@@ -12983,7 +13395,7 @@ function registerRetentionFunctions(sdk, kv) {
12983
13395
  const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
12984
13396
  const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
12985
13397
  const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
12986
- const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
13398
+ const { decrementImageRef } = await import("./image-refs-C7h9L5wx.mjs").then((n) => n.n);
12987
13399
  const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
12988
13400
  if (data?.dryRun) return {
12989
13401
  success: true,
@@ -13053,7 +13465,6 @@ function registerRetentionFunctions(sdk, kv) {
13053
13465
  };
13054
13466
  });
13055
13467
  }
13056
-
13057
13468
  //#endregion
13058
13469
  //#region src/functions/compress-file.ts
13059
13470
  const SENSITIVE_PATH_TERMS = [
@@ -13195,7 +13606,6 @@ function registerCompressFileFunction(sdk, kv, provider) {
13195
13606
  };
13196
13607
  });
13197
13608
  }
13198
-
13199
13609
  //#endregion
13200
13610
  //#region src/replay/jsonl-parser.ts
13201
13611
  function deriveProject(cwd) {
@@ -13322,7 +13732,6 @@ function parseJsonlText(text, fallbackSessionId) {
13322
13732
  observations
13323
13733
  };
13324
13734
  }
13325
-
13326
13735
  //#endregion
13327
13736
  //#region src/replay/timeline.ts
13328
13737
  const DEFAULT_CHARS_PER_SEC = 40;
@@ -13417,10 +13826,6 @@ function projectTimeline(observations) {
13417
13826
  events
13418
13827
  };
13419
13828
  }
13420
-
13421
- //#endregion
13422
- //#region src/functions/replay.ts
13423
- const MAX_FILES_DEFAULT = 200;
13424
13829
  const MAX_FILES_UPPER_BOUND = 1e3;
13425
13830
  const SENSITIVE_PATH_PATTERNS = [
13426
13831
  /(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
@@ -13640,7 +14045,7 @@ function registerReplayFunctions(sdk, kv) {
13640
14045
  error: "path not found"
13641
14046
  };
13642
14047
  }
13643
- const maxFiles = Number.isInteger(data.maxFiles) && data.maxFiles > 0 ? Math.min(data.maxFiles, MAX_FILES_UPPER_BOUND) : MAX_FILES_DEFAULT;
14048
+ const maxFiles = Number.isInteger(data.maxFiles) && data.maxFiles > 0 ? Math.min(data.maxFiles, MAX_FILES_UPPER_BOUND) : 200;
13644
14049
  let files = [];
13645
14050
  let truncated = false;
13646
14051
  let discovered = 0;
@@ -13696,7 +14101,8 @@ function registerReplayFunctions(sdk, kv) {
13696
14101
  const existingTags = existing.tags || [];
13697
14102
  if (!existingTags.includes("jsonl-import")) existing.tags = [...existingTags, "jsonl-import"];
13698
14103
  if (!existing.firstPrompt && firstPrompt) existing.firstPrompt = firstPrompt;
13699
- await kv.set(KV.sessions, existing.id, existing);
14104
+ if (!existing.id) existing.id = parsed.sessionId;
14105
+ await kv.set(KV.sessions, parsed.sessionId, existing);
13700
14106
  } else {
13701
14107
  const session = {
13702
14108
  id: parsed.sessionId,
@@ -13742,7 +14148,6 @@ function registerReplayFunctions(sdk, kv) {
13742
14148
  };
13743
14149
  });
13744
14150
  }
13745
-
13746
14151
  //#endregion
13747
14152
  //#region src/health/thresholds.ts
13748
14153
  const DEFAULTS = {
@@ -13801,7 +14206,6 @@ function evaluateHealth(snapshot, config = {}) {
13801
14206
  notes
13802
14207
  };
13803
14208
  }
13804
-
13805
14209
  //#endregion
13806
14210
  //#region src/health/monitor.ts
13807
14211
  function registerHealthMonitor(sdk, kv) {
@@ -13889,7 +14293,6 @@ function registerHealthMonitor(sdk, kv) {
13889
14293
  async function getLatestHealth(kv) {
13890
14294
  return kv.get(KV.health, "latest");
13891
14295
  }
13892
-
13893
14296
  //#endregion
13894
14297
  //#region src/auth.ts
13895
14298
  const hmacKey = randomBytes(32);
@@ -13915,7 +14318,6 @@ function buildViewerCsp(nonce) {
13915
14318
  "font-src 'self'"
13916
14319
  ].join("; ");
13917
14320
  }
13918
-
13919
14321
  //#endregion
13920
14322
  //#region src/viewer/document.ts
13921
14323
  const VIEWER_VERSION_PLACEHOLDER = "__AGENTMEMORY_VERSION__";
@@ -13941,7 +14343,6 @@ function renderViewerDocument() {
13941
14343
  csp: buildViewerCsp(nonce)
13942
14344
  };
13943
14345
  }
13944
-
13945
14346
  //#endregion
13946
14347
  //#region src/viewer/server.ts
13947
14348
  function loadViewerFavicon() {
@@ -13956,18 +14357,30 @@ function loadViewerFavicon() {
13956
14357
  } catch {}
13957
14358
  return null;
13958
14359
  }
14360
+ const VIEWER_FAVICON = loadViewerFavicon();
13959
14361
  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());
13960
- const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
13961
- function buildAllowedHosts(origins, listenPort) {
14362
+ function readAllowedHostsOverride() {
14363
+ return (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
14364
+ }
14365
+ function resolveViewerHost() {
14366
+ return process.env.AGENTMEMORY_VIEWER_HOST?.trim() || "127.0.0.1";
14367
+ }
14368
+ function isLoopbackHost(host) {
14369
+ const h = host.trim().toLowerCase();
14370
+ return h === "127.0.0.1" || h === "::1" || h === "localhost";
14371
+ }
14372
+ function buildAllowedHosts(origins, listenPort, bindHost = "127.0.0.1") {
13962
14373
  const hosts = /* @__PURE__ */ new Set();
13963
- for (const o of origins) try {
13964
- const parsed = new URL(o);
13965
- if (parsed.host) hosts.add(parsed.host.toLowerCase());
13966
- } catch {}
13967
- hosts.add(`localhost:${listenPort}`);
13968
- hosts.add(`127.0.0.1:${listenPort}`);
13969
- hosts.add(`[::1]:${listenPort}`);
13970
- for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h);
14374
+ if (isLoopbackHost(bindHost)) {
14375
+ for (const o of origins) try {
14376
+ const parsed = new URL(o);
14377
+ if (parsed.host) hosts.add(parsed.host.toLowerCase());
14378
+ } catch {}
14379
+ hosts.add(`localhost:${listenPort}`);
14380
+ hosts.add(`127.0.0.1:${listenPort}`);
14381
+ hosts.add(`[::1]:${listenPort}`);
14382
+ }
14383
+ for (const h of readAllowedHostsOverride()) hosts.add(h);
13971
14384
  return hosts;
13972
14385
  }
13973
14386
  function isHostAllowed(headerHost, allowed) {
@@ -13976,11 +14389,16 @@ function isHostAllowed(headerHost, allowed) {
13976
14389
  if (!lower) return false;
13977
14390
  return allowed.has(lower);
13978
14391
  }
14392
+ function requireInboundBearer(authHeader, secret) {
14393
+ if (typeof authHeader !== "string") return false;
14394
+ const match = /^Bearer\s+(\S+)\s*$/i.exec(authHeader);
14395
+ if (!match) return false;
14396
+ return timingSafeCompare(match[1], secret);
14397
+ }
13979
14398
  function corsHeaders(req) {
13980
14399
  const origin = req.headers.origin || "";
13981
- const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
13982
14400
  return {
13983
- "Access-Control-Allow-Origin": allowed,
14401
+ "Access-Control-Allow-Origin": ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0],
13984
14402
  "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
13985
14403
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
13986
14404
  Vary: "Origin"
@@ -14024,16 +14442,29 @@ function getBoundViewerPort() {
14024
14442
  function getViewerSkipped() {
14025
14443
  return viewerSkipped;
14026
14444
  }
14445
+ var ViewerConfigError = class extends Error {
14446
+ constructor(message) {
14447
+ super(message);
14448
+ this.name = "ViewerConfigError";
14449
+ }
14450
+ };
14027
14451
  function startViewerServer(port, _kv, _sdk, secret, restPort) {
14028
14452
  boundViewerPort = null;
14029
14453
  viewerSkipped = false;
14030
14454
  const resolvedRestPort = restPort ?? port - 2;
14031
14455
  const requestedPort = port;
14456
+ const host = resolveViewerHost();
14457
+ let inboundSecret = null;
14458
+ if (!isLoopbackHost(host)) {
14459
+ 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.`);
14460
+ 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.`);
14461
+ inboundSecret = secret;
14462
+ }
14032
14463
  let allowedHosts = null;
14033
14464
  const server = createServer(async (req, res) => {
14034
14465
  if (!allowedHosts) {
14035
14466
  const addr = server.address();
14036
- allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port);
14467
+ allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port, host);
14037
14468
  }
14038
14469
  if (!isHostAllowed(req.headers.host, allowedHosts)) {
14039
14470
  res.writeHead(403, { "Content-Type": "text/plain" });
@@ -14069,19 +14500,26 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
14069
14500
  return;
14070
14501
  }
14071
14502
  if (method === "GET" && pathname === "/favicon.svg") {
14072
- const favicon = loadViewerFavicon();
14073
- if (favicon) {
14503
+ if (VIEWER_FAVICON) {
14074
14504
  res.writeHead(200, {
14075
14505
  "Content-Type": "image/svg+xml",
14076
14506
  "Cache-Control": "public, max-age=3600"
14077
14507
  });
14078
- res.end(favicon);
14508
+ res.end(VIEWER_FAVICON);
14079
14509
  return;
14080
14510
  }
14081
14511
  res.writeHead(404, { "Content-Type": "text/plain" });
14082
14512
  res.end("favicon not found");
14083
14513
  return;
14084
14514
  }
14515
+ if (inboundSecret !== null && !requireInboundBearer(req.headers.authorization, inboundSecret)) {
14516
+ res.writeHead(401, {
14517
+ "Content-Type": "text/plain",
14518
+ "WWW-Authenticate": "Bearer realm=\"agentmemory-viewer\""
14519
+ });
14520
+ res.end("unauthorized");
14521
+ return;
14522
+ }
14085
14523
  try {
14086
14524
  await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret);
14087
14525
  } catch (err) {
@@ -14092,17 +14530,23 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
14092
14530
  let attempt = 0;
14093
14531
  let currentPort = requestedPort;
14094
14532
  const tryListen = () => {
14095
- server.listen(currentPort, "127.0.0.1");
14533
+ server.listen(currentPort, host);
14096
14534
  };
14097
14535
  server.on("listening", () => {
14098
14536
  const addr = server.address();
14099
- boundViewerPort = addr && typeof addr === "object" && "port" in addr ? addr.port : currentPort;
14537
+ const actualPort = addr && typeof addr === "object" && "port" in addr ? addr.port : currentPort;
14538
+ boundViewerPort = actualPort;
14100
14539
  viewerSkipped = false;
14101
- if (currentPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${currentPort}`);
14102
- else console.log(`[agentmemory] Viewer started on http://localhost:${currentPort} (fallback from ${requestedPort})`);
14540
+ if (inboundSecret !== null) {
14541
+ const allowedHosts = readAllowedHostsOverride().join(", ");
14542
+ console.log(`[agentmemory] Viewer: http://localhost:${actualPort} (bound to ${host}; inbound Bearer required; allowed Host headers: ${allowedHosts})`);
14543
+ return;
14544
+ }
14545
+ if (actualPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${actualPort}`);
14546
+ else console.log(`[agentmemory] Viewer started on http://localhost:${actualPort} (fallback from ${requestedPort})`);
14103
14547
  });
14104
14548
  server.on("error", (err) => {
14105
- if (err.code === "EADDRINUSE" && attempt < MAX_VIEWER_PORT_RETRIES) {
14549
+ if (err.code === "EADDRINUSE" && inboundSecret === null && attempt < MAX_VIEWER_PORT_RETRIES) {
14106
14550
  attempt++;
14107
14551
  currentPort = requestedPort + attempt;
14108
14552
  setImmediate(tryListen);
@@ -14111,7 +14555,8 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
14111
14555
  if (err.code === "EADDRINUSE") {
14112
14556
  boundViewerPort = null;
14113
14557
  viewerSkipped = true;
14114
- console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
14558
+ 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.`);
14559
+ else console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
14115
14560
  } else {
14116
14561
  boundViewerPort = null;
14117
14562
  viewerSkipped = true;
@@ -14156,7 +14601,6 @@ async function proxyToRestApi(restPort, pathname, qs, method, req, res, secret)
14156
14601
  res.writeHead(upstream.status, responseHeaders);
14157
14602
  res.end(responseBody);
14158
14603
  }
14159
-
14160
14604
  //#endregion
14161
14605
  //#region src/triggers/api.ts
14162
14606
  function parseOptionalInt(raw) {
@@ -14579,7 +15023,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14579
15023
  }
14580
15024
  if (body.maxFiles !== void 0) {
14581
15025
  const n = body.maxFiles;
14582
- if (!Number.isInteger(n) || n < 1 || n > MAX_FILES_UPPER_BOUND) return {
15026
+ if (!Number.isInteger(n) || n < 1 || n > 1e3) return {
14583
15027
  status_code: 400,
14584
15028
  body: { error: `maxFiles must be an integer between 1 and ${MAX_FILES_UPPER_BOUND}` }
14585
15029
  };
@@ -14663,9 +15107,13 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
14663
15107
  value: "completed"
14664
15108
  }]);
14665
15109
  try {
14666
- sdk.triggerVoid("event::session::stopped", { sessionId });
15110
+ sdk.trigger({
15111
+ function_id: "event::session::stopped",
15112
+ payload: { sessionId },
15113
+ action: TriggerAction.Void()
15114
+ });
14667
15115
  } catch (err) {
14668
- logger.warn("event::session::stopped triggerVoid failed", {
15116
+ logger.warn("event::session::stopped trigger failed", {
14669
15117
  sessionId,
14670
15118
  error: err instanceof Error ? err.message : String(err)
14671
15119
  });
@@ -15085,11 +15533,24 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15085
15533
  status_code: 400,
15086
15534
  body: { error: "query or expandIds is required" }
15087
15535
  };
15536
+ const headers = req.headers || {};
15537
+ const sourceHeader = headers["x-agentmemory-source"] ?? headers["X-Agentmemory-Source"];
15538
+ const sourceFromHeader = Array.isArray(sourceHeader) ? sourceHeader[0] : sourceHeader;
15539
+ const payload = {
15540
+ query: req.body?.query,
15541
+ expandIds: req.body?.expandIds,
15542
+ limit: req.body?.limit,
15543
+ project: req.body?.project,
15544
+ includeLessons: req.body?.includeLessons,
15545
+ agentId: req.body?.agentId,
15546
+ sessionId: req.body?.sessionId,
15547
+ source: req.body?.source ?? sourceFromHeader
15548
+ };
15088
15549
  return {
15089
15550
  status_code: 200,
15090
15551
  body: await sdk.trigger({
15091
15552
  function_id: "mem::smart-search",
15092
- payload: req.body
15553
+ payload
15093
15554
  })
15094
15555
  };
15095
15556
  });
@@ -15101,6 +15562,29 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15101
15562
  http_method: "POST"
15102
15563
  }
15103
15564
  });
15565
+ sdk.registerFunction("api::diagnostic-followup", async (req) => {
15566
+ const authErr = checkAuth(req, secret);
15567
+ if (authErr) return authErr;
15568
+ return {
15569
+ status_code: 200,
15570
+ body: {
15571
+ ...await sdk.trigger({
15572
+ function_id: "mem::diagnostic::followup-stats",
15573
+ payload: {}
15574
+ }),
15575
+ caveat: "Directional signal: overcounts on legitimate query refinement. Tune via AGENTMEMORY_FOLLOWUP_WINDOW_SECONDS."
15576
+ }
15577
+ };
15578
+ });
15579
+ sdk.registerTrigger({
15580
+ type: "http",
15581
+ function_id: "api::diagnostic-followup",
15582
+ config: {
15583
+ api_path: "/agentmemory/diagnostics/followup",
15584
+ http_method: "GET",
15585
+ middleware_function_ids: ["middleware::api-auth"]
15586
+ }
15587
+ });
15104
15588
  sdk.registerFunction("api::timeline", async (req) => {
15105
15589
  const authErr = checkAuth(req, secret);
15106
15590
  if (authErr) return authErr;
@@ -15322,12 +15806,20 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15322
15806
  sdk.registerFunction("api::graph-query", async (req) => {
15323
15807
  const authErr = checkAuth(req, secret);
15324
15808
  if (authErr) return authErr;
15809
+ const payload = {
15810
+ startNodeId: req.body?.startNodeId,
15811
+ nodeType: req.body?.nodeType,
15812
+ maxDepth: req.body?.maxDepth,
15813
+ query: req.body?.query,
15814
+ limit: req.body?.limit,
15815
+ offset: req.body?.offset
15816
+ };
15325
15817
  try {
15326
15818
  return {
15327
15819
  status_code: 200,
15328
15820
  body: await sdk.trigger({
15329
15821
  function_id: "mem::graph-query",
15330
- payload: req.body || {}
15822
+ payload
15331
15823
  })
15332
15824
  };
15333
15825
  } catch {
@@ -17608,7 +18100,6 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
17608
18100
  }
17609
18101
  });
17610
18102
  }
17611
-
17612
18103
  //#endregion
17613
18104
  //#region src/triggers/events.ts
17614
18105
  function registerEventTriggers(sdk, kv) {
@@ -17653,18 +18144,26 @@ function registerEventTriggers(sdk, kv) {
17653
18144
  payload: data
17654
18145
  });
17655
18146
  if (isReflectEnabled()) try {
17656
- sdk.triggerVoid("mem::slot-reflect", { sessionId: data.sessionId });
18147
+ sdk.trigger({
18148
+ function_id: "mem::slot-reflect",
18149
+ payload: { sessionId: data.sessionId },
18150
+ action: TriggerAction.Void()
18151
+ });
17657
18152
  } catch (err) {
17658
- logger.warn("slot-reflect triggerVoid failed", {
18153
+ logger.warn("slot-reflect trigger failed", {
17659
18154
  sessionId: data.sessionId,
17660
18155
  error: err instanceof Error ? err.message : String(err)
17661
18156
  });
17662
18157
  }
17663
18158
  if (isGraphExtractionEnabled()) try {
17664
18159
  const compressed = (await kv.list(KV.observations(data.sessionId))).filter((o) => o.title);
17665
- if (compressed.length > 0) sdk.triggerVoid("mem::graph-extract", { observations: compressed });
18160
+ if (compressed.length > 0) sdk.trigger({
18161
+ function_id: "mem::graph-extract",
18162
+ payload: { observations: compressed },
18163
+ action: TriggerAction.Void()
18164
+ });
17666
18165
  } catch (err) {
17667
- logger.warn("graph-extract triggerVoid failed", {
18166
+ logger.warn("graph-extract trigger failed", {
17668
18167
  sessionId: data.sessionId,
17669
18168
  error: err instanceof Error ? err.message : String(err)
17670
18169
  });
@@ -17722,7 +18221,6 @@ function registerEventTriggers(sdk, kv) {
17722
18221
  config: { scope: KV.sessions }
17723
18222
  });
17724
18223
  }
17725
-
17726
18224
  //#endregion
17727
18225
  //#region src/mcp/server.ts
17728
18226
  function asNonEmptyString(value) {
@@ -19359,7 +19857,6 @@ function registerMcpEndpoints(sdk, kv, secret) {
19359
19857
  }
19360
19858
  });
19361
19859
  }
19362
-
19363
19860
  //#endregion
19364
19861
  //#region src/eval/metrics-store.ts
19365
19862
  var MetricsStore = class {
@@ -19402,7 +19899,6 @@ var MetricsStore = class {
19402
19899
  return Array.from(merged.values());
19403
19900
  }
19404
19901
  };
19405
-
19406
19902
  //#endregion
19407
19903
  //#region src/functions/dedup.ts
19408
19904
  const TTL_MS = 300 * 1e3;
@@ -19444,57 +19940,6 @@ var DedupMap = class {
19444
19940
  return this.entries.size;
19445
19941
  }
19446
19942
  };
19447
-
19448
- //#endregion
19449
- //#region src/telemetry/setup.ts
19450
- const OTEL_CONFIG = {
19451
- serviceName: "agentmemory",
19452
- serviceVersion: VERSION,
19453
- metricsExportIntervalMs: 3e4
19454
- };
19455
- let counters = null;
19456
- let histograms = null;
19457
- const NOOP_COUNTER = { add: () => {} };
19458
- const NOOP_HISTOGRAM = { record: () => {} };
19459
- const COUNTER_NAMES = [
19460
- ["observationsTotal", "observations.total"],
19461
- ["compressionSuccess", "compression.success"],
19462
- ["compressionFailure", "compression.failure"],
19463
- ["searchTotal", "search.total"],
19464
- ["dedupSkipped", "dedup.skipped"],
19465
- ["evictionTotal", "eviction.total"],
19466
- ["circuitBreakerOpen", "circuit_breaker.open"],
19467
- ["embeddingSuccess", "embedding.success"],
19468
- ["embeddingFailure", "embedding.failure"],
19469
- ["vectorSearchTotal", "vector_search.total"],
19470
- ["autoForgetTotal", "auto_forget.total"],
19471
- ["profileGenerated", "profile.generated"],
19472
- ["claudeBridgeSync", "claude_bridge.sync"],
19473
- ["graphExtraction", "graph.extraction"],
19474
- ["consolidationRun", "consolidation.run"],
19475
- ["teamShare", "team.share"],
19476
- ["auditLog", "audit.log"],
19477
- ["snapshotCreate", "snapshot.create"],
19478
- ["governanceDelete", "governance.delete"]
19479
- ];
19480
- const HISTOGRAM_NAMES = [
19481
- ["compressionLatency", "compression.latency_ms"],
19482
- ["searchLatency", "search.latency_ms"],
19483
- ["contextTokens", "context.tokens"],
19484
- ["qualityScore", "quality.score"],
19485
- ["embeddingLatency", "embedding.latency_ms"],
19486
- ["vectorSearchLatency", "vector_search.latency_ms"]
19487
- ];
19488
- function initMetrics(getMeter) {
19489
- const meter = getMeter?.("agentmemory");
19490
- counters = Object.fromEntries(COUNTER_NAMES.map(([key, name]) => [key, meter ? meter.createCounter(name) : NOOP_COUNTER]));
19491
- histograms = Object.fromEntries(HISTOGRAM_NAMES.map(([key, name]) => [key, meter ? meter.createHistogram(name) : NOOP_HISTOGRAM]));
19492
- return {
19493
- counters,
19494
- histograms
19495
- };
19496
- }
19497
-
19498
19943
  //#endregion
19499
19944
  //#region src/index.ts
19500
19945
  function workerPidfilePath() {
@@ -19643,6 +20088,7 @@ async function main() {
19643
20088
  const graphWeight = parseFloat(getEnvVar("AGENTMEMORY_GRAPH_WEIGHT") || "0.3");
19644
20089
  const hybridSearch = new HybridSearch(bm25Index, vectorIndex, embeddingProvider, kv, embeddingConfig.bm25Weight, embeddingConfig.vectorWeight, graphWeight);
19645
20090
  registerSmartSearchFunction(sdk, kv, (query, limit) => hybridSearch.search(query, limit));
20091
+ registerRecentSearchesSweepFunction(sdk, kv);
19646
20092
  registerApiTriggers(sdk, kv, secret, metricsStore, provider);
19647
20093
  registerEventTriggers(sdk, kv);
19648
20094
  registerMcpEndpoints(sdk, kv, secret);
@@ -19710,7 +20156,7 @@ async function main() {
19710
20156
  console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
19711
20157
  }
19712
20158
  bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
19713
- bootLog(`REST API: 125 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
20159
+ bootLog(`REST API: 126 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
19714
20160
  bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
19715
20161
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
19716
20162
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
@@ -19745,6 +20191,14 @@ async function main() {
19745
20191
  });
19746
20192
  } catch {}
19747
20193
  }, 864e5).unref();
20194
+ setInterval(async () => {
20195
+ try {
20196
+ await sdk.trigger({
20197
+ function_id: "mem::diagnostic::recent-searches-sweep",
20198
+ payload: {}
20199
+ });
20200
+ } catch {}
20201
+ }, 3600 * 1e3).unref();
19748
20202
  if (isConsolidationEnabled()) {
19749
20203
  setInterval(async () => {
19750
20204
  try {
@@ -19776,7 +20230,7 @@ main().catch((err) => {
19776
20230
  console.error(`[agentmemory] Fatal:`, err);
19777
20231
  process.exit(1);
19778
20232
  });
19779
-
19780
20233
  //#endregion
19781
- export { };
19782
- //# sourceMappingURL=src-B8J9Exum.mjs.map
20234
+ export {};
20235
+
20236
+ //# sourceMappingURL=src-u7kAEUC0.mjs.map