@agentmemory/agentmemory 0.9.23 → 0.9.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/cli.d.mts.map +1 -1
  4. package/dist/cli.mjs +129 -66
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{connect-Cf9bmBqO.mjs → connect-bmZ5eqYN.mjs} +17 -56
  7. package/dist/{connect-Cf9bmBqO.mjs.map → connect-bmZ5eqYN.mjs.map} +1 -1
  8. package/dist/hooks/notification.mjs +2 -4
  9. package/dist/hooks/notification.mjs.map +1 -1
  10. package/dist/hooks/post-commit.mjs +2 -3
  11. package/dist/hooks/post-commit.mjs.map +1 -1
  12. package/dist/hooks/post-tool-failure.mjs +2 -4
  13. package/dist/hooks/post-tool-failure.mjs.map +1 -1
  14. package/dist/hooks/post-tool-use.mjs +2 -4
  15. package/dist/hooks/post-tool-use.mjs.map +1 -1
  16. package/dist/hooks/pre-compact.mjs +2 -4
  17. package/dist/hooks/pre-compact.mjs.map +1 -1
  18. package/dist/hooks/pre-tool-use.mjs +2 -2
  19. package/dist/hooks/pre-tool-use.mjs.map +1 -1
  20. package/dist/hooks/prompt-submit.mjs +2 -4
  21. package/dist/hooks/prompt-submit.mjs.map +1 -1
  22. package/dist/hooks/session-end.mjs +2 -2
  23. package/dist/hooks/session-start.mjs +2 -4
  24. package/dist/hooks/session-start.mjs.map +1 -1
  25. package/dist/hooks/stop.mjs +2 -2
  26. package/dist/hooks/subagent-start.mjs +2 -4
  27. package/dist/hooks/subagent-start.mjs.map +1 -1
  28. package/dist/hooks/subagent-stop.mjs +2 -4
  29. package/dist/hooks/subagent-stop.mjs.map +1 -1
  30. package/dist/hooks/task-completed.mjs +2 -4
  31. package/dist/hooks/task-completed.mjs.map +1 -1
  32. package/dist/image-refs-C7h9L5wx.mjs +52 -0
  33. package/dist/image-refs-C7h9L5wx.mjs.map +1 -0
  34. package/dist/{image-refs-CJS5B9Gq.mjs → image-store-Gpo2mgM9.mjs} +11 -42
  35. package/dist/image-store-Gpo2mgM9.mjs.map +1 -0
  36. package/dist/index.mjs +942 -493
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/{logger-xlVlvCWX.mjs → logger-yHTcEBAI.mjs} +2 -2
  39. package/dist/{logger-xlVlvCWX.mjs.map → logger-yHTcEBAI.mjs.map} +1 -1
  40. package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
  41. package/dist/{schema-BkALl7Z_.mjs → schema-Dsr_V2Wp.mjs} +4 -4
  42. package/dist/schema-Dsr_V2Wp.mjs.map +1 -0
  43. package/dist/{src-DvS3bhMe.mjs → src-fQOMXeCp.mjs} +937 -483
  44. package/dist/src-fQOMXeCp.mjs.map +1 -0
  45. package/dist/{standalone-DHQcPX_g.mjs → standalone-BzfA1zu8.mjs} +6 -10
  46. package/dist/{standalone-DHQcPX_g.mjs.map → standalone-BzfA1zu8.mjs.map} +1 -1
  47. package/dist/standalone.mjs +3 -14
  48. package/dist/standalone.mjs.map +1 -1
  49. package/dist/{tools-registry-DJizX9Az.mjs → tools-registry-Dzxv9iUu.mjs} +7 -5
  50. package/dist/tools-registry-Dzxv9iUu.mjs.map +1 -0
  51. package/dist/version-C3hZKw8n.mjs +6 -0
  52. package/dist/version-C3hZKw8n.mjs.map +1 -0
  53. package/dist/viewer/index.html +155 -9
  54. package/package.json +9 -4
  55. package/plugin/.claude-plugin/plugin.json +1 -1
  56. package/plugin/.codex-plugin/plugin.json +1 -1
  57. package/plugin/plugin.json +1 -1
  58. package/plugin/scripts/notification.mjs +2 -4
  59. package/plugin/scripts/notification.mjs.map +1 -1
  60. package/plugin/scripts/post-commit.mjs +2 -3
  61. package/plugin/scripts/post-commit.mjs.map +1 -1
  62. package/plugin/scripts/post-tool-failure.mjs +2 -4
  63. package/plugin/scripts/post-tool-failure.mjs.map +1 -1
  64. package/plugin/scripts/post-tool-use.mjs +2 -4
  65. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  66. package/plugin/scripts/pre-compact.mjs +2 -4
  67. package/plugin/scripts/pre-compact.mjs.map +1 -1
  68. package/plugin/scripts/pre-tool-use.mjs +2 -2
  69. package/plugin/scripts/pre-tool-use.mjs.map +1 -1
  70. package/plugin/scripts/prompt-submit.mjs +2 -4
  71. package/plugin/scripts/prompt-submit.mjs.map +1 -1
  72. package/plugin/scripts/session-end.mjs +2 -2
  73. package/plugin/scripts/session-start.mjs +2 -4
  74. package/plugin/scripts/session-start.mjs.map +1 -1
  75. package/plugin/scripts/stop.mjs +2 -2
  76. package/plugin/scripts/subagent-start.mjs +2 -4
  77. package/plugin/scripts/subagent-start.mjs.map +1 -1
  78. package/plugin/scripts/subagent-stop.mjs +2 -4
  79. package/plugin/scripts/subagent-stop.mjs.map +1 -1
  80. package/plugin/scripts/task-completed.mjs +2 -4
  81. package/plugin/scripts/task-completed.mjs.map +1 -1
  82. package/dist/image-refs-CJS5B9Gq.mjs.map +0 -1
  83. package/dist/image-store-CdE0amb1.mjs +0 -3
  84. package/dist/schema-BkALl7Z_.mjs.map +0 -1
  85. package/dist/src-DvS3bhMe.mjs.map +0 -1
  86. package/dist/tools-registry-DJizX9Az.mjs.map +0 -1
  87. package/dist/version-BPfyI4Kc.mjs +0 -6
  88. package/dist/version-BPfyI4Kc.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@ import { TriggerAction, registerWorker } from "iii-sdk";
4
4
  import { constants, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, extname, join, resolve, sep } from "node:path";
6
6
  import { homedir } from "node:os";
7
+ import { AsyncLocalStorage } from "node:async_hooks";
7
8
  import Anthropic from "@anthropic-ai/sdk";
8
9
  import { lstat, mkdir, open, readFile, readdir, stat, unlink, utimes, writeFile } from "node:fs/promises";
9
10
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
@@ -14,23 +15,17 @@ import { lookup } from "node:dns/promises";
14
15
  import { isIP } from "node:net";
15
16
  import { fileURLToPath } from "node:url";
16
17
  import { createServer } from "node:http";
17
-
18
18
  //#region \0rolldown/runtime.js
19
19
  var __defProp = Object.defineProperty;
20
20
  var __exportAll = (all, no_symbols) => {
21
21
  let target = {};
22
- for (var name in all) {
23
- __defProp(target, name, {
24
- get: all[name],
25
- enumerable: true
26
- });
27
- }
28
- if (!no_symbols) {
29
- __defProp(target, Symbol.toStringTag, { value: "Module" });
30
- }
22
+ for (var name in all) __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true
25
+ });
26
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
31
27
  return target;
32
28
  };
33
-
34
29
  //#endregion
35
30
  //#region src/config.ts
36
31
  function safeParseInt(value, fallback) {
@@ -232,6 +227,10 @@ function loadSnapshotConfig() {
232
227
  function isGraphExtractionEnabled() {
233
228
  return getMergedEnv()["GRAPH_EXTRACTION_ENABLED"] === "true";
234
229
  }
230
+ const FOLLOWUP_WINDOW_DEFAULT_SECONDS = 30;
231
+ function getFollowupWindowSeconds() {
232
+ return safeParseInt(getMergedEnv()["AGENTMEMORY_FOLLOWUP_WINDOW_SECONDS"], FOLLOWUP_WINDOW_DEFAULT_SECONDS);
233
+ }
235
234
  function isConsolidationEnabled() {
236
235
  const env = getMergedEnv();
237
236
  const explicit = env["CONSOLIDATION_ENABLED"];
@@ -274,11 +273,18 @@ function loadFallbackConfig() {
274
273
  return true;
275
274
  }) };
276
275
  }
277
-
278
276
  //#endregion
279
277
  //#region src/providers/agent-sdk.ts
278
+ const sdkChildContext = new AsyncLocalStorage();
279
+ let sdkActiveCount = 0;
280
+ let sdkOriginalEnv;
280
281
  var AgentSDKProvider = class {
281
282
  name = "agent-sdk";
283
+ sdkPromise = null;
284
+ loadSdk() {
285
+ if (!this.sdkPromise) this.sdkPromise = import("@anthropic-ai/claude-agent-sdk");
286
+ return this.sdkPromise;
287
+ }
282
288
  async compress(systemPrompt, userPrompt) {
283
289
  return this.query(systemPrompt, userPrompt);
284
290
  }
@@ -286,29 +292,37 @@ var AgentSDKProvider = class {
286
292
  return this.query(systemPrompt, userPrompt);
287
293
  }
288
294
  async query(systemPrompt, userPrompt) {
289
- if (process.env.AGENTMEMORY_SDK_CHILD === "1") return "";
290
- const prev = process.env.AGENTMEMORY_SDK_CHILD;
291
- process.env.AGENTMEMORY_SDK_CHILD = "1";
292
- try {
293
- const { query } = await import("@anthropic-ai/claude-agent-sdk");
294
- const messages = query({
295
- prompt: userPrompt,
296
- options: {
297
- systemPrompt,
298
- maxTurns: 1,
299
- allowedTools: []
295
+ if (sdkChildContext.getStore()) return "";
296
+ return sdkChildContext.run(true, async () => {
297
+ if (sdkActiveCount === 0) {
298
+ sdkOriginalEnv = process.env.AGENTMEMORY_SDK_CHILD;
299
+ process.env.AGENTMEMORY_SDK_CHILD = "1";
300
+ }
301
+ sdkActiveCount++;
302
+ try {
303
+ const { query } = await this.loadSdk();
304
+ const messages = query({
305
+ prompt: userPrompt,
306
+ options: {
307
+ systemPrompt,
308
+ maxTurns: 1,
309
+ allowedTools: []
310
+ }
311
+ });
312
+ let result = "";
313
+ for await (const msg of messages) if (msg.type === "result") result = msg.result ?? "";
314
+ return result;
315
+ } finally {
316
+ sdkActiveCount--;
317
+ if (sdkActiveCount === 0) {
318
+ if (sdkOriginalEnv === void 0) delete process.env.AGENTMEMORY_SDK_CHILD;
319
+ else process.env.AGENTMEMORY_SDK_CHILD = sdkOriginalEnv;
320
+ sdkOriginalEnv = void 0;
300
321
  }
301
- });
302
- let result = "";
303
- for await (const msg of messages) if (msg.type === "result") result = msg.result ?? "";
304
- return result;
305
- } finally {
306
- if (prev === void 0) delete process.env.AGENTMEMORY_SDK_CHILD;
307
- else process.env.AGENTMEMORY_SDK_CHILD = prev;
308
- }
322
+ }
323
+ });
309
324
  }
310
325
  };
311
-
312
326
  //#endregion
313
327
  //#region src/providers/anthropic.ts
314
328
  var AnthropicProvider = class {
@@ -362,7 +376,6 @@ var AnthropicProvider = class {
362
376
  })).content.find((b) => b.type === "text")?.text ?? "";
363
377
  }
364
378
  };
365
-
366
379
  //#endregion
367
380
  //#region src/providers/_fetch.ts
368
381
  function fetchWithTimeout(url, init, timeoutMs) {
@@ -376,7 +389,6 @@ function fetchWithTimeout(url, init, timeoutMs) {
376
389
  signal
377
390
  }).finally(() => clearTimeout(t));
378
391
  }
379
-
380
392
  //#endregion
381
393
  //#region src/providers/minimax.ts
382
394
  /**
@@ -436,7 +448,6 @@ var MinimaxProvider = class {
436
448
  return ((await response.json()).content?.find((b) => b.type === "text"))?.text ?? "";
437
449
  }
438
450
  };
439
-
440
451
  //#endregion
441
452
  //#region src/providers/noop.ts
442
453
  /**
@@ -455,11 +466,6 @@ var NoopProvider = class {
455
466
  return "";
456
467
  }
457
468
  };
458
-
459
- //#endregion
460
- //#region src/providers/_openai-shared.ts
461
- const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
462
- const DEFAULT_AZURE_API_VERSION = "2024-08-01-preview";
463
469
  function detectAzure(baseUrl) {
464
470
  try {
465
471
  return new URL(baseUrl).hostname.endsWith(".openai.azure.com");
@@ -519,9 +525,8 @@ function buildAuthHeaders(apiKey, isAzure) {
519
525
  };
520
526
  }
521
527
  function normalizeBaseUrl(raw) {
522
- return (raw || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, "");
528
+ return (raw || "https://api.openai.com").replace(/\/+$/, "");
523
529
  }
524
-
525
530
  //#endregion
526
531
  //#region src/providers/openai.ts
527
532
  const DEFAULT_TIMEOUT_MS = 6e4;
@@ -574,7 +579,7 @@ var OpenAIProvider = class {
574
579
  this.baseUrl = normalizeBaseUrl(baseURL || getEnvVar("OPENAI_BASE_URL"));
575
580
  this.reasoningEffort = getEnvVar("OPENAI_REASONING_EFFORT") || void 0;
576
581
  this.timeoutMs = resolveTimeout();
577
- this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION;
582
+ this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || "2024-08-01-preview";
578
583
  this.isAzure = detectAzure(this.baseUrl);
579
584
  }
580
585
  async compress(systemPrompt, userPrompt) {
@@ -636,7 +641,6 @@ function parsePositiveInt(raw) {
636
641
  const n = Number(trimmed);
637
642
  return Number.isFinite(n) && n > 0 ? n : void 0;
638
643
  }
639
-
640
644
  //#endregion
641
645
  //#region src/providers/openrouter.ts
642
646
  var OpenRouterProvider = class {
@@ -688,7 +692,6 @@ var OpenRouterProvider = class {
688
692
  return content;
689
693
  }
690
694
  };
691
-
692
695
  //#endregion
693
696
  //#region src/providers/circuit-breaker.ts
694
697
  function positiveFinite(val, fallback) {
@@ -750,7 +753,6 @@ var CircuitBreaker = class {
750
753
  };
751
754
  }
752
755
  };
753
-
754
756
  //#endregion
755
757
  //#region src/providers/resilient.ts
756
758
  var ResilientProvider = class {
@@ -781,7 +783,6 @@ var ResilientProvider = class {
781
783
  return this.breaker.getState();
782
784
  }
783
785
  };
784
-
785
786
  //#endregion
786
787
  //#region src/providers/fallback-chain.ts
787
788
  var FallbackChainProvider = class {
@@ -806,7 +807,6 @@ var FallbackChainProvider = class {
806
807
  throw lastError || /* @__PURE__ */ new Error("No providers available");
807
808
  }
808
809
  };
809
-
810
810
  //#endregion
811
811
  //#region src/providers/embedding/gemini.ts
812
812
  const BATCH_LIMIT = 100;
@@ -862,7 +862,6 @@ function l2Normalize(vec) {
862
862
  for (let i = 0; i < vec.length; i++) vec[i] = vec[i] / norm;
863
863
  return vec;
864
864
  }
865
-
866
865
  //#endregion
867
866
  //#region src/providers/embedding/openai.ts
868
867
  const DEFAULT_MODEL$1 = "text-embedding-3-small";
@@ -936,7 +935,7 @@ var OpenAIEmbeddingProvider = class {
936
935
  this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
937
936
  this.dimensions = resolveDimensions(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
938
937
  this.isAzure = detectAzure(this.baseUrl);
939
- this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION;
938
+ this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || "2024-08-01-preview";
940
939
  }
941
940
  async embed(text) {
942
941
  const [result] = await this.embedBatch([text]);
@@ -958,7 +957,6 @@ var OpenAIEmbeddingProvider = class {
958
957
  return (await response.json()).data.map((d) => new Float32Array(d.embedding));
959
958
  }
960
959
  };
961
-
962
960
  //#endregion
963
961
  //#region src/providers/embedding/voyage.ts
964
962
  const API_URL$2 = "https://api.voyageai.com/v1/embeddings";
@@ -994,7 +992,6 @@ var VoyageEmbeddingProvider = class {
994
992
  return (await response.json()).data.map((d) => new Float32Array(d.embedding));
995
993
  }
996
994
  };
997
-
998
995
  //#endregion
999
996
  //#region src/providers/embedding/cohere.ts
