@agentmemory/agentmemory 0.8.11 → 0.9.0

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.
@@ -1,8 +1,8 @@
1
1
  import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./cli.mjs";
2
- import { a as getEnvVar, c as isConsolidationEnabled, d as loadClaudeBridgeConfig, f as loadConfig, g as loadTeamConfig, h as loadSnapshotConfig, i as getConsolidationDecayDays, l as isContextInjectionEnabled, m as loadFallbackConfig, n as VERSION, p as loadEmbeddingConfig, r as detectEmbeddingProvider, s as isAutoCompressEnabled, t as getVisibleTools, u as isGraphExtractionEnabled } from "./tools-registry-DmTkd0if.mjs";
2
+ import { a as getEnvVar, c as isConsolidationEnabled, d as loadClaudeBridgeConfig, f as loadConfig, g as loadTeamConfig, h as loadSnapshotConfig, i as getConsolidationDecayDays, l as isContextInjectionEnabled, m as loadFallbackConfig, n as VERSION, p as loadEmbeddingConfig, r as detectEmbeddingProvider, s as isAutoCompressEnabled, t as getVisibleTools, u as isGraphExtractionEnabled } from "./tools-registry-DXIK5CxQ.mjs";
3
3
  import { execFile } from "node:child_process";
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { dirname, join, resolve, sep } from "node:path";
4
+ import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { basename, dirname, extname, join, resolve, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { homedir } from "node:os";
8
8
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
@@ -12,7 +12,7 @@ import { z } from "zod";
12
12
  import { promisify } from "node:util";
13
13
  import { lookup } from "node:dns/promises";
14
14
  import { isIP } from "node:net";
15
- import { mkdir, writeFile } from "node:fs/promises";
15
+ import { lstat, mkdir, open, readFile, readdir, writeFile } from "node:fs/promises";
16
16
  import { createServer } from "node:http";
17
17
 
18
18
  //#region src/providers/agent-sdk.ts
@@ -564,7 +564,11 @@ function createBaseProvider(config) {
564
564
  switch (config.provider) {
565
565
  case "minimax": return new MinimaxProvider(requireEnvVar("MINIMAX_API_KEY"), config.model, config.maxTokens);
566
566
  case "anthropic": return new AnthropicProvider(requireEnvVar("ANTHROPIC_API_KEY"), config.model, config.maxTokens, config.baseURL);
567
- case "gemini": return new OpenRouterProvider(requireEnvVar("GEMINI_API_KEY"), config.model, config.maxTokens, "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions");
567
+ case "gemini": {
568
+ const geminiKey = getEnvVar("GEMINI_API_KEY") || getEnvVar("GOOGLE_API_KEY");
569
+ if (!geminiKey) throw new Error("GEMINI_API_KEY (or GOOGLE_API_KEY) is required for the gemini provider");
570
+ return new OpenRouterProvider(geminiKey, config.model, config.maxTokens, "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions");
571
+ }
568
572
  case "openrouter": return new OpenRouterProvider(requireEnvVar("OPENROUTER_API_KEY"), config.model, config.maxTokens, "https://openrouter.ai/api/v1/chat/completions");
569
573
  default: return new AgentSDKProvider();
570
574
  }
@@ -1895,7 +1899,7 @@ function stringifyForNarrative(v) {
1895
1899
  return String(v);
1896
1900
  }
1897
1901
  }
1898
- function truncate$1(s, n) {
1902
+ function truncate$2(s, n) {
1899
1903
  return s.length > n ? s.slice(0, n - 1) + "…" : s;
1900
1904
  }
1901
1905
  function buildSyntheticCompression(raw) {
@@ -1912,10 +1916,10 @@ function buildSyntheticCompression(raw) {
1912
1916
  sessionId: raw.sessionId,
1913
1917
  timestamp: raw.timestamp,
1914
1918
  type: inferType(toolName, raw.hookType),
1915
- title: truncate$1(toolName || "observation", 80),
1916
- subtitle: inputStr ? truncate$1(inputStr, 120) : void 0,
1919
+ title: truncate$2(toolName || "observation", 80),
1920
+ subtitle: inputStr ? truncate$2(inputStr, 120) : void 0,
1917
1921
  facts: [],
1918
- narrative: truncate$1(narrativeParts.join(" | "), 400),
1922
+ narrative: truncate$2(narrativeParts.join(" | "), 400),
1919
1923
  concepts: [],
1920
1924
  files: extractFiles$1(raw.toolInput),
1921
1925
  importance: 5,
@@ -2041,6 +2045,17 @@ function registerSearchFunction(sdk, kv) {
2041
2045
  }
2042
2046
  const projectFilter = typeof data.project === "string" && data.project.length > 0 ? data.project : void 0;
2043
2047
  const cwdFilter = typeof data.cwd === "string" && data.cwd.length > 0 ? data.cwd : void 0;
2048
+ const format = typeof data.format === "string" ? data.format : "full";
2049
+ if (![
2050
+ "full",
2051
+ "compact",
2052
+ "narrative"
2053
+ ].includes(format)) throw new Error("mem::search: format must be one of 'full', 'compact', or 'narrative'");
2054
+ let tokenBudget;
2055
+ if (data.token_budget !== void 0) {
2056
+ if (!Number.isInteger(data.token_budget) || data.token_budget < 1) throw new Error("mem::search: token_budget must be a positive integer");
2057
+ tokenBudget = data.token_budget;
2058
+ }
2044
2059
  if (idx.size === 0) {
2045
2060
  const count = await rebuildIndex(kv);
2046
2061
  logger.info("Search index rebuilt", { entries: count });
@@ -2077,13 +2092,81 @@ function registerSearchFunction(sdk, kv) {
2077
2092
  });
2078
2093
  }
2079
2094
  recordAccessBatch(kv, enriched.map((r) => r.observation.id));
2095
+ const estimateTokens = (value) => Math.max(1, Math.ceil(JSON.stringify(value).length / 3));
2096
+ const applyTokenBudget = (items) => {
2097
+ if (!tokenBudget) return {
2098
+ items,
2099
+ used: items.reduce((sum, item) => sum + estimateTokens(item), 0),
2100
+ truncated: false
2101
+ };
2102
+ const selected = [];
2103
+ let used = 0;
2104
+ for (const item of items) {
2105
+ const itemTokens = estimateTokens(item);
2106
+ if (used + itemTokens > tokenBudget) return {
2107
+ items: selected,
2108
+ used,
2109
+ truncated: selected.length < items.length
2110
+ };
2111
+ selected.push(item);
2112
+ used += itemTokens;
2113
+ }
2114
+ return {
2115
+ items: selected,
2116
+ used,
2117
+ truncated: false
2118
+ };
2119
+ };
2120
+ if (format === "compact") {
2121
+ const packed = applyTokenBudget(enriched.map((r) => ({
2122
+ obsId: r.observation.id,
2123
+ sessionId: r.sessionId,
2124
+ title: r.observation.title,
2125
+ type: r.observation.type,
2126
+ score: r.score,
2127
+ timestamp: r.observation.timestamp
2128
+ })));
2129
+ return {
2130
+ format,
2131
+ results: packed.items,
2132
+ tokens_used: packed.used,
2133
+ tokens_budget: tokenBudget,
2134
+ truncated: packed.truncated
2135
+ };
2136
+ }
2137
+ if (format === "narrative") {
2138
+ const packed = applyTokenBudget(enriched.map((r) => ({
2139
+ obsId: r.observation.id,
2140
+ sessionId: r.sessionId,
2141
+ title: r.observation.title,
2142
+ narrative: r.observation.narrative,
2143
+ score: r.score,
2144
+ timestamp: r.observation.timestamp
2145
+ })));
2146
+ const text = packed.items.map((r, index) => `${index + 1}. ${r.title}\n${r.narrative}`).join("\n\n");
2147
+ return {
2148
+ format,
2149
+ results: packed.items,
2150
+ text,
2151
+ tokens_used: packed.used,
2152
+ tokens_budget: tokenBudget,
2153
+ truncated: packed.truncated
2154
+ };
2155
+ }
2156
+ const packed = applyTokenBudget(enriched);
2080
2157
  logger.info("Search completed", {
2081
2158
  query,
2082
- results: enriched.length,
2159
+ results: packed.items.length,
2083
2160
  hasProjectFilter: !!projectFilter,
2084
2161
  hasCwdFilter: !!cwdFilter
2085
2162
  });
2086
- return { results: enriched };
2163
+ return {
2164
+ format,
2165
+ results: packed.items,
2166
+ tokens_used: packed.used,
2167
+ tokens_budget: tokenBudget,
2168
+ truncated: packed.truncated
2169
+ };
2087
2170
  });
2088
2171
  }
