@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
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./schema-
|
|
2
|
-
import { n as logger, t as bootLog } from "./logger-
|
|
3
|
-
import {
|
|
4
|
-
import { a as isManagedImagePath, n as
|
|
5
|
-
import { r as
|
|
6
|
-
import { _ as loadConfig, a as getAgentId, b as loadSnapshotConfig, c as getFollowupWindowSeconds, d as isAutoCompressEnabled, f as isConsolidationEnabled, g as loadClaudeBridgeConfig, h as isGraphExtractionEnabled, i as detectLlmProviderKind, m as isDropStaleIndexEnabled, n as getVisibleTools, o as getConsolidationDecayDays, p as isContextInjectionEnabled, r as detectEmbeddingProvider, s as getEnvVar, t as getAllTools, u as isAgentScopeIsolated, v as loadEmbeddingConfig, x as loadTeamConfig, y as loadFallbackConfig } from "./tools-registry-Dzxv9iUu.mjs";
|
|
1
|
+
import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./schema-CNiCWzV9.mjs";
|
|
2
|
+
import { n as logger, t as bootLog } from "./logger-BEB7pCI9.mjs";
|
|
3
|
+
import { i as VERSION, n as getAllTools, r as getVisibleTools } from "./tools-registry-D3NEoe5m.mjs";
|
|
4
|
+
import { a as isManagedImagePath, i as getMaxBytes, n as IMAGES_DIR, o as saveImageToDisk, r as deleteImage, t as withKeyedLock } from "./keyed-mutex-DTHtcZrt.mjs";
|
|
5
|
+
import { _ as loadFallbackConfig, a as getEnvVar, c as isAgentScopeIsolated, d as isContextInjectionEnabled, f as isDropStaleIndexEnabled, g as loadEmbeddingConfig, h as loadConfig, i as getConsolidationDecayDays, l as isAutoCompressEnabled, m as loadClaudeBridgeConfig, n as detectLlmProviderKind, o as getFollowupWindowSeconds, p as isGraphExtractionEnabled, r as getAgentId, t as detectEmbeddingProvider, u as isConsolidationEnabled, v as loadSnapshotConfig, y as loadTeamConfig } from "./config-ChC9EtgV.mjs";
|
|
7
6
|
import { createRequire } from "node:module";
|
|
8
7
|
import { execFile } from "node:child_process";
|
|
9
8
|
import { constants, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -3109,6 +3108,12 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3109
3108
|
}
|
|
3110
3109
|
const projectFilter = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
|
|
3111
3110
|
const cwdFilter = typeof data.cwd === "string" && data.cwd.trim().length > 0 ? data.cwd.trim() : void 0;
|
|
3111
|
+
const isolated = isAgentScopeIsolated();
|
|
3112
|
+
const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
|
|
3113
|
+
const wildcardAgent = explicitAgentId === "*";
|
|
3114
|
+
const envAgentId = isolated ? getAgentId() : void 0;
|
|
3115
|
+
const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? envAgentId;
|
|
3116
|
+
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.");
|
|
3112
3117
|
const format = typeof data.format === "string" ? data.format : "full";
|
|
3113
3118
|
if (![
|
|
3114
3119
|
"full",
|
|
@@ -3124,7 +3129,7 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3124
3129
|
const count = await rebuildIndex(kv);
|
|
3125
3130
|
logger.info("Search index rebuilt", { entries: count });
|
|
3126
3131
|
}
|
|
3127
|
-
const filtering = !!(projectFilter || cwdFilter);
|
|
3132
|
+
const filtering = !!(projectFilter || cwdFilter || filterAgentId);
|
|
3128
3133
|
const fetchLimit = filtering ? Math.max(effectiveLimit * 10, 100) : effectiveLimit;
|
|
3129
3134
|
const results = idx.search(query, fetchLimit);
|
|
3130
3135
|
const sessionCache = /* @__PURE__ */ new Map();
|
|
@@ -3141,9 +3146,10 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3141
3146
|
memoryProjectCache.set(obsId, proj);
|
|
3142
3147
|
return proj;
|
|
3143
3148
|
};
|
|
3149
|
+
const earlyCap = filterAgentId ? fetchLimit : effectiveLimit;
|
|
3144
3150
|
const candidates = [];
|
|
3145
3151
|
for (const r of results) {
|
|
3146
|
-
if (candidates.length >=
|
|
3152
|
+
if (candidates.length >= earlyCap) break;
|
|
3147
3153
|
if (filtering) {
|
|
3148
3154
|
const s = await loadSession(r.sessionId);
|
|
3149
3155
|
if (s) {
|
|
@@ -3165,7 +3171,10 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3165
3171
|
const enriched = [];
|
|
3166
3172
|
for (let i = 0; i < candidates.length; i++) {
|
|
3167
3173
|
const obs = obsResults[i];
|
|
3168
|
-
if (obs)
|
|
3174
|
+
if (!obs) continue;
|
|
3175
|
+
if (filterAgentId !== void 0 && obs.agentId !== filterAgentId) continue;
|
|
3176
|
+
if (enriched.length >= effectiveLimit) break;
|
|
3177
|
+
enriched.push({
|
|
3169
3178
|
observation: obs,
|
|
3170
3179
|
score: candidates[i].score,
|
|
3171
3180
|
sessionId: candidates[i].sessionId
|
|
@@ -3327,10 +3336,9 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3327
3336
|
const inheritedAgentId = existingSession ? existingSession.agentId : getAgentId();
|
|
3328
3337
|
if (inheritedAgentId) raw.agentId = inheritedAgentId;
|
|
3329
3338
|
if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) {
|
|
3330
|
-
const { saveImageToDisk } = await import("./image-store-Gpo2mgM9.mjs").then((n) => n.i);
|
|
3331
3339
|
const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
|
|
3332
3340
|
raw.imageData = filePath;
|
|
3333
|
-
const { incrementImageRef } = await import("./image-refs-
|
|
3341
|
+
const { incrementImageRef } = await import("./image-refs-C2YU7BIm.mjs");
|
|
3334
3342
|
await incrementImageRef(kv, filePath);
|
|
3335
3343
|
sdk.trigger({
|
|
3336
3344
|
function_id: "mem::disk-size-delta",
|
|
@@ -3350,13 +3358,13 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3350
3358
|
try {
|
|
3351
3359
|
await kv.set(KV.observations(payload.sessionId), obsId, raw);
|
|
3352
3360
|
} catch (error) {
|
|
3353
|
-
if (raw.imageData) {
|
|
3354
|
-
const {
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3361
|
+
if (raw.imageData) try {
|
|
3362
|
+
const { decrementImageRef } = await import("./image-refs-C2YU7BIm.mjs");
|
|
3363
|
+
await decrementImageRef(kv, sdk, raw.imageData);
|
|
3364
|
+
} catch (rollbackError) {
|
|
3365
|
+
logger.error("Failed to roll back image ref after observation write failure", {
|
|
3366
|
+
imageRef: raw.imageData,
|
|
3367
|
+
error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
3360
3368
|
});
|
|
3361
3369
|
}
|
|
3362
3370
|
throw error;
|
|
@@ -3525,6 +3533,7 @@ function registerImageQuotaCleanup(sdk, kv) {
|
|
|
3525
3533
|
await withKeyedLock(`imgRef:${f.filePath}`, async () => {
|
|
3526
3534
|
let refCount;
|
|
3527
3535
|
try {
|
|
3536
|
+
const { getImageRefCount } = await import("./image-refs-C2YU7BIm.mjs");
|
|
3528
3537
|
refCount = await getImageRefCount(kv, f.filePath);
|
|
3529
3538
|
} catch (err) {
|
|
3530
3539
|
logger.error("Failed to read refCount; skipping eviction", {
|
|
@@ -4615,6 +4624,12 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
4615
4624
|
sdk.registerFunction("mem::context", async (data) => {
|
|
4616
4625
|
const budget = data.budget || tokenBudget;
|
|
4617
4626
|
const blocks = [];
|
|
4627
|
+
const isolated = isAgentScopeIsolated();
|
|
4628
|
+
const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
|
|
4629
|
+
const wildcardAgent = explicitAgentId === "*";
|
|
4630
|
+
const envAgentId = isolated ? getAgentId() : void 0;
|
|
4631
|
+
const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? envAgentId;
|
|
4632
|
+
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.");
|
|
4618
4633
|
const [pinnedSlots, profile, lessons] = await Promise.all([
|
|
4619
4634
|
isSlotsEnabled() ? listPinnedSlots(kv).catch(() => []) : Promise.resolve([]),
|
|
4620
4635
|
kv.get(KV.profiles, data.project).catch(() => null),
|
|
@@ -4661,7 +4676,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
4661
4676
|
sourceIds: relevantLessons.map((l) => l.id)
|
|
4662
4677
|
});
|
|
4663
4678
|
}
|
|
4664
|
-
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);
|
|
4679
|
+
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);
|
|
4665
4680
|
const summariesPerSession = await Promise.all(sessions.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
|
|
4666
4681
|
const sessionsNeedingObs = [];
|
|
4667
4682
|
for (let i = 0; i < sessions.length; i++) {
|
|
@@ -5651,7 +5666,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5651
5666
|
const deletedMemoryIds = [];
|
|
5652
5667
|
const deletedObservationIds = [];
|
|
5653
5668
|
let deletedSession = false;
|
|
5654
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
5669
|
+
const { decrementImageRef } = await import("./image-refs-C2YU7BIm.mjs");
|
|
5655
5670
|
if (data.memoryId) {
|
|
5656
5671
|
const mem = await kv.get(KV.memories, data.memoryId);
|
|
5657
5672
|
await kv.delete(KV.memories, data.memoryId);
|
|
@@ -5758,7 +5773,7 @@ async function runRecoveredSessionConsolidation(sdk) {
|
|
|
5758
5773
|
function registerEvictFunction(sdk, kv) {
|
|
5759
5774
|
sdk.registerFunction("mem::evict", async (data) => {
|
|
5760
5775
|
const dryRun = data?.dryRun ?? false;
|
|
5761
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
5776
|
+
const { decrementImageRef } = await import("./image-refs-C2YU7BIm.mjs");
|
|
5762
5777
|
const configOverride = await kv.get(KV.config, "eviction").catch(() => null);
|
|
5763
5778
|
const cfg = {
|
|
5764
5779
|
...DEFAULTS$1,
|
|
@@ -6278,7 +6293,10 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
6278
6293
|
sdk.registerFunction("mem::smart-search", async (data) => {
|
|
6279
6294
|
const isolated = isAgentScopeIsolated();
|
|
6280
6295
|
const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
|
|
6281
|
-
const
|
|
6296
|
+
const wildcardAgent = explicitAgentId === "*";
|
|
6297
|
+
const envAgentId = isolated ? getAgentId() : void 0;
|
|
6298
|
+
const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? envAgentId;
|
|
6299
|
+
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.");
|
|
6282
6300
|
if (data.expandIds && data.expandIds.length > 0) {
|
|
6283
6301
|
const raw = data.expandIds.slice(0, 20);
|
|
6284
6302
|
const items = raw.map((entry) => {
|
|
@@ -6570,7 +6588,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6570
6588
|
sdk.registerFunction("mem::auto-forget", async (data) => {
|
|
6571
6589
|
const dryRun = data?.dryRun ?? false;
|
|
6572
6590
|
const now = Date.now();
|
|
6573
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
6591
|
+
const { decrementImageRef } = await import("./image-refs-C2YU7BIm.mjs");
|
|
6574
6592
|
const result = {
|
|
6575
6593
|
ttlExpired: [],
|
|
6576
6594
|
contradictions: [],
|
|
@@ -6829,7 +6847,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
6829
6847
|
"0.9.23",
|
|
6830
6848
|
"0.9.24",
|
|
6831
6849
|
"0.9.25",
|
|
6832
|
-
"0.9.26"
|
|
6850
|
+
"0.9.26",
|
|
6851
|
+
"0.9.27",
|
|
6852
|
+
"0.9.28"
|
|
6833
6853
|
]).has(importData.version)) return {
|
|
6834
6854
|
success: false,
|
|
6835
6855
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -7353,19 +7373,167 @@ function buildGraphExtractionPrompt(observations) {
|
|
|
7353
7373
|
//#region src/functions/graph.ts
|
|
7354
7374
|
const DEFAULT_GRAPH_QUERY_LIMIT = 500;
|
|
7355
7375
|
const MAX_GRAPH_QUERY_LIMIT = 5e3;
|
|
7356
|
-
|
|
7376
|
+
const SNAPSHOT_TOP_NODES = DEFAULT_GRAPH_QUERY_LIMIT;
|
|
7377
|
+
const SNAPSHOT_KEY = "current";
|
|
7378
|
+
const LIVE_ENUMERATION_BUDGET_MS = 6e3;
|
|
7379
|
+
function withTimeout(p, ms, label) {
|
|
7380
|
+
return new Promise((resolve, reject) => {
|
|
7381
|
+
const t = setTimeout(() => reject(/* @__PURE__ */ new Error(`${label}: exceeded ${ms}ms budget`)), ms);
|
|
7382
|
+
p.then((v) => {
|
|
7383
|
+
clearTimeout(t);
|
|
7384
|
+
resolve(v);
|
|
7385
|
+
}, (err) => {
|
|
7386
|
+
clearTimeout(t);
|
|
7387
|
+
reject(err);
|
|
7388
|
+
});
|
|
7389
|
+
});
|
|
7390
|
+
}
|
|
7391
|
+
function emptySnapshot() {
|
|
7357
7392
|
return {
|
|
7358
|
-
|
|
7359
|
-
|
|
7393
|
+
version: 1,
|
|
7394
|
+
topNodes: [],
|
|
7395
|
+
topEdges: [],
|
|
7396
|
+
topDegrees: {},
|
|
7397
|
+
stats: {
|
|
7398
|
+
totalNodes: 0,
|
|
7399
|
+
totalEdges: 0,
|
|
7400
|
+
nodesByType: {},
|
|
7401
|
+
edgesByType: {}
|
|
7402
|
+
},
|
|
7403
|
+
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
7404
|
+
dirty: true
|
|
7360
7405
|
};
|
|
7361
7406
|
}
|
|
7362
|
-
function
|
|
7407
|
+
async function readSnapshot(kv) {
|
|
7408
|
+
try {
|
|
7409
|
+
const snap = await kv.get(KV.graphSnapshot, SNAPSHOT_KEY);
|
|
7410
|
+
if (snap && typeof snap === "object" && snap.version === 1) return snap;
|
|
7411
|
+
return null;
|
|
7412
|
+
} catch (err) {
|
|
7413
|
+
logger.warn("Graph snapshot read failed", { error: err instanceof Error ? err.message : String(err) });
|
|
7414
|
+
return null;
|
|
7415
|
+
}
|
|
7416
|
+
}
|
|
7417
|
+
function buildSnapshotFromArrays(nodes, edges) {
|
|
7418
|
+
const liveNodes = nodes.filter((n) => !n.stale);
|
|
7419
|
+
const liveEdges = edges.filter((e) => !e.stale);
|
|
7363
7420
|
const degree = /* @__PURE__ */ new Map();
|
|
7364
|
-
for (const
|
|
7365
|
-
degree.set(
|
|
7366
|
-
degree.set(
|
|
7421
|
+
for (const e of liveEdges) {
|
|
7422
|
+
degree.set(e.sourceNodeId, (degree.get(e.sourceNodeId) ?? 0) + 1);
|
|
7423
|
+
degree.set(e.targetNodeId, (degree.get(e.targetNodeId) ?? 0) + 1);
|
|
7424
|
+
}
|
|
7425
|
+
const ranked = [...liveNodes].sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0)).slice(0, SNAPSHOT_TOP_NODES);
|
|
7426
|
+
const rankedIds = new Set(ranked.map((n) => n.id));
|
|
7427
|
+
const topEdges = liveEdges.filter((e) => rankedIds.has(e.sourceNodeId) && rankedIds.has(e.targetNodeId));
|
|
7428
|
+
const topDegrees = {};
|
|
7429
|
+
for (const n of ranked) topDegrees[n.id] = degree.get(n.id) ?? 0;
|
|
7430
|
+
const nodesByType = {};
|
|
7431
|
+
for (const n of liveNodes) nodesByType[n.type] = (nodesByType[n.type] || 0) + 1;
|
|
7432
|
+
const edgesByType = {};
|
|
7433
|
+
for (const e of liveEdges) edgesByType[e.type] = (edgesByType[e.type] || 0) + 1;
|
|
7434
|
+
return {
|
|
7435
|
+
version: 1,
|
|
7436
|
+
topNodes: ranked,
|
|
7437
|
+
topEdges,
|
|
7438
|
+
topDegrees,
|
|
7439
|
+
stats: {
|
|
7440
|
+
totalNodes: liveNodes.length,
|
|
7441
|
+
totalEdges: liveEdges.length,
|
|
7442
|
+
nodesByType,
|
|
7443
|
+
edgesByType
|
|
7444
|
+
},
|
|
7445
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7446
|
+
dirty: false
|
|
7447
|
+
};
|
|
7448
|
+
}
|
|
7449
|
+
function paginateFromSnapshot(snap, filterType, limit, offset) {
|
|
7450
|
+
const filteredNodes = filterType ? snap.topNodes.filter((n) => n.type === filterType) : snap.topNodes;
|
|
7451
|
+
const total = filterType ? snap.stats.nodesByType[filterType] ?? 0 : snap.stats.totalNodes;
|
|
7452
|
+
const pageNodes = filteredNodes.slice(offset, offset + limit);
|
|
7453
|
+
const pageIds = new Set(pageNodes.map((n) => n.id));
|
|
7454
|
+
return {
|
|
7455
|
+
nodes: pageNodes,
|
|
7456
|
+
edges: snap.topEdges.filter((e) => pageIds.has(e.sourceNodeId) && pageIds.has(e.targetNodeId)),
|
|
7457
|
+
depth: 0,
|
|
7458
|
+
totalNodes: total,
|
|
7459
|
+
totalEdges: snap.stats.totalEdges,
|
|
7460
|
+
truncated: total > pageNodes.length,
|
|
7461
|
+
limit,
|
|
7462
|
+
offset,
|
|
7463
|
+
fromSnapshot: true
|
|
7464
|
+
};
|
|
7465
|
+
}
|
|
7466
|
+
const REBUILD_SAFE_NODE_CEILING = 25e3;
|
|
7467
|
+
function nameIndexKey(type, name) {
|
|
7468
|
+
return `${type}|${name}`;
|
|
7469
|
+
}
|
|
7470
|
+
function edgeIndexKey(sourceNodeId, targetNodeId, type) {
|
|
7471
|
+
return `${sourceNodeId}|${targetNodeId}|${type}`;
|
|
7472
|
+
}
|
|
7473
|
+
async function applyDegreeDelta(kv, snap, nodeId, delta) {
|
|
7474
|
+
const prev = await kv.get(KV.graphNodeDegree, nodeId) ?? 0;
|
|
7475
|
+
const next = Math.max(0, prev + delta);
|
|
7476
|
+
await kv.set(KV.graphNodeDegree, nodeId, next);
|
|
7477
|
+
if (snap.topNodes.findIndex((n) => n.id === nodeId) !== -1) {
|
|
7478
|
+
snap.topDegrees[nodeId] = next;
|
|
7479
|
+
snap.topNodes.sort((a, b) => (snap.topDegrees[b.id] ?? 0) - (snap.topDegrees[a.id] ?? 0));
|
|
7480
|
+
return next;
|
|
7481
|
+
}
|
|
7482
|
+
if (snap.topNodes.length < SNAPSHOT_TOP_NODES) {
|
|
7483
|
+
const node = await kv.get(KV.graphNodes, nodeId);
|
|
7484
|
+
if (node && !node.stale) {
|
|
7485
|
+
snap.topNodes.push(node);
|
|
7486
|
+
snap.topDegrees[node.id] = next;
|
|
7487
|
+
snap.topNodes.sort((a, b) => (snap.topDegrees[b.id] ?? 0) - (snap.topDegrees[a.id] ?? 0));
|
|
7488
|
+
}
|
|
7489
|
+
return next;
|
|
7367
7490
|
}
|
|
7368
|
-
|
|
7491
|
+
const tailEntry = snap.topNodes[snap.topNodes.length - 1];
|
|
7492
|
+
if (!tailEntry) return next;
|
|
7493
|
+
if (next > (snap.topDegrees[tailEntry.id] ?? 0)) {
|
|
7494
|
+
const node = await kv.get(KV.graphNodes, nodeId);
|
|
7495
|
+
if (node && !node.stale) {
|
|
7496
|
+
const evicted = snap.topNodes.pop();
|
|
7497
|
+
if (evicted) delete snap.topDegrees[evicted.id];
|
|
7498
|
+
snap.topNodes.push(node);
|
|
7499
|
+
snap.topDegrees[node.id] = next;
|
|
7500
|
+
snap.topNodes.sort((a, b) => (snap.topDegrees[b.id] ?? 0) - (snap.topDegrees[a.id] ?? 0));
|
|
7501
|
+
}
|
|
7502
|
+
}
|
|
7503
|
+
return next;
|
|
7504
|
+
}
|
|
7505
|
+
function snapshotPushEdgeIfBothInTop(snap, edge) {
|
|
7506
|
+
const topIds = new Set(snap.topNodes.map((n) => n.id));
|
|
7507
|
+
if (topIds.has(edge.sourceNodeId) && topIds.has(edge.targetNodeId)) {
|
|
7508
|
+
if (!snap.topEdges.find((e) => e.id === edge.id)) snap.topEdges.push(edge);
|
|
7509
|
+
}
|
|
7510
|
+
}
|
|
7511
|
+
function mergeNode(existing, incoming, obsIds, capturedAt) {
|
|
7512
|
+
return {
|
|
7513
|
+
...existing,
|
|
7514
|
+
sourceObservationIds: [...new Set([
|
|
7515
|
+
...existing.sourceObservationIds,
|
|
7516
|
+
...incoming.sourceObservationIds,
|
|
7517
|
+
...obsIds
|
|
7518
|
+
])],
|
|
7519
|
+
properties: {
|
|
7520
|
+
...existing.properties,
|
|
7521
|
+
...incoming.properties
|
|
7522
|
+
},
|
|
7523
|
+
updatedAt: capturedAt
|
|
7524
|
+
};
|
|
7525
|
+
}
|
|
7526
|
+
function mergeEdge(existing, obsIds) {
|
|
7527
|
+
return {
|
|
7528
|
+
...existing,
|
|
7529
|
+
sourceObservationIds: [...new Set([...existing.sourceObservationIds, ...obsIds])]
|
|
7530
|
+
};
|
|
7531
|
+
}
|
|
7532
|
+
function resolvePagination(rawLimit, rawOffset) {
|
|
7533
|
+
return {
|
|
7534
|
+
limit: Math.max(1, Math.min(typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.floor(rawLimit) : DEFAULT_GRAPH_QUERY_LIMIT, MAX_GRAPH_QUERY_LIMIT)),
|
|
7535
|
+
offset: Math.max(0, typeof rawOffset === "number" && Number.isFinite(rawOffset) ? Math.floor(rawOffset) : 0)
|
|
7536
|
+
};
|
|
7369
7537
|
}
|
|
7370
7538
|
function paginate(nodes, allEdges, depth, limit, offset) {
|
|
7371
7539
|
const totalNodes = nodes.length;
|
|
@@ -7462,45 +7630,76 @@ function registerGraphFunction(sdk, kv, provider) {
|
|
|
7462
7630
|
const response = await provider.compress(GRAPH_EXTRACTION_SYSTEM, prompt);
|
|
7463
7631
|
const obsIds = data.observations.map((o) => o.id);
|
|
7464
7632
|
const { nodes, edges } = parseGraphXml(response, obsIds);
|
|
7465
|
-
const
|
|
7466
|
-
const
|
|
7633
|
+
const snap = await readSnapshot(kv) ?? emptySnapshot();
|
|
7634
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7635
|
+
let newNodeCount = 0;
|
|
7636
|
+
let newEdgeCount = 0;
|
|
7637
|
+
const newEdgesForTopCheck = [];
|
|
7467
7638
|
for (const node of nodes) {
|
|
7468
|
-
const
|
|
7639
|
+
const indexKey = nameIndexKey(node.type, node.name);
|
|
7640
|
+
const existingId = await kv.get(KV.graphNameIndex, indexKey);
|
|
7641
|
+
let existing = null;
|
|
7642
|
+
if (existingId) {
|
|
7643
|
+
existing = await kv.get(KV.graphNodes, existingId);
|
|
7644
|
+
if (existing && snap.resetAt && typeof existing.createdAt === "string" && existing.createdAt < snap.resetAt) existing = null;
|
|
7645
|
+
}
|
|
7469
7646
|
if (existing) {
|
|
7470
|
-
const merged =
|
|
7471
|
-
...existing,
|
|
7472
|
-
sourceObservationIds: [...new Set([...existing.sourceObservationIds, ...obsIds])],
|
|
7473
|
-
properties: {
|
|
7474
|
-
...existing.properties,
|
|
7475
|
-
...node.properties
|
|
7476
|
-
}
|
|
7477
|
-
};
|
|
7647
|
+
const merged = mergeNode(existing, node, obsIds, capturedAt);
|
|
7478
7648
|
await kv.set(KV.graphNodes, existing.id, merged);
|
|
7479
|
-
const
|
|
7480
|
-
if (
|
|
7649
|
+
const topIdx = snap.topNodes.findIndex((n) => n.id === existing.id);
|
|
7650
|
+
if (topIdx !== -1) snap.topNodes[topIdx] = merged;
|
|
7481
7651
|
} else {
|
|
7482
7652
|
await kv.set(KV.graphNodes, node.id, node);
|
|
7483
|
-
|
|
7653
|
+
await kv.set(KV.graphNameIndex, indexKey, node.id);
|
|
7654
|
+
await kv.set(KV.graphNodeDegree, node.id, 0);
|
|
7655
|
+
snap.stats.totalNodes += 1;
|
|
7656
|
+
snap.stats.nodesByType[node.type] = (snap.stats.nodesByType[node.type] ?? 0) + 1;
|
|
7657
|
+
newNodeCount += 1;
|
|
7658
|
+
if (snap.topNodes.length < SNAPSHOT_TOP_NODES) {
|
|
7659
|
+
snap.topNodes.push(node);
|
|
7660
|
+
snap.topDegrees[node.id] = 0;
|
|
7661
|
+
}
|
|
7484
7662
|
}
|
|
7485
7663
|
}
|
|
7486
7664
|
for (const edge of edges) {
|
|
7487
|
-
const
|
|
7488
|
-
const
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
await kv.
|
|
7665
|
+
const eKey = edgeIndexKey(edge.sourceNodeId, edge.targetNodeId, edge.type);
|
|
7666
|
+
const existingId = await kv.get(KV.graphEdgeKey, eKey);
|
|
7667
|
+
let existing = null;
|
|
7668
|
+
if (existingId) {
|
|
7669
|
+
existing = await kv.get(KV.graphEdges, existingId);
|
|
7670
|
+
if (existing && snap.resetAt && typeof existing.createdAt === "string" && existing.createdAt < snap.resetAt) existing = null;
|
|
7671
|
+
}
|
|
7672
|
+
if (existing) {
|
|
7673
|
+
const merged = mergeEdge(existing, obsIds);
|
|
7674
|
+
await kv.set(KV.graphEdges, existing.id, merged);
|
|
7675
|
+
const topIdx = snap.topEdges.findIndex((e) => e.id === existing.id);
|
|
7676
|
+
if (topIdx !== -1) snap.topEdges[topIdx] = merged;
|
|
7492
7677
|
} else {
|
|
7493
7678
|
await kv.set(KV.graphEdges, edge.id, edge);
|
|
7494
|
-
|
|
7679
|
+
await kv.set(KV.graphEdgeKey, eKey, edge.id);
|
|
7680
|
+
snap.stats.totalEdges += 1;
|
|
7681
|
+
snap.stats.edgesByType[edge.type] = (snap.stats.edgesByType[edge.type] ?? 0) + 1;
|
|
7682
|
+
newEdgeCount += 1;
|
|
7683
|
+
await applyDegreeDelta(kv, snap, edge.sourceNodeId, 1);
|
|
7684
|
+
await applyDegreeDelta(kv, snap, edge.targetNodeId, 1);
|
|
7685
|
+
newEdgesForTopCheck.push(edge);
|
|
7495
7686
|
}
|
|
7496
7687
|
}
|
|
7688
|
+
for (const edge of newEdgesForTopCheck) snapshotPushEdgeIfBothInTop(snap, edge);
|
|
7689
|
+
if (newNodeCount > 0 || newEdgeCount > 0) {
|
|
7690
|
+
snap.updatedAt = capturedAt;
|
|
7691
|
+
snap.dirty = false;
|
|
7692
|
+
await kv.set(KV.graphSnapshot, SNAPSHOT_KEY, snap);
|
|
7693
|
+
}
|
|
7497
7694
|
await recordAudit(kv, "observe", "mem::graph-extract", obsIds, {
|
|
7498
7695
|
nodesExtracted: nodes.length,
|
|
7499
7696
|
edgesExtracted: edges.length
|
|
7500
7697
|
});
|
|
7501
7698
|
logger.info("Graph extraction complete", {
|
|
7502
7699
|
nodes: nodes.length,
|
|
7503
|
-
edges: edges.length
|
|
7700
|
+
edges: edges.length,
|
|
7701
|
+
newNodes: newNodeCount,
|
|
7702
|
+
newEdges: newEdgeCount
|
|
7504
7703
|
});
|
|
7505
7704
|
return {
|
|
7506
7705
|
success: true,
|
|
@@ -7517,10 +7716,49 @@ function registerGraphFunction(sdk, kv, provider) {
|
|
|
7517
7716
|
}
|
|
7518
7717
|
});
|
|
7519
7718
|
sdk.registerFunction("mem::graph-query", async (data) => {
|
|
7520
|
-
const allNodes = (await kv.list(KV.graphNodes)).filter((n) => !n.stale);
|
|
7521
|
-
const allEdges = (await kv.list(KV.graphEdges)).filter((e) => !e.stale);
|
|
7522
7719
|
const maxDepth = Math.min(data.maxDepth || 3, 5);
|
|
7523
7720
|
const { limit, offset } = resolvePagination(data.limit, data.offset);
|
|
7721
|
+
if (!data.query && !data.startNodeId) {
|
|
7722
|
+
const snap = await readSnapshot(kv);
|
|
7723
|
+
if (snap && snap.stats.totalNodes > 0) return paginateFromSnapshot(snap, data.nodeType, limit, offset);
|
|
7724
|
+
return {
|
|
7725
|
+
nodes: [],
|
|
7726
|
+
edges: [],
|
|
7727
|
+
depth: 0,
|
|
7728
|
+
totalNodes: 0,
|
|
7729
|
+
totalEdges: 0,
|
|
7730
|
+
truncated: false,
|
|
7731
|
+
limit,
|
|
7732
|
+
offset,
|
|
7733
|
+
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."
|
|
7734
|
+
};
|
|
7735
|
+
}
|
|
7736
|
+
let allNodes;
|
|
7737
|
+
let allEdges;
|
|
7738
|
+
try {
|
|
7739
|
+
const [rawNodes, rawEdges] = await withTimeout(Promise.all([kv.list(KV.graphNodes), kv.list(KV.graphEdges)]), LIVE_ENUMERATION_BUDGET_MS, "graph-query enumeration");
|
|
7740
|
+
allNodes = rawNodes.filter((n) => !n.stale);
|
|
7741
|
+
allEdges = rawEdges.filter((e) => !e.stale);
|
|
7742
|
+
} catch (err) {
|
|
7743
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7744
|
+
logger.warn("Graph query enumeration timed out, using snapshot", { error: msg });
|
|
7745
|
+
const snap = await readSnapshot(kv);
|
|
7746
|
+
if (snap) return {
|
|
7747
|
+
...paginateFromSnapshot(snap, data.nodeType, limit, offset),
|
|
7748
|
+
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."
|
|
7749
|
+
};
|
|
7750
|
+
return {
|
|
7751
|
+
nodes: [],
|
|
7752
|
+
edges: [],
|
|
7753
|
+
depth: 0,
|
|
7754
|
+
totalNodes: 0,
|
|
7755
|
+
totalEdges: 0,
|
|
7756
|
+
truncated: false,
|
|
7757
|
+
limit,
|
|
7758
|
+
offset,
|
|
7759
|
+
warning: "Graph enumeration exceeded budget and no snapshot is available."
|
|
7760
|
+
};
|
|
7761
|
+
}
|
|
7524
7762
|
if (data.query) {
|
|
7525
7763
|
const lower = data.query.toLowerCase();
|
|
7526
7764
|
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);
|
|
@@ -7557,22 +7795,116 @@ function registerGraphFunction(sdk, kv, provider) {
|
|
|
7557
7795
|
}
|
|
7558
7796
|
return paginate(resultNodes, resultEdges, maxDepth, limit, offset);
|
|
7559
7797
|
}
|
|
7560
|
-
|
|
7561
|
-
if (data.nodeType) filtered = allNodes.filter((n) => n.type === data.nodeType);
|
|
7562
|
-
return paginate(rankByDegree(filtered, allEdges), allEdges, 0, limit, offset);
|
|
7798
|
+
return paginate([], [], 0, limit, offset);
|
|
7563
7799
|
});
|
|
7564
7800
|
sdk.registerFunction("mem::graph-stats", async () => {
|
|
7565
|
-
const
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7801
|
+
const snap = await readSnapshot(kv);
|
|
7802
|
+
if (snap) return {
|
|
7803
|
+
...snap.stats,
|
|
7804
|
+
fromSnapshot: true,
|
|
7805
|
+
updatedAt: snap.updatedAt,
|
|
7806
|
+
...snap.dirty ? { warning: "Snapshot is marked dirty (write was in-flight when read). Counts are eventually consistent." } : {}
|
|
7807
|
+
};
|
|
7571
7808
|
return {
|
|
7572
|
-
totalNodes:
|
|
7573
|
-
totalEdges:
|
|
7574
|
-
nodesByType,
|
|
7575
|
-
edgesByType
|
|
7809
|
+
totalNodes: 0,
|
|
7810
|
+
totalEdges: 0,
|
|
7811
|
+
nodesByType: {},
|
|
7812
|
+
edgesByType: {},
|
|
7813
|
+
fromSnapshot: false,
|
|
7814
|
+
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."
|
|
7815
|
+
};
|
|
7816
|
+
});
|
|
7817
|
+
sdk.registerFunction("mem::graph-snapshot-rebuild", async (data) => {
|
|
7818
|
+
const started = Date.now();
|
|
7819
|
+
const forceRebuild = data?.force === true;
|
|
7820
|
+
try {
|
|
7821
|
+
if (!await readSnapshot(kv) && !forceRebuild) {
|
|
7822
|
+
logger.warn("Graph snapshot rebuild refused: no prior snapshot", { hint: "legacy corpus or empty store" });
|
|
7823
|
+
return {
|
|
7824
|
+
success: false,
|
|
7825
|
+
legacyCorpus: true,
|
|
7826
|
+
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."
|
|
7827
|
+
};
|
|
7828
|
+
}
|
|
7829
|
+
} catch (err) {
|
|
7830
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7831
|
+
logger.warn("Graph snapshot pre-flight read failed", { error: msg });
|
|
7832
|
+
}
|
|
7833
|
+
try {
|
|
7834
|
+
const [nodes, edges] = await withTimeout(Promise.all([kv.list(KV.graphNodes), kv.list(KV.graphEdges)]), LIVE_ENUMERATION_BUDGET_MS, "graph-snapshot-rebuild enumeration");
|
|
7835
|
+
if (nodes.length > REBUILD_SAFE_NODE_CEILING) {
|
|
7836
|
+
logger.warn("Graph snapshot rebuild aborted: corpus too large", {
|
|
7837
|
+
totalNodes: nodes.length,
|
|
7838
|
+
ceiling: REBUILD_SAFE_NODE_CEILING
|
|
7839
|
+
});
|
|
7840
|
+
return {
|
|
7841
|
+
success: false,
|
|
7842
|
+
tooLarge: true,
|
|
7843
|
+
totalNodes: nodes.length,
|
|
7844
|
+
ceiling: REBUILD_SAFE_NODE_CEILING,
|
|
7845
|
+
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.`
|
|
7846
|
+
};
|
|
7847
|
+
}
|
|
7848
|
+
const liveNodes = nodes.filter((n) => !n.stale);
|
|
7849
|
+
const liveEdges = edges.filter((e) => !e.stale);
|
|
7850
|
+
const degree = /* @__PURE__ */ new Map();
|
|
7851
|
+
for (const e of liveEdges) {
|
|
7852
|
+
degree.set(e.sourceNodeId, (degree.get(e.sourceNodeId) ?? 0) + 1);
|
|
7853
|
+
degree.set(e.targetNodeId, (degree.get(e.targetNodeId) ?? 0) + 1);
|
|
7854
|
+
}
|
|
7855
|
+
const BATCH_SIZE = 100;
|
|
7856
|
+
for (let i = 0; i < liveNodes.length; i += BATCH_SIZE) {
|
|
7857
|
+
const batch = liveNodes.slice(i, i + BATCH_SIZE);
|
|
7858
|
+
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)]));
|
|
7859
|
+
}
|
|
7860
|
+
for (let i = 0; i < liveEdges.length; i += BATCH_SIZE) {
|
|
7861
|
+
const batch = liveEdges.slice(i, i + BATCH_SIZE);
|
|
7862
|
+
await Promise.all(batch.map((e) => kv.set(KV.graphEdgeKey, edgeIndexKey(e.sourceNodeId, e.targetNodeId, e.type), e.id)));
|
|
7863
|
+
}
|
|
7864
|
+
const snap = buildSnapshotFromArrays(nodes, edges);
|
|
7865
|
+
await kv.set(KV.graphSnapshot, SNAPSHOT_KEY, snap);
|
|
7866
|
+
const tookMs = Date.now() - started;
|
|
7867
|
+
logger.info("Graph snapshot rebuilt", {
|
|
7868
|
+
totalNodes: snap.stats.totalNodes,
|
|
7869
|
+
totalEdges: snap.stats.totalEdges,
|
|
7870
|
+
topNodes: snap.topNodes.length,
|
|
7871
|
+
topEdges: snap.topEdges.length,
|
|
7872
|
+
tookMs
|
|
7873
|
+
});
|
|
7874
|
+
return {
|
|
7875
|
+
success: true,
|
|
7876
|
+
...snap.stats,
|
|
7877
|
+
topNodes: snap.topNodes.length,
|
|
7878
|
+
topEdges: snap.topEdges.length,
|
|
7879
|
+
updatedAt: snap.updatedAt,
|
|
7880
|
+
tookMs
|
|
7881
|
+
};
|
|
7882
|
+
} catch (err) {
|
|
7883
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7884
|
+
logger.error("Graph snapshot rebuild failed", { error: msg });
|
|
7885
|
+
return {
|
|
7886
|
+
success: false,
|
|
7887
|
+
error: msg
|
|
7888
|
+
};
|
|
7889
|
+
}
|
|
7890
|
+
});
|
|
7891
|
+
sdk.registerFunction("mem::graph-reset", async () => {
|
|
7892
|
+
const started = Date.now();
|
|
7893
|
+
const resetSnapshot = {
|
|
7894
|
+
...emptySnapshot(),
|
|
7895
|
+
resetAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7896
|
+
};
|
|
7897
|
+
await kv.set(KV.graphSnapshot, SNAPSHOT_KEY, resetSnapshot);
|
|
7898
|
+
const counts = { [KV.graphSnapshot]: 1 };
|
|
7899
|
+
const tookMs = Date.now() - started;
|
|
7900
|
+
logger.info("Graph state reset", {
|
|
7901
|
+
counts,
|
|
7902
|
+
tookMs
|
|
7903
|
+
});
|
|
7904
|
+
return {
|
|
7905
|
+
success: true,
|
|
7906
|
+
cleared: counts,
|
|
7907
|
+
tookMs
|
|
7576
7908
|
};
|
|
7577
7909
|
});
|
|
7578
7910
|
}
|
|
@@ -13395,7 +13727,7 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
13395
13727
|
const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
|
|
13396
13728
|
const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
|
|
13397
13729
|
const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
|
|
13398
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
13730
|
+
const { decrementImageRef } = await import("./image-refs-C2YU7BIm.mjs");
|
|
13399
13731
|
const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
|
|
13400
13732
|
if (data?.dryRun) return {
|
|
13401
13733
|
success: true,
|
|
@@ -14863,11 +15195,15 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14863
15195
|
status_code: 400,
|
|
14864
15196
|
body: { error: "budget must be a positive integer" }
|
|
14865
15197
|
};
|
|
15198
|
+
const queryAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"] : void 0;
|
|
15199
|
+
const bodyAgentId = typeof body.agentId === "string" && body.agentId.trim().length > 0 ? body.agentId.trim() : void 0;
|
|
14866
15200
|
const payload = {
|
|
14867
15201
|
sessionId,
|
|
14868
15202
|
project
|
|
14869
15203
|
};
|
|
14870
15204
|
if (budget !== void 0) payload.budget = budget;
|
|
15205
|
+
const agentId = bodyAgentId ?? queryAgentId;
|
|
15206
|
+
if (agentId !== void 0) payload.agentId = agentId;
|
|
14871
15207
|
return {
|
|
14872
15208
|
status_code: 200,
|
|
14873
15209
|
body: await sdk.trigger({
|
|
@@ -14887,6 +15223,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14887
15223
|
});
|
|
14888
15224
|
sdk.registerFunction("api::search", async (req) => {
|
|
14889
15225
|
const body = req.body ?? {};
|
|
15226
|
+
const queryAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"] : void 0;
|
|
14890
15227
|
if (typeof body.query !== "string" || !body.query.trim()) return {
|
|
14891
15228
|
status_code: 400,
|
|
14892
15229
|
body: { error: "query is required and must be a non-empty string" }
|
|
@@ -14915,13 +15252,15 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14915
15252
|
status_code: 400,
|
|
14916
15253
|
body: { error: "token_budget must be a positive integer" }
|
|
14917
15254
|
};
|
|
15255
|
+
const bodyAgentId = typeof body.agentId === "string" && body.agentId.trim().length > 0 ? body.agentId.trim() : void 0;
|
|
14918
15256
|
const payload = {
|
|
14919
15257
|
query: body.query.trim(),
|
|
14920
15258
|
limit: body.limit,
|
|
14921
15259
|
project: body.project,
|
|
14922
15260
|
cwd: body.cwd,
|
|
14923
15261
|
format: typeof body.format === "string" ? body.format.trim().toLowerCase() : void 0,
|
|
14924
|
-
token_budget: body.token_budget
|
|
15262
|
+
token_budget: body.token_budget,
|
|
15263
|
+
agentId: bodyAgentId ?? queryAgentId
|
|
14925
15264
|
};
|
|
14926
15265
|
return {
|
|
14927
15266
|
status_code: 200,
|
|
@@ -15076,7 +15415,8 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15076
15415
|
function_id: "mem::context",
|
|
15077
15416
|
payload: {
|
|
15078
15417
|
sessionId,
|
|
15079
|
-
project
|
|
15418
|
+
project,
|
|
15419
|
+
...agentId ? { agentId } : {}
|
|
15080
15420
|
}
|
|
15081
15421
|
})).context
|
|
15082
15422
|
}
|
|
@@ -15268,9 +15608,14 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15268
15608
|
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
15269
15609
|
const wildcardAgent = normalizedAgentId === "*";
|
|
15270
15610
|
const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
15611
|
+
const filtered = filterAgentId ? sessions.filter((s) => s.agentId === filterAgentId) : sessions;
|
|
15612
|
+
const summaries = await Promise.all(filtered.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
|
|
15271
15613
|
return {
|
|
15272
15614
|
status_code: 200,
|
|
15273
|
-
body: { sessions:
|
|
15615
|
+
body: { sessions: filtered.map((s, i) => summaries[i] ? {
|
|
15616
|
+
...s,
|
|
15617
|
+
summary: summaries[i]
|
|
15618
|
+
} : s) }
|
|
15274
15619
|
};
|
|
15275
15620
|
});
|
|
15276
15621
|
sdk.registerTrigger({
|
|
@@ -15857,6 +16202,52 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15857
16202
|
http_method: "GET"
|
|
15858
16203
|
}
|
|
15859
16204
|
});
|
|
16205
|
+
sdk.registerFunction("api::graph-snapshot-rebuild", async (req) => {
|
|
16206
|
+
const authErr = checkAuth(req, secret);
|
|
16207
|
+
if (authErr) return authErr;
|
|
16208
|
+
try {
|
|
16209
|
+
return {
|
|
16210
|
+
status_code: 200,
|
|
16211
|
+
body: await sdk.trigger({
|
|
16212
|
+
function_id: "mem::graph-snapshot-rebuild",
|
|
16213
|
+
payload: {}
|
|
16214
|
+
})
|
|
16215
|
+
};
|
|
16216
|
+
} catch {
|
|
16217
|
+
return graphDisabledResponse();
|
|
16218
|
+
}
|
|
16219
|
+
});
|
|
16220
|
+
sdk.registerTrigger({
|
|
16221
|
+
type: "http",
|
|
16222
|
+
function_id: "api::graph-snapshot-rebuild",
|
|
16223
|
+
config: {
|
|
16224
|
+
api_path: "/agentmemory/graph/snapshot-rebuild",
|
|
16225
|
+
http_method: "POST"
|
|
16226
|
+
}
|
|
16227
|
+
});
|
|
16228
|
+
sdk.registerFunction("api::graph-reset", async (req) => {
|
|
16229
|
+
const authErr = checkAuth(req, secret);
|
|
16230
|
+
if (authErr) return authErr;
|
|
16231
|
+
try {
|
|
16232
|
+
return {
|
|
16233
|
+
status_code: 200,
|
|
16234
|
+
body: await sdk.trigger({
|
|
16235
|
+
function_id: "mem::graph-reset",
|
|
16236
|
+
payload: {}
|
|
16237
|
+
})
|
|
16238
|
+
};
|
|
16239
|
+
} catch {
|
|
16240
|
+
return graphDisabledResponse();
|
|
16241
|
+
}
|
|
16242
|
+
});
|
|
16243
|
+
sdk.registerTrigger({
|
|
16244
|
+
type: "http",
|
|
16245
|
+
function_id: "api::graph-reset",
|
|
16246
|
+
config: {
|
|
16247
|
+
api_path: "/agentmemory/graph/reset",
|
|
16248
|
+
http_method: "POST"
|
|
16249
|
+
}
|
|
16250
|
+
});
|
|
15860
16251
|
sdk.registerFunction("api::graph-extract", async (req) => {
|
|
15861
16252
|
const authErr = checkAuth(req, secret);
|
|
15862
16253
|
if (authErr) return authErr;
|
|
@@ -18104,13 +18495,15 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
18104
18495
|
//#region src/triggers/events.ts
|
|
18105
18496
|
function registerEventTriggers(sdk, kv) {
|
|
18106
18497
|
sdk.registerFunction("event::session::started", async (data) => {
|
|
18498
|
+
const agentId = (typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim().slice(0, 128) : void 0) ?? getAgentId();
|
|
18107
18499
|
const session = {
|
|
18108
18500
|
id: data.sessionId,
|
|
18109
18501
|
project: data.project,
|
|
18110
18502
|
cwd: data.cwd,
|
|
18111
18503
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18112
18504
|
status: "active",
|
|
18113
|
-
observationCount: 0
|
|
18505
|
+
observationCount: 0,
|
|
18506
|
+
...agentId ? { agentId } : {}
|
|
18114
18507
|
};
|
|
18115
18508
|
await kv.set(KV.sessions, data.sessionId, session);
|
|
18116
18509
|
return {
|
|
@@ -18119,7 +18512,8 @@ function registerEventTriggers(sdk, kv) {
|
|
|
18119
18512
|
function_id: "mem::context",
|
|
18120
18513
|
payload: {
|
|
18121
18514
|
sessionId: data.sessionId,
|
|
18122
|
-
project: data.project
|
|
18515
|
+
project: data.project,
|
|
18516
|
+
...agentId ? { agentId } : {}
|
|
18123
18517
|
}
|
|
18124
18518
|
})).context
|
|
18125
18519
|
};
|
|
@@ -18291,13 +18685,15 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
18291
18685
|
status_code: 400,
|
|
18292
18686
|
body: { error: "token_budget must be a positive integer" }
|
|
18293
18687
|
};
|
|
18688
|
+
const recallAgentId = typeof args.agentId === "string" && args.agentId.trim().length > 0 ? args.agentId.trim() : void 0;
|
|
18294
18689
|
const result = await sdk.trigger({
|
|
18295
18690
|
function_id: "mem::search",
|
|
18296
18691
|
payload: {
|
|
18297
18692
|
query: args.query,
|
|
18298
18693
|
limit: typeof args.limit === "number" ? args.limit : 10,
|
|
18299
18694
|
format,
|
|
18300
|
-
token_budget: tokenBudget
|
|
18695
|
+
token_budget: tokenBudget,
|
|
18696
|
+
agentId: recallAgentId
|
|
18301
18697
|
}
|
|
18302
18698
|
});
|
|
18303
18699
|
return {
|
|
@@ -19785,7 +20181,10 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
19785
20181
|
limit: 10
|
|
19786
20182
|
}
|
|
19787
20183
|
}).catch(() => ({ results: [] }));
|
|
19788
|
-
const
|
|
20184
|
+
const memories = await kv.list(KV.memories);
|
|
20185
|
+
const isolated = isAgentScopeIsolated();
|
|
20186
|
+
const activeAgentId = isolated ? getAgentId() : void 0;
|
|
20187
|
+
const relevant = isolated && activeAgentId === void 0 ? [] : memories.filter((m) => m.isLatest).filter((m) => activeAgentId === void 0 || m.agentId === activeAgentId).slice(0, 5);
|
|
19789
20188
|
return {
|
|
19790
20189
|
status_code: 200,
|
|
19791
20190
|
body: { messages: [{
|
|
@@ -20039,10 +20438,10 @@ async function main() {
|
|
|
20039
20438
|
}
|
|
20040
20439
|
registerConsolidationPipelineFunction(sdk, kv, provider);
|
|
20041
20440
|
bootLog(`Consolidation pipeline: registered (CONSOLIDATION_ENABLED=${isConsolidationEnabled() ? "true" : "false"})`);
|
|
20042
|
-
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
|
|
20043
|
-
else bootLog(`Auto-compress: OFF (default
|
|
20044
|
-
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
|
|
20045
|
-
else bootLog(`Context injection: OFF (default
|
|
20441
|
+
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.`);
|
|
20442
|
+
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).`);
|
|
20443
|
+
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.`);
|
|
20444
|
+
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).`);
|
|
20046
20445
|
const teamConfig = loadTeamConfig();
|
|
20047
20446
|
if (teamConfig) {
|
|
20048
20447
|
registerTeamFunction(sdk, kv, teamConfig);
|
|
@@ -20149,14 +20548,14 @@ async function main() {
|
|
|
20149
20548
|
backfilled++;
|
|
20150
20549
|
}
|
|
20151
20550
|
if (backfilled > 0) {
|
|
20152
|
-
bootLog(`Backfilled ${backfilled} memories into BM25 (legacy gap
|
|
20551
|
+
bootLog(`Backfilled ${backfilled} memories into BM25 (legacy index gap)`);
|
|
20153
20552
|
indexPersistence.scheduleSave();
|
|
20154
20553
|
}
|
|
20155
20554
|
} catch (err) {
|
|
20156
20555
|
console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
|
|
20157
20556
|
}
|
|
20158
20557
|
bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
20159
|
-
bootLog(`REST API:
|
|
20558
|
+
bootLog(`REST API: 128 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
|
|
20160
20559
|
bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
|
|
20161
20560
|
const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
|
|
20162
20561
|
const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
|
|
@@ -20233,4 +20632,4 @@ main().catch((err) => {
|
|
|
20233
20632
|
//#endregion
|
|
20234
20633
|
export {};
|
|
20235
20634
|
|
|
20236
|
-
//# sourceMappingURL=src-
|
|
20635
|
+
//# sourceMappingURL=src-CzgoepGU.mjs.map
|