1000
997
  const API_URL$1 = "https://api.cohere.ai/v1/embed";
@@ -1030,7 +1027,6 @@ var CohereEmbeddingProvider = class {
1030
1027
  return (await response.json()).embeddings.map((e) => new Float32Array(e));
1031
1028
  }
1032
1029
  };
1033
-
1034
1030
  //#endregion
1035
1031
  //#region src/providers/embedding/openrouter.ts
1036
1032
  const API_URL = "https://openrouter.ai/api/v1/embeddings";
@@ -1067,7 +1063,6 @@ var OpenRouterEmbeddingProvider = class {
1067
1063
  return (await response.json()).data.map((d) => new Float32Array(d.embedding));
1068
1064
  }
1069
1065
  };
1070
-
1071
1066
  //#endregion
1072
1067
  //#region src/providers/embedding/local.ts
1073
1068
  var LocalEmbeddingProvider = class {
@@ -1096,7 +1091,6 @@ var LocalEmbeddingProvider = class {
1096
1091
  return this.extractor;
1097
1092
  }
1098
1093
  };
1099
-
1100
1094
  //#endregion
1101
1095
  //#region src/providers/embedding/clip.ts
1102
1096
  const DEFAULT_MODEL = "Xenova/clip-vit-base-patch32";
@@ -1137,12 +1131,14 @@ var ClipEmbeddingProvider = class {
1137
1131
  }
1138
1132
  async getTextExtractor() {
1139
1133
  if (this.textExtractor) return this.textExtractor;
1140
- this.textExtractor = await (await this.getTransformers()).pipeline("feature-extraction", this.modelId);
1134
+ const t = await this.getTransformers();
1135
+ this.textExtractor = await t.pipeline("feature-extraction", this.modelId);
1141
1136
  return this.textExtractor;
1142
1137
  }
1143
1138
  async getImageExtractor() {
1144
1139
  if (this.imageExtractor) return this.imageExtractor;
1145
- this.imageExtractor = await (await this.getTransformers()).pipeline("image-feature-extraction", this.modelId);
1140
+ const t = await this.getTransformers();
1141
+ this.imageExtractor = await t.pipeline("image-feature-extraction", this.modelId);
1146
1142
  return this.imageExtractor;
1147
1143
  }
1148
1144
  };
@@ -1167,7 +1163,6 @@ function normalize(vec) {
1167
1163
  for (let i = 0; i < vec.length; i++) out[i] = vec[i] / norm;
1168
1164
  return out;
1169
1165
  }
1170
-
1171
1166
  //#endregion
1172
1167
  //#region src/providers/embedding/index.ts
1173
1168
  let imageEmbeddingProvider = null;
@@ -1206,7 +1201,6 @@ function withDimensionGuard(provider) {
1206
1201
  if (provider.embedImage) wrapped.embedImage = async (s) => check(await provider.embedImage(s), "embedImage");
1207
1202
  return wrapped;
1208
1203
  }
1209
-
1210
1204
  //#endregion
1211
1205
  //#region src/providers/index.ts
1212
1206
  function requireEnvVar(key) {
@@ -1214,6 +1208,17 @@ function requireEnvVar(key) {
1214
1208
  if (!value) throw new Error(`Missing required environment variable: ${key}. Set it in ~/.agentmemory/.env or as an environment variable.`);
1215
1209
  return value;
1216
1210
  }
1211
+ function defaultModelFor(providerType) {
1212
+ switch (providerType) {
1213
+ case "openai": return getEnvVar("OPENAI_MODEL") || "gpt-4o-mini";
1214
+ case "anthropic": return getEnvVar("ANTHROPIC_MODEL") || "claude-sonnet-4-20250514";
1215
+ case "gemini": return getEnvVar("GEMINI_MODEL") || "gemini-2.5-flash";
1216
+ case "openrouter": return getEnvVar("OPENROUTER_MODEL") || "anthropic/claude-sonnet-4-20250514";
1217
+ case "minimax": return getEnvVar("MINIMAX_MODEL") || "MiniMax-M2.7";
1218
+ case "agent-sdk": return "claude-sonnet-4-20250514";
1219
+ default: return "noop";
1220
+ }
1221
+ }
1217
1222
  function createProvider(config) {
1218
1223
  return new ResilientProvider(createBaseProvider(config));
1219
1224
  }
@@ -1225,7 +1230,7 @@ function createFallbackProvider(config, fallbackConfig) {
1225
1230
  try {
1226
1231
  const fbConfig = {
1227
1232
  provider: providerType,
1228
- model: config.model,
1233
+ model: defaultModelFor(providerType),
1229
1234
  maxTokens: config.maxTokens
1230
1235
  };
1231
1236
  providers.push(createBaseProvider(fbConfig));
@@ -1253,7 +1258,6 @@ function createBaseProvider(config) {
1253
1258
  default: return new AgentSDKProvider();
1254
1259
  }
1255
1260
  }
1256
-
1257
1261
  //#endregion
1258
1262
  //#region src/state/kv.ts
1259
1263
  var StateKV = class {
@@ -1305,7 +1309,6 @@ var StateKV = class {
1305
1309
  });
1306
1310
  }
1307
1311
  };
1308
-
1309
1312
  //#endregion
1310
1313
  //#region src/state/schema.ts
1311
1314
  const KV = {
@@ -1353,7 +1356,8 @@ const KV = {
1353
1356
  slots: "mem:slots",
1354
1357
  globalSlots: "mem:slots:global",
1355
1358
  state: "mem:state",
1356
- commits: "mem:commits"
1359
+ commits: "mem:commits",
1360
+ recentSearches: "mem:recent-searches"
1357
1361
  };
1358
1362
  const STREAM = {
1359
1363
  name: "mem-live",
@@ -1375,7 +1379,6 @@ function jaccardSimilarity(a, b) {
1375
1379
  for (const word of setA) if (setB.has(word)) intersection++;
1376
1380
  return intersection / (setA.size + setB.size - intersection);
1377
1381
  }
1378
-
1379
1382
  //#endregion
1380
1383
  //#region src/state/vector-index.ts
1381
1384
  function float32ToBase64(arr) {
@@ -1498,7 +1501,6 @@ var VectorIndex = class VectorIndex {
1498
1501
  return idx;
1499
1502
  }
1500
1503
  };
1501
-
1502
1504
  //#endregion
1503
1505
  //#region src/state/memory-utils.ts
1504
1506
  function memoryToObservation(memory) {
@@ -1515,7 +1517,6 @@ function memoryToObservation(memory) {
1515
1517
  importance: memory.strength
1516
1518
  };
1517
1519
  }
1518
-
1519
1520
  //#endregion
1520
1521
  //#region src/functions/graph-retrieval.ts
1521
1522
  function buildGraphContext(path) {
@@ -1760,7 +1761,6 @@ var MinHeap = class {
1760
1761
  }
1761
1762
  }
1762
1763
  };
1763
-
1764
1764
  //#endregion
1765
1765
  //#region src/logger.ts
1766
1766
  function fmt(level, msg, fields) {
@@ -1798,7 +1798,6 @@ function bootLog(msg) {
1798
1798
  }
1799
1799
  if (bootBuffer.length < 500) bootBuffer.push(msg);
1800
1800
  }
1801
-
1802
1801
  //#endregion
1803
1802
  //#region src/functions/query-expansion.ts
1804
1803
  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.
@@ -1955,7 +1954,6 @@ function extractEntitiesFromQuery(query) {
1955
1954
  }
1956
1955
  return [...new Set(entities)];
1957
1956
  }
1958
-
1959
1957
  //#endregion
1960
1958
  //#region src/state/reranker.ts
1961
1959
  let pipeline = null;
@@ -2009,7 +2007,6 @@ async function rerank(query, results, topK = 20) {
2009
2007
  rerankPosition: i + 1
2010
2008
  }));
2011
2009
  }
2012
-
2013
2010
  //#endregion
2014
2011
  //#region src/state/hybrid-search.ts
2015
2012
  const RRF_K = 60;
@@ -2180,7 +2177,6 @@ var HybridSearch = class {
2180
2177
  return enriched;
2181
2178
  }
2182
2179
  };
2183
-
2184
2180
  //#endregion
2185
2181
  //#region src/state/stemmer.ts
2186
2182
  const step2map = {
@@ -2276,7 +2272,6 @@ function stem(word) {
2276
2272
  if (endsDoubleConsonant(w) && w.endsWith("l") && measure(w.slice(0, -1)) > 1) w = w.slice(0, -1);
2277
2273
  return w;
2278
2274
  }
2279
-
2280
2275
  //#endregion
2281
2276
  //#region src/state/synonyms.ts
2282
2277
  const SYNONYM_GROUPS = [
@@ -2438,7 +2433,6 @@ function getSynonyms(stemmedTerm) {
2438
2433
  const syns = synonymMap.get(stemmedTerm);
2439
2434
  return syns ? [...syns] : [];
2440
2435
  }
2441
-
2442
2436
  //#endregion
2443
2437
  //#region src/state/cjk-segmenter.ts
2444
2438
  const cjkRequire = createRequire(import.meta.url);
@@ -2542,7 +2536,6 @@ function segmentCjk(text) {
2542
2536
  }
2543
2537
  return out;
2544
2538
  }
2545
-
2546
2539
  //#endregion
2547
2540
  //#region src/state/search-index.ts
2548
2541
  var SearchIndex = class SearchIndex {
@@ -2742,18 +2735,92 @@ var SearchIndex = class SearchIndex {
2742
2735
  return lo;
2743
2736
  }
2744
2737
  };
2745
-
2738
+ //#endregion
2739
+ //#region src/functions/audit.ts
2740
+ async function recordAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
2741
+ const entry = {
2742
+ id: generateId("aud"),
2743
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2744
+ operation,
2745
+ userId,
2746
+ functionId,
2747
+ targetIds,
2748
+ details,
2749
+ qualityScore
2750
+ };
2751
+ await kv.set(KV.audit, entry.id, entry);
2752
+ return entry;
2753
+ }
2754
+ async function safeAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
2755
+ try {
2756
+ await recordAudit(kv, operation, functionId, targetIds, details, qualityScore, userId);
2757
+ } catch (err) {
2758
+ try {
2759
+ logger.warn("audit write failed", {
2760
+ functionId,
2761
+ operation,
2762
+ targetIds,
2763
+ error: err instanceof Error ? err.message : String(err)
2764
+ });
2765
+ } catch {}
2766
+ }
2767
+ }
2768
+ async function queryAudit(kv, filter) {
2769
+ let entries = [...await kv.list(KV.audit)].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
2770
+ if (filter?.operation) entries = entries.filter((e) => e.operation === filter.operation);
2771
+ if (filter?.dateFrom) {
2772
+ const from = new Date(filter.dateFrom).getTime();
2773
+ if (Number.isNaN(from)) throw new Error(`Invalid dateFrom: ${filter.dateFrom}`);
2774
+ entries = entries.filter((e) => new Date(e.timestamp).getTime() >= from);
2775
+ }
2776
+ if (filter?.dateTo) {
2777
+ const to = new Date(filter.dateTo).getTime();
2778
+ if (Number.isNaN(to)) throw new Error(`Invalid dateTo: ${filter.dateTo}`);
2779
+ entries = entries.filter((e) => new Date(e.timestamp).getTime() <= to);
2780
+ }
2781
+ return entries.slice(0, filter?.limit || 100);
2782
+ }
2746
2783
  //#endregion
2747
2784
  //#region src/state/index-persistence.ts
2748
2785
  const DEBOUNCE_MS = 5e3;
2749
2786
  const FAILURE_LOG_THROTTLE_MS = 6e4;
