@agentmemory/agentmemory 0.8.12 → 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,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-DXIK5CxQ.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,9 @@ 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"
4367
4388
  ]).has(importData.version)) return {
4368
4389
  success: false,
4369
4390
  error: `Unsupported export version: ${importData.version}`
@@ -10735,8 +10756,8 @@ function registerRetentionFunctions(sdk, kv) {
10735
10756
  let evictedSemantic = 0;
10736
10757
  const evictedIds = [];
10737
10758
  for (const candidate of candidates) try {
10738
- let scope;
10739
- let resolvedSource;
10759
+ let scope = null;
10760
+ let resolvedSource = null;
10740
10761
  if (candidate.source === "semantic") {
10741
10762
  scope = KV.semantic;
10742
10763
  resolvedSource = "semantic";
@@ -10746,10 +10767,11 @@ function registerRetentionFunctions(sdk, kv) {
10746
10767
  } else if (await kv.get(KV.memories, candidate.memoryId) !== null) {
10747
10768
  scope = KV.memories;
10748
10769
  resolvedSource = "episodic";
10749
- } else {
10770
+ } else if (await kv.get(KV.semantic, candidate.memoryId) !== null) {
10750
10771
  scope = KV.semantic;
10751
10772
  resolvedSource = "semantic";
10752
10773
  }
10774
+ if (!scope || !resolvedSource) continue;
10753
10775
  await kv.delete(scope, candidate.memoryId);
10754
10776
  await kv.delete(KV.retentionScores, candidate.memoryId);
10755
10777
  await deleteAccessLog(kv, candidate.memoryId);
@@ -10836,7 +10858,7 @@ function validateCompression(original, compressed) {
10836
10858
  }
10837
10859
  function resolveBackupPath(filePath) {
10838
10860
  const base = basename(filePath, extname(filePath));
10839
- const name = base.endsWith(".original") ? base : `${base}.original`;
10861
+ const name = base.endsWith(".original") ? `${base}.backup` : `${base}.original`;
10840
10862
  return join(dirname(filePath), `${name}.md`);
10841
10863
  }
10842
10864
  function registerCompressFileFunction(sdk, kv, provider) {
@@ -10855,6 +10877,17 @@ function registerCompressFileFunction(sdk, kv, provider) {
10855
10877
  success: false,
10856
10878
  error: "refusing to process sensitive-looking path"
10857
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
+ }
10858
10891
  let original;
10859
10892
  try {
10860
10893
  original = await readFile(absolutePath, "utf-8");
@@ -10878,7 +10911,23 @@ function registerCompressFileFunction(sdk, kv, provider) {
10878
10911
  };
10879
10912
  const backupPath = resolveBackupPath(absolutePath);
10880
10913
  await writeFile(backupPath, original, "utf-8");
10881
- await writeFile(absolutePath, compressed, "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
+ }
10882
10931
  try {
10883
10932
  await recordAudit(kv, "compress", "mem::compress-file", [], {
10884
10933
  filePath: absolutePath,
@@ -10897,6 +10946,419 @@ function registerCompressFileFunction(sdk, kv, provider) {
10897
10946
  });
10898
10947
  }
10899
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
+
10900
11362
  //#endregion
10901
11363
  //#region src/health/thresholds.ts
10902
11364
  const DEFAULTS = {
@@ -10905,7 +11367,8 @@ const DEFAULTS = {
10905
11367
  cpuWarnPercent: 80,
10906
11368
  cpuCriticalPercent: 90,
10907
11369
  memoryWarnPercent: 80,
10908
- memoryCriticalPercent: 95
11370
+ memoryCriticalPercent: 95,
11371
+ memoryRssFloorBytes: 512 * 1024 * 1024
10909
11372
  };
10910
11373
  function evaluateHealth(snapshot, config = {}) {
10911
11374
  const cfg = {
@@ -10937,13 +11400,16 @@ function evaluateHealth(snapshot, config = {}) {
10937
11400
  degraded = true;
10938
11401
  }
10939
11402
  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)}%`);
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`);
10942
11408
  critical = true;
10943
- } else if (memPercent > cfg.memoryWarnPercent) {
10944
- 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`);
10945
11411
  degraded = true;
10946
- }
11412
+ } else if (memPercent > cfg.memoryWarnPercent) alerts.push(`memory_heap_tight_${Math.round(memPercent)}%_rss${memMb}mb`);
10947
11413
  return {
10948
11414
  status: critical ? "critical" : degraded ? "degraded" : "healthy",
10949
11415
  alerts
@@ -11338,6 +11804,81 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
11338
11804
  http_method: "POST"
11339
11805
  }
11340
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
+ });
11341
11882
  sdk.registerFunction("api::session::start", async (req) => {
11342
11883
  const body = req.body ?? {};
11343
11884
  const sessionId = asNonEmptyString$1(body.sessionId);
@@ -15590,6 +16131,7 @@ async function main() {
15590
16131
  registerTemporalGraphFunctions(sdk, kv, provider);
15591
16132
  registerRetentionFunctions(sdk, kv);
15592
16133
  registerCompressFileFunction(sdk, kv, provider);
16134
+ registerReplayFunctions(sdk, kv);
15593
16135
  console.log(`[agentmemory] v0.6 advanced retrieval: sliding-window, query-expansion, temporal-graph, retention-scoring`);
15594
16136
  console.log(`[agentmemory] Orchestration layer: actions, frontier, leases, routines, signals, checkpoints, flow-compress, mesh, branch-aware, sentinels, sketches, crystallize, diagnostics, facets`);
15595
16137
  const snapshotConfig = loadSnapshotConfig();
@@ -15629,7 +16171,7 @@ async function main() {
15629
16171
  }
15630
16172
  }
15631
16173
  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`);
16174
+ console.log(`[agentmemory] Endpoints: 107 REST + 44 MCP tools + 6 MCP resources + 3 MCP prompts`);
15633
16175
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
15634
16176
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
15635
16177
  const consolidationIntervalMs = parseInt(process.env.CONSOLIDATION_INTERVAL_MS || "7200000", 10);
@@ -15696,4 +16238,4 @@ main().catch((err) => {
15696
16238
 
15697
16239
  //#endregion
15698
16240
  export { };
15699
- //# sourceMappingURL=src-68MXysnV.mjs.map
16241
+ //# sourceMappingURL=src-B3pEsBSb.mjs.map