2089
2172
 
@@ -2260,16 +2343,16 @@ function buildCompressionPrompt(observation) {
2260
2343
  if (observation.toolName) parts.push(`Tool: ${observation.toolName}`);
2261
2344
  if (observation.toolInput) {
2262
2345
  const input = typeof observation.toolInput === "string" ? observation.toolInput : JSON.stringify(observation.toolInput, null, 2);
2263
- parts.push(`Input:\n${truncate(input, 4e3)}`);
2346
+ parts.push(`Input:\n${truncate$1(input, 4e3)}`);
2264
2347
  }
2265
2348
  if (observation.toolOutput) {
2266
2349
  const output = typeof observation.toolOutput === "string" ? observation.toolOutput : JSON.stringify(observation.toolOutput, null, 2);
2267
- parts.push(`Output:\n${truncate(output, 4e3)}`);
2350
+ parts.push(`Output:\n${truncate$1(output, 4e3)}`);
2268
2351
  }
2269
- if (observation.userPrompt) parts.push(`User prompt:\n${truncate(observation.userPrompt, 2e3)}`);
2352
+ if (observation.userPrompt) parts.push(`User prompt:\n${truncate$1(observation.userPrompt, 2e3)}`);
2270
2353
  return parts.join("\n\n");
2271
2354
  }
2272
- function truncate(s, max) {
2355
+ function truncate$1(s, max) {
2273
2356
  return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s;
2274
2357
  }
2275
2358
 
@@ -3434,25 +3517,40 @@ function registerRememberFunction(sdk, kv) {
3434
3517
  });