2787
+ const INDEX_PERSISTENCE_FUNCTION_ID = "mem::index-persistence";
2788
+ const BM25_KEY = "data";
2789
+ const BM25_MANIFEST_KEY = "data:manifest";
2790
+ const BM25_SHARD_SCOPE_PREFIX = `${KV.bm25Index}:bm25:`;
2791
+ const VECTOR_KEY = "vectors";
2792
+ const VECTOR_MANIFEST_KEY = "vectors:manifest";
2793
+ const VECTOR_SHARD_SCOPE_PREFIX = `${KV.bm25Index}:vectors:`;
2794
+ const INDEX_SHARD_KEY = "data";
2795
+ const DEFAULT_INDEX_SHARD_CHARS = 2e6;
2796
+ function shardChars(options) {
2797
+ const configured = options.shardChars;
2798
+ if (typeof configured !== "number" || !Number.isFinite(configured)) return DEFAULT_INDEX_SHARD_CHARS;
2799
+ const wholeChars = Math.floor(configured);
2800
+ return wholeChars >= 1 ? wholeChars : DEFAULT_INDEX_SHARD_CHARS;
2801
+ }
2802
+ function createIndexGeneration() {
2803
+ return generateId("idx");
2804
+ }
2805
+ function statePath(scope, key) {
2806
+ return `${scope}/${key}`;
2807
+ }
2808
+ function errorMessage(err) {
2809
+ return err instanceof Error ? err.message : String(err);
2810
+ }
2811
+ function isValidShardDescriptor(shard) {
2812
+ if (!shard || typeof shard !== "object") return false;
2813
+ const candidate = shard;
2814
+ return typeof candidate.scope === "string" && candidate.scope.length > 0 && typeof candidate.key === "string" && candidate.key.length > 0 && Number.isInteger(candidate.chars) && candidate.chars >= 0;
2815
+ }
2750
2816
  var IndexPersistence = class {
2751
2817
  timer = null;
2752
2818
  lastFailureLogAt = 0;
2753
- constructor(kv, bm25, vector) {
2819
+ constructor(kv, bm25, vector, options = {}) {
2754
2820
  this.kv = kv;
2755
2821
  this.bm25 = bm25;
2756
2822
  this.vector = vector;
2823
+ this.options = options;
2757
2824
  }
2758
2825
  scheduleSave() {
2759
2826
  if (this.timer) clearTimeout(this.timer);
@@ -2767,8 +2834,8 @@ var IndexPersistence = class {
2767
2834
  this.timer = null;
2768
2835
  }
2769
2836
  try {
2770
- await this.kv.set(KV.bm25Index, "data", this.bm25.serialize());
2771
- if (this.vector && this.vector.size > 0) await this.kv.set(KV.bm25Index, "vectors", this.vector.serialize());
2837
+ await this.saveBm25Index(this.bm25.serialize());
2838
+ if (this.vector) await this.saveVectorIndex(this.vector.serialize());
2772
2839
  } catch (err) {
2773
2840
  this.logFailure(err);
2774
2841
  }
@@ -2776,9 +2843,9 @@ var IndexPersistence = class {
2776
2843
  async load() {
2777
2844
  let bm25 = null;
2778
2845
  let vector = null;
2779
- const bm25Data = await this.kv.get(KV.bm25Index, "data").catch(() => null);
2846
+ const bm25Data = await this.loadBm25Data();
2780
2847
  if (bm25Data && typeof bm25Data === "string") bm25 = SearchIndex.deserialize(bm25Data);
2781
- const vecData = await this.kv.get(KV.bm25Index, "vectors").catch(() => null);
2848
+ const vecData = await this.loadVectorData();
2782
2849
  if (vecData && typeof vecData === "string") vector = VectorIndex.deserialize(vecData);
2783
2850
  return {
2784
2851
  bm25,
@@ -2803,8 +2870,190 @@ var IndexPersistence = class {
2803
2870
  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
2804
2871
  });
2805
2872
  }
2873
+ async saveBm25Index(serialized) {
2874
+ await this.saveShardedIndex(serialized, BM25_MANIFEST_KEY, BM25_KEY, BM25_SHARD_SCOPE_PREFIX);
2875
+ }
2876
+ async saveVectorIndex(serialized) {
2877
+ await this.saveShardedIndex(serialized, VECTOR_MANIFEST_KEY, VECTOR_KEY, VECTOR_SHARD_SCOPE_PREFIX);
2878
+ }
2879
+ async saveShardedIndex(serialized, manifestKey, legacyKey, scopePrefix) {
2880
+ const previous = await this.kv.get(KV.bm25Index, manifestKey).catch(() => null);
2881
+ const generation = this.options.createGeneration?.() ?? createIndexGeneration();
2882
+ const chunkChars = shardChars(this.options);
2883
+ const shards = [];
2884
+ const chunks = [];
2885
+ for (let offset = 0; offset < serialized.length; offset += chunkChars) {
2886
+ const shardIndex = shards.length;
2887
+ const scope = `${scopePrefix}${generation}:${String(shardIndex).padStart(5, "0")}`;
2888
+ const chunk = serialized.slice(offset, offset + chunkChars);
2889
+ shards.push({
2890
+ scope,
2891
+ key: INDEX_SHARD_KEY,
2892
+ chars: chunk.length
2893
+ });
2894
+ chunks.push(chunk);
2895
+ }
2896
+ const failedWrite = (await Promise.allSettled(shards.map(async (shard, index) => {
2897
+ const chunk = chunks[index] ?? "";
2898
+ await this.kv.set(shard.scope, shard.key, chunk);
2899
+ await this.auditIndexPersistence("shard_write", [statePath(shard.scope, shard.key)], {
2900
+ scope: shard.scope,
2901
+ key: shard.key,
2902
+ manifestKey,
2903
+ generation,
2904
+ chars: chunk.length
2905
+ });
2906
+ }))).find((result) => result.status === "rejected");
2907
+ if (failedWrite) {
2908
+ await this.deleteShards(shards, "shard_write_rollback");
2909
+ throw failedWrite.reason;
2910
+ }
2911
+ const nextManifest = {
2912
+ v: 1,
2913
+ generation,
2914
+ shards,
2915
+ chars: serialized.length
2916
+ };
2917
+ try {
2918
+ await this.kv.set(KV.bm25Index, manifestKey, nextManifest);
2919
+ await this.auditIndexPersistence("manifest_publish", [statePath(KV.bm25Index, manifestKey)], {
2920
+ manifestKey,
2921
+ generation,
2922
+ chars: serialized.length,
2923
+ shards: shards.length,
2924
+ result: "committed"
2925
+ });
2926
+ } catch (err) {
2927
+ if (await this.isManifestPublished(manifestKey, nextManifest)) await this.auditIndexPersistence("manifest_publish", [statePath(KV.bm25Index, manifestKey)], {
2928
+ manifestKey,
2929
+ generation,
2930
+ chars: serialized.length,
2931
+ shards: shards.length,
2932
+ result: "committed_after_error",
2933
+ error: errorMessage(err)
2934
+ });
2935
+ else await this.deleteShards(shards, "manifest_publish_rollback");
2936
+ throw err;
2937
+ }
2938
+ await this.deleteKey(KV.bm25Index, legacyKey, "legacy_cleanup");
2939
+ if (previous?.v === 1 && Array.isArray(previous.shards)) {
2940
+ const currentShardIds = new Set(shards.map((shard) => `${shard.scope}\0${shard.key}`));
2941
+ for (const shard of previous.shards) {
2942
+ if (currentShardIds.has(`${shard.scope}\0${shard.key}`)) continue;
2943
+ await this.deleteShards([shard], "previous_generation_cleanup");
2944
+ }
2945
+ }
2946
+ }
2947
+ async auditIndexPersistence(action, targetIds, details) {
2948
+ await safeAudit(this.kv, "index_persist", INDEX_PERSISTENCE_FUNCTION_ID, targetIds, {
2949
+ action,
2950
+ ...details
2951
+ });
2952
+ }
2953
+ async deleteKey(scope, key, reason) {
2954
+ let result = "deleted";
2955
+ let error;
2956
+ try {
2957
+ await this.kv.delete(scope, key);
2958
+ } catch (err) {
2959
+ result = "failed";
2960
+ error = errorMessage(err);
2961
+ }
2962
+ await this.auditIndexPersistence("delete", [statePath(scope, key)], {
2963
+ scope,
2964
+ key,
2965
+ reason,
2966
+ result,
2967
+ error
2968
+ });
2969
+ }
2970
+ async deleteShards(shards, reason) {
2971
+ for (const shard of shards) await this.deleteKey(shard.scope, shard.key, reason);
2972
+ }
2973
+ async isManifestPublished(manifestKey, expected) {
2974
+ const published = await this.kv.get(KV.bm25Index, manifestKey).catch(() => null);
2975
+ if (published?.v !== 1 || published.generation !== expected.generation || published.chars !== expected.chars || !Array.isArray(published.shards) || published.shards.length !== expected.shards.length) return false;
2976
+ return published.shards.every((shard, index) => {
2977
+ const expectedShard = expected.shards[index];
2978
+ if (!expectedShard) return false;
2979
+ return shard.scope === expectedShard.scope && shard.key === expectedShard.key && shard.chars === expectedShard.chars;
2980
+ });
2981
+ }
2982
+ async loadBm25Data() {
2983
+ return this.loadShardedData(BM25_KEY, BM25_MANIFEST_KEY, "BM25");
2984
+ }
2985
+ async loadVectorData() {
2986
+ return this.loadShardedData(VECTOR_KEY, VECTOR_MANIFEST_KEY, "vector");
2987
+ }
2988
+ async loadShardedData(legacyKey, manifestKey, label) {
2989
+ const manifest = await this.readIndexValue(KV.bm25Index, manifestKey, label, "manifest");
2990
+ if (!manifest.ok) return null;
2991
+ if (manifest.value !== null) return this.loadManifestData(manifest.value, label);
2992
+ const legacy = await this.readIndexValue(KV.bm25Index, legacyKey, label, "legacy");
2993
+ if (!legacy.ok) return null;
2994
+ if (legacy.value && typeof legacy.value === "string") return legacy.value;
2995
+ return null;
2996
+ }
2997
+ async readIndexValue(scope, key, label, source) {
2998
+ try {
2999
+ return {
3000
+ ok: true,
3001
+ value: await this.kv.get(scope, key)
3002
+ };
3003
+ } catch (err) {
3004
+ logger.warn(`index persistence: ${label} ${source} read failed`, {
3005
+ scope,
3006
+ key,
3007
+ message: errorMessage(err)
3008
+ });
3009
+ return { ok: false };
3010
+ }
3011
+ }
3012
+ async loadManifestData(manifest, label) {
3013
+ if (manifest.v !== 1 || !Array.isArray(manifest.shards) || manifest.shards.length === 0 || !Number.isInteger(manifest.chars) || manifest.chars < 0) {
3014
+ logger.warn(`index persistence: ${label} shard manifest invalid`);
3015
+ return null;
3016
+ }
3017
+ for (const shard of manifest.shards) if (!isValidShardDescriptor(shard)) {
3018
+ logger.warn(`index persistence: ${label} shard manifest invalid`);
3019
+ return null;
3020
+ }
3021
+ const loadedShards = await Promise.all(manifest.shards.map(async (shard) => ({
3022
+ shard,
3023
+ chunk: await this.kv.get(shard.scope, shard.key).catch(() => null)
3024
+ })));
3025
+ const chunks = [];
3026
+ let chars = 0;
3027
+ for (const { shard, chunk } of loadedShards) {
3028
+ if (typeof chunk !== "string") {
3029
+ logger.warn(`index persistence: ${label} shard missing`, {
3030
+ scope: shard.scope,
3031
+ key: shard.key
3032
+ });
3033
+ return null;
3034
+ }
3035
+ if (chunk.length !== shard.chars) {
3036
+ logger.warn(`index persistence: ${label} shard length mismatch`, {
3037
+ scope: shard.scope,
3038
+ key: shard.key,
3039
+ expected: shard.chars,
3040
+ actual: chunk.length
3041
+ });
3042
+ return null;
3043
+ }
3044
+ chunks.push(chunk);
3045
+ chars += chunk.length;
3046
+ }
3047
+ if (chars !== manifest.chars) {
3048
+ logger.warn(`index persistence: ${label} total length mismatch`, {
3049
+ expected: manifest.chars,
3050
+ actual: chars
3051
+ });
3052
+ return null;
3053
+ }
3054
+ return chunks.join("");
3055
+ }
2806
3056
  };
2807
-
2808
3057
  //#endregion
2809
3058
  //#region src/functions/privacy.ts
2810
3059
  const PRIVATE_TAG_RE = /<private>[\s\S]*?<\/private>/gi;
@@ -2841,7 +3090,6 @@ function registerPrivacyFunction(sdk) {
2841
3090
  return { output: stripPrivateData(data.input) };
2842
3091
  });
2843
3092
  }
2844
-
2845
3093
  //#endregion
2846
3094
  //#region src/state/keyed-mutex.ts
2847
3095
  const locks = /* @__PURE__ */ new Map();
@@ -2854,7 +3102,6 @@ function withKeyedLock(key, fn) {
2854
3102
  });
2855
3103
  return next;
2856
3104
  }
2857
-
2858
3105
  //#endregion
2859
3106
  //#region src/functions/compress-synthetic.ts
2860
3107
  function inferType(toolName, hookType) {
@@ -2950,7 +3197,6 @@ function buildSyntheticCompression(raw) {
2950
3197
  if (raw.agentId) result.agentId = raw.agentId;
2951
3198
  return result;
2952
3199
  }
2953
-
2954
3200
  //#endregion
2955
3201
  //#region src/functions/access-tracker.ts
2956
3202
  const RECENT_CAP = 20;
@@ -3021,7 +3267,6 @@ async function deleteAccessLog(kv, memoryId) {
3021
3267
  });
3022
3268
  } catch {}
3023
3269
  }
3024
-
3025
3270
  //#endregion
3026
3271
  //#region src/functions/search.ts
3027
3272
  let index = null;
@@ -3377,7 +3622,6 @@ function registerSearchFunction(sdk, kv) {
3377
3622
  };
3378
3623
  });
3379
3624
  }
3380
-
3381
3625
  //#endregion
3382
3626
  //#region src/functions/observe.ts
3383
3627
  function extractImage(d) {
@@ -3461,11 +3705,19 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
3461
3705
  raw.imageData = filePath;
3462
3706
  const { incrementImageRef } = await Promise.resolve().then(() => image_refs_exports);
3463
3707
  await incrementImageRef(kv, filePath);
3464
- sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: bytesWritten });
3465
- if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] === "true") sdk.triggerVoid("mem::vision-embed", {
3466
- imageRef: filePath,
3467
- sessionId: payload.sessionId,
3468
- observationId: obsId
3708
+ sdk.trigger({
3709
+ function_id: "mem::disk-size-delta",
3710
+ payload: { deltaBytes: bytesWritten },
3711
+ action: TriggerAction.Void()
3712
+ });
3713
+ if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] === "true") sdk.trigger({
3714
+ function_id: "mem::vision-embed",
3715
+ payload: {
3716
+ imageRef: filePath,
3717
+ sessionId: payload.sessionId,
3718
+ observationId: obsId
3719
+ },
3720
+ action: TriggerAction.Void()
3469
3721
  });
3470
3722
  }
3471
3723
  try {
@@ -3474,7 +3726,11 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
3474
3726
  if (raw.imageData) {
3475
3727
  const { deleteImage } = await Promise.resolve().then(() => image_store_exports);
3476
3728
  const { deletedBytes } = await deleteImage(raw.imageData);
3477
- if (deletedBytes > 0) sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
3729
+ if (deletedBytes > 0) sdk.trigger({
3730
+ function_id: "mem::disk-size-delta",
3731
+ payload: { deltaBytes: -deletedBytes },
3732
+ action: TriggerAction.Void()
3733
+ });
3478
3734
  }
3479
3735
  throw error;
3480
3736
  }
@@ -3594,7 +3850,6 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
3594
3850
  });
3595
3851
  });
3596
3852
  }
3597
-
3598
3853
  //#endregion
3599
3854
  //#region src/utils/image-store.ts
3600
3855
  var image_store_exports = /* @__PURE__ */ __exportAll({
@@ -3671,7 +3926,6 @@ async function touchImage(filePath) {
3671
3926
  }
3672
3927
  } catch (err) {}
3673
3928
  }
3674
-
3675
3929
  //#endregion
3676
3930
  //#region src/functions/image-refs.ts
3677
3931
  var image_refs_exports = /* @__PURE__ */ __exportAll({
@@ -3697,11 +3951,14 @@ async function decrementImageRef(kv, sdk, filePath) {
3697
3951
  await kv.delete(KV.imageEmbeddings, filePath);
3698
3952
  await kv.delete(KV.imageRefs, filePath);
3699
3953
  const { deletedBytes } = await deleteImage(filePath);
3700
- if (deletedBytes > 0) sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
3954
+ if (deletedBytes > 0) sdk.trigger({
3955
+ function_id: "mem::disk-size-delta",
3956
+ payload: { deltaBytes: -deletedBytes },
3957
+ action: TriggerAction.Void()
3958
+ });
3701
3959
  } else await kv.set(KV.imageRefs, filePath, current - 1);
3702
3960
  });
3703
3961
  }
3704
-
3705
3962
  //#endregion
3706
3963
  //#region src/functions/image-quota-cleanup.ts
3707
3964
  const GRACE_PERIOD_MS = 3e4;
@@ -3761,7 +4018,11 @@ function registerImageQuotaCleanup(sdk, kv) {
3761
4018
  if (refCount > 0) return;
3762
4019
  const { deletedBytes } = await deleteImage(f.filePath);
3763
4020
  if (deletedBytes > 0) {
3764
- sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
4021
+ sdk.trigger({
4022
+ function_id: "mem::disk-size-delta",
4023
+ payload: { deltaBytes: -deletedBytes },
4024
+ action: TriggerAction.Void()
4025
+ });
3765
4026
  totalToFree -= deletedBytes;
3766
4027
  freedBytes += deletedBytes;
3767
4028
  evicted++;
@@ -3783,53 +4044,6 @@ function registerImageQuotaCleanup(sdk, kv) {
3783
4044
  });
3784
4045
  });
3785
4046
  }
3786
-
3787
- //#endregion
3788
- //#region src/functions/audit.ts
3789
- async function recordAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
3790
- const entry = {
3791
- id: generateId("aud"),
3792
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3793
- operation,
3794
- userId,
3795
- functionId,
3796
- targetIds,
3797
- details,
3798
- qualityScore
3799
- };
3800
- await kv.set(KV.audit, entry.id, entry);
3801
- return entry;
3802
- }
3803
- async function safeAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
3804
- try {
3805
- await recordAudit(kv, operation, functionId, targetIds, details, qualityScore, userId);
3806
- } catch (err) {
3807
- try {
3808
- logger.warn("audit write failed", {
3809
- functionId,
3810
- operation,
3811
- targetIds,
3812
- error: err instanceof Error ? err.message : String(err)
3813
- });
3814
- } catch {}
3815
- }
3816
- }
3817
- async function queryAudit(kv, filter) {
3818
- let entries = [...await kv.list(KV.audit)].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
3819
- if (filter?.operation) entries = entries.filter((e) => e.operation === filter.operation);
3820
- if (filter?.dateFrom) {
3821
- const from = new Date(filter.dateFrom).getTime();
3822
- if (Number.isNaN(from)) throw new Error(`Invalid dateFrom: ${filter.dateFrom}`);
3823
- entries = entries.filter((e) => new Date(e.timestamp).getTime() >= from);
3824
- }
3825
- if (filter?.dateTo) {
3826
- const to = new Date(filter.dateTo).getTime();
3827
- if (Number.isNaN(to)) throw new Error(`Invalid dateTo: ${filter.dateTo}`);
3828
- entries = entries.filter((e) => new Date(e.timestamp).getTime() <= to);
3829
- }
3830
- return entries.slice(0, filter?.limit || 100);
3831
- }
3832
-
3833
4047
  //#endregion
3834
4048
  //#region src/functions/vision-search.ts
3835
4049
  function registerVisionSearchFunctions(sdk, kv, imageProvider) {
@@ -3953,7 +4167,6 @@ function cosine(a, b) {
3953
4167
  const denom = Math.sqrt(normA) * Math.sqrt(normB);
3954
4168
  return denom === 0 ? 0 : dot / denom;
3955
4169
  }
3956
-
3957
4170
  //#endregion
3958
4171
  //#region src/functions/slots.ts
3959
4172
  const DEFAULT_SIZE_LIMIT = 2e3;
@@ -4392,7 +4605,6 @@ function registerSlotsFunctions(sdk, kv) {
4392
4605
  };
4393
4606
  });
4394
4607
  }
4395
-
4396
4608
  //#endregion
4397
4609
  //#region src/functions/disk-size-manager.ts
4398
4610
  const DISK_SIZE_KEY = "system:currentDiskSize";
@@ -4407,7 +4619,11 @@ function registerDiskSizeManager(sdk, kv) {
4407
4619
  if (newTotal < 0) newTotal = 0;
4408
4620
  await kv.set(KV.state, DISK_SIZE_KEY, newTotal);
4409
4621
  if (data.deltaBytes > 0 && newTotal > getMaxBytes()) {
4410
- sdk.triggerVoid("mem::image-quota-cleanup", {});
4622
+ sdk.trigger({
4623
+ function_id: "mem::image-quota-cleanup",
4624
+ payload: {},
4625
+ action: TriggerAction.Void()
4626
+ });
4411
4627
  logger.info("Disk quota exceeded, cleanup triggered", {
4412
4628
  currentBytes: newTotal,
4413
4629
  maxBytes: getMaxBytes()
@@ -4420,7 +4636,6 @@ function registerDiskSizeManager(sdk, kv) {
4420
4636
  });
4421
4637
  });
4422
4638
  }
4423
-
4424
4639
  //#endregion
4425
4640
  //#region src/prompts/compression.ts
4426
4641
  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.
