@agentmemory/agentmemory 0.9.26 → 0.9.28
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/AGENTS.md +6 -6
- package/README.md +104 -42
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +239 -170
- package/dist/cli.mjs.map +1 -1
- package/dist/config-ChC9EtgV.mjs +259 -0
- package/dist/config-ChC9EtgV.mjs.map +1 -0
- package/dist/{connect-bmZ5eqYN.mjs → connect-JnsJXc68.mjs} +136 -42
- package/dist/connect-JnsJXc68.mjs.map +1 -0
- package/dist/hooks/notification.mjs +2 -1
- package/dist/hooks/notification.mjs.map +1 -1
- package/dist/hooks/post-commit.mjs +2 -1
- package/dist/hooks/post-commit.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +2 -1
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +2 -1
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +2 -1
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +2 -1
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/prompt-submit.mjs +2 -1
- package/dist/hooks/prompt-submit.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +2 -1
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +2 -1
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +2 -1
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-start.mjs +2 -1
- package/dist/hooks/subagent-start.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +2 -1
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +2 -1
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/{image-refs-C7h9L5wx.mjs → image-refs-BKLmp_sD.mjs} +4 -22
- package/dist/image-refs-BKLmp_sD.mjs.map +1 -0
- package/dist/image-refs-C2YU7BIm.mjs +34 -0
- package/dist/image-refs-C2YU7BIm.mjs.map +1 -0
- package/dist/index.mjs +559 -205
- package/dist/index.mjs.map +1 -1
- package/dist/{image-store-Gpo2mgM9.mjs → keyed-mutex-DTHtcZrt.mjs} +14 -11
- package/dist/keyed-mutex-DTHtcZrt.mjs.map +1 -0
- package/dist/{logger-yHTcEBAI.mjs → logger-BEB7pCI9.mjs} +1 -1
- package/dist/{logger-yHTcEBAI.mjs.map → logger-BEB7pCI9.mjs.map} +1 -1
- package/dist/{schema-Dsr_V2Wp.mjs → schema-CNiCWzV9.mjs} +5 -1
- package/dist/schema-CNiCWzV9.mjs.map +1 -0
- package/dist/{src-u7kAEUC0.mjs → src-CzgoepGU.mjs} +482 -83
- package/dist/src-CzgoepGU.mjs.map +1 -0
- package/dist/{standalone-C1yPO519.mjs → standalone-YsZFFQBM.mjs} +8 -5
- package/dist/standalone-YsZFFQBM.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +6 -2
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-Dzxv9iUu.mjs → tools-registry-D3NEoe5m.mjs} +4 -254
- package/dist/tools-registry-D3NEoe5m.mjs.map +1 -0
- package/package.json +4 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/plugin.json +2 -2
- package/plugin/scripts/notification.mjs +2 -1
- package/plugin/scripts/notification.mjs.map +1 -1
- package/plugin/scripts/post-commit.mjs +2 -1
- package/plugin/scripts/post-commit.mjs.map +1 -1
- package/plugin/scripts/post-tool-failure.mjs +2 -1
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +2 -1
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +2 -1
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +2 -1
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/prompt-submit.mjs +2 -1
- package/plugin/scripts/prompt-submit.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +2 -1
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +2 -1
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +2 -1
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-start.mjs +2 -1
- package/plugin/scripts/subagent-start.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +2 -1
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +2 -1
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/plugin/skills/_shared/TROUBLESHOOTING.md +38 -0
- package/plugin/skills/agentmemory-agents/REFERENCE.md +28 -0
- package/plugin/skills/agentmemory-agents/SKILL.md +34 -0
- package/plugin/skills/agentmemory-architecture/SKILL.md +33 -0
- package/plugin/skills/agentmemory-config/REFERENCE.md +42 -0
- package/plugin/skills/agentmemory-config/SKILL.md +37 -0
- package/plugin/skills/agentmemory-hooks/REFERENCE.md +20 -0
- package/plugin/skills/agentmemory-hooks/SKILL.md +39 -0
- package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +65 -0
- package/plugin/skills/agentmemory-mcp-tools/SKILL.md +39 -0
- package/plugin/skills/agentmemory-rest-api/REFERENCE.md +129 -0
- package/plugin/skills/agentmemory-rest-api/SKILL.md +43 -0
- package/plugin/skills/commit-context/EXAMPLES.md +71 -0
- package/plugin/skills/commit-context/SKILL.md +53 -8
- package/plugin/skills/commit-history/EXAMPLES.md +71 -0
- package/plugin/skills/commit-history/SKILL.md +51 -9
- package/plugin/skills/forget/EXAMPLES.md +75 -0
- package/plugin/skills/forget/SKILL.md +54 -14
- package/plugin/skills/handoff/EXAMPLES.md +56 -0
- package/plugin/skills/handoff/SKILL.md +56 -9
- package/plugin/skills/recall/EXAMPLES.md +74 -0
- package/plugin/skills/recall/SKILL.md +50 -11
- package/plugin/skills/recap/EXAMPLES.md +60 -0
- package/plugin/skills/recap/SKILL.md +51 -13
- package/plugin/skills/remember/EXAMPLES.md +67 -0
- package/plugin/skills/remember/SKILL.md +53 -17
- package/plugin/skills/session-history/EXAMPLES.md +59 -0
- package/plugin/skills/session-history/SKILL.md +54 -11
- package/plugin/skills/write-agentmemory-skill/SKILL.md +40 -0
- package/dist/connect-bmZ5eqYN.mjs.map +0 -1
- package/dist/image-refs-C7h9L5wx.mjs.map +0 -1
- package/dist/image-store-Gpo2mgM9.mjs.map +0 -1
- package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
- package/dist/schema-Dsr_V2Wp.mjs.map +0 -1
- package/dist/src-u7kAEUC0.mjs.map +0 -1
- package/dist/standalone-C1yPO519.mjs.map +0 -1
- package/dist/tools-registry-Dzxv9iUu.mjs.map +0 -1
- package/dist/version-BMFYFFut.mjs +0 -6
- package/dist/version-BMFYFFut.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { TriggerAction, registerWorker } from "iii-sdk";
|
|
|
4
4
|
import { constants, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
|
+
import pc from "picocolors";
|
|
7
8
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
9
|
import Anthropic from "@anthropic-ai/sdk";
|
|
9
10
|
import { lstat, mkdir, open, readFile, readdir, stat, unlink, utimes, writeFile } from "node:fs/promises";
|
|
@@ -15,18 +16,6 @@ import { lookup } from "node:dns/promises";
|
|
|
15
16
|
import { isIP } from "node:net";
|
|
16
17
|
import { fileURLToPath } from "node:url";
|
|
17
18
|
import { createServer } from "node:http";
|
|
18
|
-
//#region \0rolldown/runtime.js
|
|
19
|
-
var __defProp = Object.defineProperty;
|
|
20
|
-
var __exportAll = (all, no_symbols) => {
|
|
21
|
-
let target = {};
|
|
22
|
-
for (var name in all) __defProp(target, name, {
|
|
23
|
-
get: all[name],
|
|
24
|
-
enumerable: true
|
|
25
|
-
});
|
|
26
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
27
|
-
return target;
|
|
28
|
-
};
|
|
29
|
-
//#endregion
|
|
30
19
|
//#region src/config.ts
|
|
31
20
|
function safeParseInt(value, fallback) {
|
|
32
21
|
if (!value) return fallback;
|
|
@@ -102,14 +91,14 @@ function detectProvider(env) {
|
|
|
102
91
|
};
|
|
103
92
|
}
|
|
104
93
|
if (!(env["AGENTMEMORY_ALLOW_AGENT_SDK"] === "true")) {
|
|
105
|
-
process.stderr.write("[agentmemory] No LLM provider key
|
|
94
|
+
process.stderr.write(pc.dim("[agentmemory] No LLM provider key set — running zero-LLM (BM25 + on-device embeddings). Set ANTHROPIC_API_KEY (or GEMINI/OPENAI/OPENROUTER/MINIMAX) in ~/.agentmemory/.env for LLM compression and summaries. Agent-SDK fallback stays off by default to avoid a Stop-hook recursion loop; opt in with AGENTMEMORY_AUTO_COMPRESS=true + AGENTMEMORY_ALLOW_AGENT_SDK=true.\n"));
|
|
106
95
|
return {
|
|
107
96
|
provider: "noop",
|
|
108
97
|
model: "noop",
|
|
109
98
|
maxTokens
|
|
110
99
|
};
|
|
111
100
|
}
|
|
112
|
-
process.stderr.write("[agentmemory] WARNING: agent-sdk fallback enabled via AGENTMEMORY_ALLOW_AGENT_SDK=true. This spawns @anthropic-ai/claude-agent-sdk child sessions that can trigger the Stop-hook recursion loop
|
|
101
|
+
process.stderr.write("[agentmemory] WARNING: agent-sdk fallback enabled via AGENTMEMORY_ALLOW_AGENT_SDK=true. This spawns @anthropic-ai/claude-agent-sdk child sessions that can trigger the Stop-hook recursion loop. A SDK-child env marker is set to block re-entry, but prefer setting a real API key in ~/.agentmemory/.env instead.\n");
|
|
113
102
|
return {
|
|
114
103
|
provider: "agent-sdk",
|
|
115
104
|
model: "claude-sonnet-4-20250514",
|
|
@@ -119,10 +108,12 @@ function detectProvider(env) {
|
|
|
119
108
|
function loadConfig() {
|
|
120
109
|
const env = getMergedEnv();
|
|
121
110
|
const provider = detectProvider(env);
|
|
111
|
+
const restPort = parseInt(env["III_REST_PORT"] || "3111", 10) || 3111;
|
|
112
|
+
const streamsPort = parseInt(env["III_STREAM_PORT"] || env["III_STREAMS_PORT"] || "", 10) || restPort + 1;
|
|
122
113
|
return {
|
|
123
|
-
engineUrl: env["III_ENGINE_URL"] ||
|
|
124
|
-
restPort
|
|
125
|
-
streamsPort
|
|
114
|
+
engineUrl: env["III_ENGINE_URL"] || `ws://localhost:${parseInt(env["III_ENGINE_PORT"] || "", 10) || restPort + 46023}`,
|
|
115
|
+
restPort,
|
|
116
|
+
streamsPort,
|
|
126
117
|
provider,
|
|
127
118
|
tokenBudget: safeParseInt(env["TOKEN_BUDGET"], 2e3),
|
|
128
119
|
maxObservationsPerSession: safeParseInt(env["MAX_OBS_PER_SESSION"], 500),
|
|
@@ -267,7 +258,7 @@ function loadFallbackConfig() {
|
|
|
267
258
|
const allowAgentSdk = env["AGENTMEMORY_ALLOW_AGENT_SDK"] === "true";
|
|
268
259
|
return { providers: raw.split(",").map((p) => p.trim()).filter((p) => Boolean(p) && VALID_PROVIDERS.has(p)).filter((p) => {
|
|
269
260
|
if (p === "agent-sdk" && !allowAgentSdk) {
|
|
270
|
-
process.stderr.write("[agentmemory] Ignoring FALLBACK_PROVIDERS entry 'agent-sdk' (AGENTMEMORY_ALLOW_AGENT_SDK is not 'true'). The agent-sdk fallback can spawn Claude Agent SDK child sessions that trigger the Stop-hook recursion loop
|
|
261
|
+
process.stderr.write("[agentmemory] Ignoring FALLBACK_PROVIDERS entry 'agent-sdk' (AGENTMEMORY_ALLOW_AGENT_SDK is not 'true'). The agent-sdk fallback can spawn Claude Agent SDK child sessions that trigger the Stop-hook recursion loop. Opt in explicitly with AGENTMEMORY_ALLOW_AGENT_SDK=true if this is intentional.\n");
|
|
271
262
|
return false;
|
|
272
263
|
}
|
|
273
264
|
return true;
|
|
@@ -1326,6 +1317,10 @@ const KV = {
|
|
|
1326
1317
|
claudeBridge: "mem:claude-bridge",
|
|
1327
1318
|
graphNodes: "mem:graph:nodes",
|
|
1328
1319
|
graphEdges: "mem:graph:edges",
|
|
1320
|
+
graphSnapshot: "mem:graph:snapshot",
|
|
1321
|
+
graphNameIndex: "mem:graph:name-index",
|
|
1322
|
+
graphEdgeKey: "mem:graph:edge-key",
|
|
1323
|
+
graphNodeDegree: "mem:graph:node-degree",
|
|
1329
1324
|
semantic: "mem:semantic",
|
|
1330
1325
|
procedural: "mem:procedural",
|
|
1331
1326
|
teamShared: (teamId) => `mem:team:${teamId}:shared`,
|
|
@@ -3482,6 +3477,12 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3482
3477
|
}
|
|
3483
3478
|
const projectFilter = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
|
|
3484
3479
|
const cwdFilter = typeof data.cwd === "string" && data.cwd.trim().length > 0 ? data.cwd.trim() : void 0;
|
|
3480
|
+
const isolated = isAgentScopeIsolated();
|
|
3481
|
+
const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
|
|
3482
|
+
const wildcardAgent = explicitAgentId === "*";
|
|
3483
|
+
const envAgentId = isolated ? getAgentId() : void 0;
|
|
3484
|
+
const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? envAgentId;
|
|
3485
|
+
if (isolated && !wildcardAgent && !explicitAgentId && !envAgentId) throw new Error("mem::search: AGENTMEMORY_AGENT_SCOPE=isolated is set but no agent id is available (env AGENT_ID unset and no explicit agentId in the call). Refusing to read cross-agent rows. Pass agentId: \"*\" to opt in to a wildcard read.");
|
|
3485
3486
|
const format = typeof data.format === "string" ? data.format : "full";
|
|
3486
3487
|
if (![
|
|
3487
3488
|
"full",
|
|
@@ -3497,7 +3498,7 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3497
3498
|
const count = await rebuildIndex(kv);
|
|
3498
3499
|
logger.info("Search index rebuilt", { entries: count });
|
|
3499
3500
|
}
|
|
3500
|
-
const filtering = !!(projectFilter || cwdFilter);
|
|
3501
|
+
const filtering = !!(projectFilter || cwdFilter || filterAgentId);
|
|
3501
3502
|
const fetchLimit = filtering ? Math.max(effectiveLimit * 10, 100) : effectiveLimit;
|
|
3502
3503
|
const results = idx.search(query, fetchLimit);
|
|
3503
3504
|
const sessionCache = /* @__PURE__ */ new Map();
|
|
@@ -3514,9 +3515,10 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3514
3515
|
memoryProjectCache.set(obsId, proj);
|
|
3515
3516
|
return proj;
|
|
3516
3517
|
};
|
|
3518
|
+
const earlyCap = filterAgentId ? fetchLimit : effectiveLimit;
|
|
3517
3519
|
const candidates = [];
|
|
3518
3520
|
for (const r of results) {
|
|
3519
|
-
if (candidates.length >=
|
|
3521
|
+
if (candidates.length >= earlyCap) break;
|
|
3520
3522
|
if (filtering) {
|
|
3521
3523
|
const s = await loadSession(r.sessionId);
|
|
3522
3524
|
if (s) {
|
|
@@ -3538,7 +3540,10 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3538
3540
|
const enriched = [];
|
|
3539
3541
|
for (let i = 0; i < candidates.length; i++) {
|
|
3540
3542
|
const obs = obsResults[i];
|
|
3541
|
-
if (obs)
|
|
3543
|
+
if (!obs) continue;
|
|
3544
|
+
if (filterAgentId !== void 0 && obs.agentId !== filterAgentId) continue;
|
|
3545
|
+
if (enriched.length >= effectiveLimit) break;
|
|
3546
|
+
enriched.push({
|
|
3542
3547
|
observation: obs,
|
|
3543
3548
|
score: candidates[i].score,
|
|
3544
3549
|
sessionId: candidates[i].sessionId
|
|
@@ -3623,6 +3628,74 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3623
3628
|
});
|
|
3624
3629
|
}
|
|
3625
3630
|
//#endregion
|
|
3631
|
+
//#region src/utils/image-store.ts
|
|
3632
|
+
const IMAGES_DIR = join(homedir(), ".agentmemory", "images");
|
|
3633
|
+
const DEFAULT_MAX_BYTES = 500 * 1024 * 1024;
|
|
3634
|
+
function getMaxBytes() {
|
|
3635
|
+
return Number(process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES) || DEFAULT_MAX_BYTES;
|
|
3636
|
+
}
|
|
3637
|
+
function isManagedImagePath(filePath) {
|
|
3638
|
+
const resolved = resolve(filePath);
|
|
3639
|
+
const normalizedImagesDir = resolve(IMAGES_DIR);
|
|
3640
|
+
return resolved.startsWith(normalizedImagesDir + sep) || resolved === normalizedImagesDir;
|
|
3641
|
+
}
|
|
3642
|
+
function contentHash(data) {
|
|
3643
|
+
return createHash("sha256").update(data).digest("hex");
|
|
3644
|
+
}
|
|
3645
|
+
async function saveImageToDisk(base64Data) {
|
|
3646
|
+
if (!base64Data) return {
|
|
3647
|
+
filePath: "",
|
|
3648
|
+
bytesWritten: 0
|
|
3649
|
+
};
|
|
3650
|
+
if (!existsSync(IMAGES_DIR)) await mkdir(IMAGES_DIR, { recursive: true });
|
|
3651
|
+
let cleanBase64 = base64Data;
|
|
3652
|
+
let ext = "png";
|
|
3653
|
+
if (base64Data.startsWith("data:image/")) {
|
|
3654
|
+
const commaIdx = base64Data.indexOf(",");
|
|
3655
|
+
if (commaIdx !== -1) {
|
|
3656
|
+
const meta = base64Data.substring(0, commaIdx);
|
|
3657
|
+
if (meta.includes("jpeg") || meta.includes("jpg")) ext = "jpg";
|
|
3658
|
+
else if (meta.includes("webp")) ext = "webp";
|
|
3659
|
+
else if (meta.includes("gif")) ext = "gif";
|
|
3660
|
+
cleanBase64 = base64Data.substring(commaIdx + 1);
|
|
3661
|
+
}
|
|
3662
|
+
} else if (base64Data.startsWith("/9j/")) ext = "jpg";
|
|
3663
|
+
const filePath = join(IMAGES_DIR, `${contentHash(cleanBase64)}.${ext}`);
|
|
3664
|
+
if (existsSync(filePath)) return {
|
|
3665
|
+
filePath,
|
|
3666
|
+
bytesWritten: 0
|
|
3667
|
+
};
|
|
3668
|
+
await writeFile(filePath, Buffer.from(cleanBase64, "base64"));
|
|
3669
|
+
return {
|
|
3670
|
+
filePath,
|
|
3671
|
+
bytesWritten: (await stat(filePath)).size
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3674
|
+
async function deleteImage(filePath) {
|
|
3675
|
+
if (!filePath) return { deletedBytes: 0 };
|
|
3676
|
+
if (!isManagedImagePath(filePath)) return { deletedBytes: 0 };
|
|
3677
|
+
try {
|
|
3678
|
+
if (existsSync(filePath)) {
|
|
3679
|
+
const size = (await stat(filePath)).size;
|
|
3680
|
+
await unlink(filePath);
|
|
3681
|
+
return { deletedBytes: size };
|
|
3682
|
+
}
|
|
3683
|
+
} catch (err) {
|
|
3684
|
+
console.error("[agentmemory] Failed to delete image context:", err);
|
|
3685
|
+
}
|
|
3686
|
+
return { deletedBytes: 0 };
|
|
3687
|
+
}
|
|
3688
|
+
/** Touch an image file to update its mtime (marking it as recently used for LRU eviction) */
|
|
3689
|
+
async function touchImage(filePath) {
|
|
3690
|
+
if (!filePath || !isManagedImagePath(filePath)) return;
|
|
3691
|
+
try {
|
|
3692
|
+
if (existsSync(filePath)) {
|
|
3693
|
+
const now = /* @__PURE__ */ new Date();
|
|
3694
|
+
await utimes(filePath, now, now);
|
|
3695
|
+
}
|
|
3696
|
+
} catch (err) {}
|
|
3697
|
+
}
|
|
3698
|
+
//#endregion
|
|
3626
3699
|
//#region src/functions/observe.ts
|
|
3627
3700
|
function extractImage(d) {
|
|
3628
3701
|
if (!d) return void 0;
|
|
@@ -3700,10 +3773,9 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3700
3773
|
const inheritedAgentId = existingSession ? existingSession.agentId : getAgentId();
|
|
3701
3774
|
if (inheritedAgentId) raw.agentId = inheritedAgentId;
|
|
3702
3775
|
if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) {
|
|
3703
|
-
const { saveImageToDisk } = await Promise.resolve().then(() => image_store_exports);
|
|
3704
3776
|
const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
|
|
3705
3777
|
raw.imageData = filePath;
|
|
3706
|
-
const { incrementImageRef } = await
|
|
3778
|
+
const { incrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
|
|
3707
3779
|
await incrementImageRef(kv, filePath);
|
|
3708
3780
|
sdk.trigger({
|
|
3709
3781
|
function_id: "mem::disk-size-delta",
|
|
@@ -3723,13 +3795,13 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3723
3795
|
try {
|
|
3724
3796
|
await kv.set(KV.observations(payload.sessionId), obsId, raw);
|
|
3725
3797
|
} catch (error) {
|
|
3726
|
-
if (raw.imageData) {
|
|
3727
|
-
const {
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3798
|
+
if (raw.imageData) try {
|
|
3799
|
+
const { decrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
|
|
3800
|
+
await decrementImageRef(kv, sdk, raw.imageData);
|
|
3801
|
+
} catch (rollbackError) {
|
|
3802
|
+
logger.error("Failed to roll back image ref after observation write failure", {
|
|
3803
|
+
imageRef: raw.imageData,
|
|
3804
|
+
error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
3733
3805
|
});
|
|
3734
3806
|
}
|
|
3735
3807
|
throw error;
|
|
@@ -3851,115 +3923,6 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3851
3923
|
});
|
|
3852
3924
|
}
|
|
3853
3925
|
//#endregion
|
|
3854
|
-
//#region src/utils/image-store.ts
|
|
3855
|
-
var image_store_exports = /* @__PURE__ */ __exportAll({
|
|
3856
|
-
IMAGES_DIR: () => IMAGES_DIR,
|
|
3857
|
-
deleteImage: () => deleteImage,
|
|
3858
|
-
getMaxBytes: () => getMaxBytes,
|
|
3859
|
-
isManagedImagePath: () => isManagedImagePath,
|
|
3860
|
-
saveImageToDisk: () => saveImageToDisk,
|
|
3861
|
-
touchImage: () => touchImage
|
|
3862
|
-
});
|
|
3863
|
-
const IMAGES_DIR = join(homedir(), ".agentmemory", "images");
|
|
3864
|
-
const DEFAULT_MAX_BYTES = 500 * 1024 * 1024;
|
|
3865
|
-
function getMaxBytes() {
|
|
3866
|
-
return Number(process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES) || DEFAULT_MAX_BYTES;
|
|
3867
|
-
}
|
|
3868
|
-
function isManagedImagePath(filePath) {
|
|
3869
|
-
const resolved = resolve(filePath);
|
|
3870
|
-
const normalizedImagesDir = resolve(IMAGES_DIR);
|
|
3871
|
-
return resolved.startsWith(normalizedImagesDir + sep) || resolved === normalizedImagesDir;
|
|
3872
|
-
}
|
|
3873
|
-
function contentHash(data) {
|
|
3874
|
-
return createHash("sha256").update(data).digest("hex");
|
|
3875
|
-
}
|
|
3876
|
-
async function saveImageToDisk(base64Data) {
|
|
3877
|
-
if (!base64Data) return {
|
|
3878
|
-
filePath: "",
|
|
3879
|
-
bytesWritten: 0
|
|
3880
|
-
};
|
|
3881
|
-
if (!existsSync(IMAGES_DIR)) await mkdir(IMAGES_DIR, { recursive: true });
|
|
3882
|
-
let cleanBase64 = base64Data;
|
|
3883
|
-
let ext = "png";
|
|
3884
|
-
if (base64Data.startsWith("data:image/")) {
|
|
3885
|
-
const commaIdx = base64Data.indexOf(",");
|
|
3886
|
-
if (commaIdx !== -1) {
|
|
3887
|
-
const meta = base64Data.substring(0, commaIdx);
|
|
3888
|
-
if (meta.includes("jpeg") || meta.includes("jpg")) ext = "jpg";
|
|
3889
|
-
else if (meta.includes("webp")) ext = "webp";
|
|
3890
|
-
else if (meta.includes("gif")) ext = "gif";
|
|
3891
|
-
cleanBase64 = base64Data.substring(commaIdx + 1);
|
|
3892
|
-
}
|
|
3893
|
-
} else if (base64Data.startsWith("/9j/")) ext = "jpg";
|
|
3894
|
-
const filePath = join(IMAGES_DIR, `${contentHash(cleanBase64)}.${ext}`);
|
|
3895
|
-
if (existsSync(filePath)) return {
|
|
3896
|
-
filePath,
|
|
3897
|
-
bytesWritten: 0
|
|
3898
|
-
};
|
|
3899
|
-
await writeFile(filePath, Buffer.from(cleanBase64, "base64"));
|
|
3900
|
-
return {
|
|
3901
|
-
filePath,
|
|
3902
|
-
bytesWritten: (await stat(filePath)).size
|
|
3903
|
-
};
|
|
3904
|
-
}
|
|
3905
|
-
async function deleteImage(filePath) {
|
|
3906
|
-
if (!filePath) return { deletedBytes: 0 };
|
|
3907
|
-
if (!isManagedImagePath(filePath)) return { deletedBytes: 0 };
|
|
3908
|
-
try {
|
|
3909
|
-
if (existsSync(filePath)) {
|
|
3910
|
-
const size = (await stat(filePath)).size;
|
|
3911
|
-
await unlink(filePath);
|
|
3912
|
-
return { deletedBytes: size };
|
|
3913
|
-
}
|
|
3914
|
-
} catch (err) {
|
|
3915
|
-
console.error("[agentmemory] Failed to delete image context:", err);
|
|
3916
|
-
}
|
|
3917
|
-
return { deletedBytes: 0 };
|
|
3918
|
-
}
|
|
3919
|
-
/** Touch an image file to update its mtime (marking it as recently used for LRU eviction) */
|
|
3920
|
-
async function touchImage(filePath) {
|
|
3921
|
-
if (!filePath || !isManagedImagePath(filePath)) return;
|
|
3922
|
-
try {
|
|
3923
|
-
if (existsSync(filePath)) {
|
|
3924
|
-
const now = /* @__PURE__ */ new Date();
|
|
3925
|
-
await utimes(filePath, now, now);
|
|
3926
|
-
}
|
|
3927
|
-
} catch (err) {}
|
|
3928
|
-
}
|
|
3929
|
-
//#endregion
|
|
3930
|
-
//#region src/functions/image-refs.ts
|
|
3931
|
-
var image_refs_exports = /* @__PURE__ */ __exportAll({
|
|
3932
|
-
decrementImageRef: () => decrementImageRef,
|
|
3933
|
-
getImageRefCount: () => getImageRefCount,
|
|
3934
|
-
incrementImageRef: () => incrementImageRef
|
|
3935
|
-
});
|
|
3936
|
-
async function getImageRefCount(kv, filePath) {
|
|
3937
|
-
const count = await kv.get(KV.imageRefs, filePath);
|
|
3938
|
-
return count ? Number(count) : 0;
|
|
3939
|
-
}
|
|
3940
|
-
async function incrementImageRef(kv, filePath) {
|
|
3941
|
-
return withKeyedLock(`imgRef:${filePath}`, async () => {
|
|
3942
|
-
const current = await getImageRefCount(kv, filePath);
|
|
3943
|
-
await kv.set(KV.imageRefs, filePath, current + 1);
|
|
3944
|
-
await touchImage(filePath);
|
|
3945
|
-
});
|
|
3946
|
-
}
|
|
3947
|
-
async function decrementImageRef(kv, sdk, filePath) {
|
|
3948
|
-
return withKeyedLock(`imgRef:${filePath}`, async () => {
|
|
3949
|
-
const current = await getImageRefCount(kv, filePath);
|
|
3950
|
-
if (current <= 1) {
|
|
3951
|
-
await kv.delete(KV.imageEmbeddings, filePath);
|
|
3952
|
-
await kv.delete(KV.imageRefs, filePath);
|
|
3953
|
-
const { deletedBytes } = await deleteImage(filePath);
|
|
3954
|
-
if (deletedBytes > 0) sdk.trigger({
|
|
3955
|
-
function_id: "mem::disk-size-delta",
|
|
3956
|
-
payload: { deltaBytes: -deletedBytes },
|
|
3957
|
-
action: TriggerAction.Void()
|
|
3958
|
-
});
|
|
3959
|
-
} else await kv.set(KV.imageRefs, filePath, current - 1);
|
|
3960
|
-
});
|
|
3961
|
-
}
|
|
3962
|
-
//#endregion
|
|
3963
3926
|
//#region src/functions/image-quota-cleanup.ts
|
|
3964
3927
|
const GRACE_PERIOD_MS = 3e4;
|
|
3965
3928
|
function registerImageQuotaCleanup(sdk, kv) {
|
|
@@ -4007,6 +3970,7 @@ function registerImageQuotaCleanup(sdk, kv) {
|
|
|
4007
3970
|
await withKeyedLock(`imgRef:${f.filePath}`, async () => {
|
|
4008
3971
|
let refCount;
|
|
4009
3972
|
try {
|
|
3973
|
+
const { getImageRefCount } = await import("./image-refs-BKLmp_sD.mjs");
|
|
4010
3974
|
refCount = await getImageRefCount(kv, f.filePath);
|
|
4011
3975
|
} catch (err) {
|
|
4012
3976
|
logger.error("Failed to read refCount; skipping eviction", {
|
|
@@ -5097,6 +5061,12 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
5097
5061
|
sdk.registerFunction("mem::context", async (data) => {
|
|
5098
5062
|
const budget = data.budget || tokenBudget;
|
|
5099
5063
|
const blocks = [];
|
|
5064
|
+
const isolated = isAgentScopeIsolated();
|
|
5065
|
+
const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
|
|
5066
|
+
const wildcardAgent = explicitAgentId === "*";
|
|
5067
|
+
const envAgentId = isolated ? getAgentId() : void 0;
|
|
5068
|
+
const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? envAgentId;
|
|
5069
|
+
if (isolated && !wildcardAgent && !explicitAgentId && !envAgentId) throw new Error("mem::context: AGENTMEMORY_AGENT_SCOPE=isolated is set but no agent id is available (env AGENT_ID unset and no explicit agentId in the call). Refusing to read cross-agent rows. Pass agentId: \"*\" to opt in to a wildcard read.");
|
|
5100
5070
|
const [pinnedSlots, profile, lessons] = await Promise.all([
|
|
5101
5071
|
isSlotsEnabled() ? listPinnedSlots(kv).catch(() => []) : Promise.resolve([]),
|
|
5102
5072
|
kv.get(KV.profiles, data.project).catch(() => null),
|
|
@@ -5143,7 +5113,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
5143
5113
|
sourceIds: relevantLessons.map((l) => l.id)
|
|
5144
5114
|
});
|
|
5145
5115
|
}
|
|
5146
|
-
const sessions = (await kv.list(KV.sessions)).filter((s) => s.project === data.project && s.id !== data.sessionId).sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).slice(0, 10);
|
|
5116
|
+
const sessions = (await kv.list(KV.sessions)).filter((s) => s.project === data.project && s.id !== data.sessionId && (filterAgentId === void 0 || s.agentId === filterAgentId)).sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).slice(0, 10);
|
|
5147
5117
|
const summariesPerSession = await Promise.all(sessions.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
|
|
5148
5118
|
const sessionsNeedingObs = [];
|
|
5149
5119
|
for (let i = 0; i < sessions.length; i++) {
|
|
@@ -6133,7 +6103,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
6133
6103
|
const deletedMemoryIds = [];
|
|
6134
6104
|
const deletedObservationIds = [];
|
|
6135
6105
|
let deletedSession = false;
|
|
6136
|
-
const { decrementImageRef } = await
|
|
6106
|
+
const { decrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
|
|
6137
6107
|
if (data.memoryId) {
|
|
6138
6108
|
const mem = await kv.get(KV.memories, data.memoryId);
|
|
6139
6109
|
await kv.delete(KV.memories, data.memoryId);
|
|
@@ -6240,7 +6210,7 @@ async function runRecoveredSessionConsolidation(sdk) {
|
|
|
6240
6210
|
function registerEvictFunction(sdk, kv) {
|
|
6241
6211
|
sdk.registerFunction("mem::evict", async (data) => {
|
|
6242
6212
|
const dryRun = data?.dryRun ?? false;
|
|
6243
|
-
const { decrementImageRef } = await
|
|
6213
|
+
const { decrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
|
|
6244
6214
|
const configOverride = await kv.get(KV.config, "eviction").catch(() => null);
|
|
6245
6215
|
const cfg = {
|
|
6246
6216
|
...DEFAULTS$1,
|
|
@@ -6688,7 +6658,7 @@ async function findByKeyword(kv, keyword, project) {
|
|
|
6688
6658
|
}
|
|
6689
6659
|
//#endregion
|
|
6690
6660
|
//#region src/version.ts
|
|
6691
|
-
const VERSION = "0.9.
|
|
6661
|
+
const VERSION = "0.9.28";
|
|
6692
6662
|
//#endregion
|
|
6693
6663
|
//#region src/telemetry/setup.ts
|
|
6694
6664
|
const OTEL_CONFIG = {
|
|
@@ -6763,7 +6733,10 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
6763
6733
|
sdk.registerFunction("mem::smart-search", async (data) => {
|
|
6764
6734
|
const isolated = isAgentScopeIsolated();
|
|
6765
6735
|
const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
|
|
6766
|
-
const
|
|
6736
|
+
const wildcardAgent = explicitAgentId === "*";
|
|
6737
|
+
const envAgentId = isolated ? getAgentId() : void 0;
|
|
6738
|
+
const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? envAgentId;
|
|
6739
|
+
if (isolated && !wildcardAgent && !explicitAgentId && !envAgentId) throw new Error("mem::smart-search: AGENTMEMORY_AGENT_SCOPE=isolated is set but no agent id is available (env AGENT_ID unset and no explicit agentId in the call). Refusing to read cross-agent rows. Pass agentId: \"*\" to opt in to a wildcard read.");
|
|
6767
6740
|
if (data.expandIds && data.expandIds.length > 0) {
|
|
6768
6741
|
const raw = data.expandIds.slice(0, 20);
|
|
6769
6742
|
const items = raw.map((entry) => {
|
|
@@ -7055,7 +7028,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
7055
7028
|
sdk.registerFunction("mem::auto-forget", async (data) => {
|
|
7056
7029
|
const dryRun = data?.dryRun ?? false;
|
|
7057
7030
|
const now = Date.now();
|
|
7058
|
-
const { decrementImageRef } = await
|
|
7031
|
+
const { decrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
|
|
7059
7032
|
const result = {
|
|
7060
7033
|
ttlExpired: [],
|
|
7061
7034
|
contradictions: [],
|
|
@@ -7314,7 +7287,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
7314
7287
|
"0.9.23",
|
|
7315
7288
|
"0.9.24",
|
|
7316
7289
|
"0.9.25",
|
|
7317
|
-
"0.9.26"
|
|
7290
|
+
"0.9.26",
|
|
7291
|
+
"0.9.27",
|
|
7292
|
+
"0.9.28"
|
|
7318
7293
|
]).has(importData.version)) return {
|
|
7319
7294
|
success: false,
|
|
7320
7295
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -7838,19 +7813,167 @@ function buildGraphExtractionPrompt(observations) {
|
|
|
7838
7813
|
//#region src/functions/graph.ts
|
|
7839
7814
|
const DEFAULT_GRAPH_QUERY_LIMIT = 500;
|
|
7840
7815
|
const MAX_GRAPH_QUERY_LIMIT = 5e3;
|
|
7841
|
-
|
|
7816
|
+
const SNAPSHOT_TOP_NODES = DEFAULT_GRAPH_QUERY_LIMIT;
|
|
7817
|
+
const SNAPSHOT_KEY = "current";
|
|
7818
|
+
const LIVE_ENUMERATION_BUDGET_MS = 6e3;
|
|
7819
|
+
function withTimeout(p, ms, label) {
|
|
7820
|
+
return new Promise((resolve, reject) => {
|
|
7821
|
+
const t = setTimeout(() => reject(/* @__PURE__ */ new Error(`${label}: exceeded ${ms}ms budget`)), ms);
|
|
7822
|
+
p.then((v) => {
|
|
7823
|
+
clearTimeout(t);
|
|
7824
|
+
resolve(v);
|
|
7825
|
+
}, (err) => {
|
|
7826
|
+
clearTimeout(t);
|
|
7827
|
+
reject(err);
|
|
7828
|
+
});
|
|
7829
|
+
});
|
|
7830
|
+
}
|
|
7831
|
+
function emptySnapshot() {
|
|
7842
7832
|
return {
|
|
7843
|
-
|
|
7844
|
-
|
|
7833
|
+
version: 1,
|
|
7834
|
+
topNodes: [],
|
|
7835
|
+
topEdges: [],
|
|
7836
|
+
topDegrees: {},
|
|
7837
|
+
stats: {
|
|
7838
|
+
totalNodes: 0,
|
|
7839
|
+
totalEdges: 0,
|
|
7840
|
+
nodesByType: {},
|
|
7841
|
+
edgesByType: {}
|
|
7842
|
+
},
|
|
7843
|
+
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
7844
|
+
dirty: true
|
|
7845
7845
|
};
|
|
7846
7846
|
}
|
|
7847
|
-
function
|
|
7847
|
+
async function readSnapshot(kv) {
|
|
7848
|
+
try {
|
|
7849
|
+
const snap = await kv.get(KV.graphSnapshot, SNAPSHOT_KEY);
|
|
7850
|
+
if (snap && typeof snap === "object" && snap.version === 1) return snap;
|
|
7851
|
+
return null;
|
|
7852
|
+
} catch (err) {
|
|
7853
|
+
logger.warn("Graph snapshot read failed", { error: err instanceof Error ? err.message : String(err) });
|
|
7854
|
+
return null;
|
|
7855
|
+
}
|
|
7856
|
+
}
|
|
7857
|
+
function buildSnapshotFromArrays(nodes, edges) {
|
|
7858
|
+
const liveNodes = nodes.filter((n) => !n.stale);
|
|
7859
|
+
const liveEdges = edges.filter((e) => !e.stale);
|
|
7848
7860
|
const degree = /* @__PURE__ */ new Map();
|
|
7849
|
-
for (const
|
|
7850
|
-
degree.set(
|
|
7851
|
-
degree.set(
|
|
7861
|
+
for (const e of liveEdges) {
|
|
7862
|
+
degree.set(e.sourceNodeId, (degree.get(e.sourceNodeId) ?? 0) + 1);
|
|
7863
|
+
degree.set(e.targetNodeId, (degree.get(e.targetNodeId) ?? 0) + 1);
|
|
7864
|
+
}
|
|
7865
|
+
const ranked = [...liveNodes].sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0)).slice(0, SNAPSHOT_TOP_NODES);
|
|
7866
|
+
const rankedIds = new Set(ranked.map((n) => n.id));
|
|
7867
|
+
const topEdges = liveEdges.filter((e) => rankedIds.has(e.sourceNodeId) && rankedIds.has(e.targetNodeId));
|
|
7868
|
+
const topDegrees = {};
|
|
7869
|
+
for (const n of ranked) topDegrees[n.id] = degree.get(n.id) ?? 0;
|
|
7870
|
+
const nodesByType = {};
|
|
7871
|
+
for (const n of liveNodes) nodesByType[n.type] = (nodesByType[n.type] || 0) + 1;
|
|
7872
|
+
const edgesByType = {};
|
|
7873
|
+
for (const e of liveEdges) edgesByType[e.type] = (edgesByType[e.type] || 0) + 1;
|
|
7874
|
+
return {
|
|
7875
|
+
version: 1,
|
|
7876
|
+
topNodes: ranked,
|
|
7877
|
+
topEdges,
|
|
7878
|
+
topDegrees,
|
|
7879
|
+
stats: {
|
|
7880
|
+
totalNodes: liveNodes.length,
|
|
7881
|
+
totalEdges: liveEdges.length,
|
|
7882
|
+
nodesByType,
|
|
7883
|
+
edgesByType
|
|
7884
|
+
},
|
|
7885
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7886
|
+
dirty: false
|
|
7887
|
+
};
|
|
7888
|
+
}
|
|
7889
|
+
function paginateFromSnapshot(snap, filterType, limit, offset) {
|
|
7890
|
+
const filteredNodes = filterType ? snap.topNodes.filter((n) => n.type === filterType) : snap.topNodes;
|
|
7891
|
+
const total = filterType ? snap.stats.nodesByType[filterType] ?? 0 : snap.stats.totalNodes;
|
|
7892
|
+
const pageNodes = filteredNodes.slice(offset, offset + limit);
|
|
7893
|
+
const pageIds = new Set(pageNodes.map((n) => n.id));
|
|
7894
|
+
return {
|
|
7895
|
+
nodes: pageNodes,
|
|
7896
|
+
edges: snap.topEdges.filter((e) => pageIds.has(e.sourceNodeId) && pageIds.has(e.targetNodeId)),
|
|
7897
|
+
depth: 0,
|
|
7898
|
+
totalNodes: total,
|
|
7899
|
+
totalEdges: snap.stats.totalEdges,
|
|
7900
|
+
truncated: total > pageNodes.length,
|
|
7901
|
+
limit,
|
|
7902
|
+
offset,
|
|
7903
|
+
fromSnapshot: true
|
|
7904
|
+
};
|
|
7905
|
+
}
|
|
7906
|
+
const REBUILD_SAFE_NODE_CEILING = 25e3;
|
|
7907
|
+
function nameIndexKey(type, name) {
|
|
7908
|
+
return `${type}|${name}`;
|
|
7909
|
+
}
|
|
7910
|
+
function edgeIndexKey(sourceNodeId, targetNodeId, type) {
|
|
7911
|
+
return `${sourceNodeId}|${targetNodeId}|${type}`;
|
|
7912
|
+
}
|
|
7913
|
+
async function applyDegreeDelta(kv, snap, nodeId, delta) {
|
|
7914
|
+
const prev = await kv.get(KV.graphNodeDegree, nodeId) ?? 0;
|
|
7915
|
+
const next = Math.max(0, prev + delta);
|
|
7916
|
+
await kv.set(KV.graphNodeDegree, nodeId, next);
|
|
7917
|
+
if (snap.topNodes.findIndex((n) => n.id === nodeId) !== -1) {
|
|
7918
|
+
snap.topDegrees[nodeId] = next;
|
|
7919
|
+
snap.topNodes.sort((a, b) => (snap.topDegrees[b.id] ?? 0) - (snap.topDegrees[a.id] ?? 0));
|
|
7920
|
+
return next;
|
|
7921
|
+
}
|
|
7922
|
+
if (snap.topNodes.length < SNAPSHOT_TOP_NODES) {
|
|
7923
|
+
const node = await kv.get(KV.graphNodes, nodeId);
|
|
7924
|
+
if (node && !node.stale) {
|
|
7925
|
+
snap.topNodes.push(node);
|
|
7926
|
+
snap.topDegrees[node.id] = next;
|
|
7927
|
+
snap.topNodes.sort((a, b) => (snap.topDegrees[b.id] ?? 0) - (snap.topDegrees[a.id] ?? 0));
|
|
7928
|
+
}
|
|
7929
|
+
return next;
|
|
7930
|
+
}
|
|
7931
|
+
const tailEntry = snap.topNodes[snap.topNodes.length - 1];
|
|
7932
|
+
if (!tailEntry) return next;
|
|
7933
|
+
if (next > (snap.topDegrees[tailEntry.id] ?? 0)) {
|
|
7934
|
+
const node = await kv.get(KV.graphNodes, nodeId);
|
|
7935
|
+
if (node && !node.stale) {
|
|
7936
|
+
const evicted = snap.topNodes.pop();
|
|
7937
|
+
if (evicted) delete snap.topDegrees[evicted.id];
|
|
7938
|
+
snap.topNodes.push(node);
|
|
7939
|
+
snap.topDegrees[node.id] = next;
|
|
7940
|
+
snap.topNodes.sort((a, b) => (snap.topDegrees[b.id] ?? 0) - (snap.topDegrees[a.id] ?? 0));
|
|
7941
|
+
}
|
|
7942
|
+
}
|
|
7943
|
+
return next;
|
|
7944
|
+
}
|
|
7945
|
+
function snapshotPushEdgeIfBothInTop(snap, edge) {
|
|
7946
|
+
const topIds = new Set(snap.topNodes.map((n) => n.id));
|
|
7947
|
+
if (topIds.has(edge.sourceNodeId) && topIds.has(edge.targetNodeId)) {
|
|
7948
|
+
if (!snap.topEdges.find((e) => e.id === edge.id)) snap.topEdges.push(edge);
|
|
7852
7949
|
}
|
|
7853
|
-
|
|
7950
|
+
}
|
|
7951
|
+
function mergeNode(existing, incoming, obsIds, capturedAt) {
|
|
7952
|
+
return {
|
|
7953
|
+
...existing,
|
|
7954
|
+
sourceObservationIds: [...new Set([
|
|
7955
|
+
...existing.sourceObservationIds,
|
|
7956
|
+
...incoming.sourceObservationIds,
|
|
7957
|
+
...obsIds
|
|
7958
|
+
])],
|
|
7959
|
+
properties: {
|
|
7960
|
+
...existing.properties,
|
|
7961
|
+
...incoming.properties
|
|
7962
|
+
},
|
|
7963
|
+
updatedAt: capturedAt
|
|
7964
|
+
};
|
|
7965
|
+
}
|
|
7966
|
+
function mergeEdge(existing, obsIds) {
|
|
7967
|
+
return {
|
|
7968
|
+
...existing,
|
|
7969
|
+
sourceObservationIds: [...new Set([...existing.sourceObservationIds, ...obsIds])]
|
|
7970
|
+
};
|
|
7971
|
+
}
|
|
7972
|
+
function resolvePagination(rawLimit, rawOffset) {
|
|
7973
|
+
return {
|
|
7974
|
+
limit: Math.max(1, Math.min(typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.floor(rawLimit) : DEFAULT_GRAPH_QUERY_LIMIT, MAX_GRAPH_QUERY_LIMIT)),
|
|
7975
|
+
offset: Math.max(0, typeof rawOffset === "number" && Number.isFinite(rawOffset) ? Math.floor(rawOffset) : 0)
|
|
7976
|
+
};
|
|
7854
7977
|
}
|
|
7855
7978
|
function paginate(nodes, allEdges, depth, limit, offset) {
|
|
7856
7979
|
const totalNodes = nodes.length;
|
|
@@ -7947,45 +8070,76 @@ function registerGraphFunction(sdk, kv, provider) {
|
|
|
7947
8070
|
const response = await provider.compress(GRAPH_EXTRACTION_SYSTEM, prompt);
|
|
7948
8071
|
const obsIds = data.observations.map((o) => o.id);
|
|
7949
8072
|
const { nodes, edges } = parseGraphXml(response, obsIds);
|
|
7950
|
-
const
|
|
7951
|
-
const
|
|
8073
|
+
const snap = await readSnapshot(kv) ?? emptySnapshot();
|
|
8074
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8075
|
+
let newNodeCount = 0;
|
|
8076
|
+
let newEdgeCount = 0;
|
|
8077
|
+
const newEdgesForTopCheck = [];
|
|
7952
8078
|
for (const node of nodes) {
|
|
7953
|
-
const
|
|
8079
|
+
const indexKey = nameIndexKey(node.type, node.name);
|
|
8080
|
+
const existingId = await kv.get(KV.graphNameIndex, indexKey);
|
|
8081
|
+
let existing = null;
|
|
8082
|
+
if (existingId) {
|
|
8083
|
+
existing = await kv.get(KV.graphNodes, existingId);
|
|
8084
|
+
if (existing && snap.resetAt && typeof existing.createdAt === "string" && existing.createdAt < snap.resetAt) existing = null;
|
|
8085
|
+
}
|
|
7954
8086
|
if (existing) {
|
|
7955
|
-
const merged =
|
|
7956
|
-
...existing,
|
|
7957
|
-
sourceObservationIds: [...new Set([...existing.sourceObservationIds, ...obsIds])],
|
|
7958
|
-
properties: {
|
|
7959
|
-
...existing.properties,
|
|
7960
|
-
...node.properties
|
|
7961
|
-
}
|
|
7962
|
-
};
|
|
8087
|
+
const merged = mergeNode(existing, node, obsIds, capturedAt);
|
|
7963
8088
|
await kv.set(KV.graphNodes, existing.id, merged);
|
|
7964
|
-
const
|
|
7965
|
-
if (
|
|
8089
|
+
const topIdx = snap.topNodes.findIndex((n) => n.id === existing.id);
|
|
8090
|
+
if (topIdx !== -1) snap.topNodes[topIdx] = merged;
|
|
7966
8091
|
} else {
|
|
7967
8092
|
await kv.set(KV.graphNodes, node.id, node);
|
|
7968
|
-
|
|
8093
|
+
await kv.set(KV.graphNameIndex, indexKey, node.id);
|
|
8094
|
+
await kv.set(KV.graphNodeDegree, node.id, 0);
|
|
8095
|
+
snap.stats.totalNodes += 1;
|
|
8096
|
+
snap.stats.nodesByType[node.type] = (snap.stats.nodesByType[node.type] ?? 0) + 1;
|
|
8097
|
+
newNodeCount += 1;
|
|
8098
|
+
if (snap.topNodes.length < SNAPSHOT_TOP_NODES) {
|
|
8099
|
+
snap.topNodes.push(node);
|
|
8100
|
+
snap.topDegrees[node.id] = 0;
|
|
8101
|
+
}
|
|
7969
8102
|
}
|
|
7970
8103
|
}
|
|
7971
8104
|
for (const edge of edges) {
|
|
7972
|
-
const
|
|
7973
|
-
const
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
await kv.
|
|
8105
|
+
const eKey = edgeIndexKey(edge.sourceNodeId, edge.targetNodeId, edge.type);
|
|
8106
|
+
const existingId = await kv.get(KV.graphEdgeKey, eKey);
|
|
8107
|
+
let existing = null;
|
|
8108
|
+
if (existingId) {
|
|
8109
|
+
existing = await kv.get(KV.graphEdges, existingId);
|
|
8110
|
+
if (existing && snap.resetAt && typeof existing.createdAt === "string" && existing.createdAt < snap.resetAt) existing = null;
|
|
8111
|
+
}
|
|
8112
|
+
if (existing) {
|
|
8113
|
+
const merged = mergeEdge(existing, obsIds);
|
|
8114
|
+
await kv.set(KV.graphEdges, existing.id, merged);
|
|
8115
|
+
const topIdx = snap.topEdges.findIndex((e) => e.id === existing.id);
|
|
8116
|
+
if (topIdx !== -1) snap.topEdges[topIdx] = merged;
|
|
7977
8117
|
} else {
|
|
7978
8118
|
await kv.set(KV.graphEdges, edge.id, edge);
|
|
7979
|
-
|
|
8119
|
+
await kv.set(KV.graphEdgeKey, eKey, edge.id);
|
|
8120
|
+
snap.stats.totalEdges += 1;
|
|
8121
|
+
snap.stats.edgesByType[edge.type] = (snap.stats.edgesByType[edge.type] ?? 0) + 1;
|
|
8122
|
+
newEdgeCount += 1;
|
|
8123
|
+
await applyDegreeDelta(kv, snap, edge.sourceNodeId, 1);
|
|
8124
|
+
await applyDegreeDelta(kv, snap, edge.targetNodeId, 1);
|
|
8125
|
+
newEdgesForTopCheck.push(edge);
|
|
7980
8126
|
}
|
|
7981
8127
|
}
|
|
8128
|
+
for (const edge of newEdgesForTopCheck) snapshotPushEdgeIfBothInTop(snap, edge);
|
|
8129
|
+
if (newNodeCount > 0 || newEdgeCount > 0) {
|
|
8130
|
+
snap.updatedAt = capturedAt;
|
|
8131
|
+
snap.dirty = false;
|
|
8132
|
+
await kv.set(KV.graphSnapshot, SNAPSHOT_KEY, snap);
|
|
8133
|
+
}
|
|
7982
8134
|
await recordAudit(kv, "observe", "mem::graph-extract", obsIds, {
|
|
7983
8135
|
nodesExtracted: nodes.length,
|
|
7984
8136
|
edgesExtracted: edges.length
|
|
7985
8137
|
});
|
|
7986
8138
|
logger.info("Graph extraction complete", {
|
|
7987
8139
|
nodes: nodes.length,
|
|
7988
|
-
edges: edges.length
|
|
8140
|
+
edges: edges.length,
|
|
8141
|
+
newNodes: newNodeCount,
|
|
8142
|
+
newEdges: newEdgeCount
|
|
7989
8143
|
});
|
|
7990
8144
|
return {
|
|
7991
8145
|
success: true,
|
|
@@ -8002,10 +8156,49 @@ function registerGraphFunction(sdk, kv, provider) {
|
|
|
8002
8156
|
}
|
|
8003
8157
|
});
|
|
8004
8158
|
sdk.registerFunction("mem::graph-query", async (data) => {
|
|
8005
|
-
const allNodes = (await kv.list(KV.graphNodes)).filter((n) => !n.stale);
|
|
8006
|
-
const allEdges = (await kv.list(KV.graphEdges)).filter((e) => !e.stale);
|
|
8007
8159
|
const maxDepth = Math.min(data.maxDepth || 3, 5);
|
|
8008
8160
|
const { limit, offset } = resolvePagination(data.limit, data.offset);
|
|
8161
|
+
if (!data.query && !data.startNodeId) {
|
|
8162
|
+
const snap = await readSnapshot(kv);
|
|
8163
|
+
if (snap && snap.stats.totalNodes > 0) return paginateFromSnapshot(snap, data.nodeType, limit, offset);
|
|
8164
|
+
return {
|
|
8165
|
+
nodes: [],
|
|
8166
|
+
edges: [],
|
|
8167
|
+
depth: 0,
|
|
8168
|
+
totalNodes: 0,
|
|
8169
|
+
totalEdges: 0,
|
|
8170
|
+
truncated: false,
|
|
8171
|
+
limit,
|
|
8172
|
+
offset,
|
|
8173
|
+
warning: "No graph snapshot available. Either no graph has been extracted yet, or you are on a legacy corpus from a pre-#814 agentmemory build. Run POST /agentmemory/graph/snapshot-rebuild (safe up to ~25K nodes) or POST /agentmemory/graph/reset to wipe and let future extracts repopulate."
|
|
8174
|
+
};
|
|
8175
|
+
}
|
|
8176
|
+
let allNodes;
|
|
8177
|
+
let allEdges;
|
|
8178
|
+
try {
|
|
8179
|
+
const [rawNodes, rawEdges] = await withTimeout(Promise.all([kv.list(KV.graphNodes), kv.list(KV.graphEdges)]), LIVE_ENUMERATION_BUDGET_MS, "graph-query enumeration");
|
|
8180
|
+
allNodes = rawNodes.filter((n) => !n.stale);
|
|
8181
|
+
allEdges = rawEdges.filter((e) => !e.stale);
|
|
8182
|
+
} catch (err) {
|
|
8183
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8184
|
+
logger.warn("Graph query enumeration timed out, using snapshot", { error: msg });
|
|
8185
|
+
const snap = await readSnapshot(kv);
|
|
8186
|
+
if (snap) return {
|
|
8187
|
+
...paginateFromSnapshot(snap, data.nodeType, limit, offset),
|
|
8188
|
+
warning: "Live graph enumeration exceeded budget. Query / startNodeId paths degrade on >25K-node corpora until a per-node edge index lands. Result reflects top-degree snapshot, not the requested walk."
|
|
8189
|
+
};
|
|
8190
|
+
return {
|
|
8191
|
+
nodes: [],
|
|
8192
|
+
edges: [],
|
|
8193
|
+
depth: 0,
|
|
8194
|
+
totalNodes: 0,
|
|
8195
|
+
totalEdges: 0,
|
|
8196
|
+
truncated: false,
|
|
8197
|
+
limit,
|
|
8198
|
+
offset,
|
|
8199
|
+
warning: "Graph enumeration exceeded budget and no snapshot is available."
|
|
8200
|
+
};
|
|
8201
|
+
}
|
|
8009
8202
|
if (data.query) {
|
|
8010
8203
|
const lower = data.query.toLowerCase();
|
|
8011
8204
|
return paginate(allNodes.filter((n) => n.name.toLowerCase().includes(lower) || Object.values(n.properties).some((v) => typeof v === "string" && v.toLowerCase().includes(lower))), allEdges, 0, limit, offset);
|
|
@@ -8042,22 +8235,116 @@ function registerGraphFunction(sdk, kv, provider) {
|
|
|
8042
8235
|
}
|
|
8043
8236
|
return paginate(resultNodes, resultEdges, maxDepth, limit, offset);
|
|
8044
8237
|
}
|
|
8045
|
-
|
|
8046
|
-
if (data.nodeType) filtered = allNodes.filter((n) => n.type === data.nodeType);
|
|
8047
|
-
return paginate(rankByDegree(filtered, allEdges), allEdges, 0, limit, offset);
|
|
8238
|
+
return paginate([], [], 0, limit, offset);
|
|
8048
8239
|
});
|
|
8049
8240
|
sdk.registerFunction("mem::graph-stats", async () => {
|
|
8050
|
-
const
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8241
|
+
const snap = await readSnapshot(kv);
|
|
8242
|
+
if (snap) return {
|
|
8243
|
+
...snap.stats,
|
|
8244
|
+
fromSnapshot: true,
|
|
8245
|
+
updatedAt: snap.updatedAt,
|
|
8246
|
+
...snap.dirty ? { warning: "Snapshot is marked dirty (write was in-flight when read). Counts are eventually consistent." } : {}
|
|
8247
|
+
};
|
|
8056
8248
|
return {
|
|
8057
|
-
totalNodes:
|
|
8058
|
-
totalEdges:
|
|
8059
|
-
nodesByType,
|
|
8060
|
-
edgesByType
|
|
8249
|
+
totalNodes: 0,
|
|
8250
|
+
totalEdges: 0,
|
|
8251
|
+
nodesByType: {},
|
|
8252
|
+
edgesByType: {},
|
|
8253
|
+
fromSnapshot: false,
|
|
8254
|
+
warning: "No graph snapshot available. Run POST /agentmemory/graph/snapshot-rebuild (safe up to ~25K nodes) or POST /agentmemory/graph/reset to wipe and let future extracts repopulate."
|
|
8255
|
+
};
|
|
8256
|
+
});
|
|
8257
|
+
sdk.registerFunction("mem::graph-snapshot-rebuild", async (data) => {
|
|
8258
|
+
const started = Date.now();
|
|
8259
|
+
const forceRebuild = data?.force === true;
|
|
8260
|
+
try {
|
|
8261
|
+
if (!await readSnapshot(kv) && !forceRebuild) {
|
|
8262
|
+
logger.warn("Graph snapshot rebuild refused: no prior snapshot", { hint: "legacy corpus or empty store" });
|
|
8263
|
+
return {
|
|
8264
|
+
success: false,
|
|
8265
|
+
legacyCorpus: true,
|
|
8266
|
+
error: "No prior snapshot found. Rebuild would call kv.list on KV.graphNodes/Edges, which heartbeat-crashes the worker on corpora past the iii state response budget (~25K nodes). Either (a) call POST /agentmemory/graph/reset to drop into incremental-only mode and rebuild from new extracts, or (b) re-send with `force: true` if you're certain the corpus is small."
|
|
8267
|
+
};
|
|
8268
|
+
}
|
|
8269
|
+
} catch (err) {
|
|
8270
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8271
|
+
logger.warn("Graph snapshot pre-flight read failed", { error: msg });
|
|
8272
|
+
}
|
|
8273
|
+
try {
|
|
8274
|
+
const [nodes, edges] = await withTimeout(Promise.all([kv.list(KV.graphNodes), kv.list(KV.graphEdges)]), LIVE_ENUMERATION_BUDGET_MS, "graph-snapshot-rebuild enumeration");
|
|
8275
|
+
if (nodes.length > REBUILD_SAFE_NODE_CEILING) {
|
|
8276
|
+
logger.warn("Graph snapshot rebuild aborted: corpus too large", {
|
|
8277
|
+
totalNodes: nodes.length,
|
|
8278
|
+
ceiling: REBUILD_SAFE_NODE_CEILING
|
|
8279
|
+
});
|
|
8280
|
+
return {
|
|
8281
|
+
success: false,
|
|
8282
|
+
tooLarge: true,
|
|
8283
|
+
totalNodes: nodes.length,
|
|
8284
|
+
ceiling: REBUILD_SAFE_NODE_CEILING,
|
|
8285
|
+
error: `Corpus has ${nodes.length} graph nodes; safe-rebuild ceiling is ${REBUILD_SAFE_NODE_CEILING}. Run POST /agentmemory/graph/reset to wipe and let future extracts rebuild incrementally.`
|
|
8286
|
+
};
|
|
8287
|
+
}
|
|
8288
|
+
const liveNodes = nodes.filter((n) => !n.stale);
|
|
8289
|
+
const liveEdges = edges.filter((e) => !e.stale);
|
|
8290
|
+
const degree = /* @__PURE__ */ new Map();
|
|
8291
|
+
for (const e of liveEdges) {
|
|
8292
|
+
degree.set(e.sourceNodeId, (degree.get(e.sourceNodeId) ?? 0) + 1);
|
|
8293
|
+
degree.set(e.targetNodeId, (degree.get(e.targetNodeId) ?? 0) + 1);
|
|
8294
|
+
}
|
|
8295
|
+
const BATCH_SIZE = 100;
|
|
8296
|
+
for (let i = 0; i < liveNodes.length; i += BATCH_SIZE) {
|
|
8297
|
+
const batch = liveNodes.slice(i, i + BATCH_SIZE);
|
|
8298
|
+
await Promise.all(batch.flatMap((n) => [kv.set(KV.graphNameIndex, nameIndexKey(n.type, n.name), n.id), kv.set(KV.graphNodeDegree, n.id, degree.get(n.id) ?? 0)]));
|
|
8299
|
+
}
|
|
8300
|
+
for (let i = 0; i < liveEdges.length; i += BATCH_SIZE) {
|
|
8301
|
+
const batch = liveEdges.slice(i, i + BATCH_SIZE);
|
|
8302
|
+
await Promise.all(batch.map((e) => kv.set(KV.graphEdgeKey, edgeIndexKey(e.sourceNodeId, e.targetNodeId, e.type), e.id)));
|
|
8303
|
+
}
|
|
8304
|
+
const snap = buildSnapshotFromArrays(nodes, edges);
|
|
8305
|
+
await kv.set(KV.graphSnapshot, SNAPSHOT_KEY, snap);
|
|
8306
|
+
const tookMs = Date.now() - started;
|
|
8307
|
+
logger.info("Graph snapshot rebuilt", {
|
|
8308
|
+
totalNodes: snap.stats.totalNodes,
|
|
8309
|
+
totalEdges: snap.stats.totalEdges,
|
|
8310
|
+
topNodes: snap.topNodes.length,
|
|
8311
|
+
topEdges: snap.topEdges.length,
|
|
8312
|
+
tookMs
|
|
8313
|
+
});
|
|
8314
|
+
return {
|
|
8315
|
+
success: true,
|
|
8316
|
+
...snap.stats,
|
|
8317
|
+
topNodes: snap.topNodes.length,
|
|
8318
|
+
topEdges: snap.topEdges.length,
|
|
8319
|
+
updatedAt: snap.updatedAt,
|
|
8320
|
+
tookMs
|
|
8321
|
+
};
|
|
8322
|
+
} catch (err) {
|
|
8323
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8324
|
+
logger.error("Graph snapshot rebuild failed", { error: msg });
|
|
8325
|
+
return {
|
|
8326
|
+
success: false,
|
|
8327
|
+
error: msg
|
|
8328
|
+
};
|
|
8329
|
+
}
|
|
8330
|
+
});
|
|
8331
|
+
sdk.registerFunction("mem::graph-reset", async () => {
|
|
8332
|
+
const started = Date.now();
|
|
8333
|
+
const resetSnapshot = {
|
|
8334
|
+
...emptySnapshot(),
|
|
8335
|
+
resetAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8336
|
+
};
|
|
8337
|
+
await kv.set(KV.graphSnapshot, SNAPSHOT_KEY, resetSnapshot);
|
|
8338
|
+
const counts = { [KV.graphSnapshot]: 1 };
|
|
8339
|
+
const tookMs = Date.now() - started;
|
|
8340
|
+
logger.info("Graph state reset", {
|
|
8341
|
+
counts,
|
|
8342
|
+
tookMs
|
|
8343
|
+
});
|
|
8344
|
+
return {
|
|
8345
|
+
success: true,
|
|
8346
|
+
cleared: counts,
|
|
8347
|
+
tookMs
|
|
8061
8348
|
};
|
|
8062
8349
|
});
|
|
8063
8350
|
}
|
|
@@ -13880,7 +14167,7 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
13880
14167
|
const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
|
|
13881
14168
|
const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
|
|
13882
14169
|
const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
|
|
13883
|
-
const { decrementImageRef } = await
|
|
14170
|
+
const { decrementImageRef } = await import("./image-refs-BKLmp_sD.mjs");
|
|
13884
14171
|
const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
|
|
13885
14172
|
if (data?.dryRun) return {
|
|
13886
14173
|
success: true,
|
|
@@ -15348,11 +15635,15 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15348
15635
|
status_code: 400,
|
|
15349
15636
|
body: { error: "budget must be a positive integer" }
|
|
15350
15637
|
};
|
|
15638
|
+
const queryAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"] : void 0;
|
|
15639
|
+
const bodyAgentId = typeof body.agentId === "string" && body.agentId.trim().length > 0 ? body.agentId.trim() : void 0;
|
|
15351
15640
|
const payload = {
|
|
15352
15641
|
sessionId,
|
|
15353
15642
|
project
|
|
15354
15643
|
};
|
|
15355
15644
|
if (budget !== void 0) payload.budget = budget;
|
|
15645
|
+
const agentId = bodyAgentId ?? queryAgentId;
|
|
15646
|
+
if (agentId !== void 0) payload.agentId = agentId;
|
|
15356
15647
|
return {
|
|
15357
15648
|
status_code: 200,
|
|
15358
15649
|
body: await sdk.trigger({
|
|
@@ -15372,6 +15663,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15372
15663
|
});
|
|
15373
15664
|
sdk.registerFunction("api::search", async (req) => {
|
|
15374
15665
|
const body = req.body ?? {};
|
|
15666
|
+
const queryAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"] : void 0;
|
|
15375
15667
|
if (typeof body.query !== "string" || !body.query.trim()) return {
|
|
15376
15668
|
status_code: 400,
|
|
15377
15669
|
body: { error: "query is required and must be a non-empty string" }
|
|
@@ -15400,13 +15692,15 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15400
15692
|
status_code: 400,
|
|
15401
15693
|
body: { error: "token_budget must be a positive integer" }
|
|
15402
15694
|
};
|
|
15695
|
+
const bodyAgentId = typeof body.agentId === "string" && body.agentId.trim().length > 0 ? body.agentId.trim() : void 0;
|
|
15403
15696
|
const payload = {
|
|
15404
15697
|
query: body.query.trim(),
|
|
15405
15698
|
limit: body.limit,
|
|
15406
15699
|
project: body.project,
|
|
15407
15700
|
cwd: body.cwd,
|
|
15408
15701
|
format: typeof body.format === "string" ? body.format.trim().toLowerCase() : void 0,
|
|
15409
|
-
token_budget: body.token_budget
|
|
15702
|
+
token_budget: body.token_budget,
|
|
15703
|
+
agentId: bodyAgentId ?? queryAgentId
|
|
15410
15704
|
};
|
|
15411
15705
|
return {
|
|
15412
15706
|
status_code: 200,
|
|
@@ -15561,7 +15855,8 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15561
15855
|
function_id: "mem::context",
|
|
15562
15856
|
payload: {
|
|
15563
15857
|
sessionId,
|
|
15564
|
-
project
|
|
15858
|
+
project,
|
|
15859
|
+
...agentId ? { agentId } : {}
|
|
15565
15860
|
}
|
|
15566
15861
|
})).context
|
|
15567
15862
|
}
|
|
@@ -15753,9 +16048,14 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15753
16048
|
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
15754
16049
|
const wildcardAgent = normalizedAgentId === "*";
|
|
15755
16050
|
const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
16051
|
+
const filtered = filterAgentId ? sessions.filter((s) => s.agentId === filterAgentId) : sessions;
|
|
16052
|
+
const summaries = await Promise.all(filtered.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
|
|
15756
16053
|
return {
|
|
15757
16054
|
status_code: 200,
|
|
15758
|
-
body: { sessions:
|
|
16055
|
+
body: { sessions: filtered.map((s, i) => summaries[i] ? {
|
|
16056
|
+
...s,
|
|
16057
|
+
summary: summaries[i]
|
|
16058
|
+
} : s) }
|
|
15759
16059
|
};
|
|
15760
16060
|
});
|
|
15761
16061
|
sdk.registerTrigger({
|
|
@@ -16342,6 +16642,52 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
16342
16642
|
http_method: "GET"
|
|
16343
16643
|
}
|
|
16344
16644
|
});
|
|
16645
|
+
sdk.registerFunction("api::graph-snapshot-rebuild", async (req) => {
|
|
16646
|
+
const authErr = checkAuth(req, secret);
|
|
16647
|
+
if (authErr) return authErr;
|
|
16648
|
+
try {
|
|
16649
|
+
return {
|
|
16650
|
+
status_code: 200,
|
|
16651
|
+
body: await sdk.trigger({
|
|
16652
|
+
function_id: "mem::graph-snapshot-rebuild",
|
|
16653
|
+
payload: {}
|
|
16654
|
+
})
|
|
16655
|
+
};
|
|
16656
|
+
} catch {
|
|
16657
|
+
return graphDisabledResponse();
|
|
16658
|
+
}
|
|
16659
|
+
});
|
|
16660
|
+
sdk.registerTrigger({
|
|
16661
|
+
type: "http",
|
|
16662
|
+
function_id: "api::graph-snapshot-rebuild",
|
|
16663
|
+
config: {
|
|
16664
|
+
api_path: "/agentmemory/graph/snapshot-rebuild",
|
|
16665
|
+
http_method: "POST"
|
|
16666
|
+
}
|
|
16667
|
+
});
|
|
16668
|
+
sdk.registerFunction("api::graph-reset", async (req) => {
|
|
16669
|
+
const authErr = checkAuth(req, secret);
|
|
16670
|
+
if (authErr) return authErr;
|
|
16671
|
+
try {
|
|
16672
|
+
return {
|
|
16673
|
+
status_code: 200,
|
|
16674
|
+
body: await sdk.trigger({
|
|
16675
|
+
function_id: "mem::graph-reset",
|
|
16676
|
+
payload: {}
|
|
16677
|
+
})
|
|
16678
|
+
};
|
|
16679
|
+
} catch {
|
|
16680
|
+
return graphDisabledResponse();
|
|
16681
|
+
}
|
|
16682
|
+
});
|
|
16683
|
+
sdk.registerTrigger({
|
|
16684
|
+
type: "http",
|
|
16685
|
+
function_id: "api::graph-reset",
|
|
16686
|
+
config: {
|
|
16687
|
+
api_path: "/agentmemory/graph/reset",
|
|
16688
|
+
http_method: "POST"
|
|
16689
|
+
}
|
|
16690
|
+
});
|
|
16345
16691
|
sdk.registerFunction("api::graph-extract", async (req) => {
|
|
16346
16692
|
const authErr = checkAuth(req, secret);
|
|
16347
16693
|
if (authErr) return authErr;
|
|
@@ -18589,13 +18935,15 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
18589
18935
|
//#region src/triggers/events.ts
|
|
18590
18936
|
function registerEventTriggers(sdk, kv) {
|
|
18591
18937
|
sdk.registerFunction("event::session::started", async (data) => {
|
|
18938
|
+
const agentId = (typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim().slice(0, 128) : void 0) ?? getAgentId();
|
|
18592
18939
|
const session = {
|
|
18593
18940
|
id: data.sessionId,
|
|
18594
18941
|
project: data.project,
|
|
18595
18942
|
cwd: data.cwd,
|
|
18596
18943
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18597
18944
|
status: "active",
|
|
18598
|
-
observationCount: 0
|
|
18945
|
+
observationCount: 0,
|
|
18946
|
+
...agentId ? { agentId } : {}
|
|
18599
18947
|
};
|
|
18600
18948
|
await kv.set(KV.sessions, data.sessionId, session);
|
|
18601
18949
|
return {
|
|
@@ -18604,7 +18952,8 @@ function registerEventTriggers(sdk, kv) {
|
|
|
18604
18952
|
function_id: "mem::context",
|
|
18605
18953
|
payload: {
|
|
18606
18954
|
sessionId: data.sessionId,
|
|
18607
|
-
project: data.project
|
|
18955
|
+
project: data.project,
|
|
18956
|
+
...agentId ? { agentId } : {}
|
|
18608
18957
|
}
|
|
18609
18958
|
})).context
|
|
18610
18959
|
};
|
|
@@ -19885,13 +20234,15 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
19885
20234
|
status_code: 400,
|
|
19886
20235
|
body: { error: "token_budget must be a positive integer" }
|
|
19887
20236
|
};
|
|
20237
|
+
const recallAgentId = typeof args.agentId === "string" && args.agentId.trim().length > 0 ? args.agentId.trim() : void 0;
|
|
19888
20238
|
const result = await sdk.trigger({
|
|
19889
20239
|
function_id: "mem::search",
|
|
19890
20240
|
payload: {
|
|
19891
20241
|
query: args.query,
|
|
19892
20242
|
limit: typeof args.limit === "number" ? args.limit : 10,
|
|
19893
20243
|
format,
|
|
19894
|
-
token_budget: tokenBudget
|
|
20244
|
+
token_budget: tokenBudget,
|
|
20245
|
+
agentId: recallAgentId
|
|
19895
20246
|
}
|
|
19896
20247
|
});
|
|
19897
20248
|
return {
|
|
@@ -21379,7 +21730,10 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
21379
21730
|
limit: 10
|
|
21380
21731
|
}
|
|
21381
21732
|
}).catch(() => ({ results: [] }));
|
|
21382
|
-
const
|
|
21733
|
+
const memories = await kv.list(KV.memories);
|
|
21734
|
+
const isolated = isAgentScopeIsolated();
|
|
21735
|
+
const activeAgentId = isolated ? getAgentId() : void 0;
|
|
21736
|
+
const relevant = isolated && activeAgentId === void 0 ? [] : memories.filter((m) => m.isLatest).filter((m) => activeAgentId === void 0 || m.agentId === activeAgentId).slice(0, 5);
|
|
21383
21737
|
return {
|
|
21384
21738
|
status_code: 200,
|
|
21385
21739
|
body: { messages: [{
|
|
@@ -21633,10 +21987,10 @@ async function main() {
|
|
|
21633
21987
|
}
|
|
21634
21988
|
registerConsolidationPipelineFunction(sdk, kv, provider);
|
|
21635
21989
|
bootLog(`Consolidation pipeline: registered (CONSOLIDATION_ENABLED=${isConsolidationEnabled() ? "true" : "false"})`);
|
|
21636
|
-
if (isAutoCompressEnabled()) bootLog(`WARNING: AGENTMEMORY_AUTO_COMPRESS=true — every PostToolUse observation will be sent to your LLM provider for compression. This spends API tokens proportional to your session tool-use frequency
|
|
21637
|
-
else bootLog(`Auto-compress: OFF (default
|
|
21638
|
-
if (isContextInjectionEnabled()) bootLog(`WARNING: AGENTMEMORY_INJECT_CONTEXT=true — the PreToolUse and SessionStart hooks will inject up to ~4000 chars of memory context into every tool turn. On Claude Pro this burns session tokens proportional to your tool-call frequency
|
|
21639
|
-
else bootLog(`Context injection: OFF (default
|
|
21990
|
+
if (isAutoCompressEnabled()) bootLog(`WARNING: AGENTMEMORY_AUTO_COMPRESS=true — every PostToolUse observation will be sent to your LLM provider for compression. This spends API tokens proportional to your session tool-use frequency. Set AGENTMEMORY_AUTO_COMPRESS=false to disable.`);
|
|
21991
|
+
else bootLog(`Auto-compress: OFF (default) — observations indexed via zero-LLM synthetic compression. Set AGENTMEMORY_AUTO_COMPRESS=true to opt-in to LLM-powered summaries (uses your API key).`);
|
|
21992
|
+
if (isContextInjectionEnabled()) bootLog(`WARNING: AGENTMEMORY_INJECT_CONTEXT=true — the PreToolUse and SessionStart hooks will inject up to ~4000 chars of memory context into every tool turn. On Claude Pro this burns session tokens proportional to your tool-call frequency. Set AGENTMEMORY_INJECT_CONTEXT=false to disable.`);
|
|
21993
|
+
else bootLog(`Context injection: OFF (default) — hooks capture observations but do not inject context into Claude Code's conversation. Set AGENTMEMORY_INJECT_CONTEXT=true to opt-in (warning: expect your Claude Pro allocation to drain faster).`);
|
|
21640
21994
|
const teamConfig = loadTeamConfig();
|
|
21641
21995
|
if (teamConfig) {
|
|
21642
21996
|
registerTeamFunction(sdk, kv, teamConfig);
|
|
@@ -21743,14 +22097,14 @@ async function main() {
|
|
|
21743
22097
|
backfilled++;
|
|
21744
22098
|
}
|
|
21745
22099
|
if (backfilled > 0) {
|
|
21746
|
-
bootLog(`Backfilled ${backfilled} memories into BM25 (legacy gap
|
|
22100
|
+
bootLog(`Backfilled ${backfilled} memories into BM25 (legacy index gap)`);
|
|
21747
22101
|
indexPersistence.scheduleSave();
|
|
21748
22102
|
}
|
|
21749
22103
|
} catch (err) {
|
|
21750
22104
|
console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
|
|
21751
22105
|
}
|
|
21752
22106
|
bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
21753
|
-
bootLog(`REST API:
|
|
22107
|
+
bootLog(`REST API: 128 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
|
|
21754
22108
|
bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
|
|
21755
22109
|
const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
|
|
21756
22110
|
const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
|
|
@@ -21825,6 +22179,6 @@ main().catch((err) => {
|
|
|
21825
22179
|
process.exit(1);
|
|
21826
22180
|
});
|
|
21827
22181
|
//#endregion
|
|
21828
|
-
export {};
|
|
22182
|
+
export { KV as i, touchImage as n, withKeyedLock as r, deleteImage as t };
|
|
21829
22183
|
|
|
21830
22184
|
//# sourceMappingURL=index.mjs.map
|