@agentmemory/agentmemory 0.8.12 → 0.9.1

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,7 +1,7 @@
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-D96ukJg4.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-BvWNlj6u.mjs";
3
3
  import { execFile } from "node:child_process";
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
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";
@@ -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, readFile, 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,
@@ -2339,16 +2343,16 @@ function buildCompressionPrompt(observation) {
2339
2343
  if (observation.toolName) parts.push(`Tool: ${observation.toolName}`);
2340
2344
  if (observation.toolInput) {
2341
2345
  const input = typeof observation.toolInput === "string" ? observation.toolInput : JSON.stringify(observation.toolInput, null, 2);
2342
- parts.push(`Input:\n${truncate(input, 4e3)}`);
2346
+ parts.push(`Input:\n${truncate$1(input, 4e3)}`);
2343
2347
  }
2344
2348
  if (observation.toolOutput) {
2345
2349
  const output = typeof observation.toolOutput === "string" ? observation.toolOutput : JSON.stringify(observation.toolOutput, null, 2);
2346
- parts.push(`Output:\n${truncate(output, 4e3)}`);
2350
+ parts.push(`Output:\n${truncate$1(output, 4e3)}`);
2347
2351
  }
2348
- 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)}`);
2349
2353
  return parts.join("\n\n");
2350
2354
  }
2351
- function truncate(s, max) {
2355
+ function truncate$1(s, max) {
2352
2356
  return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s;
2353
2357
  }
2354
2358
 
@@ -3513,25 +3517,40 @@ function registerRememberFunction(sdk, kv) {
3513
3517
  });
3514
3518
  sdk.registerFunction("mem::forget", async (data) => {
3515
3519
  let deleted = 0;
3520
+ const deletedMemoryIds = [];
3521
+ const deletedObservationIds = [];
3522
+ let deletedSession = false;
3516
3523
  if (data.memoryId) {
3517
3524
  await kv.delete(KV.memories, data.memoryId);
3518
3525
  await deleteAccessLog(kv, data.memoryId);
3526
+ deletedMemoryIds.push(data.memoryId);
3519
3527
  deleted++;
3520
3528
  }
3521
3529
  if (data.sessionId && data.observationIds && data.observationIds.length > 0) for (const obsId of data.observationIds) {
3522
3530
  await kv.delete(KV.observations(data.sessionId), obsId);
3531
+ deletedObservationIds.push(obsId);
3523
3532
  deleted++;
3524
3533
  }
3525
3534
  if (data.sessionId && (!data.observationIds || data.observationIds.length === 0) && !data.memoryId) {
3526
3535
  const observations = await kv.list(KV.observations(data.sessionId));
3527
3536
  for (const obs of observations) {
3528
3537
  await kv.delete(KV.observations(data.sessionId), obs.id);
3538
+ deletedObservationIds.push(obs.id);
3529
3539
  deleted++;
3530
3540
  }
3531
3541
  await kv.delete(KV.sessions, data.sessionId);
3532
3542
  await kv.delete(KV.summaries, data.sessionId);
3543
+ deletedSession = true;
3533
3544
  deleted += 2;
3534
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
+ });
3535
3554
  logger.info("Memory forgotten", { deleted });
3536
3555
  return {
3537
3556
  success: true,
@@ -4363,7 +4382,10 @@ function registerExportImportFunction(sdk, kv) {
4363
4382
  "0.8.9",
4364
4383
  "0.8.10",
4365
4384
  "0.8.11",
4366
- "0.8.12"
4385
+ "0.8.12",
4386
+ "0.8.13",
4387
+ "0.9.0",
4388
+ "0.9.1"
4367
4389
  ]).has(importData.version)) return {
4368
4390
  success: false,
4369
4391
  error: `Unsupported export version: ${importData.version}`
@@ -10735,8 +10757,8 @@ function registerRetentionFunctions(sdk, kv) {
10735
10757
  let evictedSemantic = 0;
10736
10758
  const evictedIds = [];
10737
10759
  for (const candidate of candidates) try {
10738
- let scope;
10739
- let resolvedSource;
10760
+ let scope = null;
10761
+ let resolvedSource = null;
10740
10762
  if (candidate.source === "semantic") {
10741
10763
  scope = KV.semantic;
10742
10764
  resolvedSource = "semantic";
@@ -10746,10 +10768,11 @@ function registerRetentionFunctions(sdk, kv) {
10746
10768
  } else if (await kv.get(KV.memories, candidate.memoryId) !== null) {
10747
10769
  scope = KV.memories;
10748
10770
  resolvedSource = "episodic";
10749
- } else {
10771
+ } else if (await kv.get(KV.semantic, candidate.memoryId) !== null) {
10750
10772
  scope = KV.semantic;
10751
10773
  resolvedSource = "semantic";
10752
10774
  }
10775
+ if (!scope || !resolvedSource) continue;
10753
10776
  await kv.delete(scope, candidate.memoryId);
10754
10777
  await kv.delete(KV.retentionScores, candidate.memoryId);
10755
10778
  await deleteAccessLog(kv, candidate.memoryId);
@@ -10836,7 +10859,7 @@ function validateCompression(original, compressed) {
10836
10859
  }
10837
10860
  function resolveBackupPath(filePath) {
10838
10861
  const base = basename(filePath, extname(filePath));
10839
- const name = base.endsWith(".original") ? base : `${base}.original`;
10862
+ const name = base.endsWith(".original") ? `${base}.backup` : `${base}.original`;
10840
10863
  return join(dirname(filePath), `${name}.md`);
10841
10864
  }
10842
10865
  function registerCompressFileFunction(sdk, kv, provider) {
@@ -10855,6 +10878,17 @@ function registerCompressFileFunction(sdk, kv, provider) {
10855
10878
  success: false,
10856
10879
  error: "refusing to process sensitive-looking path"
10857
10880
  };
10881
+ try {
10882
+ if ((await lstat(absolutePath)).isSymbolicLink()) return {
10883
+ success: false,
10884
+ error: "symlinks are not supported"
10885
+ };
10886
+ } catch {
10887
+ return {
10888
+ success: false,
10889
+ error: "file not found"
10890
+ };
10891
+ }
10858
10892
  let original;
10859
10893
  try {
10860
10894
  original = await readFile(absolutePath, "utf-8");
@@ -10878,7 +10912,23 @@ function registerCompressFileFunction(sdk, kv, provider) {
10878
10912
  };
10879
10913
  const backupPath = resolveBackupPath(absolutePath);
10880
10914
  await writeFile(backupPath, original, "utf-8");
10881
- await writeFile(absolutePath, compressed, "utf-8");
10915
+ let fd = null;
10916
+ try {
10917
+ fd = await open(absolutePath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW);
10918
+ await fd.writeFile(compressed, "utf-8");
10919
+ } catch (err) {
10920
+ const code = err.code;
10921
+ if (code === "ELOOP" || code === "EINVAL") return {
10922
+ success: false,
10923
+ error: "symlinks are not supported"
10924
+ };
10925
+ return {
10926
+ success: false,
10927
+ error: "failed to write compressed file"
10928
+ };
10929
+ } finally {
10930
+ await fd?.close().catch(() => {});
10931
+ }
10882
10932
  try {
10883
10933
  await recordAudit(kv, "compress", "mem::compress-file", [], {
10884
10934
  filePath: absolutePath,
@@ -10897,6 +10947,419 @@ function registerCompressFileFunction(sdk, kv, provider) {
10897
10947
  });
10898
10948
  }
10899
10949
 
10950
+ //#endregion
10951
+ //#region src/replay/jsonl-parser.ts
10952
+ function deriveProject(cwd) {
10953
+ if (!cwd) return "unknown";
10954
+ const parts = cwd.split("/").filter(Boolean);
10955
+ return parts[parts.length - 1] || "unknown";
10956
+ }
10957
+ function toText(content) {
10958
+ if (typeof content === "string") return content;
10959
+ if (!Array.isArray(content)) return "";
10960
+ const parts = [];
10961
+ for (const item of content) {
10962
+ if (!item || typeof item !== "object") continue;
10963
+ const entry = item;
10964
+ if (entry.type === "text" && typeof entry.text === "string") parts.push(entry.text);
10965
+ }
10966
+ return parts.join("\n");
10967
+ }
10968
+ function extractToolUses(content) {
10969
+ if (!Array.isArray(content)) return [];
10970
+ const out = [];
10971
+ for (const item of content) {
10972
+ if (!item || typeof item !== "object") continue;
10973
+ const entry = item;
10974
+ if (entry.type === "tool_use") out.push({
10975
+ id: typeof entry.id === "string" ? entry.id : "",
10976
+ name: typeof entry.name === "string" ? entry.name : "unknown",
10977
+ input: entry.input
10978
+ });
10979
+ }
10980
+ return out;
10981
+ }
10982
+ function extractToolResults(content) {
10983
+ if (!Array.isArray(content)) return [];
10984
+ const out = [];
10985
+ for (const item of content) {
10986
+ if (!item || typeof item !== "object") continue;
10987
+ const entry = item;
10988
+ if (entry.type === "tool_result") out.push({
10989
+ toolUseId: typeof entry.tool_use_id === "string" ? entry.tool_use_id : "",
10990
+ output: entry.content,
10991
+ isError: entry.is_error === true
10992
+ });
10993
+ }
10994
+ return out;
10995
+ }
10996
+ function parseJsonlText(text, fallbackSessionId) {
10997
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
10998
+ const entries = [];
10999
+ for (const line of lines) try {
11000
+ const parsed = JSON.parse(line);
11001
+ if (parsed && typeof parsed === "object") entries.push(parsed);
11002
+ } catch {}
11003
+ let sessionId = fallbackSessionId || "";
11004
+ let cwd = "";
11005
+ let firstTs = "";
11006
+ let lastTs = "";
11007
+ const observations = [];
11008
+ for (const entry of entries) {
11009
+ if (entry.sessionId && !sessionId) sessionId = entry.sessionId;
11010
+ if (entry.cwd && !cwd) cwd = entry.cwd;
11011
+ const ts = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
11012
+ if (!firstTs) firstTs = ts;
11013
+ lastTs = ts;
11014
+ const role = entry.message?.role;
11015
+ const content = entry.message?.content;
11016
+ if (entry.type === "user" && role === "user") {
11017
+ const toolResults = extractToolResults(content);
11018
+ if (toolResults.length > 0) for (const result of toolResults) observations.push({
11019
+ id: generateId("obs"),
11020
+ sessionId: sessionId || "imported",
11021
+ timestamp: ts,
11022
+ hookType: result.isError ? "post_tool_failure" : "post_tool_use",
11023
+ toolName: void 0,
11024
+ toolInput: { toolUseId: result.toolUseId },
11025
+ toolOutput: result.output,
11026
+ raw: entry
11027
+ });
11028
+ else {
11029
+ const text = toText(content);
11030
+ if (text.trim().length > 0) observations.push({
11031
+ id: generateId("obs"),
11032
+ sessionId: sessionId || "imported",
11033
+ timestamp: ts,
11034
+ hookType: "prompt_submit",
11035
+ userPrompt: text,
11036
+ raw: entry
11037
+ });
11038
+ }
11039
+ } else if (entry.type === "assistant" && role === "assistant") {
11040
+ const text = toText(content);
11041
+ const tools = extractToolUses(content);
11042
+ if (text.trim().length > 0) observations.push({
11043
+ id: generateId("obs"),
11044
+ sessionId: sessionId || "imported",
11045
+ timestamp: ts,
11046
+ hookType: "stop",
11047
+ assistantResponse: text,
11048
+ raw: entry
11049
+ });
11050
+ for (const tool of tools) observations.push({
11051
+ id: generateId("obs"),
11052
+ sessionId: sessionId || "imported",
11053
+ timestamp: ts,
11054
+ hookType: "pre_tool_use",
11055
+ toolName: tool.name,
11056
+ toolInput: tool.input,
11057
+ raw: {
11058
+ toolUseId: tool.id,
11059
+ entry
11060
+ }
11061
+ });
11062
+ } else if (entry.type === "summary" || entry.type === "system") {}
11063
+ }
11064
+ const effectiveSessionId = sessionId || generateId("sess");
11065
+ for (const obs of observations) if (obs.sessionId === "imported") obs.sessionId = effectiveSessionId;
11066
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
11067
+ return {
11068
+ sessionId: effectiveSessionId,
11069
+ project: deriveProject(cwd),
11070
+ cwd: cwd || process.cwd(),
11071
+ startedAt: firstTs || nowIso,
11072
+ endedAt: lastTs || nowIso,
11073
+ observations
11074
+ };
11075
+ }
11076
+
11077
+ //#endregion
11078
+ //#region src/replay/timeline.ts
11079
+ const DEFAULT_CHARS_PER_SEC = 40;
11080
+ const MIN_EVENT_MS = 300;
11081
+ const MAX_EVENT_MS = 2e4;
11082
+ function kindFromHook(obs) {
11083
+ switch (obs.hookType) {
11084
+ case "session_start": return "session_start";
11085
+ case "session_end": return "session_end";
11086
+ case "prompt_submit": return "prompt";
11087
+ case "stop": return obs.assistantResponse ? "response" : "hook";
11088
+ case "pre_tool_use": return "tool_call";
11089
+ case "post_tool_use": return "tool_result";
11090
+ case "post_tool_failure": return "tool_error";
11091
+ default: return "hook";
11092
+ }
11093
+ }
11094
+ function labelFor(obs, kind) {
11095
+ switch (kind) {
11096
+ case "prompt": return truncate(obs.userPrompt || "User prompt", 80);
11097
+ case "response": return truncate(obs.assistantResponse || "Assistant response", 80);
11098
+ case "tool_call": return `${obs.toolName || "tool"} ▸ call`;
11099
+ case "tool_result": return `${obs.toolName || "tool"} ▸ result`;
11100
+ case "tool_error": return `${obs.toolName || "tool"} ▸ error`;
11101
+ case "session_start": return "Session start";
11102
+ case "session_end": return "Session end";
11103
+ default: return obs.hookType;
11104
+ }
11105
+ }
11106
+ function truncate(text, max) {
11107
+ if (text.length <= max) return text;
11108
+ return text.slice(0, max - 1) + "…";
11109
+ }
11110
+ function bodyFor(obs, kind) {
11111
+ if (kind === "prompt") return obs.userPrompt;
11112
+ if (kind === "response") return obs.assistantResponse;
11113
+ }
11114
+ function estimateDurationMs(ev) {
11115
+ const chars = (ev.body?.length || 0) + (typeof ev.toolInput === "string" ? ev.toolInput.length : 0) + (typeof ev.toolOutput === "string" ? ev.toolOutput.length : 0);
11116
+ if (chars === 0) return MIN_EVENT_MS;
11117
+ const ms = Math.round(chars / DEFAULT_CHARS_PER_SEC * 1e3);
11118
+ return Math.max(MIN_EVENT_MS, Math.min(MAX_EVENT_MS, ms));
11119
+ }
11120
+ function projectTimeline(observations) {
11121
+ if (observations.length === 0) {
11122
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11123
+ return {
11124
+ sessionId: "",
11125
+ startedAt: now,
11126
+ endedAt: now,
11127
+ totalDurationMs: 0,
11128
+ eventCount: 0,
11129
+ events: []
11130
+ };
11131
+ }
11132
+ const sorted = [...observations].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
11133
+ const startedAt = sorted[0].timestamp;
11134
+ const startMs = Date.parse(startedAt);
11135
+ const events = [];
11136
+ let syntheticOffset = 0;
11137
+ const allSameTs = sorted.every((o) => o.timestamp === startedAt);
11138
+ for (const obs of sorted) {
11139
+ const kind = kindFromHook(obs);
11140
+ const body = bodyFor(obs, kind);
11141
+ const obsMs = Date.parse(obs.timestamp);
11142
+ const offsetMs = allSameTs ? syntheticOffset : Number.isFinite(obsMs) && Number.isFinite(startMs) ? Math.max(0, obsMs - startMs) : syntheticOffset;
11143
+ const event = {
11144
+ id: obs.id,
11145
+ sessionId: obs.sessionId,
11146
+ ts: obs.timestamp,
11147
+ offsetMs,
11148
+ durationMs: 0,
11149
+ kind,
11150
+ label: labelFor(obs, kind),
11151
+ body,
11152
+ toolName: obs.toolName,
11153
+ toolInput: obs.toolInput,
11154
+ toolOutput: obs.toolOutput
11155
+ };
11156
+ event.durationMs = estimateDurationMs(event);
11157
+ events.push(event);
11158
+ syntheticOffset += event.durationMs;
11159
+ }
11160
+ const last = events[events.length - 1];
11161
+ const totalDurationMs = last.offsetMs + last.durationMs;
11162
+ return {
11163
+ sessionId: sorted[0].sessionId,
11164
+ startedAt,
11165
+ endedAt: sorted[sorted.length - 1].timestamp,
11166
+ totalDurationMs,
11167
+ eventCount: events.length,
11168
+ events
11169
+ };
11170
+ }
11171
+
11172
+ //#endregion
11173
+ //#region src/functions/replay.ts
11174
+ const SENSITIVE_PATH_PATTERNS = [
11175
+ /(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
11176
+ /(^|[\\/_.-])credentials?([\\/_.-]|$)/i,
11177
+ /(^|[\\/_.-])private[_-]?key([\\/_.-]|$)/i,
11178
+ /(^|[\\/])\.env(\.[\w-]+)?$/i,
11179
+ /(^|[\\/_.-])id_rsa([\\/_.-]|$)/i,
11180
+ /(^|[\\/])auth[_-]?token([\\/_.-]|$)/i,
11181
+ /(^|[\\/])bearer[_-]?token([\\/_.-]|$)/i,
11182
+ /(^|[\\/])access[_-]?token([\\/_.-]|$)/i,
11183
+ /(^|[\\/])api[_-]?token([\\/_.-]|$)/i
11184
+ ];
11185
+ function isSensitive(path) {
11186
+ return SENSITIVE_PATH_PATTERNS.some((re) => re.test(path));
11187
+ }
11188
+ async function isSymlink(path) {
11189
+ try {
11190
+ return (await lstat(path)).isSymbolicLink();
11191
+ } catch {
11192
+ return false;
11193
+ }
11194
+ }
11195
+ function rawFromCompressed(obs) {
11196
+ return {
11197
+ id: obs.id,
11198
+ sessionId: obs.sessionId,
11199
+ timestamp: obs.timestamp,
11200
+ hookType: "post_tool_use",
11201
+ toolName: void 0,
11202
+ toolInput: void 0,
11203
+ toolOutput: void 0,
11204
+ userPrompt: obs.type === "conversation" ? obs.narrative : void 0,
11205
+ assistantResponse: void 0,
11206
+ raw: {
11207
+ title: obs.title,
11208
+ narrative: obs.narrative,
11209
+ facts: obs.facts
11210
+ }
11211
+ };
11212
+ }
11213
+ function isRawShape(o) {
11214
+ if (!o || typeof o !== "object") return false;
11215
+ return typeof o.hookType === "string";
11216
+ }
11217
+ async function loadObservations(kv, sessionId) {
11218
+ return (await kv.list(KV.observations(sessionId))).map((r) => isRawShape(r) ? r : rawFromCompressed(r));
11219
+ }
11220
+ async function findJsonlFiles(root, limit = 200) {
11221
+ const out = [];
11222
+ async function walk(dir) {
11223
+ if (out.length >= limit) return;
11224
+ let names;
11225
+ try {
11226
+ names = await readdir(dir);
11227
+ } catch {
11228
+ return;
11229
+ }
11230
+ for (const name of names) {
11231
+ if (out.length >= limit) return;
11232
+ const full = join(dir, name);
11233
+ let st;
11234
+ try {
11235
+ st = await lstat(full);
11236
+ } catch {
11237
+ continue;
11238
+ }
11239
+ if (st.isSymbolicLink()) continue;
11240
+ if (st.isDirectory()) await walk(full);
11241
+ else if (st.isFile() && name.endsWith(".jsonl")) out.push(full);
11242
+ }
11243
+ }
11244
+ await walk(root);
11245
+ return out;
11246
+ }
11247
+ function registerReplayFunctions(sdk, kv) {
11248
+ sdk.registerFunction("mem::replay::load", async (data) => {
11249
+ if (!data?.sessionId || typeof data.sessionId !== "string") return {
11250
+ success: false,
11251
+ error: "sessionId is required"
11252
+ };
11253
+ const session = await kv.get(KV.sessions, data.sessionId);
11254
+ return {
11255
+ success: true,
11256
+ timeline: projectTimeline(await loadObservations(kv, data.sessionId)),
11257
+ session
11258
+ };
11259
+ });
11260
+ sdk.registerFunction("mem::replay::sessions", async () => {
11261
+ const sessions = await kv.list(KV.sessions);
11262
+ sessions.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
11263
+ return {
11264
+ success: true,
11265
+ sessions
11266
+ };
11267
+ });
11268
+ sdk.registerFunction("mem::replay::import-jsonl", async (data = {}) => {
11269
+ const defaultRoot = join(homedir(), ".claude", "projects");
11270
+ const rawPath = data.path || defaultRoot;
11271
+ if (typeof rawPath !== "string" || rawPath.length === 0) return {
11272
+ success: false,
11273
+ error: "path must be a non-empty string"
11274
+ };
11275
+ const abs = resolve(rawPath.startsWith("~") ? join(homedir(), rawPath.slice(1)) : rawPath);
11276
+ if (isSensitive(abs)) return {
11277
+ success: false,
11278
+ error: "refusing to process sensitive-looking path"
11279
+ };
11280
+ if (await isSymlink(abs)) return {
11281
+ success: false,
11282
+ error: "symlinks are not supported"
11283
+ };
11284
+ let stat;
11285
+ try {
11286
+ stat = await lstat(abs);
11287
+ } catch {
11288
+ return {
11289
+ success: false,
11290
+ error: "path not found"
11291
+ };
11292
+ }
11293
+ let files = [];
11294
+ if (stat.isDirectory()) files = await findJsonlFiles(abs, data.maxFiles || 200);
11295
+ else if (stat.isFile() && abs.endsWith(".jsonl")) files = [abs];
11296
+ else return {
11297
+ success: false,
11298
+ error: "path must be a .jsonl file or directory"
11299
+ };
11300
+ if (files.length === 0) return {
11301
+ success: true,
11302
+ imported: 0,
11303
+ sessionIds: [],
11304
+ observations: 0
11305
+ };
11306
+ const sessionIds = [];
11307
+ let observationCount = 0;
11308
+ for (const file of files) {
11309
+ if (isSensitive(file)) continue;
11310
+ if (await isSymlink(file)) continue;
11311
+ let text;
11312
+ try {
11313
+ text = await readFile(file, "utf-8");
11314
+ } catch (err) {
11315
+ logger.warn("replay: failed to read jsonl", {
11316
+ file,
11317
+ error: err instanceof Error ? err.message : String(err)
11318
+ });
11319
+ continue;
11320
+ }
11321
+ const parsed = parseJsonlText(text, generateId("sess"));
11322
+ if (parsed.observations.length === 0) continue;
11323
+ const existing = await kv.get(KV.sessions, parsed.sessionId);
11324
+ if (existing) {
11325
+ existing.observationCount = (existing.observationCount || 0) + parsed.observations.length;
11326
+ if (parsed.endedAt > (existing.endedAt || "")) existing.endedAt = parsed.endedAt;
11327
+ if (existing.status === "active") existing.status = "completed";
11328
+ const existingTags = existing.tags || [];
11329
+ if (!existingTags.includes("jsonl-import")) existing.tags = [...existingTags, "jsonl-import"];
11330
+ await kv.set(KV.sessions, existing.id, existing);
11331
+ } else {
11332
+ const session = {
11333
+ id: parsed.sessionId,
11334
+ project: parsed.project,
11335
+ cwd: parsed.cwd,
11336
+ startedAt: parsed.startedAt,
11337
+ endedAt: parsed.endedAt,
11338
+ status: "completed",
11339
+ observationCount: parsed.observations.length,
11340
+ tags: ["jsonl-import"]
11341
+ };
11342
+ await kv.set(KV.sessions, session.id, session);
11343
+ }
11344
+ await Promise.all(parsed.observations.map((obs) => kv.set(KV.observations(parsed.sessionId), obs.id, obs)));
11345
+ observationCount += parsed.observations.length;
11346
+ sessionIds.push(parsed.sessionId);
11347
+ }
11348
+ await safeAudit(kv, "import", "mem::replay::import-jsonl", sessionIds, {
11349
+ source: "jsonl",
11350
+ path: abs,
11351
+ files: files.length,
11352
+ observations: observationCount
11353
+ });
11354
+ return {
11355
+ success: true,
11356
+ imported: files.length,
11357
+ sessionIds,
11358
+ observations: observationCount
11359
+ };
11360
+ });
11361
+ }
11362
+
10900
11363
  //#endregion
10901
11364
  //#region src/health/thresholds.ts
10902
11365
  const DEFAULTS = {
@@ -10905,7 +11368,8 @@ const DEFAULTS = {
10905
11368
  cpuWarnPercent: 80,
10906
11369
  cpuCriticalPercent: 90,
10907
11370
  memoryWarnPercent: 80,
10908
- memoryCriticalPercent: 95
11371
+ memoryCriticalPercent: 95,
11372
+ memoryRssFloorBytes: 512 * 1024 * 1024
10909
11373
  };
10910
11374
  function evaluateHealth(snapshot, config = {}) {
10911
11375
  const cfg = {
@@ -10937,13 +11401,16 @@ function evaluateHealth(snapshot, config = {}) {
10937
11401
  degraded = true;
10938
11402
  }
10939
11403
  const memPercent = snapshot.memory.heapTotal > 0 ? snapshot.memory.heapUsed / snapshot.memory.heapTotal * 100 : 0;
10940
- if (memPercent > cfg.memoryCriticalPercent) {
10941
- alerts.push(`memory_critical_${Math.round(memPercent)}%`);
11404
+ const rss = snapshot.memory.rss ?? 0;
11405
+ const rssAboveFloor = rss >= cfg.memoryRssFloorBytes;
11406
+ const memMb = Math.round(rss / (1024 * 1024));
11407
+ if (memPercent > cfg.memoryCriticalPercent && rssAboveFloor) {
11408
+ alerts.push(`memory_critical_${Math.round(memPercent)}%_rss${memMb}mb`);
10942
11409
  critical = true;
10943
- } else if (memPercent > cfg.memoryWarnPercent) {
10944
- alerts.push(`memory_warn_${Math.round(memPercent)}%`);
11410
+ } else if (memPercent > cfg.memoryWarnPercent && rssAboveFloor) {
11411
+ alerts.push(`memory_warn_${Math.round(memPercent)}%_rss${memMb}mb`);
10945
11412
  degraded = true;
10946
- }
11413
+ } else if (memPercent > cfg.memoryWarnPercent) alerts.push(`memory_heap_tight_${Math.round(memPercent)}%_rss${memMb}mb`);
10947
11414
  return {
10948
11415
  status: critical ? "critical" : degraded ? "degraded" : "healthy",
10949
11416
  alerts
@@ -11065,6 +11532,7 @@ function buildViewerCsp(nonce) {
11065
11532
 
11066
11533
  //#endregion
11067
11534
  //#region src/viewer/document.ts
11535
+ const VIEWER_VERSION_PLACEHOLDER = "__AGENTMEMORY_VERSION__";
11068
11536
  function loadViewerTemplate() {
11069
11537
  const base = dirname(fileURLToPath(import.meta.url));
11070
11538
  const candidates = [
@@ -11083,7 +11551,7 @@ function renderViewerDocument() {
11083
11551
  const nonce = createViewerNonce();
11084
11552
  return {
11085
11553
  found: true,
11086
- html: template.replaceAll(VIEWER_NONCE_PLACEHOLDER, nonce),
11554
+ html: template.replaceAll(VIEWER_NONCE_PLACEHOLDER, nonce).replaceAll(VIEWER_VERSION_PLACEHOLDER, VERSION),
11087
11555
  csp: buildViewerCsp(nonce)
11088
11556
  };
11089
11557
  }
@@ -11338,6 +11806,81 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
11338
11806
  http_method: "POST"
11339
11807
  }
11340
11808
  });
11809
+ sdk.registerFunction("api::replay::load", async (req) => {
11810
+ const authErr = checkAuth(req, secret);
11811
+ if (authErr) return authErr;
11812
+ const sessionId = asNonEmptyString$1(req.query_params?.["sessionId"]);
11813
+ if (!sessionId) return {
11814
+ status_code: 400,
11815
+ body: { error: "sessionId is required" }
11816
+ };
11817
+ return {
11818
+ status_code: 200,
11819
+ body: await sdk.trigger({
11820
+ function_id: "mem::replay::load",
11821
+ payload: { sessionId }
11822
+ })
11823
+ };
11824
+ });
11825
+ sdk.registerTrigger({
11826
+ type: "http",
11827
+ function_id: "api::replay::load",
11828
+ config: {
11829
+ api_path: "/agentmemory/replay/load",
11830
+ http_method: "GET"
11831
+ }
11832
+ });
11833
+ sdk.registerFunction("api::replay::sessions", async (req) => {
11834
+ const authErr = checkAuth(req, secret);
11835
+ if (authErr) return authErr;
11836
+ return {
11837
+ status_code: 200,
11838
+ body: await sdk.trigger({ function_id: "mem::replay::sessions" })
11839
+ };
11840
+ });
11841
+ sdk.registerTrigger({
11842
+ type: "http",
11843
+ function_id: "api::replay::sessions",
11844
+ config: {
11845
+ api_path: "/agentmemory/replay/sessions",
11846
+ http_method: "GET"
11847
+ }
11848
+ });
11849
+ sdk.registerFunction("api::replay::import", async (req) => {
11850
+ const authErr = checkAuth(req, secret);
11851
+ if (authErr) return authErr;
11852
+ const body = req.body ?? {};
11853
+ const payload = {};
11854
+ if (body.path !== void 0) {
11855
+ if (typeof body.path !== "string" || body.path.trim().length === 0) return {
11856
+ status_code: 400,
11857
+ body: { error: "path must be a non-empty string" }
11858
+ };
11859
+ payload.path = body.path.trim();
11860
+ }
11861
+ if (body.maxFiles !== void 0) {
11862
+ if (!Number.isInteger(body.maxFiles) || body.maxFiles < 1) return {
11863
+ status_code: 400,
11864
+ body: { error: "maxFiles must be a positive integer" }
11865
+ };
11866
+ payload.maxFiles = body.maxFiles;
11867
+ }
11868
+ return {
11869
+ status_code: 202,
11870
+ body: await sdk.trigger({
11871
+ function_id: "mem::replay::import-jsonl",
11872
+ payload
11873
+ })
11874
+ };
11875
+ });
11876
+ sdk.registerTrigger({
11877
+ type: "http",
11878
+ function_id: "api::replay::import",
11879
+ config: {
11880
+ api_path: "/agentmemory/replay/import-jsonl",
11881
+ http_method: "POST"
11882
+ }
11883
+ });
11341
11884
  sdk.registerFunction("api::session::start", async (req) => {
11342
11885
  const body = req.body ?? {};
11343
11886
  const sessionId = asNonEmptyString$1(body.sessionId);
@@ -12245,6 +12788,54 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
12245
12788
  http_method: "GET"
12246
12789
  }
12247
12790
  });
12791
+ sdk.registerFunction("api::semantic-list", async (req) => {
12792
+ const authErr = checkAuth(req, secret);
12793
+ if (authErr) return authErr;
12794
+ return {
12795
+ status_code: 200,
12796
+ body: { semantic: await kv.list(KV.semantic) }
12797
+ };
12798
+ });
12799
+ sdk.registerTrigger({
12800
+ type: "http",
12801
+ function_id: "api::semantic-list",
12802
+ config: {
12803
+ api_path: "/agentmemory/semantic",
12804
+ http_method: "GET"
12805
+ }
12806
+ });
12807
+ sdk.registerFunction("api::procedural-list", async (req) => {
12808
+ const authErr = checkAuth(req, secret);
12809
+ if (authErr) return authErr;
12810
+ return {
12811
+ status_code: 200,
12812
+ body: { procedural: await kv.list(KV.procedural) }
12813
+ };
12814
+ });
12815
+ sdk.registerTrigger({
12816
+ type: "http",
12817
+ function_id: "api::procedural-list",
12818
+ config: {
12819
+ api_path: "/agentmemory/procedural",
12820
+ http_method: "GET"
12821
+ }
12822
+ });
12823
+ sdk.registerFunction("api::relations-list", async (req) => {
12824
+ const authErr = checkAuth(req, secret);
12825
+ if (authErr) return authErr;
12826
+ return {
12827
+ status_code: 200,
12828
+ body: { relations: await kv.list(KV.relations) }
12829
+ };
12830
+ });
12831
+ sdk.registerTrigger({
12832
+ type: "http",
12833
+ function_id: "api::relations-list",
12834
+ config: {
12835
+ api_path: "/agentmemory/relations",
12836
+ http_method: "GET"
12837
+ }
12838
+ });
12248
12839
  sdk.registerFunction("api::action-create", async (req) => {
12249
12840
  const authErr = checkAuth(req, secret);
12250
12841
  if (authErr) return authErr;
@@ -15590,6 +16181,7 @@ async function main() {
15590
16181
  registerTemporalGraphFunctions(sdk, kv, provider);
15591
16182
  registerRetentionFunctions(sdk, kv);
15592
16183
  registerCompressFileFunction(sdk, kv, provider);
16184
+ registerReplayFunctions(sdk, kv);
15593
16185
  console.log(`[agentmemory] v0.6 advanced retrieval: sliding-window, query-expansion, temporal-graph, retention-scoring`);
15594
16186
  console.log(`[agentmemory] Orchestration layer: actions, frontier, leases, routines, signals, checkpoints, flow-compress, mesh, branch-aware, sentinels, sketches, crystallize, diagnostics, facets`);
15595
16187
  const snapshotConfig = loadSnapshotConfig();
@@ -15629,7 +16221,7 @@ async function main() {
15629
16221
  }
15630
16222
  }
15631
16223
  console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
15632
- console.log(`[agentmemory] Endpoints: 104 REST + 44 MCP tools + 6 MCP resources + 3 MCP prompts`);
16224
+ console.log(`[agentmemory] Endpoints: 107 REST + 44 MCP tools + 6 MCP resources + 3 MCP prompts`);
15633
16225
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
15634
16226
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
15635
16227
  const consolidationIntervalMs = parseInt(process.env.CONSOLIDATION_INTERVAL_MS || "7200000", 10);
@@ -15696,4 +16288,4 @@ main().catch((err) => {
15696
16288
 
15697
16289
  //#endregion
15698
16290
  export { };
15699
- //# sourceMappingURL=src-68MXysnV.mjs.map
16291
+ //# sourceMappingURL=src-Dw_gJcCy.mjs.map