@@ -4468,7 +4683,6 @@ function buildCompressionPrompt(observation) {
4468
4683
  function truncate$1(s, max) {
4469
4684
  return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s;
4470
4685
  }
4471
-
4472
4686
  //#endregion
4473
4687
  //#region src/prompts/vision.ts
4474
4688
  const VISION_DESCRIPTION_PROMPT = `Describe what this image shows in the context of software development. Extract:
@@ -4479,7 +4693,6 @@ const VISION_DESCRIPTION_PROMPT = `Describe what this image shows in the context
4479
4693
  - Text content visible in the image
4480
4694
 
4481
4695
  Be concise but preserve all technically relevant details. Output plain text, no XML.`;
4482
-
4483
4696
  //#endregion
4484
4697
  //#region src/prompts/xml.ts
4485
4698
  const VALID_TAG = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
@@ -4498,7 +4711,6 @@ function getXmlChildren(xml, parentTag, childTag) {
4498
4711
  while ((m = re.exec(parentMatch[1])) !== null) items.push(m[1].trim());
4499
4712
  return items;
4500
4713
  }
4501
-
4502
4714
  //#endregion
4503
4715
  //#region src/eval/schemas.ts
4504
4716
  const HookTypeEnum = z.enum([
@@ -4531,7 +4743,7 @@ const ObservationTypeEnum = z.enum([
4531
4743
  "task",
4532
4744
  "other"
4533
4745
  ]);
4534
- const ObserveInputSchema = z.object({
4746
+ z.object({
4535
4747
  hookType: HookTypeEnum,
4536
4748
  sessionId: z.string().min(1),
4537
4749
  project: z.string().min(1),
@@ -4556,16 +4768,16 @@ const SummaryOutputSchema = z.object({
4556
4768
  filesModified: z.array(z.string()),
4557
4769
  concepts: z.array(z.string())
4558
4770
  });
4559
- const SearchInputSchema = z.object({
4771
+ z.object({
4560
4772
  query: z.string().min(1),
4561
4773
  limit: z.number().int().positive().optional()
4562
4774
  });
4563
- const ContextInputSchema = z.object({
4775
+ z.object({
4564
4776
  sessionId: z.string().min(1),
4565
4777
  project: z.string().min(1),
4566
4778
  budget: z.number().positive().optional()
4567
4779
  });
4568
- const RememberInputSchema = z.object({
4780
+ z.object({
4569
4781
  content: z.string().min(1),
4570
4782
  type: z.enum([
4571
4783
  "pattern",
@@ -4578,22 +4790,22 @@ const RememberInputSchema = z.object({
4578
4790
  concepts: z.array(z.string()).optional(),
4579
4791
  files: z.array(z.string()).optional()
4580
4792
  });
4581
- const SmartSearchInputSchema = z.object({
4793
+ z.object({
4582
4794
  query: z.string().optional(),
4583
4795
  expandIds: z.array(z.string()).optional(),
4584
4796
  limit: z.number().int().positive().optional()
4585
4797
  });
4586
- const TimelineInputSchema = z.object({
4798
+ z.object({
4587
4799
  anchor: z.string().min(1),
4588
4800
  project: z.string().optional(),
4589
4801
  before: z.number().int().nonnegative().optional(),
4590
4802
  after: z.number().int().nonnegative().optional()
4591
4803
  });
4592
- const ProfileInputSchema = z.object({
4804
+ z.object({
4593
4805
  project: z.string().min(1),
4594
4806
  refresh: z.boolean().optional()
4595
4807
  });
4596
- const RelateInputSchema = z.object({
4808
+ z.object({
4597
4809
  sourceId: z.string().min(1),
4598
4810
  targetId: z.string().min(1),
4599
4811
  type: z.enum([
@@ -4604,12 +4816,12 @@ const RelateInputSchema = z.object({
4604
4816
  "related"
4605
4817
  ])
4606
4818
  });
4607
- const EvolveInputSchema = z.object({
4819
+ z.object({
4608
4820
  memoryId: z.string().min(1),
4609
4821
  newContent: z.string().min(1),
4610
4822
  newTitle: z.string().optional()
4611
4823
  });
4612
- const ExportImportInputSchema = z.object({
4824
+ z.object({
4613
4825
  exportData: z.object({
4614
4826
  version: z.union([z.literal("0.3.0"), z.literal("0.4.0")]),
4615
4827
  exportedAt: z.string(),
@@ -4625,7 +4837,6 @@ const ExportImportInputSchema = z.object({
4625
4837
  "skip"
4626
4838
  ]).optional()
4627
4839
  });
4628
-
4629
4840
  //#endregion
4630
4841
  //#region src/eval/validator.ts
4631
4842
  function validateInput(schema, data, functionId) {
@@ -4648,7 +4859,6 @@ function validateInput(schema, data, functionId) {
4648
4859
  function validateOutput(schema, data, functionId) {
4649
4860
  return validateInput(schema, data, functionId);
4650
4861
  }
4651
-
4652
4862
  //#endregion
4653
4863
  //#region src/eval/quality.ts
4654
4864
  function scoreCompression(obs) {
@@ -4672,7 +4882,6 @@ function scoreSummary(summary) {
4672
4882
  if (summary.concepts && summary.concepts.length > 0) score += 15;
4673
4883
  return Math.min(100, score);
4674
4884
  }
4675
-
4676
4885
  //#endregion
4677
4886
  //#region src/eval/self-correct.ts
4678
4887
  const STRICTER_SUFFIX = `
@@ -4696,7 +4905,6 @@ async function compressWithRetry(provider, systemPrompt, userPrompt, validator,
4696
4905
  retried: true
4697
4906
  };
4698
4907
  }
4699
-
4700
4908
  //#endregion
4701
4909
  //#region src/functions/compress.ts
4702
4910
  const VALID_TYPES$1 = new Set([
@@ -4877,7 +5085,6 @@ function registerCompressFunction(sdk, kv, provider, metricsStore) {
4877
5085
  }
4878
5086
  });
4879
5087
  }
4880
-
4881
5088
  //#endregion
4882
5089
  //#region src/functions/context.ts
4883
5090
  function estimateTokens$1(text) {
@@ -5002,7 +5209,6 @@ function registerContextFunction(sdk, kv, tokenBudget) {
5002
5209
  };
5003
5210
  });
5004
5211
  }
5005
-
5006
5212
  //#endregion
5007
5213
  //#region src/prompts/summary.ts
5008
5214
  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.
@@ -5074,7 +5280,6 @@ Concepts: ${concepts}`;
5074
5280
  });
5075
5281
  return `Partial summaries (${partials.length} chunks of one session, chronological):\n\n${sections.join("\n\n---\n\n")}`;
5076
5282
  }
5077
-
5078
5283
  //#endregion
5079
5284
  //#region src/functions/summarize.ts
5080
5285
  const CHUNK_SIZE_DEFAULT = 400;
@@ -5163,18 +5368,29 @@ async function produceSummaryXml(provider, compressed, sessionId, project) {
5163
5368
  skipped
5164
5369
  };
5165
5370
  }
5371
+ function stripXmlWrappers(raw) {
5372
+ if (!raw) return "";
5373
+ let cleaned = raw.trim();
5374
+ cleaned = cleaned.replace(/```\s*xml\s*\n?/gi, "");
5375
+ cleaned = cleaned.replace(/```/g, "");
5376
+ cleaned = cleaned.trim();
5377
+ const rootMatch = cleaned.match(/(<[a-zA-Z_][a-zA-Z0-9_-]*>[\s\S]*<\/[a-zA-Z_][a-zA-Z0-9_-]*>)/);
5378
+ if (rootMatch && rootMatch[1]) return rootMatch[1].trim();
5379
+ return cleaned;
5380
+ }
5166
5381
  function parseSummaryXml(xml, sessionId, project, obsCount) {
5167
- const title = getXmlTag(xml, "title");
5382
+ const cleaned = stripXmlWrappers(xml);
5383
+ const title = getXmlTag(cleaned, "title");
5168
5384
  if (!title) return null;
5169
5385
  return {
5170
5386
  sessionId,
5171
5387
  project,
5172
5388
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5173
5389
  title,
5174
- narrative: getXmlTag(xml, "narrative"),
5175
- keyDecisions: getXmlChildren(xml, "decisions", "decision"),
5176
- filesModified: getXmlChildren(xml, "files", "file"),
5177
- concepts: getXmlChildren(xml, "concepts", "concept"),
5390
+ narrative: getXmlTag(cleaned, "narrative"),
5391
+ keyDecisions: getXmlChildren(cleaned, "decisions", "decision"),
5392
+ filesModified: getXmlChildren(cleaned, "files", "file"),
5393
+ concepts: getXmlChildren(cleaned, "concepts", "concept"),
5178
5394
  observationCount: obsCount
5179
5395
  };
5180
5396
  }
@@ -5211,27 +5427,44 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
5211
5427
  };
5212
5428
  }
5213
5429
  try {
5214
- const { response, mode, chunks } = await produceSummaryXml(provider, compressed, sessionId, session.project);
5430
+ let summary = null;
5431
+ let response = "";
5432
+ let mode = "single";
5433
+ let chunks = 1;
5434
+ for (let attempt = 1; attempt <= 2; attempt++) {
5435
+ const produced = await produceSummaryXml(provider, compressed, sessionId, session.project);
5436
+ response = produced.response;
5437
+ mode = produced.mode;
5438
+ chunks = produced.chunks;
5439
+ if (!response || !response.trim()) {
5440
+ logger.warn("Empty provider response on summarize", {
5441
+ sessionId,
5442
+ provider: provider.name,
5443
+ mode,
5444
+ chunks,
5445
+ observationCount: compressed.length,
5446
+ attempt
5447
+ });
5448
+ continue;
5449
+ }
5450
+ summary = parseSummaryXml(response, sessionId, session.project, compressed.length);
5451
+ if (summary) break;
5452
+ logger.warn("Failed to parse summary XML", {
5453
+ sessionId,
5454
+ attempt
5455
+ });
5456
+ }
5215
5457
  if (!response || !response.trim()) {
5216
5458
  const latencyMs = Date.now() - startMs;
5217
5459
  if (metricsStore) await metricsStore.record("mem::summarize", latencyMs, false);
5218
- logger.warn("Empty provider response on summarize", {
5219
- sessionId,
5220
- provider: provider.name,
5221
- mode,
5222
- chunks,
5223
- observationCount: compressed.length
5224
- });
5225
5460
  return {
5226
5461
  success: false,
5227
5462
  error: "empty_provider_response"
5228
5463
  };
5229
5464
  }
5230
- const summary = parseSummaryXml(response, sessionId, session.project, compressed.length);
5231
5465
  if (!summary) {
5232
5466
  const latencyMs = Date.now() - startMs;
5233
5467
  if (metricsStore) await metricsStore.record("mem::summarize", latencyMs, false);
5234
- logger.warn("Failed to parse summary XML", { sessionId });
5235
5468
  return {
5236
5469
  success: false,
5237
5470
  error: "parse_failed"
@@ -5292,7 +5525,6 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
5292
5525
  }
5293
5526
  });
5294
5527
  }
5295
-
5296
5528
  //#endregion
5297
5529
  //#region src/functions/migrate.ts
5298
5530
  const ALLOWED_DIRS = [resolve(homedir(), ".agentmemory")];
@@ -5493,7 +5725,6 @@ function safeJsonParse(value, fallback) {
5493
5725
  }
5494
5726
  return fallback;
5495
5727
  }
5496
-
5497
5728
  //#endregion
5498
5729
  //#region src/functions/file-index.ts
5499
5730
  function registerFileIndexFunction(sdk, kv) {
@@ -5563,7 +5794,6 @@ function registerFileIndexFunction(sdk, kv) {
5563
5794
  return { context };
5564
5795
  });
5565
5796
  }
5566
-
5567
5797
  //#endregion
5568
5798
  //#region src/functions/consolidate.ts
5569
5799
  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.
@@ -5715,7 +5945,6 @@ function registerConsolidateFunction(sdk, kv, provider) {
5715
5945
  };
5716
5946
  });
5717
5947
  }
5718
-
5719
5948
  //#endregion
5720
5949
  //#region src/functions/patterns.ts
5721
5950
  function registerPatternsFunction(sdk, kv) {
@@ -5799,7 +6028,6 @@ function registerPatternsFunction(sdk, kv) {
5799
6028
  return { rules };
5800
6029
  });
5801
6030
  }
5802
-
5803
6031
  //#endregion
5804
6032
  //#region src/functions/remember.ts
5805
6033
  function registerRememberFunction(sdk, kv) {
@@ -5960,7 +6188,6 @@ function registerRememberFunction(sdk, kv) {
5960
6188
  };
5961
6189
  });
5962
6190
  }
5963
-
5964
6191
  //#endregion
5965
6192
  //#region src/functions/evict.ts
5966
6193
  const MS_PER_DAY$1 = 1440 * 60 * 1e3;
@@ -6191,7 +6418,6 @@ function registerEvictFunction(sdk, kv) {
6191
6418
  return stats;
6192
6419
  });
6193
6420
  }
6194
-
6195
6421
  //#endregion
6196
6422
  //#region src/functions/relations.ts
6197
6423
  function computeConfidence(source, target, relationType) {
@@ -6379,7 +6605,6 @@ function registerRelationsFunction(sdk, kv) {
6379
6605
  return { results: result };
6380
6606
  });
6381
6607
  }
6382
-
6383
6608
  //#endregion
6384
6609
  //#region src/functions/timeline.ts
6385
6610
  function registerTimelineFunction(sdk, kv) {
@@ -6461,9 +6686,78 @@ async function findByKeyword(kv, keyword, project) {
6461
6686
  }
6462
6687
  return matches.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
6463
6688
  }
6464
-
6689
+ //#endregion
6690
+ //#region src/version.ts
6691
+ const VERSION = "0.9.25";
6692
+ //#endregion
6693
+ //#region src/telemetry/setup.ts
6694
+ const OTEL_CONFIG = {
6695
+ serviceName: "agentmemory",
6696
+ serviceVersion: VERSION,
6697
+ metricsExportIntervalMs: 3e4
6698
+ };
6699
+ let counters = null;
6700
+ let histograms = null;
6701
+ const NOOP_COUNTER = { add: () => {} };
6702
+ const NOOP_HISTOGRAM = { record: () => {} };
6703
+ const COUNTER_NAMES = [
6704
+ ["observationsTotal", "observations.total"],
6705
+ ["compressionSuccess", "compression.success"],
6706
+ ["compressionFailure", "compression.failure"],
6707
+ ["searchTotal", "search.total"],
6708
+ ["dedupSkipped", "dedup.skipped"],
6709
+ ["evictionTotal", "eviction.total"],
6710
+ ["circuitBreakerOpen", "circuit_breaker.open"],
6711
+ ["embeddingSuccess", "embedding.success"],
6712
+ ["embeddingFailure", "embedding.failure"],
6713
+ ["vectorSearchTotal", "vector_search.total"],
6714
+ ["autoForgetTotal", "auto_forget.total"],
6715
+ ["profileGenerated", "profile.generated"],
6716
+ ["claudeBridgeSync", "claude_bridge.sync"],
6717
+ ["graphExtraction", "graph.extraction"],
6718
+ ["consolidationRun", "consolidation.run"],
6719
+ ["teamShare", "team.share"],
6720
+ ["auditLog", "audit.log"],
6721
+ ["snapshotCreate", "snapshot.create"],
6722
+ ["governanceDelete", "governance.delete"],
6723
+ ["smartSearchFollowupWithinWindow", "smart_search.followup_within_window_total"],
6724
+ ["readerFailureWithEvidence", "reader_failure_with_evidence_total"]
6725
+ ];
6726
+ const HISTOGRAM_NAMES = [
6727
+ ["compressionLatency", "compression.latency_ms"],
6728
+ ["searchLatency", "search.latency_ms"],
6729
+ ["contextTokens", "context.tokens"],
6730
+ ["qualityScore", "quality.score"],
6731
+ ["embeddingLatency", "embedding.latency_ms"],
6732
+ ["vectorSearchLatency", "vector_search.latency_ms"]
6733
+ ];
6734
+ function getCounters() {
6735
+ if (counters) return counters;
6736
+ return Object.fromEntries(COUNTER_NAMES.map(([key]) => [key, NOOP_COUNTER]));
6737
+ }
6738
+ function initMetrics(getMeter) {
6739
+ const meter = getMeter?.("agentmemory");
6740
+ counters = Object.fromEntries(COUNTER_NAMES.map(([key, name]) => [key, meter ? meter.createCounter(name) : NOOP_COUNTER]));
6741
+ histograms = Object.fromEntries(HISTOGRAM_NAMES.map(([key, name]) => [key, meter ? meter.createHistogram(name) : NOOP_HISTOGRAM]));
6742
+ return {
6743
+ counters,
6744
+ histograms
6745
+ };
6746
+ }
6465
6747
  //#endregion
6466
6748
  //#region src/functions/smart-search.ts
6749
+ const followupStats = {
6750
+ followupWithinWindow: 0,
6751
+ agentInitiatedSearches: 0
6752
+ };
6753
+ const pendingFollowups = /* @__PURE__ */ new Set();
6754
+ function getFollowupStats() {
6755
+ const total = followupStats.agentInitiatedSearches;
6756
+ return {
6757
+ ...followupStats,
6758
+ rate: total > 0 ? followupStats.followupWithinWindow / total : 0
6759
+ };
6760
+ }
6467
6761
  const LESSON_CONTENT_PREVIEW_CHARS = 240;
6468
6762
  function registerSmartSearchFunction(sdk, kv, searchFn) {
6469
6763
  sdk.registerFunction("mem::smart-search", async (data) => {
@@ -6525,6 +6819,21 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
6525
6819
  timestamp: r.observation.timestamp
6526
6820
  }));
6527
6821
  recordAccessBatch(kv, compact.map((r) => r.obsId));
6822
+ if (data.sessionId && typeof data.sessionId === "string" && data.source !== "viewer" && compact.length > 0) {
6823
+ followupStats.agentInitiatedSearches++;
6824
+ const sessionIdForFollowup = data.sessionId;
6825
+ const queryForFollowup = data.query;
6826
+ const compactForFollowup = compact;
6827
+ const detection = withKeyedLock(`recent-searches:${sessionIdForFollowup}`, () => detectFollowup(kv, sessionIdForFollowup, queryForFollowup, compactForFollowup)).catch((err) => {
6828
+ logger.warn("Smart search followup detection failed", {
6829
+ sessionId: sessionIdForFollowup,
6830
+ error: err instanceof Error ? err.message : String(err)
6831
+ });
6832
+ }).finally(() => {
6833
+ pendingFollowups.delete(detection);
6834
+ });
6835
+ pendingFollowups.add(detection);
6836
+ }
6528
6837
  logger.info("Smart search compact", {
6529
6838
  query: data.query,
6530
6839
  results: compact.length,
@@ -6563,6 +6872,35 @@ async function recallLessons(sdk, query, limit, project) {
6563
6872
  return [];
6564
6873
  }
6565
6874
  }
6875
+ async function detectFollowup(kv, sessionId, query, compact) {
6876
+ const now = Date.now();
6877
+ const windowMs = Math.max(1, getFollowupWindowSeconds()) * 1e3;
6878
+ const currentIds = compact.map((r) => r.obsId);
6879
+ const current = {
6880
+ sessionId,
6881
+ query,
6882
+ resultIds: currentIds,
6883
+ at: now
6884
+ };
6885
+ const prior = await kv.get(KV.recentSearches, sessionId).catch(() => null);
6886
+ await kv.set(KV.recentSearches, sessionId, current);
6887
+ if (!prior || typeof prior.at !== "number") return;
6888
+ if (now - prior.at > windowMs) return;
6889
+ if (typeof prior.query === "string" && prior.query === query) return;
6890
+ const priorIds = Array.isArray(prior.resultIds) ? prior.resultIds : [];
6891
+ const priorSet = new Set(priorIds);
6892
+ if (currentIds.some((id) => priorSet.has(id))) return;
6893
+ getCounters().smartSearchFollowupWithinWindow.add(1);
6894
+ followupStats.followupWithinWindow++;
6895
+ logger.info("Smart search followup detected", {
6896
+ sessionId,
6897
+ windowSeconds: Math.round(windowMs / 1e3),
6898
+ priorQuery: prior.query,
6899
+ nextQuery: query,
6900
+ priorResultCount: priorIds.length,
6901
+ nextResultCount: currentIds.length
6902
+ });
6903
+ }
6566
6904
  async function findObservation$1(kv, obsId, sessionIdHint) {
6567
6905
  if (sessionIdHint) {
6568
6906
  const obs = await kv.get(KV.observations(sessionIdHint), obsId).catch(() => null);
@@ -6576,7 +6914,52 @@ async function findObservation$1(kv, obsId, sessionIdHint) {
6576
6914
  }
6577
6915
  return null;
6578
6916
  }
6579
-
6917
+ //#endregion
6918
+ //#region src/functions/recent-searches-sweep.ts
6919
+ const RETENTION_MS = 1440 * 60 * 1e3;
6920
+ function registerRecentSearchesSweepFunction(sdk, kv) {
6921
+ sdk.registerFunction("mem::diagnostic::recent-searches-sweep", async () => {
6922
+ const cutoff = Date.now() - RETENTION_MS;
6923
+ const rows = await kv.list(KV.recentSearches).catch(() => []);
6924
+ let swept = 0;
6925
+ let skipped = 0;
6926
+ for (const row of rows) {
6927
+ if (!row || typeof row.sessionId !== "string" || !row.sessionId) {
6928
+ skipped++;
6929
+ continue;
6930
+ }
6931
+ if ((typeof row.at === "number" ? row.at : 0) >= cutoff) continue;
6932
+ try {
6933
+ await kv.delete(KV.recentSearches, row.sessionId);
6934
+ swept++;
6935
+ } catch (err) {
6936
+ logger.warn("recent-searches sweep delete failed", {
6937
+ sessionId: row.sessionId,
6938
+ error: err instanceof Error ? err.message : String(err)
6939
+ });
6940
+ }
6941
+ }
6942
+ if (swept > 0 || skipped > 0) logger.info("Recent-searches sweep complete", {
6943
+ swept,
6944
+ skipped
6945
+ });
6946
+ return {
6947
+ success: true,
6948
+ swept,
6949
+ skipped
6950
+ };
6951
+ });
6952
+ sdk.registerFunction("mem::diagnostic::followup-stats", async () => {
6953
+ const stats = getFollowupStats();
6954
+ return {
6955
+ success: true,
6956
+ windowSeconds: getFollowupWindowSeconds(),
6957
+ agentInitiatedSearches: stats.agentInitiatedSearches,
6958
+ followupWithinWindow: stats.followupWithinWindow,
6959
+ rate: stats.rate
6960
+ };
6961
+ });
6962
+ }
6580
6963
  //#endregion
6581
6964
  //#region src/functions/profile.ts
6582
6965
  function registerProfileFunction(sdk, kv) {
@@ -6664,7 +7047,6 @@ function extractConventions(concepts, files) {
6664
7047
  for (const { concept, frequency } of concepts.slice(0, 5)) if (frequency >= 3) conventions.push(`Frequently uses: ${concept}`);
6665
7048
  return conventions;
6666
7049
  }
6667
-
6668
7050
  //#endregion
6669
7051
  //#region src/functions/auto-forget.ts
6670
7052
  const MS_PER_DAY = 1440 * 60 * 1e3;
@@ -6790,11 +7172,6 @@ function registerAutoForgetFunction(sdk, kv) {
6790
7172
  return result;
6791
7173
  });
6792
7174
  }
6793
-
6794
- //#endregion
6795
- //#region src/version.ts
6796
- const VERSION = "0.9.23";
6797
-
6798
7175
  //#endregion
6799
7176
  //#region src/functions/export-import.ts
6800
7177
  function registerExportImportFunction(sdk, kv) {
@@ -6934,7 +7311,9 @@ function registerExportImportFunction(sdk, kv) {
6934
7311
  "0.9.20",
6935
7312
  "0.9.21",
6936
7313
  "0.9.22",
6937
- "0.9.23"
7314
+ "0.9.23",
7315
+ "0.9.24",
7316
+ "0.9.25"
6938
7317
  ]).has(importData.version)) return {
6939
7318
  success: false,
6940
7319
  error: `Unsupported export version: ${importData.version}`
@@ -7252,7 +7631,6 @@ function registerExportImportFunction(sdk, kv) {
7252
7631
  };
7253
7632
  });
7254
7633
  }
7255
-
7256
7634
  //#endregion
7257
7635
  //#region src/functions/enrich.ts
7258
7636
  const MAX_CONTEXT_LENGTH = 4e3;
@@ -7313,7 +7691,6 @@ function registerEnrichFunction(sdk, kv) {
7313
7691
  };
7314
7692
  });
7315
7693
  }
7316
-
7317
7694
  //#endregion
7318
7695
  //#region src/functions/claude-bridge.ts
7319
7696
  function parseMemoryMd(content) {
@@ -7434,7 +7811,6 @@ function registerClaudeBridgeFunction(sdk, kv, config) {
7434
7811
  }
7435
7812
  });
7436
7813
  }
7437
-
7438
7814
  //#endregion
7439
7815
  //#region src/prompts/graph-extraction.ts
7440
7816
  const GRAPH_EXTRACTION_SYSTEM = `You are a knowledge graph extraction engine. Given a compressed observation from a coding session, extract entities and relationships.
@@ -7457,9 +7833,41 @@ Rules:
7457
7833
  function buildGraphExtractionPrompt(observations) {
7458
7834
  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")}`;
7459
7835
  }
7460
-
7461
7836
  //#endregion
7462
7837
  //#region src/functions/graph.ts
7838
+ const DEFAULT_GRAPH_QUERY_LIMIT = 500;
7839
+ const MAX_GRAPH_QUERY_LIMIT = 5e3;
7840
+ function resolvePagination(rawLimit, rawOffset) {
7841
+ return {
7842
+ limit: Math.max(1, Math.min(typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.floor(rawLimit) : DEFAULT_GRAPH_QUERY_LIMIT, MAX_GRAPH_QUERY_LIMIT)),
7843
+ offset: Math.max(0, typeof rawOffset === "number" && Number.isFinite(rawOffset) ? Math.floor(rawOffset) : 0)
7844
+ };
7845
+ }
7846
+ function rankByDegree(nodes, edges) {
7847
+ const degree = /* @__PURE__ */ new Map();
7848
+ for (const edge of edges) {
7849
+ degree.set(edge.sourceNodeId, (degree.get(edge.sourceNodeId) ?? 0) + 1);
7850
+ degree.set(edge.targetNodeId, (degree.get(edge.targetNodeId) ?? 0) + 1);
7851
+ }
7852
+ return [...nodes].sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0));
7853
+ }
7854
+ function paginate(nodes, allEdges, depth, limit, offset) {
7855
+ const totalNodes = nodes.length;
7856
+ const pageNodes = nodes.slice(offset, offset + limit);
7857
+ const pageNodeIds = new Set(pageNodes.map((n) => n.id));
7858
+ const pageEdges = allEdges.filter((e) => pageNodeIds.has(e.sourceNodeId) && pageNodeIds.has(e.targetNodeId));
7859
+ const universeIds = new Set(nodes.map((n) => n.id));
7860
+ return {
7861
+ nodes: pageNodes,
7862
+ edges: pageEdges,
7863
+ depth,
7864
+ totalNodes,
7865
+ totalEdges: allEdges.reduce((count, e) => universeIds.has(e.sourceNodeId) && universeIds.has(e.targetNodeId) ? count + 1 : count, 0),
7866
+ truncated: totalNodes > pageNodes.length,
7867
+ limit,
7868
+ offset
7869
+ };
7870
+ }
7463
7871
  function parseAttrs(raw) {
7464
7872
  const attrs = {};
7465
7873
  const attrRegex = /([A-Za-z_][\w:-]*)="([^"]*)"/g;
