@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.
- package/README.md +70 -3
- package/dist/cli.mjs +80 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +627 -31
- package/dist/index.mjs.map +1 -1
- package/dist/{src-68MXysnV.mjs → src-Dw_gJcCy.mjs} +619 -27
- package/dist/src-Dw_gJcCy.mjs.map +1 -0
- package/dist/standalone-BEWvWM5P.mjs +457 -0
- package/dist/standalone-BEWvWM5P.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +210 -67
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-D96ukJg4.mjs → tools-registry-BvWNlj6u.mjs} +11 -7
- package/dist/tools-registry-BvWNlj6u.mjs.map +1 -0
- package/dist/viewer/index.html +265 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/src-68MXysnV.mjs.map +0 -1
- package/dist/standalone-c2xiEQJ9.mjs +0 -314
- package/dist/standalone-c2xiEQJ9.mjs.map +0 -1
- package/dist/tools-registry-D96ukJg4.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { TriggerAction, registerWorker } from "iii-sdk";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -10,7 +10,7 @@ import { execFile } from "node:child_process";
|
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
import { lookup } from "node:dns/promises";
|
|
12
12
|
import { isIP } from "node:net";
|
|
13
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13
|
+
import { lstat, mkdir, open, readFile, readdir, writeFile } from "node:fs/promises";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { createServer } from "node:http";
|
|
16
16
|
|
|
@@ -51,16 +51,20 @@ function detectProvider(env) {
|
|
|
51
51
|
maxTokens,
|
|
52
52
|
baseURL: env["ANTHROPIC_BASE_URL"]
|
|
53
53
|
};
|
|
54
|
-
if (env["GEMINI_API_KEY"])
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
if (env["GEMINI_API_KEY"] || env["GOOGLE_API_KEY"]) {
|
|
55
|
+
if (!env["GEMINI_API_KEY"] && env["GOOGLE_API_KEY"]) process.stderr.write("[agentmemory] GOOGLE_API_KEY detected — treating as GEMINI_API_KEY. Set GEMINI_API_KEY in ~/.agentmemory/.env to silence this warning.\n");
|
|
56
|
+
return {
|
|
57
|
+
provider: "gemini",
|
|
58
|
+
model: env["GEMINI_MODEL"] || "gemini-2.0-flash",
|
|
59
|
+
maxTokens
|
|
60
|
+
};
|
|
61
|
+
}
|
|
59
62
|
if (env["OPENROUTER_API_KEY"]) return {
|
|
60
63
|
provider: "openrouter",
|
|
61
64
|
model: env["OPENROUTER_MODEL"] || "anthropic/claude-sonnet-4-20250514",
|
|
62
65
|
maxTokens
|
|
63
66
|
};
|
|
67
|
+
if (env["AGENTMEMORY_AUTO_COMPRESS"] === "true") process.stderr.write("[agentmemory] WARNING: AGENTMEMORY_AUTO_COMPRESS=true but no LLM provider key found (GEMINI_API_KEY, ANTHROPIC_API_KEY, OPENROUTER_API_KEY). Falling back to agent-sdk which shares Claude Code's API quota — this can exhaust a Pro subscription during heavy sessions. Set an API key in ~/.agentmemory/.env to avoid rate limits (#149).\n");
|
|
64
68
|
return {
|
|
65
69
|
provider: "agent-sdk",
|
|
66
70
|
model: "claude-sonnet-4-20250514",
|
|
@@ -726,7 +730,11 @@ function createBaseProvider(config) {
|
|
|
726
730
|
switch (config.provider) {
|
|
727
731
|
case "minimax": return new MinimaxProvider(requireEnvVar("MINIMAX_API_KEY"), config.model, config.maxTokens);
|
|
728
732
|
case "anthropic": return new AnthropicProvider(requireEnvVar("ANTHROPIC_API_KEY"), config.model, config.maxTokens, config.baseURL);
|
|
729
|
-
case "gemini":
|
|
733
|
+
case "gemini": {
|
|
734
|
+
const geminiKey = getEnvVar("GEMINI_API_KEY") || getEnvVar("GOOGLE_API_KEY");
|
|
735
|
+
if (!geminiKey) throw new Error("GEMINI_API_KEY (or GOOGLE_API_KEY) is required for the gemini provider");
|
|
736
|
+
return new OpenRouterProvider(geminiKey, config.model, config.maxTokens, "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions");
|
|
737
|
+
}
|
|
730
738
|
case "openrouter": return new OpenRouterProvider(requireEnvVar("OPENROUTER_API_KEY"), config.model, config.maxTokens, "https://openrouter.ai/api/v1/chat/completions");
|
|
731
739
|
default: return new AgentSDKProvider();
|
|
732
740
|
}
|
|
@@ -2121,7 +2129,7 @@ function stringifyForNarrative(v) {
|
|
|
2121
2129
|
return String(v);
|
|
2122
2130
|
}
|
|
2123
2131
|
}
|
|
2124
|
-
function truncate$
|
|
2132
|
+
function truncate$2(s, n) {
|
|
2125
2133
|
return s.length > n ? s.slice(0, n - 1) + "…" : s;
|
|
2126
2134
|
}
|
|
2127
2135
|
function buildSyntheticCompression(raw) {
|
|
@@ -2138,10 +2146,10 @@ function buildSyntheticCompression(raw) {
|
|
|
2138
2146
|
sessionId: raw.sessionId,
|
|
2139
2147
|
timestamp: raw.timestamp,
|
|
2140
2148
|
type: inferType(toolName, raw.hookType),
|
|
2141
|
-
title: truncate$
|
|
2142
|
-
subtitle: inputStr ? truncate$
|
|
2149
|
+
title: truncate$2(toolName || "observation", 80),
|
|
2150
|
+
subtitle: inputStr ? truncate$2(inputStr, 120) : void 0,
|
|
2143
2151
|
facts: [],
|
|
2144
|
-
narrative: truncate$
|
|
2152
|
+
narrative: truncate$2(narrativeParts.join(" | "), 400),
|
|
2145
2153
|
concepts: [],
|
|
2146
2154
|
files: extractFiles$1(raw.toolInput),
|
|
2147
2155
|
importance: 5,
|
|
@@ -2565,16 +2573,16 @@ function buildCompressionPrompt(observation) {
|
|
|
2565
2573
|
if (observation.toolName) parts.push(`Tool: ${observation.toolName}`);
|
|
2566
2574
|
if (observation.toolInput) {
|
|
2567
2575
|
const input = typeof observation.toolInput === "string" ? observation.toolInput : JSON.stringify(observation.toolInput, null, 2);
|
|
2568
|
-
parts.push(`Input:\n${truncate(input, 4e3)}`);
|
|
2576
|
+
parts.push(`Input:\n${truncate$1(input, 4e3)}`);
|
|
2569
2577
|
}
|
|
2570
2578
|
if (observation.toolOutput) {
|
|
2571
2579
|
const output = typeof observation.toolOutput === "string" ? observation.toolOutput : JSON.stringify(observation.toolOutput, null, 2);
|
|
2572
|
-
parts.push(`Output:\n${truncate(output, 4e3)}`);
|
|
2580
|
+
parts.push(`Output:\n${truncate$1(output, 4e3)}`);
|
|
2573
2581
|
}
|
|
2574
|
-
if (observation.userPrompt) parts.push(`User prompt:\n${truncate(observation.userPrompt, 2e3)}`);
|
|
2582
|
+
if (observation.userPrompt) parts.push(`User prompt:\n${truncate$1(observation.userPrompt, 2e3)}`);
|
|
2575
2583
|
return parts.join("\n\n");
|
|
2576
2584
|
}
|
|
2577
|
-
function truncate(s, max) {
|
|
2585
|
+
function truncate$1(s, max) {
|
|
2578
2586
|
return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s;
|
|
2579
2587
|
}
|
|
2580
2588
|
|
|
@@ -3739,25 +3747,40 @@ function registerRememberFunction(sdk, kv) {
|
|
|
3739
3747
|
});
|
|
3740
3748
|
sdk.registerFunction("mem::forget", async (data) => {
|
|
3741
3749
|
let deleted = 0;
|
|
3750
|
+
const deletedMemoryIds = [];
|
|
3751
|
+
const deletedObservationIds = [];
|
|
3752
|
+
let deletedSession = false;
|
|
3742
3753
|
if (data.memoryId) {
|
|
3743
3754
|
await kv.delete(KV.memories, data.memoryId);
|
|
3744
3755
|
await deleteAccessLog(kv, data.memoryId);
|
|
3756
|
+
deletedMemoryIds.push(data.memoryId);
|
|
3745
3757
|
deleted++;
|
|
3746
3758
|
}
|
|
3747
3759
|
if (data.sessionId && data.observationIds && data.observationIds.length > 0) for (const obsId of data.observationIds) {
|
|
3748
3760
|
await kv.delete(KV.observations(data.sessionId), obsId);
|
|
3761
|
+
deletedObservationIds.push(obsId);
|
|
3749
3762
|
deleted++;
|
|
3750
3763
|
}
|
|
3751
3764
|
if (data.sessionId && (!data.observationIds || data.observationIds.length === 0) && !data.memoryId) {
|
|
3752
3765
|
const observations = await kv.list(KV.observations(data.sessionId));
|
|
3753
3766
|
for (const obs of observations) {
|
|
3754
3767
|
await kv.delete(KV.observations(data.sessionId), obs.id);
|
|
3768
|
+
deletedObservationIds.push(obs.id);
|
|
3755
3769
|
deleted++;
|
|
3756
3770
|
}
|
|
3757
3771
|
await kv.delete(KV.sessions, data.sessionId);
|
|
3758
3772
|
await kv.delete(KV.summaries, data.sessionId);
|
|
3773
|
+
deletedSession = true;
|
|
3759
3774
|
deleted += 2;
|
|
3760
3775
|
}
|
|
3776
|
+
if (deleted > 0) await recordAudit(kv, "forget", "mem::forget", [...deletedMemoryIds, ...deletedObservationIds], {
|
|
3777
|
+
sessionId: data.sessionId,
|
|
3778
|
+
deleted,
|
|
3779
|
+
memoriesDeleted: deletedMemoryIds.length,
|
|
3780
|
+
observationsDeleted: deletedObservationIds.length,
|
|
3781
|
+
sessionDeleted: deletedSession,
|
|
3782
|
+
reason: "user-initiated forget"
|
|
3783
|
+
});
|
|
3761
3784
|
logger.info("Memory forgotten", { deleted });
|
|
3762
3785
|
return {
|
|
3763
3786
|
success: true,
|
|
@@ -4477,7 +4500,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
4477
4500
|
|
|
4478
4501
|
//#endregion
|
|
4479
4502
|
//#region src/version.ts
|
|
4480
|
-
const VERSION = "0.
|
|
4503
|
+
const VERSION = "0.9.1";
|
|
4481
4504
|
|
|
4482
4505
|
//#endregion
|
|
4483
4506
|
//#region src/functions/export-import.ts
|
|
@@ -4593,7 +4616,10 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
4593
4616
|
"0.8.9",
|
|
4594
4617
|
"0.8.10",
|
|
4595
4618
|
"0.8.11",
|
|
4596
|
-
"0.8.12"
|
|
4619
|
+
"0.8.12",
|
|
4620
|
+
"0.8.13",
|
|
4621
|
+
"0.9.0",
|
|
4622
|
+
"0.9.1"
|
|
4597
4623
|
]).has(importData.version)) return {
|
|
4598
4624
|
success: false,
|
|
4599
4625
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -10965,8 +10991,8 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10965
10991
|
let evictedSemantic = 0;
|
|
10966
10992
|
const evictedIds = [];
|
|
10967
10993
|
for (const candidate of candidates) try {
|
|
10968
|
-
let scope;
|
|
10969
|
-
let resolvedSource;
|
|
10994
|
+
let scope = null;
|
|
10995
|
+
let resolvedSource = null;
|
|
10970
10996
|
if (candidate.source === "semantic") {
|
|
10971
10997
|
scope = KV.semantic;
|
|
10972
10998
|
resolvedSource = "semantic";
|
|
@@ -10976,10 +11002,11 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10976
11002
|
} else if (await kv.get(KV.memories, candidate.memoryId) !== null) {
|
|
10977
11003
|
scope = KV.memories;
|
|
10978
11004
|
resolvedSource = "episodic";
|
|
10979
|
-
} else {
|
|
11005
|
+
} else if (await kv.get(KV.semantic, candidate.memoryId) !== null) {
|
|
10980
11006
|
scope = KV.semantic;
|
|
10981
11007
|
resolvedSource = "semantic";
|
|
10982
11008
|
}
|
|
11009
|
+
if (!scope || !resolvedSource) continue;
|
|
10983
11010
|
await kv.delete(scope, candidate.memoryId);
|
|
10984
11011
|
await kv.delete(KV.retentionScores, candidate.memoryId);
|
|
10985
11012
|
await deleteAccessLog(kv, candidate.memoryId);
|
|
@@ -11066,7 +11093,7 @@ function validateCompression(original, compressed) {
|
|
|
11066
11093
|
}
|
|
11067
11094
|
function resolveBackupPath(filePath) {
|
|
11068
11095
|
const base = basename(filePath, extname(filePath));
|
|
11069
|
-
const name = base.endsWith(".original") ? base : `${base}.original`;
|
|
11096
|
+
const name = base.endsWith(".original") ? `${base}.backup` : `${base}.original`;
|
|
11070
11097
|
return join(dirname(filePath), `${name}.md`);
|
|
11071
11098
|
}
|
|
11072
11099
|
function registerCompressFileFunction(sdk, kv, provider) {
|
|
@@ -11085,6 +11112,17 @@ function registerCompressFileFunction(sdk, kv, provider) {
|
|
|
11085
11112
|
success: false,
|
|
11086
11113
|
error: "refusing to process sensitive-looking path"
|
|
11087
11114
|
};
|
|
11115
|
+
try {
|
|
11116
|
+
if ((await lstat(absolutePath)).isSymbolicLink()) return {
|
|
11117
|
+
success: false,
|
|
11118
|
+
error: "symlinks are not supported"
|
|
11119
|
+
};
|
|
11120
|
+
} catch {
|
|
11121
|
+
return {
|
|
11122
|
+
success: false,
|
|
11123
|
+
error: "file not found"
|
|
11124
|
+
};
|
|
11125
|
+
}
|
|
11088
11126
|
let original;
|
|
11089
11127
|
try {
|
|
11090
11128
|
original = await readFile(absolutePath, "utf-8");
|
|
@@ -11108,7 +11146,23 @@ function registerCompressFileFunction(sdk, kv, provider) {
|
|
|
11108
11146
|
};
|
|
11109
11147
|
const backupPath = resolveBackupPath(absolutePath);
|
|
11110
11148
|
await writeFile(backupPath, original, "utf-8");
|
|
11111
|
-
|
|
11149
|
+
let fd = null;
|
|
11150
|
+
try {
|
|
11151
|
+
fd = await open(absolutePath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW);
|
|
11152
|
+
await fd.writeFile(compressed, "utf-8");
|
|
11153
|
+
} catch (err) {
|
|
11154
|
+
const code = err.code;
|
|
11155
|
+
if (code === "ELOOP" || code === "EINVAL") return {
|
|
11156
|
+
success: false,
|
|
11157
|
+
error: "symlinks are not supported"
|
|
11158
|
+
};
|
|
11159
|
+
return {
|
|
11160
|
+
success: false,
|
|
11161
|
+
error: "failed to write compressed file"
|
|
11162
|
+
};
|
|
11163
|
+
} finally {
|
|
11164
|
+
await fd?.close().catch(() => {});
|
|
11165
|
+
}
|
|
11112
11166
|
try {
|
|
11113
11167
|
await recordAudit(kv, "compress", "mem::compress-file", [], {
|
|
11114
11168
|
filePath: absolutePath,
|
|
@@ -11127,6 +11181,419 @@ function registerCompressFileFunction(sdk, kv, provider) {
|
|
|
11127
11181
|
});
|
|
11128
11182
|
}
|
|
11129
11183
|
|
|
11184
|
+
//#endregion
|
|
11185
|
+
//#region src/replay/jsonl-parser.ts
|
|
11186
|
+
function deriveProject(cwd) {
|
|
11187
|
+
if (!cwd) return "unknown";
|
|
11188
|
+
const parts = cwd.split("/").filter(Boolean);
|
|
11189
|
+
return parts[parts.length - 1] || "unknown";
|
|
11190
|
+
}
|
|
11191
|
+
function toText(content) {
|
|
11192
|
+
if (typeof content === "string") return content;
|
|
11193
|
+
if (!Array.isArray(content)) return "";
|
|
11194
|
+
const parts = [];
|
|
11195
|
+
for (const item of content) {
|
|
11196
|
+
if (!item || typeof item !== "object") continue;
|
|
11197
|
+
const entry = item;
|
|
11198
|
+
if (entry.type === "text" && typeof entry.text === "string") parts.push(entry.text);
|
|
11199
|
+
}
|
|
11200
|
+
return parts.join("\n");
|
|
11201
|
+
}
|
|
11202
|
+
function extractToolUses(content) {
|
|
11203
|
+
if (!Array.isArray(content)) return [];
|
|
11204
|
+
const out = [];
|
|
11205
|
+
for (const item of content) {
|
|
11206
|
+
if (!item || typeof item !== "object") continue;
|
|
11207
|
+
const entry = item;
|
|
11208
|
+
if (entry.type === "tool_use") out.push({
|
|
11209
|
+
id: typeof entry.id === "string" ? entry.id : "",
|
|
11210
|
+
name: typeof entry.name === "string" ? entry.name : "unknown",
|
|
11211
|
+
input: entry.input
|
|
11212
|
+
});
|
|
11213
|
+
}
|
|
11214
|
+
return out;
|
|
11215
|
+
}
|
|
11216
|
+
function extractToolResults(content) {
|
|
11217
|
+
if (!Array.isArray(content)) return [];
|
|
11218
|
+
const out = [];
|
|
11219
|
+
for (const item of content) {
|
|
11220
|
+
if (!item || typeof item !== "object") continue;
|
|
11221
|
+
const entry = item;
|
|
11222
|
+
if (entry.type === "tool_result") out.push({
|
|
11223
|
+
toolUseId: typeof entry.tool_use_id === "string" ? entry.tool_use_id : "",
|
|
11224
|
+
output: entry.content,
|
|
11225
|
+
isError: entry.is_error === true
|
|
11226
|
+
});
|
|
11227
|
+
}
|
|
11228
|
+
return out;
|
|
11229
|
+
}
|
|
11230
|
+
function parseJsonlText(text, fallbackSessionId) {
|
|
11231
|
+
const lines = text.split("\n").filter((l) => l.trim().length > 0);
|
|
11232
|
+
const entries = [];
|
|
11233
|
+
for (const line of lines) try {
|
|
11234
|
+
const parsed = JSON.parse(line);
|
|
11235
|
+
if (parsed && typeof parsed === "object") entries.push(parsed);
|
|
11236
|
+
} catch {}
|
|
11237
|
+
let sessionId = fallbackSessionId || "";
|
|
11238
|
+
let cwd = "";
|
|
11239
|
+
let firstTs = "";
|
|
11240
|
+
let lastTs = "";
|
|
11241
|
+
const observations = [];
|
|
11242
|
+
for (const entry of entries) {
|
|
11243
|
+
if (entry.sessionId && !sessionId) sessionId = entry.sessionId;
|
|
11244
|
+
if (entry.cwd && !cwd) cwd = entry.cwd;
|
|
11245
|
+
const ts = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
11246
|
+
if (!firstTs) firstTs = ts;
|
|
11247
|
+
lastTs = ts;
|
|
11248
|
+
const role = entry.message?.role;
|
|
11249
|
+
const content = entry.message?.content;
|
|
11250
|
+
if (entry.type === "user" && role === "user") {
|
|
11251
|
+
const toolResults = extractToolResults(content);
|
|
11252
|
+
if (toolResults.length > 0) for (const result of toolResults) observations.push({
|
|
11253
|
+
id: generateId("obs"),
|
|
11254
|
+
sessionId: sessionId || "imported",
|
|
11255
|
+
timestamp: ts,
|
|
11256
|
+
hookType: result.isError ? "post_tool_failure" : "post_tool_use",
|
|
11257
|
+
toolName: void 0,
|
|
11258
|
+
toolInput: { toolUseId: result.toolUseId },
|
|
11259
|
+
toolOutput: result.output,
|
|
11260
|
+
raw: entry
|
|
11261
|
+
});
|
|
11262
|
+
else {
|
|
11263
|
+
const text = toText(content);
|
|
11264
|
+
if (text.trim().length > 0) observations.push({
|
|
11265
|
+
id: generateId("obs"),
|
|
11266
|
+
sessionId: sessionId || "imported",
|
|
11267
|
+
timestamp: ts,
|
|
11268
|
+
hookType: "prompt_submit",
|
|
11269
|
+
userPrompt: text,
|
|
11270
|
+
raw: entry
|
|
11271
|
+
});
|
|
11272
|
+
}
|
|
11273
|
+
} else if (entry.type === "assistant" && role === "assistant") {
|
|
11274
|
+
const text = toText(content);
|
|
11275
|
+
const tools = extractToolUses(content);
|
|
11276
|
+
if (text.trim().length > 0) observations.push({
|
|
11277
|
+
id: generateId("obs"),
|
|
11278
|
+
sessionId: sessionId || "imported",
|
|
11279
|
+
timestamp: ts,
|
|
11280
|
+
hookType: "stop",
|
|
11281
|
+
assistantResponse: text,
|
|
11282
|
+
raw: entry
|
|
11283
|
+
});
|
|
11284
|
+
for (const tool of tools) observations.push({
|
|
11285
|
+
id: generateId("obs"),
|
|
11286
|
+
sessionId: sessionId || "imported",
|
|
11287
|
+
timestamp: ts,
|
|
11288
|
+
hookType: "pre_tool_use",
|
|
11289
|
+
toolName: tool.name,
|
|
11290
|
+
toolInput: tool.input,
|
|
11291
|
+
raw: {
|
|
11292
|
+
toolUseId: tool.id,
|
|
11293
|
+
entry
|
|
11294
|
+
}
|
|
11295
|
+
});
|
|
11296
|
+
} else if (entry.type === "summary" || entry.type === "system") {}
|
|
11297
|
+
}
|
|
11298
|
+
const effectiveSessionId = sessionId || generateId("sess");
|
|
11299
|
+
for (const obs of observations) if (obs.sessionId === "imported") obs.sessionId = effectiveSessionId;
|
|
11300
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
11301
|
+
return {
|
|
11302
|
+
sessionId: effectiveSessionId,
|
|
11303
|
+
project: deriveProject(cwd),
|
|
11304
|
+
cwd: cwd || process.cwd(),
|
|
11305
|
+
startedAt: firstTs || nowIso,
|
|
11306
|
+
endedAt: lastTs || nowIso,
|
|
11307
|
+
observations
|
|
11308
|
+
};
|
|
11309
|
+
}
|
|
11310
|
+
|
|
11311
|
+
//#endregion
|
|
11312
|
+
//#region src/replay/timeline.ts
|
|
11313
|
+
const DEFAULT_CHARS_PER_SEC = 40;
|
|
11314
|
+
const MIN_EVENT_MS = 300;
|
|
11315
|
+
const MAX_EVENT_MS = 2e4;
|
|
11316
|
+
function kindFromHook(obs) {
|
|
11317
|
+
switch (obs.hookType) {
|
|
11318
|
+
case "session_start": return "session_start";
|
|
11319
|
+
case "session_end": return "session_end";
|
|
11320
|
+
case "prompt_submit": return "prompt";
|
|
11321
|
+
case "stop": return obs.assistantResponse ? "response" : "hook";
|
|
11322
|
+
case "pre_tool_use": return "tool_call";
|
|
11323
|
+
case "post_tool_use": return "tool_result";
|
|
11324
|
+
case "post_tool_failure": return "tool_error";
|
|
11325
|
+
default: return "hook";
|
|
11326
|
+
}
|
|
11327
|
+
}
|
|
11328
|
+
function labelFor(obs, kind) {
|
|
11329
|
+
switch (kind) {
|
|
11330
|
+
case "prompt": return truncate(obs.userPrompt || "User prompt", 80);
|
|
11331
|
+
case "response": return truncate(obs.assistantResponse || "Assistant response", 80);
|
|
11332
|
+
case "tool_call": return `${obs.toolName || "tool"} ▸ call`;
|
|
11333
|
+
case "tool_result": return `${obs.toolName || "tool"} ▸ result`;
|
|
11334
|
+
case "tool_error": return `${obs.toolName || "tool"} ▸ error`;
|
|
11335
|
+
case "session_start": return "Session start";
|
|
11336
|
+
case "session_end": return "Session end";
|
|
11337
|
+
default: return obs.hookType;
|
|
11338
|
+
}
|
|
11339
|
+
}
|
|
11340
|
+
function truncate(text, max) {
|
|
11341
|
+
if (text.length <= max) return text;
|
|
11342
|
+
return text.slice(0, max - 1) + "…";
|
|
11343
|
+
}
|
|
11344
|
+
function bodyFor(obs, kind) {
|
|
11345
|
+
if (kind === "prompt") return obs.userPrompt;
|
|
11346
|
+
if (kind === "response") return obs.assistantResponse;
|
|
11347
|
+
}
|
|
11348
|
+
function estimateDurationMs(ev) {
|
|
11349
|
+
const chars = (ev.body?.length || 0) + (typeof ev.toolInput === "string" ? ev.toolInput.length : 0) + (typeof ev.toolOutput === "string" ? ev.toolOutput.length : 0);
|
|
11350
|
+
if (chars === 0) return MIN_EVENT_MS;
|
|
11351
|
+
const ms = Math.round(chars / DEFAULT_CHARS_PER_SEC * 1e3);
|
|
11352
|
+
return Math.max(MIN_EVENT_MS, Math.min(MAX_EVENT_MS, ms));
|
|
11353
|
+
}
|
|
11354
|
+
function projectTimeline(observations) {
|
|
11355
|
+
if (observations.length === 0) {
|
|
11356
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11357
|
+
return {
|
|
11358
|
+
sessionId: "",
|
|
11359
|
+
startedAt: now,
|
|
11360
|
+
endedAt: now,
|
|
11361
|
+
totalDurationMs: 0,
|
|
11362
|
+
eventCount: 0,
|
|
11363
|
+
events: []
|
|
11364
|
+
};
|
|
11365
|
+
}
|
|
11366
|
+
const sorted = [...observations].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
11367
|
+
const startedAt = sorted[0].timestamp;
|
|
11368
|
+
const startMs = Date.parse(startedAt);
|
|
11369
|
+
const events = [];
|
|
11370
|
+
let syntheticOffset = 0;
|
|
11371
|
+
const allSameTs = sorted.every((o) => o.timestamp === startedAt);
|
|
11372
|
+
for (const obs of sorted) {
|
|
11373
|
+
const kind = kindFromHook(obs);
|
|
11374
|
+
const body = bodyFor(obs, kind);
|
|
11375
|
+
const obsMs = Date.parse(obs.timestamp);
|
|
11376
|
+
const offsetMs = allSameTs ? syntheticOffset : Number.isFinite(obsMs) && Number.isFinite(startMs) ? Math.max(0, obsMs - startMs) : syntheticOffset;
|
|
11377
|
+
const event = {
|
|
11378
|
+
id: obs.id,
|
|
11379
|
+
sessionId: obs.sessionId,
|
|
11380
|
+
ts: obs.timestamp,
|
|
11381
|
+
offsetMs,
|
|
11382
|
+
durationMs: 0,
|
|
11383
|
+
kind,
|
|
11384
|
+
label: labelFor(obs, kind),
|
|
11385
|
+
body,
|
|
11386
|
+
toolName: obs.toolName,
|
|
11387
|
+
toolInput: obs.toolInput,
|
|
11388
|
+
toolOutput: obs.toolOutput
|
|
11389
|
+
};
|
|
11390
|
+
event.durationMs = estimateDurationMs(event);
|
|
11391
|
+
events.push(event);
|
|
11392
|
+
syntheticOffset += event.durationMs;
|
|
11393
|
+
}
|
|
11394
|
+
const last = events[events.length - 1];
|
|
11395
|
+
const totalDurationMs = last.offsetMs + last.durationMs;
|
|
11396
|
+
return {
|
|
11397
|
+
sessionId: sorted[0].sessionId,
|
|
11398
|
+
startedAt,
|
|
11399
|
+
endedAt: sorted[sorted.length - 1].timestamp,
|
|
11400
|
+
totalDurationMs,
|
|
11401
|
+
eventCount: events.length,
|
|
11402
|
+
events
|
|
11403
|
+
};
|
|
11404
|
+
}
|
|
11405
|
+
|
|
11406
|
+
//#endregion
|
|
11407
|
+
//#region src/functions/replay.ts
|
|
11408
|
+
const SENSITIVE_PATH_PATTERNS = [
|
|
11409
|
+
/(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
|
|
11410
|
+
/(^|[\\/_.-])credentials?([\\/_.-]|$)/i,
|
|
11411
|
+
/(^|[\\/_.-])private[_-]?key([\\/_.-]|$)/i,
|
|
11412
|
+
/(^|[\\/])\.env(\.[\w-]+)?$/i,
|
|
11413
|
+
/(^|[\\/_.-])id_rsa([\\/_.-]|$)/i,
|
|
11414
|
+
/(^|[\\/])auth[_-]?token([\\/_.-]|$)/i,
|
|
11415
|
+
/(^|[\\/])bearer[_-]?token([\\/_.-]|$)/i,
|
|
11416
|
+
/(^|[\\/])access[_-]?token([\\/_.-]|$)/i,
|
|
11417
|
+
/(^|[\\/])api[_-]?token([\\/_.-]|$)/i
|
|
11418
|
+
];
|
|
11419
|
+
function isSensitive(path) {
|
|
11420
|
+
return SENSITIVE_PATH_PATTERNS.some((re) => re.test(path));
|
|
11421
|
+
}
|
|
11422
|
+
async function isSymlink(path) {
|
|
11423
|
+
try {
|
|
11424
|
+
return (await lstat(path)).isSymbolicLink();
|
|
11425
|
+
} catch {
|
|
11426
|
+
return false;
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
function rawFromCompressed(obs) {
|
|
11430
|
+
return {
|
|
11431
|
+
id: obs.id,
|
|
11432
|
+
sessionId: obs.sessionId,
|
|
11433
|
+
timestamp: obs.timestamp,
|
|
11434
|
+
hookType: "post_tool_use",
|
|
11435
|
+
toolName: void 0,
|
|
11436
|
+
toolInput: void 0,
|
|
11437
|
+
toolOutput: void 0,
|
|
11438
|
+
userPrompt: obs.type === "conversation" ? obs.narrative : void 0,
|
|
11439
|
+
assistantResponse: void 0,
|
|
11440
|
+
raw: {
|
|
11441
|
+
title: obs.title,
|
|
11442
|
+
narrative: obs.narrative,
|
|
11443
|
+
facts: obs.facts
|
|
11444
|
+
}
|
|
11445
|
+
};
|
|
11446
|
+
}
|
|
11447
|
+
function isRawShape(o) {
|
|
11448
|
+
if (!o || typeof o !== "object") return false;
|
|
11449
|
+
return typeof o.hookType === "string";
|
|
11450
|
+
}
|
|
11451
|
+
async function loadObservations(kv, sessionId) {
|
|
11452
|
+
return (await kv.list(KV.observations(sessionId))).map((r) => isRawShape(r) ? r : rawFromCompressed(r));
|
|
11453
|
+
}
|
|
11454
|
+
async function findJsonlFiles(root, limit = 200) {
|
|
11455
|
+
const out = [];
|
|
11456
|
+
async function walk(dir) {
|
|
11457
|
+
if (out.length >= limit) return;
|
|
11458
|
+
let names;
|
|
11459
|
+
try {
|
|
11460
|
+
names = await readdir(dir);
|
|
11461
|
+
} catch {
|
|
11462
|
+
return;
|
|
11463
|
+
}
|
|
11464
|
+
for (const name of names) {
|
|
11465
|
+
if (out.length >= limit) return;
|
|
11466
|
+
const full = join(dir, name);
|
|
11467
|
+
let st;
|
|
11468
|
+
try {
|
|
11469
|
+
st = await lstat(full);
|
|
11470
|
+
} catch {
|
|
11471
|
+
continue;
|
|
11472
|
+
}
|
|
11473
|
+
if (st.isSymbolicLink()) continue;
|
|
11474
|
+
if (st.isDirectory()) await walk(full);
|
|
11475
|
+
else if (st.isFile() && name.endsWith(".jsonl")) out.push(full);
|
|
11476
|
+
}
|
|
11477
|
+
}
|
|
11478
|
+
await walk(root);
|
|
11479
|
+
return out;
|
|
11480
|
+
}
|
|
11481
|
+
function registerReplayFunctions(sdk, kv) {
|
|
11482
|
+
sdk.registerFunction("mem::replay::load", async (data) => {
|
|
11483
|
+
if (!data?.sessionId || typeof data.sessionId !== "string") return {
|
|
11484
|
+
success: false,
|
|
11485
|
+
error: "sessionId is required"
|
|
11486
|
+
};
|
|
11487
|
+
const session = await kv.get(KV.sessions, data.sessionId);
|
|
11488
|
+
return {
|
|
11489
|
+
success: true,
|
|
11490
|
+
timeline: projectTimeline(await loadObservations(kv, data.sessionId)),
|
|
11491
|
+
session
|
|
11492
|
+
};
|
|
11493
|
+
});
|
|
11494
|
+
sdk.registerFunction("mem::replay::sessions", async () => {
|
|
11495
|
+
const sessions = await kv.list(KV.sessions);
|
|
11496
|
+
sessions.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
|
|
11497
|
+
return {
|
|
11498
|
+
success: true,
|
|
11499
|
+
sessions
|
|
11500
|
+
};
|
|
11501
|
+
});
|
|
11502
|
+
sdk.registerFunction("mem::replay::import-jsonl", async (data = {}) => {
|
|
11503
|
+
const defaultRoot = join(homedir(), ".claude", "projects");
|
|
11504
|
+
const rawPath = data.path || defaultRoot;
|
|
11505
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) return {
|
|
11506
|
+
success: false,
|
|
11507
|
+
error: "path must be a non-empty string"
|
|
11508
|
+
};
|
|
11509
|
+
const abs = resolve(rawPath.startsWith("~") ? join(homedir(), rawPath.slice(1)) : rawPath);
|
|
11510
|
+
if (isSensitive(abs)) return {
|
|
11511
|
+
success: false,
|
|
11512
|
+
error: "refusing to process sensitive-looking path"
|
|
11513
|
+
};
|
|
11514
|
+
if (await isSymlink(abs)) return {
|
|
11515
|
+
success: false,
|
|
11516
|
+
error: "symlinks are not supported"
|
|
11517
|
+
};
|
|
11518
|
+
let stat;
|
|
11519
|
+
try {
|
|
11520
|
+
stat = await lstat(abs);
|
|
11521
|
+
} catch {
|
|
11522
|
+
return {
|
|
11523
|
+
success: false,
|
|
11524
|
+
error: "path not found"
|
|
11525
|
+
};
|
|
11526
|
+
}
|
|
11527
|
+
let files = [];
|
|
11528
|
+
if (stat.isDirectory()) files = await findJsonlFiles(abs, data.maxFiles || 200);
|
|
11529
|
+
else if (stat.isFile() && abs.endsWith(".jsonl")) files = [abs];
|
|
11530
|
+
else return {
|
|
11531
|
+
success: false,
|
|
11532
|
+
error: "path must be a .jsonl file or directory"
|
|
11533
|
+
};
|
|
11534
|
+
if (files.length === 0) return {
|
|
11535
|
+
success: true,
|
|
11536
|
+
imported: 0,
|
|
11537
|
+
sessionIds: [],
|
|
11538
|
+
observations: 0
|
|
11539
|
+
};
|
|
11540
|
+
const sessionIds = [];
|
|
11541
|
+
let observationCount = 0;
|
|
11542
|
+
for (const file of files) {
|
|
11543
|
+
if (isSensitive(file)) continue;
|
|
11544
|
+
if (await isSymlink(file)) continue;
|
|
11545
|
+
let text;
|
|
11546
|
+
try {
|
|
11547
|
+
text = await readFile(file, "utf-8");
|
|
11548
|
+
} catch (err) {
|
|
11549
|
+
logger.warn("replay: failed to read jsonl", {
|
|
11550
|
+
file,
|
|
11551
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11552
|
+
});
|
|
11553
|
+
continue;
|
|
11554
|
+
}
|
|
11555
|
+
const parsed = parseJsonlText(text, generateId("sess"));
|
|
11556
|
+
if (parsed.observations.length === 0) continue;
|
|
11557
|
+
const existing = await kv.get(KV.sessions, parsed.sessionId);
|
|
11558
|
+
if (existing) {
|
|
11559
|
+
existing.observationCount = (existing.observationCount || 0) + parsed.observations.length;
|
|
11560
|
+
if (parsed.endedAt > (existing.endedAt || "")) existing.endedAt = parsed.endedAt;
|
|
11561
|
+
if (existing.status === "active") existing.status = "completed";
|
|
11562
|
+
const existingTags = existing.tags || [];
|
|
11563
|
+
if (!existingTags.includes("jsonl-import")) existing.tags = [...existingTags, "jsonl-import"];
|
|
11564
|
+
await kv.set(KV.sessions, existing.id, existing);
|
|
11565
|
+
} else {
|
|
11566
|
+
const session = {
|
|
11567
|
+
id: parsed.sessionId,
|
|
11568
|
+
project: parsed.project,
|
|
11569
|
+
cwd: parsed.cwd,
|
|
11570
|
+
startedAt: parsed.startedAt,
|
|
11571
|
+
endedAt: parsed.endedAt,
|
|
11572
|
+
status: "completed",
|
|
11573
|
+
observationCount: parsed.observations.length,
|
|
11574
|
+
tags: ["jsonl-import"]
|
|
11575
|
+
};
|
|
11576
|
+
await kv.set(KV.sessions, session.id, session);
|
|
11577
|
+
}
|
|
11578
|
+
await Promise.all(parsed.observations.map((obs) => kv.set(KV.observations(parsed.sessionId), obs.id, obs)));
|
|
11579
|
+
observationCount += parsed.observations.length;
|
|
11580
|
+
sessionIds.push(parsed.sessionId);
|
|
11581
|
+
}
|
|
11582
|
+
await safeAudit(kv, "import", "mem::replay::import-jsonl", sessionIds, {
|
|
11583
|
+
source: "jsonl",
|
|
11584
|
+
path: abs,
|
|
11585
|
+
files: files.length,
|
|
11586
|
+
observations: observationCount
|
|
11587
|
+
});
|
|
11588
|
+
return {
|
|
11589
|
+
success: true,
|
|
11590
|
+
imported: files.length,
|
|
11591
|
+
sessionIds,
|
|
11592
|
+
observations: observationCount
|
|
11593
|
+
};
|
|
11594
|
+
});
|
|
11595
|
+
}
|
|
11596
|
+
|
|
11130
11597
|
//#endregion
|
|
11131
11598
|
//#region src/health/thresholds.ts
|
|
11132
11599
|
const DEFAULTS = {
|
|
@@ -11135,7 +11602,8 @@ const DEFAULTS = {
|
|
|
11135
11602
|
cpuWarnPercent: 80,
|
|
11136
11603
|
cpuCriticalPercent: 90,
|
|
11137
11604
|
memoryWarnPercent: 80,
|
|
11138
|
-
memoryCriticalPercent: 95
|
|
11605
|
+
memoryCriticalPercent: 95,
|
|
11606
|
+
memoryRssFloorBytes: 512 * 1024 * 1024
|
|
11139
11607
|
};
|
|
11140
11608
|
function evaluateHealth(snapshot, config = {}) {
|
|
11141
11609
|
const cfg = {
|
|
@@ -11167,13 +11635,16 @@ function evaluateHealth(snapshot, config = {}) {
|
|
|
11167
11635
|
degraded = true;
|
|
11168
11636
|
}
|
|
11169
11637
|
const memPercent = snapshot.memory.heapTotal > 0 ? snapshot.memory.heapUsed / snapshot.memory.heapTotal * 100 : 0;
|
|
11170
|
-
|
|
11171
|
-
|
|
11638
|
+
const rss = snapshot.memory.rss ?? 0;
|
|
11639
|
+
const rssAboveFloor = rss >= cfg.memoryRssFloorBytes;
|
|
11640
|
+
const memMb = Math.round(rss / (1024 * 1024));
|
|
11641
|
+
if (memPercent > cfg.memoryCriticalPercent && rssAboveFloor) {
|
|
11642
|
+
alerts.push(`memory_critical_${Math.round(memPercent)}%_rss${memMb}mb`);
|
|
11172
11643
|
critical = true;
|
|
11173
|
-
} else if (memPercent > cfg.memoryWarnPercent) {
|
|
11174
|
-
alerts.push(`memory_warn_${Math.round(memPercent)}
|
|
11644
|
+
} else if (memPercent > cfg.memoryWarnPercent && rssAboveFloor) {
|
|
11645
|
+
alerts.push(`memory_warn_${Math.round(memPercent)}%_rss${memMb}mb`);
|
|
11175
11646
|
degraded = true;
|
|
11176
|
-
}
|
|
11647
|
+
} else if (memPercent > cfg.memoryWarnPercent) alerts.push(`memory_heap_tight_${Math.round(memPercent)}%_rss${memMb}mb`);
|
|
11177
11648
|
return {
|
|
11178
11649
|
status: critical ? "critical" : degraded ? "degraded" : "healthy",
|
|
11179
11650
|
alerts
|
|
@@ -11295,6 +11766,7 @@ function buildViewerCsp(nonce) {
|
|
|
11295
11766
|
|
|
11296
11767
|
//#endregion
|
|
11297
11768
|
//#region src/viewer/document.ts
|
|
11769
|
+
const VIEWER_VERSION_PLACEHOLDER = "__AGENTMEMORY_VERSION__";
|
|
11298
11770
|
function loadViewerTemplate() {
|
|
11299
11771
|
const base = dirname(fileURLToPath(import.meta.url));
|
|
11300
11772
|
const candidates = [
|
|
@@ -11313,7 +11785,7 @@ function renderViewerDocument() {
|
|
|
11313
11785
|
const nonce = createViewerNonce();
|
|
11314
11786
|
return {
|
|
11315
11787
|
found: true,
|
|
11316
|
-
html: template.replaceAll(VIEWER_NONCE_PLACEHOLDER, nonce),
|
|
11788
|
+
html: template.replaceAll(VIEWER_NONCE_PLACEHOLDER, nonce).replaceAll(VIEWER_VERSION_PLACEHOLDER, VERSION),
|
|
11317
11789
|
csp: buildViewerCsp(nonce)
|
|
11318
11790
|
};
|
|
11319
11791
|
}
|
|
@@ -11568,6 +12040,81 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
11568
12040
|
http_method: "POST"
|
|
11569
12041
|
}
|
|
11570
12042
|
});
|
|
12043
|
+
sdk.registerFunction("api::replay::load", async (req) => {
|
|
12044
|
+
const authErr = checkAuth(req, secret);
|
|
12045
|
+
if (authErr) return authErr;
|
|
12046
|
+
const sessionId = asNonEmptyString$1(req.query_params?.["sessionId"]);
|
|
12047
|
+
if (!sessionId) return {
|
|
12048
|
+
status_code: 400,
|
|
12049
|
+
body: { error: "sessionId is required" }
|
|
12050
|
+
};
|
|
12051
|
+
return {
|
|
12052
|
+
status_code: 200,
|
|
12053
|
+
body: await sdk.trigger({
|
|
12054
|
+
function_id: "mem::replay::load",
|
|
12055
|
+
payload: { sessionId }
|
|
12056
|
+
})
|
|
12057
|
+
};
|
|
12058
|
+
});
|
|
12059
|
+
sdk.registerTrigger({
|
|
12060
|
+
type: "http",
|
|
12061
|
+
function_id: "api::replay::load",
|
|
12062
|
+
config: {
|
|
12063
|
+
api_path: "/agentmemory/replay/load",
|
|
12064
|
+
http_method: "GET"
|
|
12065
|
+
}
|
|
12066
|
+
});
|
|
12067
|
+
sdk.registerFunction("api::replay::sessions", async (req) => {
|
|
12068
|
+
const authErr = checkAuth(req, secret);
|
|
12069
|
+
if (authErr) return authErr;
|
|
12070
|
+
return {
|
|
12071
|
+
status_code: 200,
|
|
12072
|
+
body: await sdk.trigger({ function_id: "mem::replay::sessions" })
|
|
12073
|
+
};
|
|
12074
|
+
});
|
|
12075
|
+
sdk.registerTrigger({
|
|
12076
|
+
type: "http",
|
|
12077
|
+
function_id: "api::replay::sessions",
|
|
12078
|
+
config: {
|
|
12079
|
+
api_path: "/agentmemory/replay/sessions",
|
|
12080
|
+
http_method: "GET"
|
|
12081
|
+
}
|
|
12082
|
+
});
|
|
12083
|
+
sdk.registerFunction("api::replay::import", async (req) => {
|
|
12084
|
+
const authErr = checkAuth(req, secret);
|
|
12085
|
+
if (authErr) return authErr;
|
|
12086
|
+
const body = req.body ?? {};
|
|
12087
|
+
const payload = {};
|
|
12088
|
+
if (body.path !== void 0) {
|
|
12089
|
+
if (typeof body.path !== "string" || body.path.trim().length === 0) return {
|
|
12090
|
+
status_code: 400,
|
|
12091
|
+
body: { error: "path must be a non-empty string" }
|
|
12092
|
+
};
|
|
12093
|
+
payload.path = body.path.trim();
|
|
12094
|
+
}
|
|
12095
|
+
if (body.maxFiles !== void 0) {
|
|
12096
|
+
if (!Number.isInteger(body.maxFiles) || body.maxFiles < 1) return {
|
|
12097
|
+
status_code: 400,
|
|
12098
|
+
body: { error: "maxFiles must be a positive integer" }
|
|
12099
|
+
};
|
|
12100
|
+
payload.maxFiles = body.maxFiles;
|
|
12101
|
+
}
|
|
12102
|
+
return {
|
|
12103
|
+
status_code: 202,
|
|
12104
|
+
body: await sdk.trigger({
|
|
12105
|
+
function_id: "mem::replay::import-jsonl",
|
|
12106
|
+
payload
|
|
12107
|
+
})
|
|
12108
|
+
};
|
|
12109
|
+
});
|
|
12110
|
+
sdk.registerTrigger({
|
|
12111
|
+
type: "http",
|
|
12112
|
+
function_id: "api::replay::import",
|
|
12113
|
+
config: {
|
|
12114
|
+
api_path: "/agentmemory/replay/import-jsonl",
|
|
12115
|
+
http_method: "POST"
|
|
12116
|
+
}
|
|
12117
|
+
});
|
|
11571
12118
|
sdk.registerFunction("api::session::start", async (req) => {
|
|
11572
12119
|
const body = req.body ?? {};
|
|
11573
12120
|
const sessionId = asNonEmptyString$1(body.sessionId);
|
|
@@ -12475,6 +13022,54 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
12475
13022
|
http_method: "GET"
|
|
12476
13023
|
}
|
|
12477
13024
|
});
|
|
13025
|
+
sdk.registerFunction("api::semantic-list", async (req) => {
|
|
13026
|
+
const authErr = checkAuth(req, secret);
|
|
13027
|
+
if (authErr) return authErr;
|
|
13028
|
+
return {
|
|
13029
|
+
status_code: 200,
|
|
13030
|
+
body: { semantic: await kv.list(KV.semantic) }
|
|
13031
|
+
};
|
|
13032
|
+
});
|
|
13033
|
+
sdk.registerTrigger({
|
|
13034
|
+
type: "http",
|
|
13035
|
+
function_id: "api::semantic-list",
|
|
13036
|
+
config: {
|
|
13037
|
+
api_path: "/agentmemory/semantic",
|
|
13038
|
+
http_method: "GET"
|
|
13039
|
+
}
|
|
13040
|
+
});
|
|
13041
|
+
sdk.registerFunction("api::procedural-list", async (req) => {
|
|
13042
|
+
const authErr = checkAuth(req, secret);
|
|
13043
|
+
if (authErr) return authErr;
|
|
13044
|
+
return {
|
|
13045
|
+
status_code: 200,
|
|
13046
|
+
body: { procedural: await kv.list(KV.procedural) }
|
|
13047
|
+
};
|
|
13048
|
+
});
|
|
13049
|
+
sdk.registerTrigger({
|
|
13050
|
+
type: "http",
|
|
13051
|
+
function_id: "api::procedural-list",
|
|
13052
|
+
config: {
|
|
13053
|
+
api_path: "/agentmemory/procedural",
|
|
13054
|
+
http_method: "GET"
|
|
13055
|
+
}
|
|
13056
|
+
});
|
|
13057
|
+
sdk.registerFunction("api::relations-list", async (req) => {
|
|
13058
|
+
const authErr = checkAuth(req, secret);
|
|
13059
|
+
if (authErr) return authErr;
|
|
13060
|
+
return {
|
|
13061
|
+
status_code: 200,
|
|
13062
|
+
body: { relations: await kv.list(KV.relations) }
|
|
13063
|
+
};
|
|
13064
|
+
});
|
|
13065
|
+
sdk.registerTrigger({
|
|
13066
|
+
type: "http",
|
|
13067
|
+
function_id: "api::relations-list",
|
|
13068
|
+
config: {
|
|
13069
|
+
api_path: "/agentmemory/relations",
|
|
13070
|
+
http_method: "GET"
|
|
13071
|
+
}
|
|
13072
|
+
});
|
|
12478
13073
|
sdk.registerFunction("api::action-create", async (req) => {
|
|
12479
13074
|
const authErr = checkAuth(req, secret);
|
|
12480
13075
|
if (authErr) return authErr;
|
|
@@ -16759,6 +17354,7 @@ async function main() {
|
|
|
16759
17354
|
registerTemporalGraphFunctions(sdk, kv, provider);
|
|
16760
17355
|
registerRetentionFunctions(sdk, kv);
|
|
16761
17356
|
registerCompressFileFunction(sdk, kv, provider);
|
|
17357
|
+
registerReplayFunctions(sdk, kv);
|
|
16762
17358
|
console.log(`[agentmemory] v0.6 advanced retrieval: sliding-window, query-expansion, temporal-graph, retention-scoring`);
|
|
16763
17359
|
console.log(`[agentmemory] Orchestration layer: actions, frontier, leases, routines, signals, checkpoints, flow-compress, mesh, branch-aware, sentinels, sketches, crystallize, diagnostics, facets`);
|
|
16764
17360
|
const snapshotConfig = loadSnapshotConfig();
|
|
@@ -16798,7 +17394,7 @@ async function main() {
|
|
|
16798
17394
|
}
|
|
16799
17395
|
}
|
|
16800
17396
|
console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
16801
|
-
console.log(`[agentmemory] Endpoints:
|
|
17397
|
+
console.log(`[agentmemory] Endpoints: 107 REST + 44 MCP tools + 6 MCP resources + 3 MCP prompts`);
|
|
16802
17398
|
const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
|
|
16803
17399
|
const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
|
|
16804
17400
|
const consolidationIntervalMs = parseInt(process.env.CONSOLIDATION_INTERVAL_MS || "7200000", 10);
|