3435
3518
  sdk.registerFunction("mem::forget", async (data) => {
3436
3519
  let deleted = 0;
3520
+ const deletedMemoryIds = [];
3521
+ const deletedObservationIds = [];
3522
+ let deletedSession = false;
3437
3523
  if (data.memoryId) {
3438
3524
  await kv.delete(KV.memories, data.memoryId);
3439
3525
  await deleteAccessLog(kv, data.memoryId);
3526
+ deletedMemoryIds.push(data.memoryId);
3440
3527
  deleted++;
3441
3528
  }
3442
3529
  if (data.sessionId && data.observationIds && data.observationIds.length > 0) for (const obsId of data.observationIds) {
3443
3530
  await kv.delete(KV.observations(data.sessionId), obsId);
3531
+ deletedObservationIds.push(obsId);
3444
3532
  deleted++;
3445
3533
  }
3446
3534
  if (data.sessionId && (!data.observationIds || data.observationIds.length === 0) && !data.memoryId) {
3447
3535
  const observations = await kv.list(KV.observations(data.sessionId));
3448
3536
  for (const obs of observations) {
3449
3537
  await kv.delete(KV.observations(data.sessionId), obs.id);
3538
+ deletedObservationIds.push(obs.id);
3450
3539
  deleted++;
3451
3540
  }
3452
3541
  await kv.delete(KV.sessions, data.sessionId);
3453
3542
  await kv.delete(KV.summaries, data.sessionId);
3543
+ deletedSession = true;
3454
3544
  deleted += 2;
3455
3545
  }
3546
+ if (deleted > 0) await recordAudit(kv, "forget", "mem::forget", [...deletedMemoryIds, ...deletedObservationIds], {
3547
+ sessionId: data.sessionId,
3548
+ deleted,
3549
+ memoriesDeleted: deletedMemoryIds.length,
3550
+ observationsDeleted: deletedObservationIds.length,
3551
+ sessionDeleted: deletedSession,
3552
+ reason: "user-initiated forget"
3553
+ });
3456
3554
  logger.info("Memory forgotten", { deleted });
3457
3555
  return {
3458
3556
  success: true,
@@ -4283,7 +4381,10 @@ function registerExportImportFunction(sdk, kv) {
4283
4381
  "0.8.8",
4284
4382
  "0.8.9",
4285
4383
  "0.8.10",
4286
- "0.8.11"
4384
+ "0.8.11",
4385
+ "0.8.12",
4386
+ "0.8.13",
4387
+ "0.9.0"
4287
4388
  ]).has(importData.version)) return {
4288
4389
  success: false,
4289
4390
  error: `Unsupported export version: ${importData.version}`
@@ -10655,8 +10756,8 @@ function registerRetentionFunctions(sdk, kv) {
10655
10756
  let evictedSemantic = 0;
10656
10757
  const evictedIds = [];
10657
10758
  for (const candidate of candidates) try {
10658
- let scope;
10659
- let resolvedSource;
10759
+ let scope = null;
10760
+ let resolvedSource = null;
10660
10761
  if (candidate.source === "semantic") {
10661
10762
  scope = KV.semantic;
10662
10763
  resolvedSource = "semantic";
@@ -10666,10 +10767,11 @@ function registerRetentionFunctions(sdk, kv) {
10666
10767
  } else if (await kv.get(KV.memories, candidate.memoryId) !== null) {
10667
10768
  scope = KV.memories;
10668
10769
  resolvedSource = "episodic";
10669
- } else {
10770
+ } else if (await kv.get(KV.semantic, candidate.memoryId) !== null) {
10670
10771
  scope = KV.semantic;
10671
10772
  resolvedSource = "semantic";
10672
10773
  }
10774
+ if (!scope || !resolvedSource) continue;
10673
10775
  await kv.delete(scope, candidate.memoryId);
10674
10776
  await kv.delete(KV.retentionScores, candidate.memoryId);
10675
10777
  await deleteAccessLog(kv, candidate.memoryId);
@@ -10702,6 +10804,561 @@ function registerRetentionFunctions(sdk, kv) {
10702
10804
  });
10703
10805
  }
10704
10806
 
10807
+ //#endregion
10808
+ //#region src/functions/compress-file.ts
10809
+ const SENSITIVE_PATH_TERMS = [
10810
+ "secret",
10811
+ "credential",
10812
+ "private_key",
10813
+ ".env",
10814
+ "id_rsa",
10815
+ "token"
10816
+ ];
10817
+ const COMPRESS_FILE_SYSTEM_PROMPT = `You compress markdown while preserving structure.
10818
+ Rules:
10819
+ - Keep all headings exactly as-is.
10820
+ - Keep all URLs exactly as-is.
10821
+ - Keep all fenced code blocks exactly as-is.
10822
+ - Do not remove sections; shorten prose under each section.
10823
+ - Output only markdown, no wrappers or explanations.`;
10824
+ function stripMarkdownFence(text) {
10825
+ const trimmed = text.trim();
10826
+ const match = trimmed.match(/^```(?:markdown|md)?\s*([\s\S]*?)\s*```$/i);
10827
+ return match ? match[1].trim() : trimmed;
10828
+ }
10829
+ function extractUrls(text) {
10830
+ return Array.from(new Set(text.match(/https?:\/\/[^\s)]+/g) || []));
10831
+ }
10832
+ function extractHeadings(text) {
10833
+ return text.split("\n").map((line) => line.trim()).filter((line) => /^#{1,6}\s+/.test(line));
10834
+ }
10835
+ function extractCodeBlocks(text) {
10836
+ return text.match(/```[\s\S]*?```/g) || [];
10837
+ }
10838
+ function validateCompression(original, compressed) {
10839
+ const errors = [];
10840
+ const originalHeadings = extractHeadings(original);
10841
+ const compressedHeadings = extractHeadings(compressed);
10842
+ for (const heading of originalHeadings) if (!compressedHeadings.includes(heading)) errors.push(`missing heading: ${heading}`);
10843
+ const originalUrls = extractUrls(original).sort();
10844
+ const compressedUrls = extractUrls(compressed).sort();
10845
+ if (originalUrls.length !== compressedUrls.length) errors.push("url count changed");
10846
+ else for (let i = 0; i < originalUrls.length; i++) if (originalUrls[i] !== compressedUrls[i]) {
10847
+ errors.push("url set changed");
10848
+ break;
10849
+ }
10850
+ const originalBlocks = extractCodeBlocks(original);
10851
+ const compressedBlocks = extractCodeBlocks(compressed);
10852
+ if (originalBlocks.length !== compressedBlocks.length) errors.push("code block count changed");
10853
+ else for (let i = 0; i < originalBlocks.length; i++) if (originalBlocks[i] !== compressedBlocks[i]) {
10854
+ errors.push("code block content changed");
10855
+ break;
10856
+ }
10857
+ return errors;
10858
+ }
10859
+ function resolveBackupPath(filePath) {
10860
+ const base = basename(filePath, extname(filePath));
10861
+ const name = base.endsWith(".original") ? `${base}.backup` : `${base}.original`;
10862
+ return join(dirname(filePath), `${name}.md`);
10863
+ }
10864
+ function registerCompressFileFunction(sdk, kv, provider) {
10865
+ sdk.registerFunction("mem::compress-file", async (data) => {
10866
+ if (!data?.filePath || typeof data.filePath !== "string") return {
10867
+ success: false,
10868
+ error: "filePath is required"
10869
+ };
10870
+ const absolutePath = resolve(data.filePath);
10871
+ const lowerPath = absolutePath.toLowerCase();
10872
+ if (extname(absolutePath).toLowerCase() !== ".md") return {
10873
+ success: false,
10874
+ error: "filePath must point to a .md file"
10875
+ };
10876
+ if (SENSITIVE_PATH_TERMS.some((term) => lowerPath.includes(term))) return {
10877
+ success: false,
10878
+ error: "refusing to process sensitive-looking path"
10879
+ };
10880
+ try {
10881
+ if ((await lstat(absolutePath)).isSymbolicLink()) return {
10882
+ success: false,
10883
+ error: "symlinks are not supported"
10884
+ };
10885
+ } catch {
10886
+ return {
10887
+ success: false,
10888
+ error: "file not found"
10889
+ };
10890
+ }
10891
+ let original;
10892
+ try {
10893
+ original = await readFile(absolutePath, "utf-8");
10894
+ } catch {
10895
+ return {
10896
+ success: false,
10897
+ error: "failed to read file"
10898
+ };
10899
+ }
10900
+ if (!original.trim()) return {
10901
+ success: true,
10902
+ skipped: true,
10903
+ reason: "file is empty"
10904
+ };
10905
+ const compressed = stripMarkdownFence(await provider.summarize(COMPRESS_FILE_SYSTEM_PROMPT, `Compress this markdown file while preserving structure and code blocks:\n\n${original}`));
10906
+ const validationErrors = validateCompression(original, compressed);
10907
+ if (validationErrors.length > 0) return {
10908
+ success: false,
10909
+ error: "compression validation failed",
10910
+ details: validationErrors
10911
+ };
10912
+ const backupPath = resolveBackupPath(absolutePath);
10913
+ await writeFile(backupPath, original, "utf-8");
10914
+ let fd = null;
10915
+ try {
10916
+ fd = await open(absolutePath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW);
10917
+ await fd.writeFile(compressed, "utf-8");
10918
+ } catch (err) {
10919
+ const code = err.code;
10920
+ if (code === "ELOOP" || code === "EINVAL") return {
10921
+ success: false,
10922
+ error: "symlinks are not supported"
10923
+ };
10924
+ return {
10925
+ success: false,
10926
+ error: "failed to write compressed file"
10927
+ };
10928
+ } finally {
10929
+ await fd?.close().catch(() => {});
10930
+ }
10931
+ try {
10932
+ await recordAudit(kv, "compress", "mem::compress-file", [], {
10933
+ filePath: absolutePath,
10934
+ backupPath,
10935
+ originalChars: original.length,
10936
+ compressedChars: compressed.length
10937
+ });
10938
+ } catch {}
10939
+ return {
10940
+ success: true,
10941
+ filePath: absolutePath,
10942
+ backupPath,
10943
+ originalChars: original.length,
10944
+ compressedChars: compressed.length
10945
+ };
10946
+ });
10947
+ }
10948
+
10949
+ //#endregion
10950
+ //#region src/replay/jsonl-parser.ts
10951
+ function deriveProject(cwd) {
10952
+ if (!cwd) return "unknown";
10953
+ const parts = cwd.split("/").filter(Boolean);
10954
+ return parts[parts.length - 1] || "unknown";
10955
+ }
10956
+ function toText(content) {
10957
+ if (typeof content === "string") return content;
10958
+ if (!Array.isArray(content)) return "";
10959
+ const parts = [];
10960
+ for (const item of content) {
10961
+ if (!item || typeof item !== "object") continue;
10962
+ const entry = item;
10963
+ if (entry.type === "text" && typeof entry.text === "string") parts.push(entry.text);
10964
+ }
10965
+ return parts.join("\n");
10966
+ }
10967
+ function extractToolUses(content) {
10968
+ if (!Array.isArray(content)) return [];
10969
+ const out = [];
10970
+ for (const item of content) {
10971
+ if (!item || typeof item !== "object") continue;
10972
+ const entry = item;
10973
+ if (entry.type === "tool_use") out.push({
10974
+ id: typeof entry.id === "string" ? entry.id : "",
10975
+ name: typeof entry.name === "string" ? entry.name : "unknown",
10976
+ input: entry.input
10977
+ });
10978
+ }
10979
+ return out;
10980
+ }
10981
+ function extractToolResults(content) {
10982
+ if (!Array.isArray(content)) return [];
10983
+ const out = [];
10984
+ for (const item of content) {
10985
+ if (!item || typeof item !== "object") continue;
10986
+ const entry = item;
10987
+ if (entry.type === "tool_result") out.push({
10988
+ toolUseId: typeof entry.tool_use_id === "string" ? entry.tool_use_id : "",
10989
+ output: entry.content,
10990
+ isError: entry.is_error === true
10991
+ });
10992
+ }
10993
+ return out;
10994
+ }
10995
+ function parseJsonlText(text, fallbackSessionId) {
10996
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
10997
+ const entries = [];
10998
+ for (const line of lines) try {
10999
+ const parsed = JSON.parse(line);
11000
+ if (parsed && typeof parsed === "object") entries.push(parsed);
11001
+ } catch {}
11002
+ let sessionId = fallbackSessionId || "";
11003
+ let cwd = "";
11004
+ let firstTs = "";
11005
+ let lastTs = "";
11006
+ const observations = [];
11007
+ for (const entry of entries) {
11008
+ if (entry.sessionId && !sessionId) sessionId = entry.sessionId;
11009
+ if (entry.cwd && !cwd) cwd = entry.cwd;
11010
+ const ts = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
11011
+ if (!firstTs) firstTs = ts;
11012
+ lastTs = ts;
11013
+ const role = entry.message?.role;
11014
+ const content = entry.message?.content;
11015
+ if (entry.type === "user" && role === "user") {
11016
+ const toolResults = extractToolResults(content);
11017
+ if (toolResults.length > 0) for (const result of toolResults) observations.push({
11018
+ id: generateId("obs"),
11019
+ sessionId: sessionId || "imported",
11020
+ timestamp: ts,
11021
+ hookType: result.isError ? "post_tool_failure" : "post_tool_use",
11022
+ toolName: void 0,
11023
+ toolInput: { toolUseId: result.toolUseId },
11024
+ toolOutput: result.output,
11025
+ raw: entry
11026
+ });
11027
+ else {
11028
+ const text = toText(content);
11029
+ if (text.trim().length > 0) observations.push({
11030
+ id: generateId("obs"),
11031
+ sessionId: sessionId || "imported",
11032
+ timestamp: ts,
11033
+ hookType: "prompt_submit",
11034
+ userPrompt: text,
11035
+ raw: entry
11036
+ });
11037
+ }
11038
+ } else if (entry.type === "assistant" && role === "assistant") {
11039
+ const text = toText(content);
11040
+ const tools = extractToolUses(content);
11041
+ if (text.trim().length > 0) observations.push({
11042
+ id: generateId("obs"),
11043
+ sessionId: sessionId || "imported",
11044
+ timestamp: ts,
11045
+ hookType: "stop",
11046
+ assistantResponse: text,
11047
+ raw: entry
11048
+ });
11049
+ for (const tool of tools) observations.push({
11050
+ id: generateId("obs"),
11051
+ sessionId: sessionId || "imported",
11052
+ timestamp: ts,
11053
+ hookType: "pre_tool_use",
11054
+ toolName: tool.name,
11055
+ toolInput: tool.input,
11056
+ raw: {
11057
+ toolUseId: tool.id,
11058
+ entry
11059
+ }
11060
+ });
11061
+ } else if (entry.type === "summary" || entry.type === "system") {}
11062
+ }
11063
+ const effectiveSessionId = sessionId || generateId("sess");
11064
+ for (const obs of observations) if (obs.sessionId === "imported") obs.sessionId = effectiveSessionId;
11065
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
11066
+ return {
11067
+ sessionId: effectiveSessionId,
11068
+ project: deriveProject(cwd),
11069
+ cwd: cwd || process.cwd(),
11070
+ startedAt: firstTs || nowIso,
11071
+ endedAt: lastTs || nowIso,
11072
+ observations
11073
+ };
11074
+ }
11075
+
11076
+ //#endregion
11077
+ //#region src/replay/timeline.ts
11078
+ const DEFAULT_CHARS_PER_SEC = 40;
11079
+ const MIN_EVENT_MS = 300;
11080
+ const MAX_EVENT_MS = 2e4;
11081
+ function kindFromHook(obs) {
11082
+ switch (obs.hookType) {
11083
+ case "session_start": return "session_start";
11084
+ case "session_end": return "session_end";
11085
+ case "prompt_submit": return "prompt";
11086
+ case "stop": return obs.assistantResponse ? "response" : "hook";
11087
+ case "pre_tool_use": return "tool_call";
11088
+ case "post_tool_use": return "tool_result";
11089
+ case "post_tool_failure": return "tool_error";
11090
+ default: return "hook";
11091
+ }
11092
+ }
11093
+ function labelFor(obs, kind) {
11094
+ switch (kind) {
11095
+ case "prompt": return truncate(obs.userPrompt || "User prompt", 80);
11096
+ case "response": return truncate(obs.assistantResponse || "Assistant response", 80);
11097
+ case "tool_call": return `${obs.toolName || "tool"} ▸ call`;
11098
+ case "tool_result": return `${obs.toolName || "tool"} ▸ result`;
11099
+ case "tool_error": return `${obs.toolName || "tool"} ▸ error`;
11100
+ case "session_start": return "Session start";
11101
+ case "session_end": return "Session end";
11102
+ default: return obs.hookType;
11103
+ }
11104
+ }
11105
+ function truncate(text, max) {
11106
+ if (text.length <= max) return text;
11107
+ return text.slice(0, max - 1) + "…";
11108
+ }
11109
+ function bodyFor(obs, kind) {
11110
+ if (kind === "prompt") return obs.userPrompt;
11111
+ if (kind === "response") return obs.assistantResponse;
11112
+ }
11113
+ function estimateDurationMs(ev) {
11114
+ const chars = (ev.body?.length || 0) + (typeof ev.toolInput === "string" ? ev.toolInput.length : 0) + (typeof ev.toolOutput === "string" ? ev.toolOutput.length : 0);
11115
+ if (chars === 0) return MIN_EVENT_MS;
11116
+ const ms = Math.round(chars / DEFAULT_CHARS_PER_SEC * 1e3);
11117
+ return Math.max(MIN_EVENT_MS, Math.min(MAX_EVENT_MS, ms));
11118
+ }
11119
+ function projectTimeline(observations) {
11120
+ if (observations.length === 0) {
11121
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11122
+ return {
11123
+ sessionId: "",
11124
+ startedAt: now,
11125
+ endedAt: now,
11126
+ totalDurationMs: 0,
11127
+ eventCount: 0,
11128
+ events: []
11129
+ };
11130
+ }
11131
+ const sorted = [...observations].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
11132
+ const startedAt = sorted[0].timestamp;
11133
+ const startMs = Date.parse(startedAt);
11134
+ const events = [];
11135
+ let syntheticOffset = 0;
11136
+ const allSameTs = sorted.every((o) => o.timestamp === startedAt);
11137
+ for (const obs of sorted) {
11138
+ const kind = kindFromHook(obs);
11139
+ const body = bodyFor(obs, kind);
11140
+ const obsMs = Date.parse(obs.timestamp);
11141
+ const offsetMs = allSameTs ? syntheticOffset : Number.isFinite(obsMs) && Number.isFinite(startMs) ? Math.max(0, obsMs - startMs) : syntheticOffset;
11142
+ const event = {
11143
+ id: obs.id,
11144
+ sessionId: obs.sessionId,
11145
+ ts: obs.timestamp,
11146
+ offsetMs,
11147
+ durationMs: 0,
11148
+ kind,
11149
+ label: labelFor(obs, kind),
11150
+ body,
11151
+ toolName: obs.toolName,
11152
+ toolInput: obs.toolInput,
11153
+ toolOutput: obs.toolOutput
11154
+ };
11155
+ event.durationMs = estimateDurationMs(event);
11156
+ events.push(event);
11157
+ syntheticOffset += event.durationMs;
11158
+ }
11159
+ const last = events[events.length - 1];
11160
+ const totalDurationMs = last.offsetMs + last.durationMs;
11161
+ return {
11162
+ sessionId: sorted[0].sessionId,
11163
+ startedAt,
11164
+ endedAt: sorted[sorted.length - 1].timestamp,
11165
+ totalDurationMs,
11166
+ eventCount: events.length,
11167
+ events
11168
+ };
11169
+ }
11170
+
11171
+ //#endregion
11172
+ //#region src/functions/replay.ts
11173
+ const SENSITIVE_PATH_PATTERNS = [
11174
+ /(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
11175
+ /(^|[\\/_.-])credentials?([\\/_.-]|$)/i,
11176
+ /(^|[\\/_.-])private[_-]?key([\\/_.-]|$)/i,
11177
+ /(^|[\\/])\.env(\.[\w-]+)?$/i,
11178
+ /(^|[\\/_.-])id_rsa([\\/_.-]|$)/i,
11179
+ /(^|[\\/])auth[_-]?token([\\/_.-]|$)/i,
11180
+ /(^|[\\/])bearer[_-]?token([\\/_.-]|$)/i,
11181
+ /(^|[\\/])access[_-]?token([\\/_.-]|$)/i,
11182
+ /(^|[\\/])api[_-]?token([\\/_.-]|$)/i
11183
+ ];
11184
+ function isSensitive(path) {
11185
+ return SENSITIVE_PATH_PATTERNS.some((re) => re.test(path));
11186
+ }
11187
+ async function isSymlink(path) {
11188
+ try {
11189
+ return (await lstat(path)).isSymbolicLink();
11190
+ } catch {
11191
+ return false;
11192
+ }
11193
+ }
11194
+ function rawFromCompressed(obs) {
11195
+ return {
11196
+ id: obs.id,
11197
+ sessionId: obs.sessionId,
11198
+ timestamp: obs.timestamp,
11199
+ hookType: "post_tool_use",
11200
+ toolName: void 0,
11201
+ toolInput: void 0,
11202
+ toolOutput: void 0,
11203
+ userPrompt: obs.type === "conversation" ? obs.narrative : void 0,
11204
+ assistantResponse: void 0,
11205
+ raw: {
11206
+ title: obs.title,
11207
+ narrative: obs.narrative,
11208
+ facts: obs.facts
11209
+ }
11210
+ };
11211
+ }
11212
+ function isRawShape(o) {
11213
+ if (!o || typeof o !== "object") return false;
11214
+ return typeof o.hookType === "string";
11215
+ }
11216
+ async function loadObservations(kv, sessionId) {
11217
+ return (await kv.list(KV.observations(sessionId))).map((r) => isRawShape(r) ? r : rawFromCompressed(r));
11218
+ }
11219
+ async function findJsonlFiles(root, limit = 200) {
11220
+ const out = [];
11221
+ async function walk(dir) {
11222
+ if (out.length >= limit) return;
11223
+ let names;
11224
+ try {
11225
+ names = await readdir(dir);
11226
+ } catch {
11227
+ return;
11228
+ }
11229
+ for (const name of names) {
11230
+ if (out.length >= limit) return;
11231
+ const full = join(dir, name);
11232
+ let st;
11233
+ try {
11234
+ st = await lstat(full);
11235
+ } catch {
11236
+ continue;
11237
+ }
11238
+ if (st.isSymbolicLink()) continue;
11239
+ if (st.isDirectory()) await walk(full);
11240
+ else if (st.isFile() && name.endsWith(".jsonl")) out.push(full);
11241
+ }
11242
+ }
11243
+ await walk(root);
11244
+ return out;
11245
+ }
11246
+ function registerReplayFunctions(sdk, kv) {
11247
+ sdk.registerFunction("mem::replay::load", async (data) => {
11248
+ if (!data?.sessionId || typeof data.sessionId !== "string") return {
11249
+ success: false,
11250
+ error: "sessionId is required"
11251
+ };
11252
+ const session = await kv.get(KV.sessions, data.sessionId);
11253
+ return {
11254
+ success: true,
11255
+ timeline: projectTimeline(await loadObservations(kv, data.sessionId)),
11256
+ session
11257
+ };
11258
+ });
11259
+ sdk.registerFunction("mem::replay::sessions", async () => {
11260
+ const sessions = await kv.list(KV.sessions);
11261
+ sessions.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
11262
+ return {
11263
+ success: true,
11264
+ sessions
11265
+ };
11266
+ });
11267
+ sdk.registerFunction("mem::replay::import-jsonl", async (data = {}) => {
11268
+ const defaultRoot = join(homedir(), ".claude", "projects");
11269
+ const rawPath = data.path || defaultRoot;
11270
+ if (typeof rawPath !== "string" || rawPath.length === 0) return {
11271
+ success: false,
11272
+ error: "path must be a non-empty string"
11273
+ };
11274
+ const abs = resolve(rawPath.startsWith("~") ? join(homedir(), rawPath.slice(1)) : rawPath);
11275
+ if (isSensitive(abs)) return {
11276
+ success: false,
11277
+ error: "refusing to process sensitive-looking path"
11278
+ };
11279
+ if (await isSymlink(abs)) return {
11280
+ success: false,
11281
+ error: "symlinks are not supported"
11282
+ };
11283
+ let stat;
11284
+ try {
11285
+ stat = await lstat(abs);
11286
+ } catch {
11287
+ return {
11288
+ success: false,
11289
+ error: "path not found"
11290
+ };
11291
+ }
11292
+ let files = [];
11293
+ if (stat.isDirectory()) files = await findJsonlFiles(abs, data.maxFiles || 200);
11294
+ else if (stat.isFile() && abs.endsWith(".jsonl")) files = [abs];
11295
+ else return {
11296
+ success: false,
11297
+ error: "path must be a .jsonl file or directory"
11298
+ };
11299
+ if (files.length === 0) return {
11300
+ success: true,
11301
+ imported: 0,
11302
+ sessionIds: [],
11303
+ observations: 0
11304
+ };
11305
+ const sessionIds = [];
11306
+ let observationCount = 0;
11307
+ for (const file of files) {
11308
+ if (isSensitive(file)) continue;
11309
+ if (await isSymlink(file)) continue;
11310
+ let text;
11311
+ try {
11312
+ text = await readFile(file, "utf-8");
11313
+ } catch (err) {
11314
+ logger.warn("replay: failed to read jsonl", {
11315
+ file,
11316
+ error: err instanceof Error ? err.message : String(err)
11317
+ });
11318
+ continue;
11319
+ }
11320
+ const parsed = parseJsonlText(text, generateId("sess"));
11321
+ if (parsed.observations.length === 0) continue;
11322
+ const existing = await kv.get(KV.sessions, parsed.sessionId);
11323
+ if (existing) {
11324
+ existing.observationCount = (existing.observationCount || 0) + parsed.observations.length;
11325
+ if (parsed.endedAt > (existing.endedAt || "")) existing.endedAt = parsed.endedAt;
11326
+ if (existing.status === "active") existing.status = "completed";
11327
+ const existingTags = existing.tags || [];
11328
+ if (!existingTags.includes("jsonl-import")) existing.tags = [...existingTags, "jsonl-import"];
11329
+ await kv.set(KV.sessions, existing.id, existing);
11330
+ } else {
11331
+ const session = {
11332
+ id: parsed.sessionId,
11333
+ project: parsed.project,
11334
+ cwd: parsed.cwd,
11335
+ startedAt: parsed.startedAt,
11336
+ endedAt: parsed.endedAt,
11337
+ status: "completed",
11338
+ observationCount: parsed.observations.length,
11339
+ tags: ["jsonl-import"]
11340
+ };
11341
+ await kv.set(KV.sessions, session.id, session);
11342
+ }
11343
+ await Promise.all(parsed.observations.map((obs) => kv.set(KV.observations(parsed.sessionId), obs.id, obs)));
11344
+ observationCount += parsed.observations.length;
11345
+ sessionIds.push(parsed.sessionId);
11346
+ }
11347
+ await safeAudit(kv, "import", "mem::replay::import-jsonl", sessionIds, {
11348
+ source: "jsonl",
11349
+ path: abs,
11350
+ files: files.length,
11351
+ observations: observationCount
11352
+ });
11353
+ return {
11354
+ success: true,
11355
+ imported: files.length,
11356
+ sessionIds,
11357
+ observations: observationCount
11358
+ };
11359
+ });
11360
+ }
11361
+
10705
11362
  //#endregion
10706
11363
  //#region src/health/thresholds.ts
10707
11364
  const DEFAULTS = {
@@ -10710,7 +11367,8 @@ const DEFAULTS = {
10710
11367
  cpuWarnPercent: 80,
10711
11368
  cpuCriticalPercent: 90,
10712
11369
  memoryWarnPercent: 80,
10713
- memoryCriticalPercent: 95
11370
+ memoryCriticalPercent: 95,
11371
+ memoryRssFloorBytes: 512 * 1024 * 1024
10714
11372
  };
10715
11373
  function evaluateHealth(snapshot, config = {}) {
10716
11374
  const cfg = {
@@ -10742,13 +11400,16 @@ function evaluateHealth(snapshot, config = {}) {
10742
11400
  degraded = true;
10743
11401
  }
10744
11402
  const memPercent = snapshot.memory.heapTotal > 0 ? snapshot.memory.heapUsed / snapshot.memory.heapTotal * 100 : 0;
10745
- if (memPercent > cfg.memoryCriticalPercent) {
10746
- alerts.push(`memory_critical_${Math.round(memPercent)}%`);
11403
+ const rss = snapshot.memory.rss ?? 0;
11404
+ const rssAboveFloor = rss >= cfg.memoryRssFloorBytes;
11405
+ const memMb = Math.round(rss / (1024 * 1024));
11406
+ if (memPercent > cfg.memoryCriticalPercent && rssAboveFloor) {
11407
+ alerts.push(`memory_critical_${Math.round(memPercent)}%_rss${memMb}mb`);
10747
11408
  critical = true;
10748
- } else if (memPercent > cfg.memoryWarnPercent) {
10749
- alerts.push(`memory_warn_${Math.round(memPercent)}%`);
11409
+ } else if (memPercent > cfg.memoryWarnPercent && rssAboveFloor) {
11410
+ alerts.push(`memory_warn_${Math.round(memPercent)}%_rss${memMb}mb`);
10750
11411
  degraded = true;
10751
- }
11412
+ } else if (memPercent > cfg.memoryWarnPercent) alerts.push(`memory_heap_tight_${Math.round(memPercent)}%_rss${memMb}mb`);
10752
11413
  return {
10753
11414
  status: critical ? "critical" : degraded ? "degraded" : "healthy",
10754
11415
  alerts
@@ -11082,11 +11743,25 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
11082
11743
  status_code: 400,
11083
11744
  body: { error: "cwd must be a string" }
11084
11745
  };
11746
+ if (body.format !== void 0 && (typeof body.format !== "string" || ![
11747
+ "full",
11748
+ "compact",
11749
+ "narrative"
11750
+ ].includes(body.format.trim().toLowerCase()))) return {
11751
+ status_code: 400,
11752
+ body: { error: "format must be one of: full, compact, narrative" }
11753
+ };
11754
+ if (body.token_budget !== void 0 && (!Number.isInteger(body.token_budget) || body.token_budget < 1)) return {
11755
+ status_code: 400,
11756
+ body: { error: "token_budget must be a positive integer" }
11757
+ };
11085
11758
  const payload = {
11086
11759
  query: body.query.trim(),
11087
11760
  limit: body.limit,
11088
11761
  project: body.project,
11089
- cwd: body.cwd
11762
+ cwd: body.cwd,
11763
+ format: typeof body.format === "string" ? body.format.trim().toLowerCase() : void 0,
11764
+ token_budget: body.token_budget
11090
11765
  };
11091
11766
  return {
11092
11767
  status_code: 200,
@@ -11105,6 +11780,105 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
11105
11780
  middleware_function_ids: ["middleware::api-auth"]
11106
11781
  }
11107
11782
  });
11783
+ sdk.registerFunction("api::compress-file", async (req) => {
11784
+ const authErr = checkAuth(req, secret);
11785
+ if (authErr) return authErr;
11786
+ const filePath = asNonEmptyString$1((req.body ?? {}).filePath);
11787
+ if (!filePath) return {
11788
+ status_code: 400,
11789
+ body: { error: "filePath is required and must be a non-empty string" }
11790
+ };
11791
+ return {
11792
+ status_code: 200,
11793
+ body: await sdk.trigger({
11794
+ function_id: "mem::compress-file",
11795
+ payload: { filePath }
11796
+ })
11797
+ };
11798
+ });
11799
+ sdk.registerTrigger({
11800
+ type: "http",
11801
+ function_id: "api::compress-file",
11802
+ config: {
11803
+ api_path: "/agentmemory/compress-file",
11804
+ http_method: "POST"
11805
+ }
11806
+ });
11807
+ sdk.registerFunction("api::replay::load", async (req) => {
11808
+ const authErr = checkAuth(req, secret);
11809
+ if (authErr) return authErr;
11810
+ const sessionId = asNonEmptyString$1(req.query_params?.["sessionId"]);
11811
+ if (!sessionId) return {
11812
+ status_code: 400,
11813
+ body: { error: "sessionId is required" }
11814
+ };
11815
+ return {
11816
+ status_code: 200,
11817
+ body: await sdk.trigger({
11818
+ function_id: "mem::replay::load",
11819
+ payload: { sessionId }
11820
+ })
11821
+ };
11822
+ });
11823
+ sdk.registerTrigger({
11824
+ type: "http",
11825
+ function_id: "api::replay::load",
11826
+ config: {
11827
+ api_path: "/agentmemory/replay/load",
11828
+ http_method: "GET"
11829
+ }
11830
+ });
11831
+ sdk.registerFunction("api::replay::sessions", async (req) => {
11832
+ const authErr = checkAuth(req, secret);
11833
+ if (authErr) return authErr;
11834
+ return {
11835
+ status_code: 200,
11836
+ body: await sdk.trigger({ function_id: "mem::replay::sessions" })
11837
+ };
11838
+ });
11839
+ sdk.registerTrigger({
11840
+ type: "http",
11841
+ function_id: "api::replay::sessions",
11842
+ config: {
11843
+ api_path: "/agentmemory/replay/sessions",
11844
+ http_method: "GET"
11845
+ }
11846
+ });
11847
+ sdk.registerFunction("api::replay::import", async (req) => {
11848
+ const authErr = checkAuth(req, secret);
11849
+ if (authErr) return authErr;
11850
+ const body = req.body ?? {};
11851
+ const payload = {};
11852
+ if (body.path !== void 0) {
11853
+ if (typeof body.path !== "string" || body.path.trim().length === 0) return {
11854
+ status_code: 400,
11855
+ body: { error: "path must be a non-empty string" }
11856
+ };
11857
+ payload.path = body.path.trim();
11858
+ }
11859
+ if (body.maxFiles !== void 0) {
11860
+ if (!Number.isInteger(body.maxFiles) || body.maxFiles < 1) return {
11861
+ status_code: 400,
11862
+ body: { error: "maxFiles must be a positive integer" }
11863
+ };
11864
+ payload.maxFiles = body.maxFiles;
11865
+ }
11866
+ return {
11867
+ status_code: 202,
11868
+ body: await sdk.trigger({
11869
+ function_id: "mem::replay::import-jsonl",
11870
+ payload
11871
+ })
11872
+ };
11873
+ });
11874
+ sdk.registerTrigger({
11875
+ type: "http",
11876
+ function_id: "api::replay::import",
11877
+ config: {
11878
+ api_path: "/agentmemory/replay/import-jsonl",
11879
+ http_method: "POST"
11880
+ }
11881
+ });
11108
11882
  sdk.registerFunction("api::session::start", async (req) => {
11109
11883
  const body = req.body ?? {};
11110
11884
  const sessionId = asNonEmptyString$1(body.sessionId);
@@ -13609,13 +14383,46 @@ function registerMcpEndpoints(sdk, kv, secret) {
13609
14383
  status_code: 400,
13610
14384
  body: { error: "query is required for memory_recall" }
13611
14385
  };
14386
+ const format = typeof args.format === "string" ? args.format.trim().toLowerCase() : "full";
14387
+ if (![
14388
+ "full",
14389
+ "compact",
14390
+ "narrative"
14391
+ ].includes(format)) return {
14392
+ status_code: 400,
14393
+ body: { error: "format must be one of: full, compact, narrative" }
14394
+ };
14395
+ const tokenBudget = asNumber(args.token_budget);
14396
+ if (args.token_budget !== void 0 && (!Number.isInteger(tokenBudget) || (tokenBudget ?? 0) < 1)) return {
14397
+ status_code: 400,
14398
+ body: { error: "token_budget must be a positive integer" }
14399
+ };
13612
14400
  const result = await sdk.trigger({
13613
14401
  function_id: "mem::search",
13614
14402
  payload: {
13615
14403
  query: args.query,
13616
- limit: typeof args.limit === "number" ? args.limit : 10
14404
+ limit: typeof args.limit === "number" ? args.limit : 10,
14405
+ format,
14406
+ token_budget: tokenBudget
13617
14407
  }
13618
14408
  });
14409
+ return {
14410
+ status_code: 200,
14411
+ body: { content: [{
14412
+ type: "text",
14413
+ text: format === "narrative" && result && typeof result === "object" && "text" in result && typeof result.text === "string" ? result.text : JSON.stringify(result, null, 2)
14414
+ }] }
14415
+ };
14416
+ }
14417
+ case "memory_compress_file": {
14418
+ if (typeof args.filePath !== "string" || !args.filePath.trim()) return {
14419
+ status_code: 400,
14420
+ body: { error: "filePath is required for memory_compress_file" }
14421
+ };
14422
+ const result = await sdk.trigger({
14423
+ function_id: "mem::compress-file",
14424
+ payload: { filePath: args.filePath.trim() }
14425
+ });
13619
14426
  return {
13620
14427
  status_code: 200,
13621
14428
  body: { content: [{
@@ -15323,6 +16130,8 @@ async function main() {
15323
16130
  registerQueryExpansionFunction(sdk, provider);
15324
16131
  registerTemporalGraphFunctions(sdk, kv, provider);
15325
16132
  registerRetentionFunctions(sdk, kv);
16133
+ registerCompressFileFunction(sdk, kv, provider);
16134
+ registerReplayFunctions(sdk, kv);
15326
16135
  console.log(`[agentmemory] v0.6 advanced retrieval: sliding-window, query-expansion, temporal-graph, retention-scoring`);
15327
16136
  console.log(`[agentmemory] Orchestration layer: actions, frontier, leases, routines, signals, checkpoints, flow-compress, mesh, branch-aware, sentinels, sketches, crystallize, diagnostics, facets`);
15328
16137
  const snapshotConfig = loadSnapshotConfig();
@@ -15362,7 +16171,7 @@ async function main() {
15362
16171
  }
15363
16172
  }
15364
16173
  console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
15365
- console.log(`[agentmemory] Endpoints: 103 REST + 43 MCP tools + 6 MCP resources + 3 MCP prompts`);
16174
+ console.log(`[agentmemory] Endpoints: 107 REST + 44 MCP tools + 6 MCP resources + 3 MCP prompts`);
15366
16175
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
15367
16176
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
15368
16177
  const consolidationIntervalMs = parseInt(process.env.CONSOLIDATION_INTERVAL_MS || "7200000", 10);
@@ -15429,4 +16238,4 @@ main().catch((err) => {
15429
16238
 
15430
16239
  //#endregion
15431
16240
  export { };
15432
- //# sourceMappingURL=src-M6V9yZW5.mjs.map
16241
+ //# sourceMappingURL=src-B3pEsBSb.mjs.map