@@ -7596,15 +8004,10 @@ function registerGraphFunction(sdk, kv, provider) {
7596
8004
  const allNodes = (await kv.list(KV.graphNodes)).filter((n) => !n.stale);
7597
8005
  const allEdges = (await kv.list(KV.graphEdges)).filter((e) => !e.stale);
7598
8006
  const maxDepth = Math.min(data.maxDepth || 3, 5);
8007
+ const { limit, offset } = resolvePagination(data.limit, data.offset);
7599
8008
  if (data.query) {
7600
8009
  const lower = data.query.toLowerCase();
7601
- const matchingNodes = allNodes.filter((n) => n.name.toLowerCase().includes(lower) || Object.values(n.properties).some((v) => typeof v === "string" && v.toLowerCase().includes(lower)));
7602
- const nodeIds = new Set(matchingNodes.map((n) => n.id));
7603
- return {
7604
- nodes: matchingNodes,
7605
- edges: allEdges.filter((e) => nodeIds.has(e.sourceNodeId) || nodeIds.has(e.targetNodeId)),
7606
- depth: 0
7607
- };
8010
+ 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);
7608
8011
  }
7609
8012
  if (data.startNodeId) {
7610
8013
  const visited = /* @__PURE__ */ new Set();
@@ -7636,19 +8039,11 @@ function registerGraphFunction(sdk, kv, provider) {
7636
8039
  });
7637
8040
  }
7638
8041
  }
7639
- return {
7640
- nodes: resultNodes,
7641
- edges: resultEdges,
7642
- depth: maxDepth
7643
- };
8042
+ return paginate(resultNodes, resultEdges, maxDepth, limit, offset);
7644
8043
  }
7645
8044
  let filtered = allNodes;
7646
8045
  if (data.nodeType) filtered = allNodes.filter((n) => n.type === data.nodeType);
7647
- return {
7648
- nodes: filtered,
7649
- edges: allEdges,
7650
- depth: 0
7651
- };
8046
+ return paginate(rankByDegree(filtered, allEdges), allEdges, 0, limit, offset);
7652
8047
  });
7653
8048
  sdk.registerFunction("mem::graph-stats", async () => {
7654
8049
  const nodes = await kv.list(KV.graphNodes);
@@ -7665,7 +8060,6 @@ function registerGraphFunction(sdk, kv, provider) {
7665
8060
  };
7666
8061
  });
7667
8062
  }
7668
-
7669
8063
  //#endregion
7670
8064
  //#region src/prompts/consolidation.ts
7671
8065
  const SEMANTIC_MERGE_SYSTEM = `You are a memory consolidation engine. Given overlapping episodic memories (session summaries), extract stable factual knowledge.
@@ -7700,7 +8094,6 @@ Rules:
7700
8094
  function buildProceduralExtractionPrompt(patterns) {
7701
8095
  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")}`;
7702
8096
  }
7703
-
7704
8097
  //#endregion
7705
8098
  //#region src/functions/consolidation-pipeline.ts
7706
8099
  function applyDecay(items, decayDays) {
@@ -7894,7 +8287,6 @@ function registerConsolidationPipelineFunction(sdk, kv, provider) {
7894
8287
  };
7895
8288
  });
7896
8289
  }
7897
-
7898
8290
  //#endregion
7899
8291
  //#region src/functions/team.ts
7900
8292
  const VALID_ITEM_TYPES = new Set([
@@ -7998,7 +8390,6 @@ function registerTeamFunction(sdk, kv, config) {
7998
8390
  return profile;
7999
8391
  });
8000
8392
  }
8001
-
8002
8393
  //#endregion
8003
8394
  //#region src/functions/governance.ts
8004
8395
  function registerGovernanceFunction(sdk, kv) {
@@ -8107,7 +8498,6 @@ function registerGovernanceFunction(sdk, kv) {
8107
8498
  return queryAudit(kv, data);
8108
8499
  });
8109
8500
  }
8110
-
8111
8501
  //#endregion
8112
8502
  //#region src/functions/snapshot.ts
8113
8503
  const COMMIT_HASH_RE = /^[0-9a-f]{7,40}$/i;
@@ -8272,7 +8662,6 @@ function registerSnapshotFunction(sdk, kv, snapshotDir) {
8272
8662
  }
8273
8663
  });
8274
8664
  }
8275
-
8276
8665
  //#endregion
8277
8666
  //#region src/functions/actions.ts
8278
8667
  function registerActionsFunction(sdk, kv) {
@@ -8474,7 +8863,6 @@ async function propagateCompletion(kv, completedActionId) {
8474
8863
  });
8475
8864
  }
8476
8865
  }
8477
-
8478
8866
  //#endregion
8479
8867
  //#region src/functions/frontier.ts
8480
8868
  function registerFrontierFunction(sdk, kv) {
@@ -8579,7 +8967,6 @@ function computeScore(action, edges, now) {
8579
8967
  if (action.status === "active") score += 15;
8580
8968
  return Math.round(score * 100) / 100;
8581
8969
  }
8582
-
8583
8970
  //#endregion
8584
8971
  //#region src/functions/leases.ts
8585
8972
  const DEFAULT_LEASE_TTL_MS = 600 * 1e3;
@@ -8760,7 +9147,6 @@ function registerLeasesFunction(sdk, kv) {
8760
9147
  };
8761
9148
  });
8762
9149
  }
8763
-
8764
9150
  //#endregion
8765
9151
  //#region src/functions/routines.ts
8766
9152
  function registerRoutinesFunction(sdk, kv) {
@@ -9007,7 +9393,6 @@ function registerRoutinesFunction(sdk, kv) {
9007
9393
  });
9008
9394
  });
9009
9395
  }
9010
-
9011
9396
  //#endregion
9012
9397
  //#region src/functions/signals.ts
9013
9398
  function registerSignalsFunction(sdk, kv) {
@@ -9139,7 +9524,6 @@ function registerSignalsFunction(sdk, kv) {
9139
9524
  };
9140
9525
  });
9141
9526
  }
9142
-
9143
9527
  //#endregion
9144
9528
  //#region src/functions/checkpoints.ts
