@agentmemory/agentmemory 0.9.22 → 0.9.24
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 +7 -2
- package/README.md +144 -32
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +42 -25
- package/dist/cli.mjs.map +1 -1
- package/dist/{connect-BQQXpyDS.mjs → connect-Cf9bmBqO.mjs} +290 -33
- package/dist/connect-Cf9bmBqO.mjs.map +1 -0
- package/dist/hooks/notification.mjs +46 -21
- package/dist/hooks/notification.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +47 -21
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +57 -22
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +26 -2
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +19 -12
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/prompt-submit.mjs +39 -16
- package/dist/hooks/prompt-submit.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +26 -33
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +28 -3
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +14 -17
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-start.mjs +31 -4
- package/dist/hooks/subagent-start.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +45 -20
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +44 -21
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/iii-config.docker.yaml +3 -2
- package/dist/iii-config.yaml +11 -2
- package/dist/index.mjs +336 -57
- package/dist/index.mjs.map +1 -1
- package/dist/{src-gpTAJuBy.mjs → src-B8J9Exum.mjs} +323 -58
- package/dist/src-B8J9Exum.mjs.map +1 -0
- package/dist/{standalone-C4i7ktpn.mjs → standalone-CPfsVTBA.mjs} +92 -11
- package/dist/standalone-CPfsVTBA.mjs.map +1 -0
- package/dist/standalone.mjs +94 -9
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-B7Y6nJsr.mjs → tools-registry-DJizX9Az.mjs} +16 -2
- package/dist/tools-registry-DJizX9Az.mjs.map +1 -0
- package/dist/version-BWEBnKAp.mjs +6 -0
- package/dist/version-BWEBnKAp.mjs.map +1 -0
- package/dist/viewer/index.html +9 -2
- package/iii-config.docker.yaml +3 -2
- package/iii-config.yaml +11 -2
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/.mcp.copilot.json +15 -0
- package/plugin/hooks/hooks.copilot.json +72 -0
- package/plugin/plugin.json +15 -0
- package/plugin/scripts/notification.mjs +46 -21
- package/plugin/scripts/notification.mjs.map +1 -1
- package/plugin/scripts/post-tool-failure.mjs +47 -21
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +57 -22
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +26 -2
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +19 -12
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/prompt-submit.mjs +39 -16
- package/plugin/scripts/prompt-submit.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +26 -33
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +28 -3
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +14 -17
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-start.mjs +31 -4
- package/plugin/scripts/subagent-start.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +45 -20
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +44 -21
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/dist/connect-BQQXpyDS.mjs.map +0 -1
- package/dist/src-gpTAJuBy.mjs.map +0 -1
- package/dist/standalone-C4i7ktpn.mjs.map +0 -1
- package/dist/tools-registry-B7Y6nJsr.mjs.map +0 -1
- package/dist/version-DvQMNbEH.mjs +0 -6
- package/dist/version-DvQMNbEH.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -233,7 +233,17 @@ function isGraphExtractionEnabled() {
|
|
|
233
233
|
return getMergedEnv()["GRAPH_EXTRACTION_ENABLED"] === "true";
|
|
234
234
|
}
|
|
235
235
|
function isConsolidationEnabled() {
|
|
236
|
-
|
|
236
|
+
const env = getMergedEnv();
|
|
237
|
+
const explicit = env["CONSOLIDATION_ENABLED"];
|
|
238
|
+
if (explicit === "false" || explicit === "0") return false;
|
|
239
|
+
if (explicit === "true" || explicit === "1") return true;
|
|
240
|
+
return hasLLMProviderConfigured(env);
|
|
241
|
+
}
|
|
242
|
+
function hasLLMProviderConfigured(env) {
|
|
243
|
+
const provider = (env["AGENTMEMORY_PROVIDER"] || "").toLowerCase();
|
|
244
|
+
if (provider === "noop") return false;
|
|
245
|
+
const openaiKeyForLlm = env["OPENAI_API_KEY"] && (env["OPENAI_API_KEY_FOR_LLM"] || "").toLowerCase() !== "false";
|
|
246
|
+
return Boolean(env["ANTHROPIC_API_KEY"] || openaiKeyForLlm || env["OPENROUTER_API_KEY"] || env["GEMINI_API_KEY"] || env["GOOGLE_API_KEY"] || env["MINIMAX_API_KEY"] || env["OPENAI_BASE_URL"] || provider === "agent-sdk");
|
|
237
247
|
}
|
|
238
248
|
function isAutoCompressEnabled() {
|
|
239
249
|
return getMergedEnv()["AGENTMEMORY_AUTO_COMPRESS"] === "true";
|
|
@@ -886,16 +896,30 @@ function resolveDimensions(model, override) {
|
|
|
886
896
|
* `api-key` header instead of `Authorization: Bearer`.
|
|
887
897
|
*
|
|
888
898
|
* Required env vars:
|
|
889
|
-
* OPENAI_API_KEY
|
|
899
|
+
* OPENAI_API_KEY — API key (fallback for OPENAI_EMBEDDING_API_KEY)
|
|
890
900
|
*
|
|
891
901
|
* Optional:
|
|
892
|
-
* OPENAI_BASE_URL
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
902
|
+
* OPENAI_BASE_URL — base URL without path (default: https://api.openai.com).
|
|
903
|
+
* Azure: https://<resource>.openai.azure.com/openai/deployments/<deployment>
|
|
904
|
+
* OPENAI_EMBEDDING_BASE_URL — embedding-specific base URL override (defaults
|
|
905
|
+
* to OPENAI_BASE_URL). Lets operators run
|
|
906
|
+
* embeddings on a separate endpoint from chat —
|
|
907
|
+
* e.g. local Ollama / LM Studio / llama.cpp /
|
|
908
|
+
* vLLM at http://localhost:1234 for unlimited
|
|
909
|
+
* free embeddings, while keeping chat
|
|
910
|
+
* completions on a rate-limited but high-quality
|
|
911
|
+
* hosted provider. Azure detection runs on
|
|
912
|
+
* whichever URL ends up selected.
|
|
913
|
+
* OPENAI_EMBEDDING_API_KEY — separate API key for the embedding endpoint
|
|
914
|
+
* (defaults to OPENAI_API_KEY). Useful when the
|
|
915
|
+
* embedding endpoint requires a different key
|
|
916
|
+
* or no key at all (set to e.g. "local" for
|
|
917
|
+
* endpoints that ignore Authorization).
|
|
918
|
+
* OPENAI_API_VERSION — Azure api-version query param (default: 2024-08-01-preview)
|
|
919
|
+
* OPENAI_EMBEDDING_MODEL — model name (default: text-embedding-3-small)
|
|
920
|
+
* OPENAI_EMBEDDING_DIMENSIONS — override reported dimensions (required for
|
|
921
|
+
* custom / self-hosted models not in the
|
|
922
|
+
* MODEL_DIMENSIONS table above)
|
|
899
923
|
*/
|
|
900
924
|
var OpenAIEmbeddingProvider = class {
|
|
901
925
|
name = "openai";
|
|
@@ -906,9 +930,9 @@ var OpenAIEmbeddingProvider = class {
|
|
|
906
930
|
isAzure;
|
|
907
931
|
azureApiVersion;
|
|
908
932
|
constructor(apiKey) {
|
|
909
|
-
this.apiKey = apiKey || getEnvVar("OPENAI_API_KEY") || "";
|
|
910
|
-
if (!this.apiKey) throw new Error("
|
|
911
|
-
this.baseUrl = normalizeBaseUrl(getEnvVar("OPENAI_BASE_URL"));
|
|
933
|
+
this.apiKey = apiKey || getEnvVar("OPENAI_EMBEDDING_API_KEY") || getEnvVar("OPENAI_API_KEY") || "";
|
|
934
|
+
if (!this.apiKey) throw new Error("API key is required (via constructor, OPENAI_EMBEDDING_API_KEY, or OPENAI_API_KEY)");
|
|
935
|
+
this.baseUrl = normalizeBaseUrl(getEnvVar("OPENAI_EMBEDDING_BASE_URL") || getEnvVar("OPENAI_BASE_URL"));
|
|
912
936
|
this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
|
|
913
937
|
this.dimensions = resolveDimensions(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
|
|
914
938
|
this.isAzure = detectAzure(this.baseUrl);
|
|
@@ -1355,10 +1379,11 @@ function jaccardSimilarity(a, b) {
|
|
|
1355
1379
|
//#endregion
|
|
1356
1380
|
//#region src/state/vector-index.ts
|
|
1357
1381
|
function float32ToBase64(arr) {
|
|
1358
|
-
return Buffer.from(arr.buffer).toString("base64");
|
|
1382
|
+
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
|
|
1359
1383
|
}
|
|
1360
1384
|
function base64ToFloat32(b64) {
|
|
1361
|
-
|
|
1385
|
+
const buf = Buffer.from(b64, "base64");
|
|
1386
|
+
return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / Float32Array.BYTES_PER_ELEMENT);
|
|
1362
1387
|
}
|
|
1363
1388
|
function cosineSimilarity(a, b) {
|
|
1364
1389
|
if (a.length !== b.length) return 0;
|
|
@@ -1479,7 +1504,7 @@ var VectorIndex = class VectorIndex {
|
|
|
1479
1504
|
function memoryToObservation(memory) {
|
|
1480
1505
|
return {
|
|
1481
1506
|
id: memory.id,
|
|
1482
|
-
sessionId: memory.sessionIds[0] ?? "memory",
|
|
1507
|
+
sessionId: memory.sessionIds?.[0] ?? "memory",
|
|
1483
1508
|
timestamp: memory.createdAt,
|
|
1484
1509
|
type: "decision",
|
|
1485
1510
|
title: memory.title,
|
|
@@ -3151,7 +3176,7 @@ async function rebuildIndex(kv) {
|
|
|
3151
3176
|
idx.add(memoryToObservation(memory));
|
|
3152
3177
|
await enqueue({
|
|
3153
3178
|
id: memory.id,
|
|
3154
|
-
sessionId: memory.sessionIds[0] ?? "memory",
|
|
3179
|
+
sessionId: memory.sessionIds?.[0] ?? "memory",
|
|
3155
3180
|
text: memory.title + " " + memory.content,
|
|
3156
3181
|
context: {
|
|
3157
3182
|
kind: "memory",
|
|
@@ -3210,8 +3235,8 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3210
3235
|
if (!Number.isInteger(data.limit) || data.limit < 1) throw new Error("mem::search: limit must be a positive integer");
|
|
3211
3236
|
effectiveLimit = Math.min(data.limit, MAX_LIMIT);
|
|
3212
3237
|
}
|
|
3213
|
-
const projectFilter = typeof data.project === "string" && data.project.length > 0 ? data.project : void 0;
|
|
3214
|
-
const cwdFilter = typeof data.cwd === "string" && data.cwd.length > 0 ? data.cwd : void 0;
|
|
3238
|
+
const projectFilter = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
|
|
3239
|
+
const cwdFilter = typeof data.cwd === "string" && data.cwd.trim().length > 0 ? data.cwd.trim() : void 0;
|
|
3215
3240
|
const format = typeof data.format === "string" ? data.format : "full";
|
|
3216
3241
|
if (![
|
|
3217
3242
|
"full",
|
|
@@ -3237,14 +3262,25 @@ function registerSearchFunction(sdk, kv) {
|
|
|
3237
3262
|
sessionCache.set(sessionId, s ?? null);
|
|
3238
3263
|
return s ?? null;
|
|
3239
3264
|
};
|
|
3265
|
+
const memoryProjectCache = /* @__PURE__ */ new Map();
|
|
3266
|
+
const loadMemoryProject = async (obsId) => {
|
|
3267
|
+
if (memoryProjectCache.has(obsId)) return memoryProjectCache.get(obsId);
|
|
3268
|
+
const proj = (await kv.get(KV.memories, obsId).catch(() => null))?.project ?? null;
|
|
3269
|
+
memoryProjectCache.set(obsId, proj);
|
|
3270
|
+
return proj;
|
|
3271
|
+
};
|
|
3240
3272
|
const candidates = [];
|
|
3241
3273
|
for (const r of results) {
|
|
3242
3274
|
if (candidates.length >= effectiveLimit) break;
|
|
3243
3275
|
if (filtering) {
|
|
3244
3276
|
const s = await loadSession(r.sessionId);
|
|
3245
|
-
if (
|
|
3246
|
-
|
|
3247
|
-
|
|
3277
|
+
if (s) {
|
|
3278
|
+
if (projectFilter && s.project !== projectFilter) continue;
|
|
3279
|
+
if (cwdFilter && s.cwd !== cwdFilter) continue;
|
|
3280
|
+
} else if (projectFilter) {
|
|
3281
|
+
const memProject = await loadMemoryProject(r.obsId);
|
|
3282
|
+
if (memProject !== null && memProject !== projectFilter) continue;
|
|
3283
|
+
}
|
|
3248
3284
|
}
|
|
3249
3285
|
candidates.push(r);
|
|
3250
3286
|
}
|
|
@@ -3996,10 +4032,10 @@ const DEFAULT_SLOTS = [
|
|
|
3996
4032
|
}
|
|
3997
4033
|
];
|
|
3998
4034
|
function isSlotsEnabled() {
|
|
3999
|
-
return
|
|
4035
|
+
return getEnvVar("AGENTMEMORY_SLOTS") === "true";
|
|
4000
4036
|
}
|
|
4001
4037
|
function isReflectEnabled() {
|
|
4002
|
-
return
|
|
4038
|
+
return getEnvVar("AGENTMEMORY_REFLECT") === "true";
|
|
4003
4039
|
}
|
|
4004
4040
|
function scopeKv(scope) {
|
|
4005
4041
|
return scope === "global" ? KV.globalSlots : KV.slots;
|
|
@@ -5264,8 +5300,78 @@ function isAllowedPath(dbPath) {
|
|
|
5264
5300
|
const resolved = resolve(dbPath);
|
|
5265
5301
|
return ALLOWED_DIRS.some((dir) => resolved.startsWith(dir + "/"));
|
|
5266
5302
|
}
|
|
5303
|
+
async function inferMemoryProjects(kv, dryRun = false) {
|
|
5304
|
+
const memories = await kv.list(KV.memories);
|
|
5305
|
+
const sessionCache = /* @__PURE__ */ new Map();
|
|
5306
|
+
const loadSession = async (sid) => {
|
|
5307
|
+
if (sessionCache.has(sid)) return sessionCache.get(sid);
|
|
5308
|
+
const s = await kv.get(KV.sessions, sid).catch(() => null);
|
|
5309
|
+
sessionCache.set(sid, s);
|
|
5310
|
+
return s;
|
|
5311
|
+
};
|
|
5312
|
+
let updated = 0;
|
|
5313
|
+
let skipped = 0;
|
|
5314
|
+
let ambiguous = 0;
|
|
5315
|
+
for (const memory of memories) {
|
|
5316
|
+
if (memory.project) {
|
|
5317
|
+
skipped++;
|
|
5318
|
+
continue;
|
|
5319
|
+
}
|
|
5320
|
+
const sessionIds = memory.sessionIds ?? [];
|
|
5321
|
+
if (sessionIds.length === 0) {
|
|
5322
|
+
ambiguous++;
|
|
5323
|
+
continue;
|
|
5324
|
+
}
|
|
5325
|
+
const projects = [];
|
|
5326
|
+
for (const sid of sessionIds) {
|
|
5327
|
+
const session = await loadSession(sid);
|
|
5328
|
+
if (session?.project) projects.push(session.project);
|
|
5329
|
+
}
|
|
5330
|
+
if (projects.length === 0) {
|
|
5331
|
+
ambiguous++;
|
|
5332
|
+
continue;
|
|
5333
|
+
}
|
|
5334
|
+
const freq = /* @__PURE__ */ new Map();
|
|
5335
|
+
for (const p of projects) freq.set(p, (freq.get(p) ?? 0) + 1);
|
|
5336
|
+
const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1]);
|
|
5337
|
+
const [topProject, topCount] = sorted[0];
|
|
5338
|
+
if (topCount <= projects.length / 2 && sorted.length > 1) {
|
|
5339
|
+
ambiguous++;
|
|
5340
|
+
continue;
|
|
5341
|
+
}
|
|
5342
|
+
if (!dryRun) {
|
|
5343
|
+
memory.project = topProject;
|
|
5344
|
+
await kv.set(KV.memories, memory.id, memory);
|
|
5345
|
+
}
|
|
5346
|
+
updated++;
|
|
5347
|
+
}
|
|
5348
|
+
logger.info("inferMemoryProjects complete", {
|
|
5349
|
+
updated,
|
|
5350
|
+
skipped,
|
|
5351
|
+
ambiguous,
|
|
5352
|
+
dryRun
|
|
5353
|
+
});
|
|
5354
|
+
return {
|
|
5355
|
+
updated,
|
|
5356
|
+
skipped,
|
|
5357
|
+
ambiguous
|
|
5358
|
+
};
|
|
5359
|
+
}
|
|
5267
5360
|
function registerMigrateFunction(sdk, kv) {
|
|
5268
5361
|
sdk.registerFunction("mem::migrate", async (data) => {
|
|
5362
|
+
if (data.step === "infer-memory-projects") {
|
|
5363
|
+
const dryRun = data.dryRun ?? false;
|
|
5364
|
+
logger.info("Migration step: infer-memory-projects", { dryRun });
|
|
5365
|
+
return {
|
|
5366
|
+
success: true,
|
|
5367
|
+
step: "infer-memory-projects",
|
|
5368
|
+
...await inferMemoryProjects(kv, dryRun)
|
|
5369
|
+
};
|
|
5370
|
+
}
|
|
5371
|
+
if (!data.dbPath) return {
|
|
5372
|
+
success: false,
|
|
5373
|
+
error: "Either step or dbPath is required"
|
|
5374
|
+
};
|
|
5269
5375
|
logger.info("Migration started", { dbPath: data.dbPath });
|
|
5270
5376
|
if (!isAllowedPath(data.dbPath)) return {
|
|
5271
5377
|
success: false,
|
|
@@ -5541,9 +5647,10 @@ function registerConsolidateFunction(sdk, kv, provider) {
|
|
|
5541
5647
|
llmCallCount++;
|
|
5542
5648
|
const parsed = parseMemoryXml(response, sessionIds);
|
|
5543
5649
|
if (!parsed) continue;
|
|
5544
|
-
const existingMatch = existingMemories.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase());
|
|
5545
5650
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5546
5651
|
const obsIds = [...new Set(top.map((o) => o.id))];
|
|
5652
|
+
const scopedProject = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
|
|
5653
|
+
const existingMatch = existingMemories.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase() && (!scopedProject || !m.project || m.project === scopedProject));
|
|
5547
5654
|
if (existingMatch) {
|
|
5548
5655
|
existingMatch.isLatest = false;
|
|
5549
5656
|
await kv.set(KV.memories, existingMatch.id, existingMatch);
|
|
@@ -5560,7 +5667,8 @@ function registerConsolidateFunction(sdk, kv, provider) {
|
|
|
5560
5667
|
parentId: existingMatch.id,
|
|
5561
5668
|
supersedes: [existingMatch.id, ...existingMatch.supersedes || []],
|
|
5562
5669
|
sourceObservationIds: obsIds,
|
|
5563
|
-
isLatest: true
|
|
5670
|
+
isLatest: true,
|
|
5671
|
+
...scopedProject !== void 0 && { project: scopedProject }
|
|
5564
5672
|
};
|
|
5565
5673
|
await kv.set(KV.memories, evolved.id, evolved);
|
|
5566
5674
|
await recordAudit(kv, "evolve", "mem::consolidate", [evolved.id], {
|
|
@@ -5579,7 +5687,8 @@ function registerConsolidateFunction(sdk, kv, provider) {
|
|
|
5579
5687
|
...parsed,
|
|
5580
5688
|
sourceObservationIds: obsIds,
|
|
5581
5689
|
version: 1,
|
|
5582
|
-
isLatest: true
|
|
5690
|
+
isLatest: true,
|
|
5691
|
+
...scopedProject !== void 0 && { project: scopedProject }
|
|
5583
5692
|
};
|
|
5584
5693
|
await kv.set(KV.memories, memory.id, memory);
|
|
5585
5694
|
await recordAudit(kv, "remember", "mem::consolidate", [memory.id], {
|
|
@@ -5720,6 +5829,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5720
5829
|
"fact"
|
|
5721
5830
|
]).has(data.type || "") ? data.type : "fact";
|
|
5722
5831
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5832
|
+
const project = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
|
|
5723
5833
|
return withKeyedLock("mem:remember", async () => {
|
|
5724
5834
|
const existingMemories = await kv.list(KV.memories);
|
|
5725
5835
|
let supersededId;
|
|
@@ -5728,6 +5838,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5728
5838
|
const lowerContent = data.content.toLowerCase();
|
|
5729
5839
|
for (const existing of existingMemories) {
|
|
5730
5840
|
if (existing.isLatest === false) continue;
|
|
5841
|
+
if (project && existing.project && existing.project !== project) continue;
|
|
5731
5842
|
if (jaccardSimilarity(lowerContent, existing.content.toLowerCase()) > .7) {
|
|
5732
5843
|
supersededId = existing.id;
|
|
5733
5844
|
supersededVersion = existing.version ?? 1;
|
|
@@ -5752,7 +5863,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5752
5863
|
supersedes: supersededId ? [supersededId] : [],
|
|
5753
5864
|
sourceObservationIds: (data.sourceObservationIds || []).filter((id) => typeof id === "string" && id.length > 0),
|
|
5754
5865
|
isLatest: true,
|
|
5755
|
-
...callAgentId ? { agentId: callAgentId } : {}
|
|
5866
|
+
...callAgentId ? { agentId: callAgentId } : {},
|
|
5867
|
+
...project !== void 0 && { project }
|
|
5756
5868
|
};
|
|
5757
5869
|
if (data.ttlDays && typeof data.ttlDays === "number" && data.ttlDays > 0) memory.forgetAfter = new Date(Date.now() + data.ttlDays * 864e5).toISOString();
|
|
5758
5870
|
if (supersededMemory) {
|
|
@@ -5768,7 +5880,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5768
5880
|
error: err instanceof Error ? err.message : String(err)
|
|
5769
5881
|
});
|
|
5770
5882
|
}
|
|
5771
|
-
await vectorIndexAddGuarded(memory.id, memory.sessionIds[0] ?? "memory", memory.title + " " + memory.content, {
|
|
5883
|
+
await vectorIndexAddGuarded(memory.id, memory.sessionIds?.[0] ?? "memory", memory.title + " " + memory.content, {
|
|
5772
5884
|
kind: "memory",
|
|
5773
5885
|
logId: memory.id
|
|
5774
5886
|
});
|
|
@@ -5779,7 +5891,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5779
5891
|
});
|
|
5780
5892
|
logger.info("Memory saved", {
|
|
5781
5893
|
memId: memory.id,
|
|
5782
|
-
type: memory.type
|
|
5894
|
+
type: memory.type,
|
|
5895
|
+
project: memory.project
|
|
5783
5896
|
});
|
|
5784
5897
|
return {
|
|
5785
5898
|
success: true,
|
|
@@ -6680,7 +6793,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6680
6793
|
|
|
6681
6794
|
//#endregion
|
|
6682
6795
|
//#region src/version.ts
|
|
6683
|
-
const VERSION = "0.9.
|
|
6796
|
+
const VERSION = "0.9.24";
|
|
6684
6797
|
|
|
6685
6798
|
//#endregion
|
|
6686
6799
|
//#region src/functions/export-import.ts
|
|
@@ -6820,7 +6933,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
6820
6933
|
"0.9.19",
|
|
6821
6934
|
"0.9.20",
|
|
6822
6935
|
"0.9.21",
|
|
6823
|
-
"0.9.22"
|
|
6936
|
+
"0.9.22",
|
|
6937
|
+
"0.9.23",
|
|
6938
|
+
"0.9.24"
|
|
6824
6939
|
]).has(importData.version)) return {
|
|
6825
6940
|
success: false,
|
|
6826
6941
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -6943,6 +7058,7 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
6943
7058
|
continue;
|
|
6944
7059
|
}
|
|
6945
7060
|
}
|
|
7061
|
+
if (!Array.isArray(memory.sessionIds)) memory.sessionIds = [];
|
|
6946
7062
|
await kv.set(KV.memories, memory.id, memory);
|
|
6947
7063
|
stats.memories++;
|
|
6948
7064
|
}
|
|
@@ -7146,6 +7262,7 @@ function escapeXml(s) {
|
|
|
7146
7262
|
}
|
|
7147
7263
|
function registerEnrichFunction(sdk, kv) {
|
|
7148
7264
|
sdk.registerFunction("mem::enrich", async (data) => {
|
|
7265
|
+
const project = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
|
|
7149
7266
|
const parts = [];
|
|
7150
7267
|
const fileContextPromise = sdk.trigger({
|
|
7151
7268
|
function_id: "mem::file-context",
|
|
@@ -7159,10 +7276,11 @@ function registerEnrichFunction(sdk, kv) {
|
|
|
7159
7276
|
function_id: "mem::search",
|
|
7160
7277
|
payload: {
|
|
7161
7278
|
query: searchQueries.join(" "),
|
|
7162
|
-
limit: 5
|
|
7279
|
+
limit: 5,
|
|
7280
|
+
...project !== void 0 && { project }
|
|
7163
7281
|
}
|
|
7164
7282
|
}).catch(() => ({ results: [] })) : Promise.resolve({ results: [] });
|
|
7165
|
-
const bugMemoriesPromise = kv.list(KV.memories).then((memories) => memories.filter((m) => m.type === "bug" && m.isLatest && m.files.some((f) => data.files.some((df) => f.includes(df) || df.includes(f)))).sort((a, b) => new Date(b.updatedAt || b.createdAt).getTime() - new Date(a.updatedAt || a.createdAt).getTime())).catch(() => []);
|
|
7283
|
+
const bugMemoriesPromise = kv.list(KV.memories).then((memories) => memories.filter((m) => m.type === "bug" && m.isLatest && (!project || !m.project || m.project === project) && m.files.some((f) => data.files.some((df) => f.includes(df) || df.includes(f)))).sort((a, b) => new Date(b.updatedAt || b.createdAt).getTime() - new Date(a.updatedAt || a.createdAt).getTime())).catch(() => []);
|
|
7166
7284
|
const [fileContext, searchResult, bugMemories] = await Promise.all([
|
|
7167
7285
|
fileContextPromise,
|
|
7168
7286
|
searchPromise,
|
|
@@ -7185,6 +7303,7 @@ function registerEnrichFunction(sdk, kv) {
|
|
|
7185
7303
|
}
|
|
7186
7304
|
logger.info("Enrichment completed", {
|
|
7187
7305
|
sessionId: data.sessionId,
|
|
7306
|
+
project,
|
|
7188
7307
|
fileCount: data.files.length,
|
|
7189
7308
|
contextLength: context.length,
|
|
7190
7309
|
truncated
|
|
@@ -7342,16 +7461,24 @@ function buildGraphExtractionPrompt(observations) {
|
|
|
7342
7461
|
|
|
7343
7462
|
//#endregion
|
|
7344
7463
|
//#region src/functions/graph.ts
|
|
7464
|
+
function parseAttrs(raw) {
|
|
7465
|
+
const attrs = {};
|
|
7466
|
+
const attrRegex = /([A-Za-z_][\w:-]*)="([^"]*)"/g;
|
|
7467
|
+
let m;
|
|
7468
|
+
while ((m = attrRegex.exec(raw)) !== null) attrs[m[1]] = m[2];
|
|
7469
|
+
return attrs;
|
|
7470
|
+
}
|
|
7345
7471
|
function parseGraphXml(xml, observationIds) {
|
|
7346
7472
|
const nodes = [];
|
|
7347
7473
|
const edges = [];
|
|
7348
7474
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7349
|
-
const
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
const
|
|
7353
|
-
const
|
|
7354
|
-
const
|
|
7475
|
+
const entitySelfClose = /<entity\b([^>]*?)\/>/g;
|
|
7476
|
+
const entityWithBody = /<entity\b([^>]*[^/])>([\s\S]*?)<\/entity>/g;
|
|
7477
|
+
const addEntity = (rawAttrs, propsBlock = "") => {
|
|
7478
|
+
const attrs = parseAttrs(rawAttrs);
|
|
7479
|
+
const type = attrs["type"];
|
|
7480
|
+
const name = attrs["name"];
|
|
7481
|
+
if (!type || !name) return;
|
|
7355
7482
|
const properties = {};
|
|
7356
7483
|
const propRegex = /<property\s+key="([^"]+)">([^<]*)<\/property>/g;
|
|
7357
7484
|
let propMatch;
|
|
@@ -7364,17 +7491,23 @@ function parseGraphXml(xml, observationIds) {
|
|
|
7364
7491
|
sourceObservationIds: observationIds,
|
|
7365
7492
|
createdAt: now
|
|
7366
7493
|
});
|
|
7367
|
-
}
|
|
7368
|
-
|
|
7494
|
+
};
|
|
7495
|
+
let match;
|
|
7496
|
+
while ((match = entitySelfClose.exec(xml)) !== null) addEntity(match[1]);
|
|
7497
|
+
while ((match = entityWithBody.exec(xml)) !== null) addEntity(match[1], match[2]);
|
|
7498
|
+
const relRegex = /<relationship\b([^>]*?)\/>/g;
|
|
7369
7499
|
while ((match = relRegex.exec(xml)) !== null) {
|
|
7370
|
-
const
|
|
7371
|
-
const
|
|
7372
|
-
const
|
|
7373
|
-
const
|
|
7374
|
-
|
|
7500
|
+
const attrs = parseAttrs(match[1]);
|
|
7501
|
+
const type = attrs["type"];
|
|
7502
|
+
const sourceName = attrs["source"];
|
|
7503
|
+
const targetName = attrs["target"];
|
|
7504
|
+
if (!type || !sourceName || !targetName) continue;
|
|
7505
|
+
const parsedWeight = parseFloat(attrs["weight"] ?? "");
|
|
7506
|
+
const weight = Number.isFinite(parsedWeight) ? parsedWeight : .5;
|
|
7375
7507
|
const sourceNode = nodes.find((n) => n.name === sourceName);
|
|
7376
7508
|
const targetNode = nodes.find((n) => n.name === targetName);
|
|
7377
|
-
if (sourceNode
|
|
7509
|
+
if (!sourceNode || !targetNode) continue;
|
|
7510
|
+
edges.push({
|
|
7378
7511
|
id: generateId("ge"),
|
|
7379
7512
|
type,
|
|
7380
7513
|
sourceNodeId: sourceNode.id,
|
|
@@ -7588,7 +7721,7 @@ function registerConsolidationPipelineFunction(sdk, kv, provider) {
|
|
|
7588
7721
|
if (!data?.force && !isConsolidationEnabled()) return {
|
|
7589
7722
|
success: false,
|
|
7590
7723
|
skipped: true,
|
|
7591
|
-
reason: "CONSOLIDATION_ENABLED
|
|
7724
|
+
reason: "Consolidation disabled: set CONSOLIDATION_ENABLED=true or configure an LLM provider (ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY / GEMINI_API_KEY / GOOGLE_API_KEY / MINIMAX_API_KEY / OPENAI_BASE_URL / AGENTMEMORY_PROVIDER=agent-sdk)"
|
|
7592
7725
|
};
|
|
7593
7726
|
const tier = data?.tier || "all";
|
|
7594
7727
|
const decayDays = getConsolidationDecayDays();
|
|
@@ -10758,11 +10891,34 @@ function registerDiagnosticsFunction(sdk, kv) {
|
|
|
10758
10891
|
});
|
|
10759
10892
|
memoryIssues++;
|
|
10760
10893
|
}
|
|
10894
|
+
const latestMemories = memories.filter((m) => m.isLatest);
|
|
10895
|
+
const unscopedCount = latestMemories.filter((m) => !m.project).length;
|
|
10896
|
+
if (unscopedCount === 0) checks.push({
|
|
10897
|
+
name: "memory-project-coverage",
|
|
10898
|
+
category: "memories",
|
|
10899
|
+
status: "pass",
|
|
10900
|
+
message: `All ${latestMemories.length} latest memories have a project scope`,
|
|
10901
|
+
fixable: false
|
|
10902
|
+
});
|
|
10903
|
+
else if (unscopedCount <= 10) checks.push({
|
|
10904
|
+
name: "memory-project-coverage",
|
|
10905
|
+
category: "memories",
|
|
10906
|
+
status: "warn",
|
|
10907
|
+
message: `${unscopedCount} of ${latestMemories.length} latest memories have no project scope — run POST /agentmemory/migrate {"step":"infer-memory-projects"} to backfill`,
|
|
10908
|
+
fixable: true
|
|
10909
|
+
});
|
|
10910
|
+
else checks.push({
|
|
10911
|
+
name: "memory-project-coverage",
|
|
10912
|
+
category: "memories",
|
|
10913
|
+
status: "fail",
|
|
10914
|
+
message: `${unscopedCount} of ${latestMemories.length} latest memories have no project scope — run POST /agentmemory/migrate {"step":"infer-memory-projects"} to backfill`,
|
|
10915
|
+
fixable: true
|
|
10916
|
+
});
|
|
10761
10917
|
if (memoryIssues === 0) checks.push({
|
|
10762
10918
|
name: "memories-ok",
|
|
10763
10919
|
category: "memories",
|
|
10764
10920
|
status: "pass",
|
|
10765
|
-
message: `All ${memories.length} memories are consistent`,
|
|
10921
|
+
message: `All ${memories.length} memories are structurally consistent`,
|
|
10766
10922
|
fixable: false
|
|
10767
10923
|
});
|
|
10768
10924
|
}
|
|
@@ -14535,6 +14691,22 @@ function consolidationDisabledResponse() {
|
|
|
14535
14691
|
docsHref: "https://github.com/rohitg00/agentmemory#consolidation"
|
|
14536
14692
|
});
|
|
14537
14693
|
}
|
|
14694
|
+
function slotsDisabledResponse() {
|
|
14695
|
+
return flagDisabledResponse({
|
|
14696
|
+
error: "Memory slots not enabled",
|
|
14697
|
+
flag: "AGENTMEMORY_SLOTS",
|
|
14698
|
+
enableHow: "Set AGENTMEMORY_SLOTS=true (in ~/.agentmemory/.env or the shell) and restart.",
|
|
14699
|
+
docsHref: "https://github.com/rohitg00/agentmemory#memory-slots"
|
|
14700
|
+
});
|
|
14701
|
+
}
|
|
14702
|
+
function reflectDisabledResponse() {
|
|
14703
|
+
return flagDisabledResponse({
|
|
14704
|
+
error: "Slot reflection not enabled",
|
|
14705
|
+
flag: "AGENTMEMORY_REFLECT",
|
|
14706
|
+
enableHow: "Set AGENTMEMORY_REFLECT=true (in ~/.agentmemory/.env or the shell) and restart. Requires AGENTMEMORY_SLOTS=true.",
|
|
14707
|
+
docsHref: "https://github.com/rohitg00/agentmemory#memory-slots"
|
|
14708
|
+
});
|
|
14709
|
+
}
|
|
14538
14710
|
function asNonEmptyString$1(value) {
|
|
14539
14711
|
if (typeof value !== "string") return null;
|
|
14540
14712
|
const trimmed = value.trim();
|
|
@@ -14979,6 +15151,14 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14979
15151
|
path: "status",
|
|
14980
15152
|
value: "completed"
|
|
14981
15153
|
}]);
|
|
15154
|
+
try {
|
|
15155
|
+
sdk.triggerVoid("event::session::stopped", { sessionId });
|
|
15156
|
+
} catch (err) {
|
|
15157
|
+
logger.warn("event::session::stopped triggerVoid failed", {
|
|
15158
|
+
sessionId,
|
|
15159
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15160
|
+
});
|
|
15161
|
+
}
|
|
14982
15162
|
return {
|
|
14983
15163
|
status_code: 200,
|
|
14984
15164
|
body: { success: true }
|
|
@@ -15197,11 +15377,21 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15197
15377
|
status_code: 400,
|
|
15198
15378
|
body: { error: "terms must be an array of strings" }
|
|
15199
15379
|
};
|
|
15380
|
+
if (req.body.project !== void 0 && (typeof req.body.project !== "string" || !req.body.project.trim())) return {
|
|
15381
|
+
status_code: 400,
|
|
15382
|
+
body: { error: "project must be a non-empty string" }
|
|
15383
|
+
};
|
|
15200
15384
|
return {
|
|
15201
15385
|
status_code: 200,
|
|
15202
15386
|
body: await sdk.trigger({
|
|
15203
15387
|
function_id: "mem::enrich",
|
|
15204
|
-
payload:
|
|
15388
|
+
payload: {
|
|
15389
|
+
sessionId: req.body.sessionId,
|
|
15390
|
+
files: req.body.files,
|
|
15391
|
+
...req.body.terms !== void 0 && { terms: req.body.terms },
|
|
15392
|
+
...req.body.toolName !== void 0 && { toolName: req.body.toolName },
|
|
15393
|
+
...req.body.project !== void 0 && { project: req.body.project }
|
|
15394
|
+
}
|
|
15205
15395
|
})
|
|
15206
15396
|
};
|
|
15207
15397
|
});
|
|
@@ -15220,11 +15410,23 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15220
15410
|
status_code: 400,
|
|
15221
15411
|
body: { error: "content is required" }
|
|
15222
15412
|
};
|
|
15413
|
+
if (req.body.project !== void 0 && (typeof req.body.project !== "string" || !req.body.project.trim())) return {
|
|
15414
|
+
status_code: 400,
|
|
15415
|
+
body: { error: "project must be a non-empty string" }
|
|
15416
|
+
};
|
|
15223
15417
|
return {
|
|
15224
15418
|
status_code: 201,
|
|
15225
15419
|
body: await sdk.trigger({
|
|
15226
15420
|
function_id: "mem::remember",
|
|
15227
|
-
payload:
|
|
15421
|
+
payload: {
|
|
15422
|
+
content: req.body.content,
|
|
15423
|
+
...req.body.type !== void 0 && { type: req.body.type },
|
|
15424
|
+
...req.body.concepts !== void 0 && { concepts: req.body.concepts },
|
|
15425
|
+
...req.body.files !== void 0 && { files: req.body.files },
|
|
15426
|
+
...req.body.ttlDays !== void 0 && { ttlDays: req.body.ttlDays },
|
|
15427
|
+
...req.body.sourceObservationIds !== void 0 && { sourceObservationIds: req.body.sourceObservationIds },
|
|
15428
|
+
...req.body.project !== void 0 && { project: req.body.project }
|
|
15429
|
+
}
|
|
15228
15430
|
})
|
|
15229
15431
|
};
|
|
15230
15432
|
});
|
|
@@ -15319,15 +15521,21 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15319
15521
|
sdk.registerFunction("api::migrate", async (req) => {
|
|
15320
15522
|
const authErr = checkAuth(req, secret);
|
|
15321
15523
|
if (authErr) return authErr;
|
|
15322
|
-
|
|
15524
|
+
const hasStep = typeof req.body?.step === "string" && req.body.step.trim().length > 0;
|
|
15525
|
+
const hasDbPath = typeof req.body?.dbPath === "string" && req.body.dbPath.trim().length > 0;
|
|
15526
|
+
if (!hasStep && !hasDbPath) return {
|
|
15323
15527
|
status_code: 400,
|
|
15324
|
-
body: { error: "dbPath is required" }
|
|
15528
|
+
body: { error: "Either step (string) or dbPath (string) is required" }
|
|
15325
15529
|
};
|
|
15326
15530
|
return {
|
|
15327
15531
|
status_code: 200,
|
|
15328
15532
|
body: await sdk.trigger({
|
|
15329
15533
|
function_id: "mem::migrate",
|
|
15330
|
-
payload:
|
|
15534
|
+
payload: {
|
|
15535
|
+
...req.body.step !== void 0 && { step: req.body.step },
|
|
15536
|
+
...req.body.dbPath !== void 0 && { dbPath: req.body.dbPath },
|
|
15537
|
+
...req.body.dryRun !== void 0 && { dryRun: req.body.dryRun }
|
|
15538
|
+
}
|
|
15331
15539
|
})
|
|
15332
15540
|
};
|
|
15333
15541
|
});
|
|
@@ -15673,6 +15881,63 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15673
15881
|
http_method: "POST"
|
|
15674
15882
|
}
|
|
15675
15883
|
});
|
|
15884
|
+
sdk.registerFunction("api::graph-build", async (req) => {
|
|
15885
|
+
const authErr = checkAuth(req, secret);
|
|
15886
|
+
if (authErr) return authErr;
|
|
15887
|
+
const batchSize = Math.max(1, Math.min(100, Number(req.body?.batchSize) || 25));
|
|
15888
|
+
try {
|
|
15889
|
+
const sessions = await kv.list(KV.sessions);
|
|
15890
|
+
let totalNodes = 0;
|
|
15891
|
+
let totalEdges = 0;
|
|
15892
|
+
let batchesRun = 0;
|
|
15893
|
+
for (const session of sessions) {
|
|
15894
|
+
const sid = session?.id;
|
|
15895
|
+
if (typeof sid !== "string" || sid.length === 0) continue;
|
|
15896
|
+
const compressed = (await kv.list(KV.observations(sid))).filter((o) => o && typeof o.title === "string" && o.title.length > 0);
|
|
15897
|
+
if (compressed.length === 0) continue;
|
|
15898
|
+
for (let i = 0; i < compressed.length; i += batchSize) {
|
|
15899
|
+
const batch = compressed.slice(i, i + batchSize);
|
|
15900
|
+
try {
|
|
15901
|
+
const result = await sdk.trigger({
|
|
15902
|
+
function_id: "mem::graph-extract",
|
|
15903
|
+
payload: { observations: batch }
|
|
15904
|
+
});
|
|
15905
|
+
if (result?.success) {
|
|
15906
|
+
totalNodes += Number(result.nodesAdded) || 0;
|
|
15907
|
+
totalEdges += Number(result.edgesAdded) || 0;
|
|
15908
|
+
}
|
|
15909
|
+
batchesRun++;
|
|
15910
|
+
} catch (err) {
|
|
15911
|
+
logger.warn("graph-build batch failed", {
|
|
15912
|
+
sessionId: sid,
|
|
15913
|
+
batchIndex: Math.floor(i / batchSize),
|
|
15914
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15915
|
+
});
|
|
15916
|
+
}
|
|
15917
|
+
}
|
|
15918
|
+
}
|
|
15919
|
+
return {
|
|
15920
|
+
status_code: 200,
|
|
15921
|
+
body: {
|
|
15922
|
+
success: true,
|
|
15923
|
+
sessions: sessions.length,
|
|
15924
|
+
batches: batchesRun,
|
|
15925
|
+
nodes: totalNodes,
|
|
15926
|
+
edges: totalEdges
|
|
15927
|
+
}
|
|
15928
|
+
};
|
|
15929
|
+
} catch {
|
|
15930
|
+
return graphDisabledResponse();
|
|
15931
|
+
}
|
|
15932
|
+
});
|
|
15933
|
+
sdk.registerTrigger({
|
|
15934
|
+
type: "http",
|
|
15935
|
+
function_id: "api::graph-build",
|
|
15936
|
+
config: {
|
|
15937
|
+
api_path: "/agentmemory/graph/build",
|
|
15938
|
+
http_method: "POST"
|
|
15939
|
+
}
|
|
15940
|
+
});
|
|
15676
15941
|
sdk.registerFunction("api::consolidate-pipeline", async (req) => {
|
|
15677
15942
|
const authErr = checkAuth(req, secret);
|
|
15678
15943
|
if (authErr) return authErr;
|
|
@@ -16130,6 +16395,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
16130
16395
|
sdk.registerFunction("api::slot-list", async (req) => {
|
|
16131
16396
|
const authErr = checkAuth(req, secret);
|
|
16132
16397
|
if (authErr) return authErr;
|
|
16398
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
16133
16399
|
return {
|
|
16134
16400
|
status_code: 200,
|
|
16135
16401
|
body: await sdk.trigger({
|
|
@@ -16149,6 +16415,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
16149
16415
|
sdk.registerFunction("api::slot-get", async (req) => {
|
|
16150
16416
|
const authErr = checkAuth(req, secret);
|
|
16151
16417
|
if (authErr) return authErr;
|
|
16418
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
16152
16419
|
const label = asNonEmptyString$1(req.query_params?.["label"]);
|
|
16153
16420
|
if (!label) return {
|
|
16154
16421
|
status_code: 400,
|
|
@@ -16179,6 +16446,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
16179
16446
|
sdk.registerFunction("api::slot-create", async (req) => {
|
|
16180
16447
|
const authErr = checkAuth(req, secret);
|
|
16181
16448
|
if (authErr) return authErr;
|
|
16449
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
16182
16450
|
const body = req.body ?? {};
|
|
16183
16451
|
const label = asNonEmptyString$1(body["label"]);
|
|
16184
16452
|
if (!label) return {
|
|
@@ -16241,6 +16509,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
16241
16509
|
sdk.registerFunction("api::slot-append", async (req) => {
|
|
16242
16510
|
const authErr = checkAuth(req, secret);
|
|
16243
16511
|
if (authErr) return authErr;
|
|
16512
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
16244
16513
|
const body = req.body ?? {};
|
|
16245
16514
|
const label = asNonEmptyString$1(body["label"]);
|
|
16246
16515
|
const text = typeof body["text"] === "string" ? body["text"] : null;
|
|
@@ -16280,6 +16549,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
16280
16549
|
sdk.registerFunction("api::slot-replace", async (req) => {
|
|
16281
16550
|
const authErr = checkAuth(req, secret);
|
|
16282
16551
|
if (authErr) return authErr;
|
|
16552
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
16283
16553
|
const body = req.body ?? {};
|
|
16284
16554
|
const label = asNonEmptyString$1(body["label"]);
|
|
16285
16555
|
const content = body["content"];
|
|
@@ -16319,6 +16589,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
16319
16589
|
sdk.registerFunction("api::slot-delete", async (req) => {
|
|
16320
16590
|
const authErr = checkAuth(req, secret);
|
|
16321
16591
|
if (authErr) return authErr;
|
|
16592
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
16322
16593
|
const label = asNonEmptyString$1(req.query_params?.["label"]);
|
|
16323
16594
|
if (!label) return {
|
|
16324
16595
|
status_code: 400,
|
|
@@ -16349,6 +16620,8 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
16349
16620
|
sdk.registerFunction("api::slot-reflect", async (req) => {
|
|
16350
16621
|
const authErr = checkAuth(req, secret);
|
|
16351
16622
|
if (authErr) return authErr;
|
|
16623
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
16624
|
+
if (!isReflectEnabled()) return reflectDisabledResponse();
|
|
16352
16625
|
const body = req.body ?? {};
|
|
16353
16626
|
const sessionId = asNonEmptyString$1(body["sessionId"]);
|
|
16354
16627
|
if (!sessionId) return {
|
|
@@ -18001,6 +18274,10 @@ const CORE_TOOLS = [
|
|
|
18001
18274
|
files: {
|
|
18002
18275
|
type: "string",
|
|
18003
18276
|
description: "Comma-separated relevant file paths"
|
|
18277
|
+
},
|
|
18278
|
+
project: {
|
|
18279
|
+
type: "string",
|
|
18280
|
+
description: "Stable canonical project identifier this memory belongs to (e.g. a slug, UUID, or registry key). Must match the value used when the session was started. Do not use filesystem paths or ad-hoc display names — those change across machines and will silently break project scoping."
|
|
18004
18281
|
}
|
|
18005
18282
|
},
|
|
18006
18283
|
required: ["content"]
|
|
@@ -19157,13 +19434,15 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
19157
19434
|
const type = args.type || "fact";
|
|
19158
19435
|
const concepts = typeof args.concepts === "string" ? args.concepts.split(",").map((c) => c.trim()).filter(Boolean) : [];
|
|
19159
19436
|
const files = typeof args.files === "string" ? args.files.split(",").map((f) => f.trim()).filter(Boolean) : [];
|
|
19437
|
+
const project = typeof args.project === "string" && args.project.trim().length > 0 ? args.project.trim() : void 0;
|
|
19160
19438
|
const result = await sdk.trigger({
|
|
19161
19439
|
function_id: "mem::remember",
|
|
19162
19440
|
payload: {
|
|
19163
19441
|
content: args.content,
|
|
19164
19442
|
type,
|
|
19165
19443
|
concepts,
|
|
19166
|
-
files
|
|
19444
|
+
files,
|
|
19445
|
+
...project !== void 0 && { project }
|
|
19167
19446
|
}
|
|
19168
19447
|
});
|
|
19169
19448
|
return {
|
|
@@ -21010,7 +21289,7 @@ async function main() {
|
|
|
21010
21289
|
if (bm25Index.has(memory.id)) continue;
|
|
21011
21290
|
bm25Index.add({
|
|
21012
21291
|
id: memory.id,
|
|
21013
|
-
sessionId: memory.sessionIds[0] ?? "memory",
|
|
21292
|
+
sessionId: memory.sessionIds?.[0] ?? "memory",
|
|
21014
21293
|
timestamp: memory.createdAt,
|
|
21015
21294
|
type: "decision",
|
|
21016
21295
|
title: memory.title,
|
|
@@ -21030,7 +21309,7 @@ async function main() {
|
|
|
21030
21309
|
console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
|
|
21031
21310
|
}
|
|
21032
21311
|
bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
21033
|
-
bootLog(`REST API:
|
|
21312
|
+
bootLog(`REST API: 125 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
|
|
21034
21313
|
bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
|
|
21035
21314
|
const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
|
|
21036
21315
|
const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
|