9145
9529
  function registerCheckpointsFunction(sdk, kv) {
@@ -9312,7 +9696,6 @@ function registerCheckpointsFunction(sdk, kv) {
9312
9696
  };
9313
9697
  });
9314
9698
  }
9315
-
9316
9699
  //#endregion
9317
9700
  //#region src/functions/flow-compress.ts
9318
9701
  const FLOW_COMPRESS_SYSTEM = `You are a workflow summarizer. Given a completed action chain, produce a concise summary capturing:
@@ -9457,7 +9840,6 @@ function extractFiles(actions) {
9457
9840
  }
9458
9841
  return Array.from(files);
9459
9842
  }
9460
-
9461
9843
  //#endregion
9462
9844
  //#region src/functions/mesh.ts
9463
9845
  function isPrivateIP(ip) {
@@ -9774,7 +10156,6 @@ async function applySyncData(kv, data, scopes) {
9774
10156
  if (scopes.includes("graph:edges")) applied += await lwwMergeList(kv, KV.graphEdges, data.graphEdges, "mem:gedge", "createdAt");
9775
10157
  return applied;
9776
10158
  }
9777
-
9778
10159
  //#endregion
9779
10160
  //#region src/functions/branch-aware.ts
9780
10161
  function execAsync(cmd, args, cwd) {
@@ -9889,7 +10270,6 @@ function registerBranchAwareFunction(sdk, kv) {
9889
10270
  };
9890
10271
  });
9891
10272
  }
9892
-
9893
10273
  //#endregion
9894
10274
  //#region src/functions/sentinels.ts
9895
10275
  const VALID_TYPES = [
@@ -10210,7 +10590,6 @@ async function unblockLinkedActions(kv, sentinel) {
10210
10590
  });
10211
10591
  return unblockedCount;
10212
10592
  }
10213
-
10214
10593
  //#endregion
10215
10594
  //#region src/functions/sketches.ts
10216
10595
  function registerSketchesFunction(sdk, kv) {
@@ -10452,7 +10831,6 @@ function registerSketchesFunction(sdk, kv) {
10452
10831
  };
10453
10832
  });
10454
10833
  }
10455
-
10456
10834
  //#endregion
10457
10835
  //#region src/functions/crystallize.ts
10458
10836
  const CRYSTALLIZE_SYSTEM = `You are summarizing a completed chain of agent actions into a compact digest.
@@ -10649,7 +11027,6 @@ function parseDigest(response) {
10649
11027
  };
10650
11028
  }
10651
11029
  }
10652
-
10653
11030
  //#endregion
10654
11031
  //#region src/functions/diagnostics.ts
10655
11032
  const ALL_CATEGORIES = [
@@ -11363,7 +11740,6 @@ function registerDiagnosticsFunction(sdk, kv) {
11363
11740
  };
11364
11741
  });
11365
11742
  }
11366
-
11367
11743
  //#endregion
11368
11744
  //#region src/functions/facets.ts
11369
11745
  function registerFacetsFunction(sdk, kv) {
@@ -11535,7 +11911,6 @@ function registerFacetsFunction(sdk, kv) {
11535
11911
  };
11536
11912
  });
11537
11913
  }
11538
-
11539
11914
  //#endregion
11540
11915
  //#region src/functions/verify.ts
11541
11916
  function registerVerifyFunction(sdk, kv) {
@@ -11630,7 +12005,6 @@ async function findObservation(kv, obsId, hintSessionIds) {
11630
12005
  }
11631
12006
  return null;
11632
12007
  }
11633
-
11634
12008
  //#endregion
11635
12009
  //#region src/functions/cascade.ts
11636
12010
  function registerCascadeFunction(sdk, kv) {
@@ -11700,7 +12074,6 @@ function registerCascadeFunction(sdk, kv) {
11700
12074
  };
11701
12075
  });
11702
12076
  }
11703
-
11704
12077
  //#endregion
11705
12078
  //#region src/functions/lessons.ts
11706
12079
  function reinforceLesson(lesson) {
@@ -11886,7 +12259,6 @@ function registerLessonsFunctions(sdk, kv) {
11886
12259
  };
11887
12260
  });
11888
12261
  }
11889
-
11890
12262
  //#endregion
11891
12263
  //#region src/functions/obsidian-export.ts
11892
12264
  const DEFAULT_EXPORT_ROOT = join(homedir(), ".agentmemory");
@@ -11902,6 +12274,20 @@ function resolveVaultDir(vaultDir) {
11902
12274
  function sanitize(name) {
11903
12275
  return name.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_").slice(0, 100);
11904
12276
  }
12277
+ function hasExportId(item) {
12278
+ return !!item && typeof item.id === "string" && item.id.length > 0;
12279
+ }
12280
+ function safeArray(value) {
12281
+ return Array.isArray(value) ? value : [];
12282
+ }
12283
+ function safeString(value, fallback = "") {
12284
+ return typeof value === "string" ? value : fallback;
12285
+ }
12286
+ function safeTimestamp(value) {
12287
+ if (typeof value !== "string") return 0;
12288
+ const time = new Date(value).getTime();
12289
+ return Number.isFinite(time) ? time : 0;
12290
+ }
11905
12291
  function toFrontmatter(obj) {
11906
12292
  const lines = ["---"];
11907
12293
  for (const [key, value] of Object.entries(obj)) {
@@ -11913,6 +12299,11 @@ function toFrontmatter(obj) {
11913
12299
  return lines.join("\n");
11914
12300
  }
11915
12301
  function memoryToMd(m) {
12302
+ const concepts = safeArray(m.concepts);
12303
+ const files = safeArray(m.files);
12304
+ const relatedIds = safeArray(m.relatedIds);
12305
+ const supersedes = safeArray(m.supersedes);
12306
+ const title = safeString(m.title, m.id);
11916
12307
  const fm = toFrontmatter({
11917
12308
  id: m.id,
11918
12309
  type: m.type,
@@ -11920,24 +12311,28 @@ function memoryToMd(m) {
11920
12311
  updated: m.updatedAt,
11921
12312
  strength: m.strength,
11922
12313
  version: m.version,
11923
- concepts: m.concepts,
11924
- files: m.files
12314
+ concepts,
12315
+ files
11925
12316
  });
11926
- const related = (m.relatedIds || []).map((id) => `- [[${id}]]`).join("\n");
11927
- const supersedes = (m.supersedes || []).map((id) => `- [[${id}]] (superseded)`).join("\n");
12317
+ const relatedLines = relatedIds.map((id) => `- [[${id}]]`).join("\n");
12318
+ const supersedesLines = supersedes.map((id) => `- [[${id}]] (superseded)`).join("\n");
11928
12319
  const sections = [
11929
12320
  fm,
11930
12321
  "",
11931
- `# ${m.title}`,
12322
+ `# ${title}`,
11932
12323
  "",
11933
- m.content
12324
+ safeString(m.content)
11934
12325
  ];
11935
- if (m.concepts.length > 0) sections.push("", "## Concepts", m.concepts.map((c) => `#${c.replace(/\s+/g, "-")}`).join(" "));
11936
- if (related) sections.push("", "## Related", related);
11937
- if (supersedes) sections.push("", "## Supersedes", supersedes);
12326
+ if (concepts.length > 0) sections.push("", "## Concepts", concepts.map((c) => `#${c.replace(/\s+/g, "-")}`).join(" "));
12327
+ if (relatedLines) sections.push("", "## Related", relatedLines);
12328
+ if (supersedesLines) sections.push("", "## Supersedes", supersedesLines);
11938
12329
  return sections.join("\n");
11939
12330
  }
11940
12331
  function lessonToMd(l) {
12332
+ const tags = safeArray(l.tags);
12333
+ const sourceIds = safeArray(l.sourceIds);
12334
+ const content = safeString(l.content);
12335
+ const headline = content ? content.slice(0, 80) : l.id;
11941
12336
  const fm = toFrontmatter({
11942
12337
  id: l.id,
11943
12338
  type: "lesson",
@@ -11947,66 +12342,76 @@ function lessonToMd(l) {
11947
12342
  created: l.createdAt,
11948
12343
  updated: l.updatedAt,
11949
12344
  project: l.project,
11950
- tags: l.tags,
12345
+ tags,
11951
12346
  decayRate: l.decayRate
11952
12347
  });
11953
- const sourceLinks = l.sourceIds.map((id) => `- [[${id}]]`).join("\n");
12348
+ const sourceLinks = sourceIds.map((id) => `- [[${id}]]`).join("\n");
11954
12349
  const sections = [
11955
12350
  fm,
11956
12351
  "",
11957
- `# Lesson: ${l.content.slice(0, 80)}`,
12352
+ `# Lesson: ${headline}`,
11958
12353
  "",
11959
- l.content
12354
+ content
11960
12355
  ];
11961
12356
  if (l.context) sections.push("", "## Context", l.context);
11962
- if (l.tags.length > 0) sections.push("", "## Tags", l.tags.map((t) => `#${t.replace(/\s+/g, "-")}`).join(" "));
12357
+ if (tags.length > 0) sections.push("", "## Tags", tags.map((t) => `#${t.replace(/\s+/g, "-")}`).join(" "));
11963
12358
  if (sourceLinks) sections.push("", "## Sources", sourceLinks);
11964
12359
  return sections.join("\n");
11965
12360
  }
11966
12361
  function crystalToMd(c) {
12362
+ const keyOutcomes = safeArray(c.keyOutcomes);
12363
+ const lessons = safeArray(c.lessons);
12364
+ const filesAffected = safeArray(c.filesAffected);
12365
+ const sourceActionIds = safeArray(c.sourceActionIds);
12366
+ const narrative = safeString(c.narrative);
12367
+ const headline = narrative ? narrative.slice(0, 80) : c.id;
11967
12368
  const fm = toFrontmatter({
11968
12369
  id: c.id,
11969
12370
  type: "crystal",
11970
12371
  created: c.createdAt,
11971
12372
  project: c.project,
11972
12373
  sessionId: c.sessionId,
11973
- filesAffected: c.filesAffected
12374
+ filesAffected
11974
12375
  });
11975
- const actionLinks = c.sourceActionIds.map((id) => `- [[${id}]]`).join("\n");
12376
+ const actionLinks = sourceActionIds.map((id) => `- [[${id}]]`).join("\n");
11976
12377
  const sections = [
11977
12378
  fm,
11978
12379
  "",
11979
- `# Crystal: ${c.narrative.slice(0, 80)}`,
12380
+ `# Crystal: ${headline}`,
11980
12381
  "",
11981
- c.narrative,
12382
+ narrative,
11982
12383
  "",
11983
12384
  "## Key Outcomes",
11984
- ...c.keyOutcomes.map((o) => `- ${o}`)
12385
+ ...keyOutcomes.map((o) => `- ${o}`)
11985
12386
  ];
11986
- if (c.lessons.length > 0) sections.push("", "## Lessons", ...c.lessons.map((l) => `- ${l}`));
11987
- if (c.filesAffected.length > 0) sections.push("", "## Files", ...c.filesAffected.map((f) => `- \`${f}\``));
12387
+ if (lessons.length > 0) sections.push("", "## Lessons", ...lessons.map((l) => `- ${l}`));
12388
+ if (filesAffected.length > 0) sections.push("", "## Files", ...filesAffected.map((f) => `- \`${f}\``));
11988
12389
  if (actionLinks) sections.push("", "## Source Actions", actionLinks);
11989
12390
  return sections.join("\n");
11990
12391
  }
11991
12392
  function sessionToMd(s) {
12393
+ const project = safeString(s.project, "unknown");
12394
+ const status = safeString(s.status, "unknown");
12395
+ const startedAt = safeString(s.startedAt, "");
12396
+ const cwd = safeString(s.cwd, "");
11992
12397
  return [
11993
12398
  toFrontmatter({
11994
12399
  id: s.id,
11995
12400
  type: "session",
11996
- project: s.project,
11997
- status: s.status,
11998
- started: s.startedAt,
12401
+ project,
12402
+ status,
12403
+ started: startedAt || void 0,
11999
12404
  ended: s.endedAt,
12000
12405
  observations: s.observationCount
12001
12406
  }),
12002
12407
  "",
12003
- `# Session: ${s.project}`,
12408
+ `# Session: ${project}`,
12004
12409
  "",
12005
- `**Status:** ${s.status}`,
12006
- `**Started:** ${s.startedAt}`,
12410
+ `**Status:** ${status}`,
12411
+ startedAt ? `**Started:** ${startedAt}` : "",
12007
12412
  s.endedAt ? `**Ended:** ${s.endedAt}` : "",
12008
- `**Observations:** ${s.observationCount}`,
12009
- `**CWD:** \`${s.cwd}\``
12413
+ `**Observations:** ${s.observationCount ?? 0}`,
12414
+ cwd ? `**CWD:** \`${cwd}\`` : ""
12010
12415
  ].filter(Boolean).join("\n");
12011
12416
  }
12012
12417
  function registerObsidianExportFunction(sdk, kv) {
@@ -12042,122 +12447,131 @@ function registerObsidianExportFunction(sdk, kv) {
12042
12447
  crystals: join(vaultDir, "crystals"),
12043
12448
  sessions: join(vaultDir, "sessions")
12044
12449
  };
12045
- await Promise.all(Object.values(dirs).map((dir) => mkdir(dir, { recursive: true })));
12046
- const stats = {
12047
- memories: 0,
12048
- lessons: 0,
12049
- crystals: 0,
12050
- sessions: 0
12051
- };
12052
- const errors = [];
12053
- const memoryMoc = [];
12054
- const lessonMoc = [];
12055
- const crystalMoc = [];
12056
- const sessionMoc = [];
12057
- const [memories, lessons, crystals, sessions] = await Promise.all([
12058
- exportTypes.has("memories") ? kv.list(KV.memories) : Promise.resolve([]),
12059
- exportTypes.has("lessons") ? kv.list(KV.lessons) : Promise.resolve([]),
12060
- exportTypes.has("crystals") ? kv.list(KV.crystals) : Promise.resolve([]),
12061
- exportTypes.has("sessions") ? kv.list(KV.sessions) : Promise.resolve([])
12062
- ]);
12063
- for (const m of memories.filter((m) => m.isLatest)) {
12064
- const filename = `${sanitize(m.id)}.md`;
12065
- const filepath = join(dirs.memories, filename);
12066
- try {
12067
- await writeFile(filepath, memoryToMd(m));
12068
- stats.memories++;
12069
- memoryMoc.push(`- [[memories/${sanitize(m.id)}|${m.title}]] (${m.type}, strength: ${m.strength})`);
12070
- } catch (err) {
12071
- errors.push({
12072
- id: m.id,
12073
- path: filepath,
12074
- error: err instanceof Error ? err.message : String(err)
12075
- });
12450
+ try {
12451
+ await Promise.all(Object.values(dirs).map((dir) => mkdir(dir, { recursive: true })));
12452
+ const stats = {
12453
+ memories: 0,
12454
+ lessons: 0,
12455
+ crystals: 0,
12456
+ sessions: 0
12457
+ };
12458
+ const errors = [];
12459
+ const memoryMoc = [];
12460
+ const lessonMoc = [];
12461
+ const crystalMoc = [];
12462
+ const sessionMoc = [];
12463
+ const [memories, lessons, crystals, sessions] = await Promise.all([
12464
+ exportTypes.has("memories") ? kv.list(KV.memories) : Promise.resolve([]),
12465
+ exportTypes.has("lessons") ? kv.list(KV.lessons) : Promise.resolve([]),
12466
+ exportTypes.has("crystals") ? kv.list(KV.crystals) : Promise.resolve([]),
12467
+ exportTypes.has("sessions") ? kv.list(KV.sessions) : Promise.resolve([])
12468
+ ]);
12469
+ for (const m of memories.filter((m) => hasExportId(m) && m.isLatest === true)) {
12470
+ const filename = `${sanitize(m.id)}.md`;
12471
+ const filepath = join(dirs.memories, filename);
12472
+ try {
12473
+ await writeFile(filepath, memoryToMd(m));
12474
+ stats.memories++;
12475
+ memoryMoc.push(`- [[memories/${sanitize(m.id)}|${safeString(m.title, m.id)}]] (${m.type}, strength: ${m.strength ?? 0})`);
12476
+ } catch (err) {
12477
+ errors.push({
12478
+ id: m.id,
12479
+ path: filepath,
12480
+ error: err instanceof Error ? err.message : String(err)
12481
+ });
12482
+ }
12076
12483
  }
12077
- }
12078
- for (const l of lessons.filter((l) => !l.deleted)) {
12079
- const filename = `${sanitize(l.id)}.md`;
12080
- const filepath = join(dirs.lessons, filename);
12081
- try {
12082
- await writeFile(filepath, lessonToMd(l));
12083
- stats.lessons++;
12084
- lessonMoc.push(`- [[lessons/${sanitize(l.id)}|${l.content.slice(0, 60)}]] (confidence: ${l.confidence})`);
12085
- } catch (err) {
12086
- errors.push({
12087
- id: l.id,
12088
- path: filepath,
12089
- error: err instanceof Error ? err.message : String(err)
12090
- });
12484
+ for (const l of lessons.filter((l) => hasExportId(l) && !l.deleted)) {
12485
+ const filename = `${sanitize(l.id)}.md`;
12486
+ const filepath = join(dirs.lessons, filename);
12487
+ try {
12488
+ await writeFile(filepath, lessonToMd(l));
12489
+ stats.lessons++;
12490
+ const headline = safeString(l.content).slice(0, 60) || l.id;
12491
+ lessonMoc.push(`- [[lessons/${sanitize(l.id)}|${headline}]] (confidence: ${l.confidence ?? 0})`);
12492
+ } catch (err) {
12493
+ errors.push({
12494
+ id: l.id,
12495
+ path: filepath,
12496
+ error: err instanceof Error ? err.message : String(err)
12497
+ });
12498
+ }
12091
12499
  }
12092
- }
12093
- for (const c of crystals) {
12094
- const filename = `${sanitize(c.id)}.md`;
12095
- const filepath = join(dirs.crystals, filename);
12096
- try {
12097
- await writeFile(filepath, crystalToMd(c));
12098
- stats.crystals++;
12099
- crystalMoc.push(`- [[crystals/${sanitize(c.id)}|${c.narrative.slice(0, 60)}]]`);
12100
- } catch (err) {
12101
- errors.push({
12102
- id: c.id,
12103
- path: filepath,
12104
- error: err instanceof Error ? err.message : String(err)
12105
- });
12500
+ for (const c of crystals.filter(hasExportId)) {
12501
+ const filename = `${sanitize(c.id)}.md`;
12502
+ const filepath = join(dirs.crystals, filename);
12503
+ try {
12504
+ await writeFile(filepath, crystalToMd(c));
12505
+ stats.crystals++;
12506
+ const headline = safeString(c.narrative).slice(0, 60) || c.id;
12507
+ crystalMoc.push(`- [[crystals/${sanitize(c.id)}|${headline}]]`);
12508
+ } catch (err) {
12509
+ errors.push({
12510
+ id: c.id,
12511
+ path: filepath,
12512
+ error: err instanceof Error ? err.message : String(err)
12513
+ });
12514
+ }
12106
12515
  }
12107
- }
12108
- const recent = sessions.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).slice(0, 50);
12109
- for (const s of recent) {
12110
- const filename = `${sanitize(s.id)}.md`;
12111
- const filepath = join(dirs.sessions, filename);
12112
- try {
12113
- await writeFile(filepath, sessionToMd(s));
12114
- stats.sessions++;
12115
- sessionMoc.push(`- [[sessions/${sanitize(s.id)}|${s.project} (${s.status})]]`);
12116
- } catch (err) {
12117
- errors.push({
12118
- id: s.id,
12119
- path: filepath,
12120
- error: err instanceof Error ? err.message : String(err)
12121
- });
12516
+ const recent = sessions.filter(hasExportId).sort((a, b) => safeTimestamp(b.startedAt) - safeTimestamp(a.startedAt)).slice(0, 50);
12517
+ for (const s of recent) {
12518
+ const filename = `${sanitize(s.id)}.md`;
12519
+ const filepath = join(dirs.sessions, filename);
12520
+ try {
12521
+ await writeFile(filepath, sessionToMd(s));
12522
+ stats.sessions++;
12523
+ sessionMoc.push(`- [[sessions/${sanitize(s.id)}|${safeString(s.project, "unknown")} (${safeString(s.status, "unknown")})]]`);
12524
+ } catch (err) {
12525
+ errors.push({
12526
+ id: s.id,
12527
+ path: filepath,
12528
+ error: err instanceof Error ? err.message : String(err)
12529
+ });
12530
+ }
12122
12531
  }
12532
+ const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
12533
+ const moc = [
12534
+ "---",
12535
+ "type: moc",
12536
+ `exported: ${exportedAt}`,
12537
+ "---",
12538
+ "",
12539
+ "# agentmemory vault",
12540
+ "",
12541
+ `Exported: ${exportedAt}`,
12542
+ "",
12543
+ `## Memories (${stats.memories})`,
12544
+ ...memoryMoc,
12545
+ "",
12546
+ `## Lessons (${stats.lessons})`,
12547
+ ...lessonMoc,
12548
+ "",
12549
+ `## Crystals (${stats.crystals})`,
12550
+ ...crystalMoc,
12551
+ "",
12552
+ `## Sessions (${stats.sessions})`,
12553
+ ...sessionMoc
12554
+ ].join("\n");
12555
+ await writeFile(join(vaultDir, "MOC.md"), moc);
12556
+ await recordAudit(kv, "obsidian_export", "mem::obsidian-export", [], {
12557
+ vaultDir,
12558
+ stats
12559
+ });
12560
+ return {
12561
+ success: true,
12562
+ exported: stats,
12563
+ errors: errors.length > 0 ? errors : void 0,
12564
+ vaultDir
12565
+ };
12566
+ } catch (err) {
12567
+ return {
12568
+ success: false,
12569
+ error: err instanceof Error ? err.message : String(err),
12570
+ vaultDir
12571
+ };
12123
12572
  }
12124
- const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
12125
- const moc = [
12126
- "---",
12127
- "type: moc",
12128
- `exported: ${exportedAt}`,
12129
- "---",
12130
- "",
12131
- "# agentmemory vault",
12132
- "",
12133
- `Exported: ${exportedAt}`,
12134
- "",
12135
- `## Memories (${stats.memories})`,
12136
- ...memoryMoc,
12137
- "",
12138
- `## Lessons (${stats.lessons})`,
12139
- ...lessonMoc,
12140
- "",
12141
- `## Crystals (${stats.crystals})`,
12142
- ...crystalMoc,
12143
- "",
12144
- `## Sessions (${stats.sessions})`,
12145
- ...sessionMoc
12146
- ].join("\n");
12147
- await writeFile(join(vaultDir, "MOC.md"), moc);
12148
- await recordAudit(kv, "obsidian_export", "mem::obsidian-export", [], {
12149
- vaultDir,
12150
- stats
12151
- });
12152
- return {
12153
- success: true,
12154
- exported: stats,
12155
- errors: errors.length > 0 ? errors : void 0,
12156
- vaultDir
12157
- };
12158
12573
  });
12159
12574
  }
12160
-
12161
12575
  //#endregion
12162
12576
  //#region src/prompts/reflect.ts
12163
12577
  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.
@@ -12185,7 +12599,6 @@ function buildReflectPrompt(cluster) {
12185
12599
  if (cluster.crystalNarratives.length > 0) sections.push("\n## Completed Work Summaries", ...cluster.crystalNarratives.map((n) => `- ${n}`));
12186
12600
  return `Synthesize higher-order insights from this cluster of related memories:\n\n${sections.join("\n")}`;
12187
12601
  }
12188
-
12189
12602
  //#endregion
12190
12603
  //#region src/functions/reflect.ts
12191
12604
  function reinforceInsight(insight) {
@@ -12478,7 +12891,6 @@ function registerReflectFunctions(sdk, kv, provider) {
12478
12891
  };
12479
12892
  });
12480
12893
  }
12481
-
12482
12894
  //#endregion
12483
12895
  //#region src/functions/working-memory.ts
12484
12896
  const CORE_SCOPE = "mem:core-memory";
@@ -12653,7 +13065,6 @@ function registerWorkingMemoryFunctions(sdk, kv, tokenBudget) {
12653
13065
  };
12654
13066
  });
12655
13067
  }
12656
-
12657
13068
  //#endregion
12658
13069
  //#region src/functions/skill-extract.ts
12659
13070
  const SKILL_EXTRACT_SYSTEM = `You are a skill extraction engine. Given a completed multi-step task session, extract a reusable procedural skill document.
@@ -12856,7 +13267,6 @@ function registerSkillExtractFunctions(sdk, kv, provider) {
12856
13267
  };
12857
13268
  });
12858
13269
  }
12859
-
12860
13270
  //#endregion
12861
13271
  //#region src/functions/sliding-window.ts
12862
13272
  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.
@@ -13043,7 +13453,6 @@ function registerSlidingWindowFunction(sdk, kv, provider) {
13043
13453
  };
13044
13454
  });
13045
13455
  }
13046
-
13047
13456
  //#endregion
13048
13457
  //#region src/functions/temporal-graph.ts
13049
13458
  const TEMPORAL_EXTRACTION_SYSTEM = `You are a temporal knowledge extraction engine. Given observations, extract entities AND their temporal relationships with full context metadata.
@@ -13307,7 +13716,6 @@ function buildTimeline(edges) {
13307
13716
  context: e.context
13308
13717
  }));
13309
13718
  }
13310
-
13311
13719
  //#endregion
13312
13720
  //#region src/functions/retention.ts
13313
13721
  const DEFAULT_DECAY = {
@@ -13541,7 +13949,6 @@ function registerRetentionFunctions(sdk, kv) {
13541
13949
  };
13542
13950
  });
13543
13951
  }
13544
-
13545
13952
  //#endregion
13546
13953
  //#region src/functions/compress-file.ts
13547
13954
  const SENSITIVE_PATH_TERMS = [
@@ -13683,7 +14090,6 @@ function registerCompressFileFunction(sdk, kv, provider) {
13683
14090
  };
13684
14091
  });
13685
14092
  }
13686
-
13687
14093
  //#endregion
13688
14094
  //#region src/replay/jsonl-parser.ts
13689
14095
  function deriveProject(cwd) {
@@ -13810,7 +14216,6 @@ function parseJsonlText(text, fallbackSessionId) {
13810
14216
  observations
13811
14217
  };
13812
14218
  }
13813
-
13814
14219
  //#endregion
13815
14220
  //#region src/replay/timeline.ts
13816
14221
  const DEFAULT_CHARS_PER_SEC = 40;
@@ -13905,10 +14310,6 @@ function projectTimeline(observations) {
13905
14310
  events
13906
14311
  };
13907
14312
  }
13908
-
13909
- //#endregion
13910
- //#region src/functions/replay.ts
13911
- const MAX_FILES_DEFAULT = 200;
13912
14313
  const MAX_FILES_UPPER_BOUND = 1e3;
13913
14314
  const SENSITIVE_PATH_PATTERNS = [
13914
14315
  /(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
@@ -14128,7 +14529,7 @@ function registerReplayFunctions(sdk, kv) {
14128
14529
  error: "path not found"
14129
14530
  };
14130
14531
  }
14131
- const maxFiles = Number.isInteger(data.maxFiles) && data.maxFiles > 0 ? Math.min(data.maxFiles, MAX_FILES_UPPER_BOUND) : MAX_FILES_DEFAULT;
14532
+ const maxFiles = Number.isInteger(data.maxFiles) && data.maxFiles > 0 ? Math.min(data.maxFiles, MAX_FILES_UPPER_BOUND) : 200;
14132
14533
  let files = [];
14133
14534
  let truncated = false;
14134
14535
  let discovered = 0;
@@ -14184,7 +14585,8 @@ function registerReplayFunctions(sdk, kv) {
14184
14585
  const existingTags = existing.tags || [];
14185
14586
  if (!existingTags.includes("jsonl-import")) existing.tags = [...existingTags, "jsonl-import"];
14186
14587
  if (!existing.firstPrompt && firstPrompt) existing.firstPrompt = firstPrompt;
14187
- await kv.set(KV.sessions, existing.id, existing);
14588
+ if (!existing.id) existing.id = parsed.sessionId;
14589
+ await kv.set(KV.sessions, parsed.sessionId, existing);
14188
14590
  } else {
14189
14591
  const session = {
14190
14592
  id: parsed.sessionId,
@@ -14230,7 +14632,6 @@ function registerReplayFunctions(sdk, kv) {
14230
14632
  };
14231
14633
  });
14232
14634
  }
14233
-
14234
14635
  //#endregion
14235
14636
  //#region src/health/thresholds.ts
14236
14637
  const DEFAULTS = {
@@ -14289,7 +14690,6 @@ function evaluateHealth(snapshot, config = {}) {
14289
14690
  notes
14290
14691
  };
14291
14692
  }
14292
-
14293
14693
  //#endregion
14294
14694
  //#region src/health/monitor.ts
14295
14695
  function registerHealthMonitor(sdk, kv) {
@@ -14377,7 +14777,6 @@ function registerHealthMonitor(sdk, kv) {
14377
14777
  async function getLatestHealth(kv) {
14378
14778
  return kv.get(KV.health, "latest");
14379
14779
  }
14380
-
14381
14780
  //#endregion
14382
14781
  //#region src/auth.ts
14383
14782
  const hmacKey = randomBytes(32);
@@ -14403,7 +14802,6 @@ function buildViewerCsp(nonce) {
14403
14802
  "font-src 'self'"
14404
14803
  ].join("; ");
14405
14804
  }
14406
-
14407
14805
  //#endregion
14408
14806
  //#region src/viewer/document.ts
14409
14807
  const VIEWER_VERSION_PLACEHOLDER = "__AGENTMEMORY_VERSION__";
@@ -14429,7 +14827,6 @@ function renderViewerDocument() {
14429
14827
  csp: buildViewerCsp(nonce)
14430
14828
  };
14431
14829
  }
14432
-
14433
14830
  //#endregion
14434
14831
  //#region src/viewer/server.ts
14435
14832
  function loadViewerFavicon() {
@@ -14444,18 +14841,30 @@ function loadViewerFavicon() {
14444
14841
  } catch {}
14445
14842
  return null;
14446
14843
  }
14844
+ const VIEWER_FAVICON = loadViewerFavicon();
14447
14845
  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());
14448
- const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
14449
- function buildAllowedHosts(origins, listenPort) {
14846
+ function readAllowedHostsOverride() {
14847
+ return (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
14848
+ }
14849
+ function resolveViewerHost() {
14850
+ return process.env.AGENTMEMORY_VIEWER_HOST?.trim() || "127.0.0.1";
14851
+ }
14852
+ function isLoopbackHost(host) {
14853
+ const h = host.trim().toLowerCase();
14854
+ return h === "127.0.0.1" || h === "::1" || h === "localhost";
14855
+ }
14856
+ function buildAllowedHosts(origins, listenPort, bindHost = "127.0.0.1") {
14450
14857
  const hosts = /* @__PURE__ */ new Set();
14451
- for (const o of origins) try {
14452
- const parsed = new URL(o);
14453
- if (parsed.host) hosts.add(parsed.host.toLowerCase());
14454
- } catch {}
14455
- hosts.add(`localhost:${listenPort}`);
14456
- hosts.add(`127.0.0.1:${listenPort}`);
14457
- hosts.add(`[::1]:${listenPort}`);
14458
- for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h);
14858
+ if (isLoopbackHost(bindHost)) {
14859
+ for (const o of origins) try {
14860
+ const parsed = new URL(o);
14861
+ if (parsed.host) hosts.add(parsed.host.toLowerCase());
14862
+ } catch {}
14863
+ hosts.add(`localhost:${listenPort}`);
14864
+ hosts.add(`127.0.0.1:${listenPort}`);
14865
+ hosts.add(`[::1]:${listenPort}`);
14866
+ }
14867
+ for (const h of readAllowedHostsOverride()) hosts.add(h);
14459
14868
  return hosts;
14460
14869
  }
14461
14870
  function isHostAllowed(headerHost, allowed) {
@@ -14464,11 +14873,16 @@ function isHostAllowed(headerHost, allowed) {
14464
14873
  if (!lower) return false;
14465
14874
  return allowed.has(lower);
14466
14875
  }
14876
+ function requireInboundBearer(authHeader, secret) {
14877
+ if (typeof authHeader !== "string") return false;
14878
+ const match = /^Bearer\s+(\S+)\s*$/i.exec(authHeader);
14879
+ if (!match) return false;
14880
+ return timingSafeCompare(match[1], secret);
14881
+ }
14467
14882
  function corsHeaders(req) {
14468
14883
  const origin = req.headers.origin || "";
14469
- const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
14470
14884
  return {
14471
- "Access-Control-Allow-Origin": allowed,
14885
+ "Access-Control-Allow-Origin": ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0],
14472
14886
  "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
14473
14887
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
14474
14888
  Vary: "Origin"
@@ -14512,16 +14926,29 @@ function getBoundViewerPort() {
14512
14926
  function getViewerSkipped() {
14513
14927
  return viewerSkipped;
14514
14928
  }
14929
+ var ViewerConfigError = class extends Error {
14930
+ constructor(message) {
14931
+ super(message);
14932
+ this.name = "ViewerConfigError";
14933
+ }
14934
+ };
14515
14935
  function startViewerServer(port, _kv, _sdk, secret, restPort) {
14516
14936
  boundViewerPort = null;
14517
14937
  viewerSkipped = false;
14518
14938
  const resolvedRestPort = restPort ?? port - 2;
14519
14939
  const requestedPort = port;
14940
+ const host = resolveViewerHost();
14941
+ let inboundSecret = null;
14942
+ if (!isLoopbackHost(host)) {
14943
+ 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.`);
14944
+ 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.`);
14945
+ inboundSecret = secret;
14946
+ }
14520
14947
  let allowedHosts = null;
14521
14948
  const server = createServer(async (req, res) => {
14522
14949
  if (!allowedHosts) {
14523
14950
  const addr = server.address();
14524
- allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port);
14951
+ allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port, host);
14525
14952
  }
14526
14953
  if (!isHostAllowed(req.headers.host, allowedHosts)) {
14527
14954
  res.writeHead(403, { "Content-Type": "text/plain" });
@@ -14557,19 +14984,26 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
14557
14984
  return;
14558
14985
  }
14559
14986
  if (method === "GET" && pathname === "/favicon.svg") {
14560
- const favicon = loadViewerFavicon();
14561
- if (favicon) {
14987
+ if (VIEWER_FAVICON) {
14562
14988
  res.writeHead(200, {
14563
14989
  "Content-Type": "image/svg+xml",
14564
14990
  "Cache-Control": "public, max-age=3600"
14565
14991
  });
14566
- res.end(favicon);
14992
+ res.end(VIEWER_FAVICON);
14567
14993
  return;
14568
14994
  }
14569
14995
  res.writeHead(404, { "Content-Type": "text/plain" });
14570
14996
  res.end("favicon not found");
14571
14997
  return;
14572
14998
  }
14999
+ if (inboundSecret !== null && !requireInboundBearer(req.headers.authorization, inboundSecret)) {
15000
+ res.writeHead(401, {
15001
+ "Content-Type": "text/plain",
15002
+ "WWW-Authenticate": "Bearer realm=\"agentmemory-viewer\""
15003
+ });
15004
+ res.end("unauthorized");
15005
+ return;
15006
+ }
14573
15007
  try {
14574
15008
  await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret);
14575
15009
  } catch (err) {
@@ -14580,17 +15014,23 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
14580
15014
  let attempt = 0;
14581
15015
  let currentPort = requestedPort;
14582
15016
  const tryListen = () => {
14583
- server.listen(currentPort, "127.0.0.1");
15017
+ server.listen(currentPort, host);
14584
15018
  };
14585
15019
  server.on("listening", () => {
14586
15020
  const addr = server.address();
14587
- boundViewerPort = addr && typeof addr === "object" && "port" in addr ? addr.port : currentPort;
15021
+ const actualPort = addr && typeof addr === "object" && "port" in addr ? addr.port : currentPort;
15022
+ boundViewerPort = actualPort;
14588
15023
  viewerSkipped = false;
14589
- if (currentPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${currentPort}`);
14590
- else console.log(`[agentmemory] Viewer started on http://localhost:${currentPort} (fallback from ${requestedPort})`);
15024
+ if (inboundSecret !== null) {
15025
+ const allowedHosts = readAllowedHostsOverride().join(", ");
15026
+ console.log(`[agentmemory] Viewer: http://localhost:${actualPort} (bound to ${host}; inbound Bearer required; allowed Host headers: ${allowedHosts})`);
15027
+ return;
15028
+ }
15029
+ if (actualPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${actualPort}`);
15030
+ else console.log(`[agentmemory] Viewer started on http://localhost:${actualPort} (fallback from ${requestedPort})`);
14591
15031
  });
14592
15032
  server.on("error", (err) => {
14593
- if (err.code === "EADDRINUSE" && attempt < MAX_VIEWER_PORT_RETRIES) {
15033
+ if (err.code === "EADDRINUSE" && inboundSecret === null && attempt < MAX_VIEWER_PORT_RETRIES) {
14594
15034
  attempt++;
14595
15035
  currentPort = requestedPort + attempt;
14596
15036
  setImmediate(tryListen);
@@ -14599,7 +15039,8 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
14599
15039
  if (err.code === "EADDRINUSE") {
14600
15040
  boundViewerPort = null;
14601
15041
  viewerSkipped = true;
14602
- console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
15042
+ 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.`);
15043
+ else console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
14603
15044
  } else {
14604
15045
  boundViewerPort = null;
14605
15046
  viewerSkipped = true;
@@ -14644,7 +15085,6 @@ async function proxyToRestApi(restPort, pathname, qs, method, req, res, secret)
14644
15085
  res.writeHead(upstream.status, responseHeaders);
14645
15086
  res.end(responseBody);
14646
15087
  }
14647
-
14648
15088
  //#endregion
14649
15089
  //#region src/triggers/api.ts
14650
15090
  function parseOptionalInt(raw) {
@@ -15067,7 +15507,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15067
15507
  }
15068
15508
  if (body.maxFiles !== void 0) {
15069
15509
  const n = body.maxFiles;
15070
- if (!Number.isInteger(n) || n < 1 || n > MAX_FILES_UPPER_BOUND) return {
15510
+ if (!Number.isInteger(n) || n < 1 || n > 1e3) return {
15071
15511
  status_code: 400,
15072
15512
  body: { error: `maxFiles must be an integer between 1 and ${MAX_FILES_UPPER_BOUND}` }
15073
15513
  };
@@ -15151,9 +15591,13 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15151
15591
  value: "completed"
15152
15592
  }]);
15153
15593
  try {
15154
- sdk.triggerVoid("event::session::stopped", { sessionId });
15594
+ sdk.trigger({
15595
+ function_id: "event::session::stopped",
15596
+ payload: { sessionId },
15597
+ action: TriggerAction.Void()
15598
+ });
15155
15599
  } catch (err) {
15156
- logger.warn("event::session::stopped triggerVoid failed", {
15600
+ logger.warn("event::session::stopped trigger failed", {
15157
15601
  sessionId,
15158
15602
  error: err instanceof Error ? err.message : String(err)
15159
15603
  });
@@ -15573,11 +16017,24 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15573
16017
  status_code: 400,
15574
16018
  body: { error: "query or expandIds is required" }
15575
16019
  };
16020
+ const headers = req.headers || {};
16021
+ const sourceHeader = headers["x-agentmemory-source"] ?? headers["X-Agentmemory-Source"];
16022
+ const sourceFromHeader = Array.isArray(sourceHeader) ? sourceHeader[0] : sourceHeader;
16023
+ const payload = {
16024
+ query: req.body?.query,
16025
+ expandIds: req.body?.expandIds,
16026
+ limit: req.body?.limit,
16027
+ project: req.body?.project,
16028
+ includeLessons: req.body?.includeLessons,
16029
+ agentId: req.body?.agentId,
16030
+ sessionId: req.body?.sessionId,
16031
+ source: req.body?.source ?? sourceFromHeader
16032
+ };
15576
16033
  return {
15577
16034
  status_code: 200,
15578
16035
  body: await sdk.trigger({
15579
16036
  function_id: "mem::smart-search",
15580
- payload: req.body
16037
+ payload
15581
16038
  })
15582
16039
  };
15583
16040
  });
@@ -15589,6 +16046,29 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15589
16046
  http_method: "POST"
15590
16047
  }
15591
16048
  });
16049
+ sdk.registerFunction("api::diagnostic-followup", async (req) => {
16050
+ const authErr = checkAuth(req, secret);
16051
+ if (authErr) return authErr;
16052
+ return {
16053
+ status_code: 200,
16054
+ body: {
16055
+ ...await sdk.trigger({
16056
+ function_id: "mem::diagnostic::followup-stats",
16057
+ payload: {}
16058
+ }),
16059
+ caveat: "Directional signal: overcounts on legitimate query refinement. Tune via AGENTMEMORY_FOLLOWUP_WINDOW_SECONDS."
16060
+ }
16061
+ };
16062
+ });
16063
+ sdk.registerTrigger({
16064
+ type: "http",
16065
+ function_id: "api::diagnostic-followup",
16066
+ config: {
16067
+ api_path: "/agentmemory/diagnostics/followup",
16068
+ http_method: "GET",
16069
+ middleware_function_ids: ["middleware::api-auth"]
16070
+ }
16071
+ });
15592
16072
  sdk.registerFunction("api::timeline", async (req) => {
15593
16073
  const authErr = checkAuth(req, secret);
15594
16074
  if (authErr) return authErr;
@@ -15810,12 +16290,20 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15810
16290
  sdk.registerFunction("api::graph-query", async (req) => {
15811
16291
  const authErr = checkAuth(req, secret);
15812
16292
  if (authErr) return authErr;
16293
+ const payload = {
16294
+ startNodeId: req.body?.startNodeId,
16295
+ nodeType: req.body?.nodeType,
16296
+ maxDepth: req.body?.maxDepth,
16297
+ query: req.body?.query,
16298
+ limit: req.body?.limit,
16299
+ offset: req.body?.offset
16300
+ };
15813
16301
  try {
15814
16302
  return {
15815
16303
  status_code: 200,
15816
16304
  body: await sdk.trigger({
15817
16305
  function_id: "mem::graph-query",
15818
- payload: req.body || {}
16306
+ payload
15819
16307
  })
15820
16308
  };
15821
16309
  } catch {
@@ -18096,7 +18584,6 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
18096
18584
  }
18097
18585
  });
18098
18586
  }
18099
-
18100
18587
  //#endregion
18101
18588
  //#region src/triggers/events.ts
18102
18589
  function registerEventTriggers(sdk, kv) {
@@ -18141,18 +18628,26 @@ function registerEventTriggers(sdk, kv) {
18141
18628
  payload: data
18142
18629
  });
18143
18630
  if (isReflectEnabled()) try {
18144
- sdk.triggerVoid("mem::slot-reflect", { sessionId: data.sessionId });
18631
+ sdk.trigger({
18632
+ function_id: "mem::slot-reflect",
18633
+ payload: { sessionId: data.sessionId },
18634
+ action: TriggerAction.Void()
18635
+ });
18145
18636
  } catch (err) {
18146
- logger.warn("slot-reflect triggerVoid failed", {
18637
+ logger.warn("slot-reflect trigger failed", {
18147
18638
  sessionId: data.sessionId,
18148
18639
  error: err instanceof Error ? err.message : String(err)
18149
18640
  });
18150
18641
  }
18151
18642
  if (isGraphExtractionEnabled()) try {
18152
18643
  const compressed = (await kv.list(KV.observations(data.sessionId))).filter((o) => o.title);
18153
- if (compressed.length > 0) sdk.triggerVoid("mem::graph-extract", { observations: compressed });
18644
+ if (compressed.length > 0) sdk.trigger({
18645
+ function_id: "mem::graph-extract",
18646
+ payload: { observations: compressed },
18647
+ action: TriggerAction.Void()
18648
+ });
18154
18649
  } catch (err) {
18155
- logger.warn("graph-extract triggerVoid failed", {
18650
+ logger.warn("graph-extract trigger failed", {
18156
18651
  sessionId: data.sessionId,
18157
18652
  error: err instanceof Error ? err.message : String(err)
18158
18653
  });
@@ -18210,7 +18705,6 @@ function registerEventTriggers(sdk, kv) {
18210
18705
  config: { scope: KV.sessions }
18211
18706
  });
18212
18707
  }
18213
-
18214
18708
  //#endregion
18215
18709
  //#region src/mcp/tools-registry.ts
18216
18710
  const CORE_TOOLS = [
@@ -19320,7 +19814,6 @@ function getVisibleTools() {
19320
19814
  if ((process.env["AGENTMEMORY_TOOLS"] || "all") === "core") return getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name));
19321
19815
  return getAllTools();
19322
19816
  }
19323
-
19324
19817
  //#endregion
19325
19818
  //#region src/mcp/server.ts
19326
19819
  function asNonEmptyString(value) {
@@ -20957,7 +21450,6 @@ function registerMcpEndpoints(sdk, kv, secret) {
20957
21450
  }
20958
21451
  });
20959
21452
  }
20960
-
20961
21453
  //#endregion
20962
21454
  //#region src/eval/metrics-store.ts
20963
21455
  var MetricsStore = class {
@@ -21000,7 +21492,6 @@ var MetricsStore = class {
21000
21492
  return Array.from(merged.values());
21001
21493
  }
21002
21494
  };
21003
-
21004
21495
  //#endregion
21005
21496
  //#region src/functions/dedup.ts
21006
21497
  const TTL_MS = 300 * 1e3;
@@ -21042,57 +21533,6 @@ var DedupMap = class {
21042
21533
  return this.entries.size;
21043
21534
  }
21044
21535
  };
21045
-
21046
- //#endregion
21047
- //#region src/telemetry/setup.ts
21048
- const OTEL_CONFIG = {
21049
- serviceName: "agentmemory",
21050
- serviceVersion: VERSION,
21051
- metricsExportIntervalMs: 3e4
21052
- };
21053
- let counters = null;
21054
- let histograms = null;
21055
- const NOOP_COUNTER = { add: () => {} };
21056
- const NOOP_HISTOGRAM = { record: () => {} };
21057
- const COUNTER_NAMES = [
21058
- ["observationsTotal", "observations.total"],
21059
- ["compressionSuccess", "compression.success"],
21060
- ["compressionFailure", "compression.failure"],
21061
- ["searchTotal", "search.total"],
21062
- ["dedupSkipped", "dedup.skipped"],
21063
- ["evictionTotal", "eviction.total"],
21064
- ["circuitBreakerOpen", "circuit_breaker.open"],
21065
- ["embeddingSuccess", "embedding.success"],
21066
- ["embeddingFailure", "embedding.failure"],
21067
- ["vectorSearchTotal", "vector_search.total"],
21068
- ["autoForgetTotal", "auto_forget.total"],
21069
- ["profileGenerated", "profile.generated"],
21070
- ["claudeBridgeSync", "claude_bridge.sync"],
21071
- ["graphExtraction", "graph.extraction"],
21072
- ["consolidationRun", "consolidation.run"],
21073
- ["teamShare", "team.share"],
21074
- ["auditLog", "audit.log"],
21075
- ["snapshotCreate", "snapshot.create"],
21076
- ["governanceDelete", "governance.delete"]
21077
- ];
21078
- const HISTOGRAM_NAMES = [
21079
- ["compressionLatency", "compression.latency_ms"],
21080
- ["searchLatency", "search.latency_ms"],
21081
- ["contextTokens", "context.tokens"],
21082
- ["qualityScore", "quality.score"],
21083
- ["embeddingLatency", "embedding.latency_ms"],
21084
- ["vectorSearchLatency", "vector_search.latency_ms"]
21085
- ];
21086
- function initMetrics(getMeter) {
21087
- const meter = getMeter?.("agentmemory");
21088
- counters = Object.fromEntries(COUNTER_NAMES.map(([key, name]) => [key, meter ? meter.createCounter(name) : NOOP_COUNTER]));
21089
- histograms = Object.fromEntries(HISTOGRAM_NAMES.map(([key, name]) => [key, meter ? meter.createHistogram(name) : NOOP_HISTOGRAM]));
21090
- return {
21091
- counters,
21092
- histograms
21093
- };
21094
- }
21095
-
21096
21536
  //#endregion
21097
21537
  //#region src/index.ts
21098
21538
  function workerPidfilePath() {
@@ -21241,6 +21681,7 @@ async function main() {
21241
21681
  const graphWeight = parseFloat(getEnvVar("AGENTMEMORY_GRAPH_WEIGHT") || "0.3");
21242
21682
  const hybridSearch = new HybridSearch(bm25Index, vectorIndex, embeddingProvider, kv, embeddingConfig.bm25Weight, embeddingConfig.vectorWeight, graphWeight);
21243
21683
  registerSmartSearchFunction(sdk, kv, (query, limit) => hybridSearch.search(query, limit));
21684
+ registerRecentSearchesSweepFunction(sdk, kv);
21244
21685
  registerApiTriggers(sdk, kv, secret, metricsStore, provider);
21245
21686
  registerEventTriggers(sdk, kv);
21246
21687
  registerMcpEndpoints(sdk, kv, secret);
@@ -21308,7 +21749,7 @@ async function main() {
21308
21749
  console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
21309
21750
  }
21310
21751
  bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
21311
- bootLog(`REST API: 125 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
21752
+ bootLog(`REST API: 126 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
21312
21753
  bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
21313
21754
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
21314
21755
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
@@ -21343,6 +21784,14 @@ async function main() {
21343
21784
  });
21344
21785
  } catch {}
21345
21786
  }, 864e5).unref();
21787
+ setInterval(async () => {
21788
+ try {
21789
+ await sdk.trigger({
21790
+ function_id: "mem::diagnostic::recent-searches-sweep",
21791
+ payload: {}
21792
+ });
21793
+ } catch {}
21794
+ }, 3600 * 1e3).unref();
21346
21795
  if (isConsolidationEnabled()) {
21347
21796
  setInterval(async () => {
21348
21797
  try {
@@ -21374,7 +21823,7 @@ main().catch((err) => {
21374
21823
  console.error(`[agentmemory] Fatal:`, err);
21375
21824
  process.exit(1);
21376
21825
  });
21377
-
21378
21826
  //#endregion
21379
- export { };
21827
+ export {};
21828
+
21380
21829
  //# sourceMappingURL=index.mjs.map