@agentmemory/agentmemory 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -13
- package/dist/cli.mjs +147 -26
- package/dist/cli.mjs.map +1 -1
- package/dist/hooks/notification.mjs +6 -0
- package/dist/hooks/notification.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +6 -0
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +35 -1
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +6 -0
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +6 -0
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/prompt-submit.mjs +6 -0
- package/dist/hooks/prompt-submit.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +6 -0
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +6 -0
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +6 -0
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-start.mjs +6 -0
- package/dist/hooks/subagent-start.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +6 -0
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +6 -0
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/image-refs-CESf9ndJ.mjs +3 -0
- package/dist/image-store-DGvZMMrI.mjs +3 -0
- package/dist/index.mjs +2100 -157
- package/dist/index.mjs.map +1 -1
- package/dist/{src-Dw_gJcCy.mjs → src-3Snd7D3T.mjs} +2021 -267
- package/dist/src-3Snd7D3T.mjs.map +1 -0
- package/dist/{standalone-BEWvWM5P.mjs → standalone-BG9uPsDK.mjs} +2 -2
- package/dist/{standalone-BEWvWM5P.mjs.map → standalone-BG9uPsDK.mjs.map} +1 -1
- package/dist/standalone.mjs +136 -2
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-BvWNlj6u.mjs → tools-registry-m8Ofn9vV.mjs} +166 -12
- package/dist/tools-registry-m8Ofn9vV.mjs.map +1 -0
- package/dist/viewer/index.html +528 -68
- package/package.json +5 -3
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/scripts/notification.mjs +6 -0
- package/plugin/scripts/notification.mjs.map +1 -1
- package/plugin/scripts/post-tool-failure.mjs +6 -0
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +35 -1
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +6 -0
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +6 -0
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/prompt-submit.mjs +6 -0
- package/plugin/scripts/prompt-submit.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +6 -0
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +6 -0
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +6 -0
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-start.mjs +6 -0
- package/plugin/scripts/subagent-start.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +6 -0
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +6 -0
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/dist/src-Dw_gJcCy.mjs.map +0 -1
- package/dist/tools-registry-BvWNlj6u.mjs.map +0 -1
- package/dist/transformers-BX_tgxdO.mjs +0 -38684
- package/dist/transformers-BX_tgxdO.mjs.map +0 -1
- package/dist/transformers-KMm1i9no.mjs +0 -38683
- package/dist/transformers-KMm1i9no.mjs.map +0 -1
|
@@ -1,20 +1,127 @@
|
|
|
1
1
|
import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./cli.mjs";
|
|
2
|
-
import { a as
|
|
2
|
+
import { _ as loadTeamConfig, a as getConsolidationDecayDays, c as isAutoCompressEnabled, d as isGraphExtractionEnabled, f as loadClaudeBridgeConfig, g as loadSnapshotConfig, h as loadFallbackConfig, i as detectEmbeddingProvider, l as isConsolidationEnabled, m as loadEmbeddingConfig, n as getVisibleTools, o as getEnvVar, p as loadConfig, r as VERSION, t as getAllTools, u as isContextInjectionEnabled } from "./tools-registry-m8Ofn9vV.mjs";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
9
|
+
import { lstat, mkdir, open, readFile, readdir, stat, unlink, utimes, writeFile } from "node:fs/promises";
|
|
9
10
|
import { TriggerAction, registerWorker } from "iii-sdk";
|
|
10
11
|
import Anthropic from "@anthropic-ai/sdk";
|
|
11
12
|
import { z } from "zod";
|
|
12
13
|
import { promisify } from "node:util";
|
|
13
14
|
import { lookup } from "node:dns/promises";
|
|
14
15
|
import { isIP } from "node:net";
|
|
15
|
-
import { lstat, mkdir, open, readFile, readdir, writeFile } from "node:fs/promises";
|
|
16
16
|
import { createServer } from "node:http";
|
|
17
17
|
|
|
18
|
+
//#region src/utils/image-store.ts
|
|
19
|
+
const IMAGES_DIR = join(homedir(), ".agentmemory", "images");
|
|
20
|
+
const DEFAULT_MAX_BYTES = 500 * 1024 * 1024;
|
|
21
|
+
function getMaxBytes() {
|
|
22
|
+
return Number(process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES) || DEFAULT_MAX_BYTES;
|
|
23
|
+
}
|
|
24
|
+
function isManagedImagePath(filePath) {
|
|
25
|
+
const resolved = resolve(filePath);
|
|
26
|
+
const normalizedImagesDir = resolve(IMAGES_DIR);
|
|
27
|
+
return resolved.startsWith(normalizedImagesDir + sep) || resolved === normalizedImagesDir;
|
|
28
|
+
}
|
|
29
|
+
function contentHash(data) {
|
|
30
|
+
return createHash("sha256").update(data).digest("hex");
|
|
31
|
+
}
|
|
32
|
+
async function saveImageToDisk(base64Data) {
|
|
33
|
+
if (!base64Data) return {
|
|
34
|
+
filePath: "",
|
|
35
|
+
bytesWritten: 0
|
|
36
|
+
};
|
|
37
|
+
if (!existsSync(IMAGES_DIR)) await mkdir(IMAGES_DIR, { recursive: true });
|
|
38
|
+
let cleanBase64 = base64Data;
|
|
39
|
+
let ext = "png";
|
|
40
|
+
if (base64Data.startsWith("data:image/")) {
|
|
41
|
+
const commaIdx = base64Data.indexOf(",");
|
|
42
|
+
if (commaIdx !== -1) {
|
|
43
|
+
const meta = base64Data.substring(0, commaIdx);
|
|
44
|
+
if (meta.includes("jpeg") || meta.includes("jpg")) ext = "jpg";
|
|
45
|
+
else if (meta.includes("webp")) ext = "webp";
|
|
46
|
+
else if (meta.includes("gif")) ext = "gif";
|
|
47
|
+
cleanBase64 = base64Data.substring(commaIdx + 1);
|
|
48
|
+
}
|
|
49
|
+
} else if (base64Data.startsWith("/9j/")) ext = "jpg";
|
|
50
|
+
const filePath = join(IMAGES_DIR, `${contentHash(cleanBase64)}.${ext}`);
|
|
51
|
+
if (existsSync(filePath)) return {
|
|
52
|
+
filePath,
|
|
53
|
+
bytesWritten: 0
|
|
54
|
+
};
|
|
55
|
+
await writeFile(filePath, Buffer.from(cleanBase64, "base64"));
|
|
56
|
+
return {
|
|
57
|
+
filePath,
|
|
58
|
+
bytesWritten: (await stat(filePath)).size
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function deleteImage(filePath) {
|
|
62
|
+
if (!filePath) return { deletedBytes: 0 };
|
|
63
|
+
if (!isManagedImagePath(filePath)) return { deletedBytes: 0 };
|
|
64
|
+
try {
|
|
65
|
+
if (existsSync(filePath)) {
|
|
66
|
+
const size = (await stat(filePath)).size;
|
|
67
|
+
await unlink(filePath);
|
|
68
|
+
return { deletedBytes: size };
|
|
69
|
+
}
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error("[agentmemory] Failed to delete image context:", err);
|
|
72
|
+
}
|
|
73
|
+
return { deletedBytes: 0 };
|
|
74
|
+
}
|
|
75
|
+
/** Touch an image file to update its mtime (marking it as recently used for LRU eviction) */
|
|
76
|
+
async function touchImage(filePath) {
|
|
77
|
+
if (!filePath || !isManagedImagePath(filePath)) return;
|
|
78
|
+
try {
|
|
79
|
+
if (existsSync(filePath)) {
|
|
80
|
+
const now = /* @__PURE__ */ new Date();
|
|
81
|
+
await utimes(filePath, now, now);
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/state/keyed-mutex.ts
|
|
88
|
+
const locks = /* @__PURE__ */ new Map();
|
|
89
|
+
function withKeyedLock(key, fn) {
|
|
90
|
+
const next = (locks.get(key) ?? Promise.resolve()).then(fn, fn);
|
|
91
|
+
const cleanup = next.then(() => {}, () => {});
|
|
92
|
+
locks.set(key, cleanup);
|
|
93
|
+
cleanup.then(() => {
|
|
94
|
+
if (locks.get(key) === cleanup) locks.delete(key);
|
|
95
|
+
});
|
|
96
|
+
return next;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/functions/image-refs.ts
|
|
101
|
+
async function getImageRefCount(kv, filePath) {
|
|
102
|
+
const count = await kv.get(KV.imageRefs, filePath);
|
|
103
|
+
return count ? Number(count) : 0;
|
|
104
|
+
}
|
|
105
|
+
async function incrementImageRef(kv, filePath) {
|
|
106
|
+
return withKeyedLock(`imgRef:${filePath}`, async () => {
|
|
107
|
+
const current = await getImageRefCount(kv, filePath);
|
|
108
|
+
await kv.set(KV.imageRefs, filePath, current + 1);
|
|
109
|
+
await touchImage(filePath);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function decrementImageRef(kv, sdk, filePath) {
|
|
113
|
+
return withKeyedLock(`imgRef:${filePath}`, async () => {
|
|
114
|
+
const current = await getImageRefCount(kv, filePath);
|
|
115
|
+
if (current <= 1) {
|
|
116
|
+
await kv.delete(KV.imageEmbeddings, filePath);
|
|
117
|
+
await kv.delete(KV.imageRefs, filePath);
|
|
118
|
+
const { deletedBytes } = await deleteImage(filePath);
|
|
119
|
+
if (deletedBytes > 0) sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
|
|
120
|
+
} else await kv.set(KV.imageRefs, filePath, current - 1);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
18
125
|
//#region src/providers/agent-sdk.ts
|
|
19
126
|
var AgentSDKProvider = class {
|
|
20
127
|
name = "agent-sdk";
|
|
@@ -25,18 +132,26 @@ var AgentSDKProvider = class {
|
|
|
25
132
|
return this.query(systemPrompt, userPrompt);
|
|
26
133
|
}
|
|
27
134
|
async query(systemPrompt, userPrompt) {
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
135
|
+
if (process.env.AGENTMEMORY_SDK_CHILD === "1") return "";
|
|
136
|
+
const prev = process.env.AGENTMEMORY_SDK_CHILD;
|
|
137
|
+
process.env.AGENTMEMORY_SDK_CHILD = "1";
|
|
138
|
+
try {
|
|
139
|
+
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
140
|
+
const messages = query({
|
|
141
|
+
prompt: userPrompt,
|
|
142
|
+
options: {
|
|
143
|
+
systemPrompt,
|
|
144
|
+
maxTurns: 1,
|
|
145
|
+
allowedTools: []
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
let result = "";
|
|
149
|
+
for await (const msg of messages) if (msg.type === "result") result = msg.result ?? "";
|
|
150
|
+
return result;
|
|
151
|
+
} finally {
|
|
152
|
+
if (prev === void 0) delete process.env.AGENTMEMORY_SDK_CHILD;
|
|
153
|
+
else process.env.AGENTMEMORY_SDK_CHILD = prev;
|
|
154
|
+
}
|
|
40
155
|
}
|
|
41
156
|
};
|
|
42
157
|
|
|
@@ -61,6 +176,26 @@ var AnthropicProvider = class {
|
|
|
61
176
|
async summarize(systemPrompt, userPrompt) {
|
|
62
177
|
return this.call(systemPrompt, userPrompt);
|
|
63
178
|
}
|
|
179
|
+
async describeImage(imageData, mimeType, prompt) {
|
|
180
|
+
return (await this.client.messages.create({
|
|
181
|
+
model: this.model,
|
|
182
|
+
max_tokens: this.maxTokens,
|
|
183
|
+
messages: [{
|
|
184
|
+
role: "user",
|
|
185
|
+
content: [{
|
|
186
|
+
type: "image",
|
|
187
|
+
source: {
|
|
188
|
+
type: "base64",
|
|
189
|
+
media_type: mimeType,
|
|
190
|
+
data: imageData
|
|
191
|
+
}
|
|
192
|
+
}, {
|
|
193
|
+
type: "text",
|
|
194
|
+
text: prompt
|
|
195
|
+
}]
|
|
196
|
+
}]
|
|
197
|
+
})).content.find((b) => b.type === "text")?.text ?? "";
|
|
198
|
+
}
|
|
64
199
|
async call(systemPrompt, userPrompt) {
|
|
65
200
|
return (await this.client.messages.create({
|
|
66
201
|
model: this.model,
|
|
@@ -135,6 +270,25 @@ var MinimaxProvider = class {
|
|
|
135
270
|
}
|
|
136
271
|
};
|
|
137
272
|
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/providers/noop.ts
|
|
275
|
+
/**
|
|
276
|
+
* Returns empty strings for every call. Used when no LLM API key is set
|
|
277
|
+
* AND the user has not opted into the agent-sdk fallback via
|
|
278
|
+
* AGENTMEMORY_ALLOW_AGENT_SDK=true. Callers (compress, summarize) must
|
|
279
|
+
* detect the empty result and short-circuit instead of spawning a
|
|
280
|
+
* provider session (#149 / Stop-hook recursion loop fix).
|
|
281
|
+
*/
|
|
282
|
+
var NoopProvider = class {
|
|
283
|
+
name = "noop";
|
|
284
|
+
async compress() {
|
|
285
|
+
return "";
|
|
286
|
+
}
|
|
287
|
+
async summarize() {
|
|
288
|
+
return "";
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
138
292
|
//#endregion
|
|
139
293
|
//#region src/providers/openrouter.ts
|
|
140
294
|
var OpenRouterProvider = class {
|
|
@@ -346,28 +500,67 @@ var GeminiEmbeddingProvider = class {
|
|
|
346
500
|
|
|
347
501
|
//#endregion
|
|
348
502
|
//#region src/providers/embedding/openai.ts
|
|
349
|
-
const
|
|
503
|
+
const DEFAULT_BASE_URL = "https://api.openai.com";
|
|
504
|
+
const DEFAULT_MODEL$1 = "text-embedding-3-small";
|
|
505
|
+
/**
|
|
506
|
+
* Known OpenAI embedding model dimensions. Extend as new models ship.
|
|
507
|
+
* Override in any case via OPENAI_EMBEDDING_DIMENSIONS for custom or
|
|
508
|
+
* self-hosted OpenAI-compatible endpoints returning non-standard sizes.
|
|
509
|
+
*/
|
|
510
|
+
const MODEL_DIMENSIONS = {
|
|
511
|
+
"text-embedding-3-small": 1536,
|
|
512
|
+
"text-embedding-3-large": 3072,
|
|
513
|
+
"text-embedding-ada-002": 1536
|
|
514
|
+
};
|
|
515
|
+
const DEFAULT_DIMENSIONS = MODEL_DIMENSIONS[DEFAULT_MODEL$1] ?? 1536;
|
|
516
|
+
function resolveDimensions(model, override) {
|
|
517
|
+
if (override !== void 0 && override.trim().length > 0) {
|
|
518
|
+
const parsed = parseInt(override, 10);
|
|
519
|
+
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`OPENAI_EMBEDDING_DIMENSIONS must be a positive integer, got: ${override}`);
|
|
520
|
+
return parsed;
|
|
521
|
+
}
|
|
522
|
+
return MODEL_DIMENSIONS[model] ?? DEFAULT_DIMENSIONS;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* OpenAI-compatible embedding provider.
|
|
526
|
+
*
|
|
527
|
+
* Required env vars:
|
|
528
|
+
* OPENAI_API_KEY — API key
|
|
529
|
+
*
|
|
530
|
+
* Optional:
|
|
531
|
+
* OPENAI_BASE_URL — base URL without path (default: https://api.openai.com)
|
|
532
|
+
* OPENAI_EMBEDDING_MODEL — model name (default: text-embedding-3-small)
|
|
533
|
+
* OPENAI_EMBEDDING_DIMENSIONS — override reported dimensions (required for
|
|
534
|
+
* custom / self-hosted models not in the
|
|
535
|
+
* MODEL_DIMENSIONS table above)
|
|
536
|
+
*/
|
|
350
537
|
var OpenAIEmbeddingProvider = class {
|
|
351
538
|
name = "openai";
|
|
352
|
-
dimensions
|
|
539
|
+
dimensions;
|
|
353
540
|
apiKey;
|
|
541
|
+
baseUrl;
|
|
542
|
+
model;
|
|
354
543
|
constructor(apiKey) {
|
|
355
544
|
this.apiKey = apiKey || getEnvVar("OPENAI_API_KEY") || "";
|
|
356
545
|
if (!this.apiKey) throw new Error("OPENAI_API_KEY is required");
|
|
546
|
+
this.baseUrl = getEnvVar("OPENAI_BASE_URL") || DEFAULT_BASE_URL;
|
|
547
|
+
this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
|
|
548
|
+
this.dimensions = resolveDimensions(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
|
|
357
549
|
}
|
|
358
550
|
async embed(text) {
|
|
359
551
|
const [result] = await this.embedBatch([text]);
|
|
360
552
|
return result;
|
|
361
553
|
}
|
|
362
554
|
async embedBatch(texts) {
|
|
363
|
-
const
|
|
555
|
+
const url = `${this.baseUrl}/v1/embeddings`;
|
|
556
|
+
const response = await fetch(url, {
|
|
364
557
|
method: "POST",
|
|
365
558
|
headers: {
|
|
366
559
|
Authorization: `Bearer ${this.apiKey}`,
|
|
367
560
|
"Content-Type": "application/json"
|
|
368
561
|
},
|
|
369
562
|
body: JSON.stringify({
|
|
370
|
-
model:
|
|
563
|
+
model: this.model,
|
|
371
564
|
input: texts
|
|
372
565
|
})
|
|
373
566
|
});
|
|
@@ -508,7 +701,7 @@ var LocalEmbeddingProvider = class {
|
|
|
508
701
|
if (this.extractor) return this.extractor;
|
|
509
702
|
let transformers;
|
|
510
703
|
try {
|
|
511
|
-
transformers = await import("
|
|
704
|
+
transformers = await import("@xenova/transformers");
|
|
512
705
|
} catch {
|
|
513
706
|
throw new Error("Install @xenova/transformers for local embeddings: npm install @xenova/transformers");
|
|
514
707
|
}
|
|
@@ -517,8 +710,86 @@ var LocalEmbeddingProvider = class {
|
|
|
517
710
|
}
|
|
518
711
|
};
|
|
519
712
|
|
|
713
|
+
//#endregion
|
|
714
|
+
//#region src/providers/embedding/clip.ts
|
|
715
|
+
const DEFAULT_MODEL = "Xenova/clip-vit-base-patch32";
|
|
716
|
+
const DIMENSIONS = 512;
|
|
717
|
+
var ClipEmbeddingProvider = class {
|
|
718
|
+
name = "clip";
|
|
719
|
+
dimensions = DIMENSIONS;
|
|
720
|
+
textExtractor = null;
|
|
721
|
+
imageExtractor = null;
|
|
722
|
+
transformers = null;
|
|
723
|
+
modelId;
|
|
724
|
+
constructor(modelId = DEFAULT_MODEL) {
|
|
725
|
+
this.modelId = modelId;
|
|
726
|
+
}
|
|
727
|
+
async embed(text) {
|
|
728
|
+
const [vec] = await this.embedBatch([text]);
|
|
729
|
+
return vec;
|
|
730
|
+
}
|
|
731
|
+
async embedBatch(texts) {
|
|
732
|
+
return (await (await this.getTextExtractor())(texts, {
|
|
733
|
+
pooling: "mean",
|
|
734
|
+
normalize: true
|
|
735
|
+
})).tolist().map((v) => new Float32Array(v));
|
|
736
|
+
}
|
|
737
|
+
async embedImage(src) {
|
|
738
|
+
const image = await loadImage(await this.getTransformers(), src);
|
|
739
|
+
const output = await (await this.getImageExtractor())(image);
|
|
740
|
+
return normalize(output.data ?? new Float32Array(output.tolist()[0] || []));
|
|
741
|
+
}
|
|
742
|
+
async getTransformers() {
|
|
743
|
+
if (this.transformers) return this.transformers;
|
|
744
|
+
try {
|
|
745
|
+
this.transformers = await import("@xenova/transformers");
|
|
746
|
+
} catch {
|
|
747
|
+
throw new Error("Install @xenova/transformers for CLIP image embeddings: npm install @xenova/transformers");
|
|
748
|
+
}
|
|
749
|
+
return this.transformers;
|
|
750
|
+
}
|
|
751
|
+
async getTextExtractor() {
|
|
752
|
+
if (this.textExtractor) return this.textExtractor;
|
|
753
|
+
this.textExtractor = await (await this.getTransformers()).pipeline("feature-extraction", this.modelId);
|
|
754
|
+
return this.textExtractor;
|
|
755
|
+
}
|
|
756
|
+
async getImageExtractor() {
|
|
757
|
+
if (this.imageExtractor) return this.imageExtractor;
|
|
758
|
+
this.imageExtractor = await (await this.getTransformers()).pipeline("image-feature-extraction", this.modelId);
|
|
759
|
+
return this.imageExtractor;
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
async function loadImage(t, src) {
|
|
763
|
+
if (src.startsWith("data:")) {
|
|
764
|
+
const comma = src.indexOf(",");
|
|
765
|
+
const b64 = comma >= 0 ? src.slice(comma + 1) : src;
|
|
766
|
+
const buf = Buffer.from(b64, "base64");
|
|
767
|
+
const blob = new Blob([buf]);
|
|
768
|
+
return t.RawImage.fromBlob(blob);
|
|
769
|
+
}
|
|
770
|
+
const data = await readFile(src);
|
|
771
|
+
const blob = new Blob([data]);
|
|
772
|
+
return t.RawImage.fromBlob(blob);
|
|
773
|
+
}
|
|
774
|
+
function normalize(vec) {
|
|
775
|
+
let sum = 0;
|
|
776
|
+
for (let i = 0; i < vec.length; i++) sum += vec[i] * vec[i];
|
|
777
|
+
const norm = Math.sqrt(sum);
|
|
778
|
+
if (norm === 0) return vec;
|
|
779
|
+
const out = new Float32Array(vec.length);
|
|
780
|
+
for (let i = 0; i < vec.length; i++) out[i] = vec[i] / norm;
|
|
781
|
+
return out;
|
|
782
|
+
}
|
|
783
|
+
|
|
520
784
|
//#endregion
|
|
521
785
|
//#region src/providers/embedding/index.ts
|
|
786
|
+
let imageEmbeddingProvider = null;
|
|
787
|
+
function createImageEmbeddingProvider() {
|
|
788
|
+
if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] !== "true") return null;
|
|
789
|
+
if (imageEmbeddingProvider) return imageEmbeddingProvider;
|
|
790
|
+
imageEmbeddingProvider = new ClipEmbeddingProvider();
|
|
791
|
+
return imageEmbeddingProvider;
|
|
792
|
+
}
|
|
522
793
|
function createEmbeddingProvider() {
|
|
523
794
|
const detected = detectEmbeddingProvider();
|
|
524
795
|
if (!detected) return null;
|
|
@@ -570,6 +841,7 @@ function createBaseProvider(config) {
|
|
|
570
841
|
return new OpenRouterProvider(geminiKey, config.model, config.maxTokens, "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions");
|
|
571
842
|
}
|
|
572
843
|
case "openrouter": return new OpenRouterProvider(requireEnvVar("OPENROUTER_API_KEY"), config.model, config.maxTokens, "https://openrouter.ai/api/v1/chat/completions");
|
|
844
|
+
case "noop": return new NoopProvider();
|
|
573
845
|
default: return new AgentSDKProvider();
|
|
574
846
|
}
|
|
575
847
|
}
|
|
@@ -1105,7 +1377,7 @@ async function loadPipeline() {
|
|
|
1105
1377
|
if (pipelineLoading) return pipelineLoading;
|
|
1106
1378
|
pipelineLoading = (async () => {
|
|
1107
1379
|
try {
|
|
1108
|
-
const { pipeline: createPipeline } = await import("
|
|
1380
|
+
const { pipeline: createPipeline } = await import("@xenova/transformers");
|
|
1109
1381
|
pipeline = await createPipeline("text-classification", "Xenova/ms-marco-MiniLM-L-6-v2", { quantized: true });
|
|
1110
1382
|
return pipeline;
|
|
1111
1383
|
} catch {
|
|
@@ -1822,19 +2094,6 @@ function registerPrivacyFunction(sdk) {
|
|
|
1822
2094
|
});
|
|
1823
2095
|
}
|
|
1824
2096
|
|
|
1825
|
-
//#endregion
|
|
1826
|
-
//#region src/state/keyed-mutex.ts
|
|
1827
|
-
const locks = /* @__PURE__ */ new Map();
|
|
1828
|
-
function withKeyedLock(key, fn) {
|
|
1829
|
-
const next = (locks.get(key) ?? Promise.resolve()).then(fn, fn);
|
|
1830
|
-
const cleanup = next.then(() => {}, () => {});
|
|
1831
|
-
locks.set(key, cleanup);
|
|
1832
|
-
cleanup.then(() => {
|
|
1833
|
-
if (locks.get(key) === cleanup) locks.delete(key);
|
|
1834
|
-
});
|
|
1835
|
-
return next;
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
2097
|
//#endregion
|
|
1839
2098
|
//#region src/functions/compress-synthetic.ts
|
|
1840
2099
|
function inferType(toolName, hookType) {
|
|
@@ -1911,7 +2170,7 @@ function buildSyntheticCompression(raw) {
|
|
|
1911
2170
|
inputStr,
|
|
1912
2171
|
outputStr
|
|
1913
2172
|
].filter((s) => s.length > 0);
|
|
1914
|
-
|
|
2173
|
+
const result = {
|
|
1915
2174
|
id: raw.id,
|
|
1916
2175
|
sessionId: raw.sessionId,
|
|
1917
2176
|
timestamp: raw.timestamp,
|
|
@@ -1925,6 +2184,9 @@ function buildSyntheticCompression(raw) {
|
|
|
1925
2184
|
importance: 5,
|
|
1926
2185
|
confidence: .3
|
|
1927
2186
|
};
|
|
2187
|
+
if (raw.modality) result.modality = raw.modality;
|
|
2188
|
+
if (raw.imageData) result.imageData = raw.imageData;
|
|
2189
|
+
return result;
|
|
1928
2190
|
}
|
|
1929
2191
|
|
|
1930
2192
|
//#endregion
|
|
@@ -2172,6 +2434,24 @@ function registerSearchFunction(sdk, kv) {
|
|
|
2172
2434
|
|
|
2173
2435
|
//#endregion
|
|
2174
2436
|
//#region src/functions/observe.ts
|
|
2437
|
+
function extractImage(d) {
|
|
2438
|
+
if (!d) return void 0;
|
|
2439
|
+
if (typeof d === "string") {
|
|
2440
|
+
if (d.startsWith("data:image/") || d.startsWith("iVBORw0KGgo") || d.startsWith("/9j/")) return d;
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
if (typeof d === "object" && d !== null) {
|
|
2444
|
+
const obj = d;
|
|
2445
|
+
if (typeof obj["image_data"] === "string") return obj["image_data"];
|
|
2446
|
+
if (typeof obj["image_path"] === "string") return obj["image_path"];
|
|
2447
|
+
if (typeof obj["imageBase64"] === "string") return obj["imageBase64"];
|
|
2448
|
+
if (typeof obj["imagePath"] === "string") return obj["imagePath"];
|
|
2449
|
+
for (const key of Object.keys(obj)) {
|
|
2450
|
+
const match = extractImage(obj[key]);
|
|
2451
|
+
if (match) return match;
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2175
2455
|
function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
2176
2456
|
sdk.registerFunction("mem::observe", async (payload) => {
|
|
2177
2457
|
if (!payload?.sessionId || typeof payload.sessionId !== "string" || !payload.hookType || typeof payload.hookType !== "string" || !payload.timestamp || typeof payload.timestamp !== "string") return {
|
|
@@ -2203,6 +2483,7 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
2203
2483
|
hookType: payload.hookType,
|
|
2204
2484
|
raw: sanitizedRaw
|
|
2205
2485
|
};
|
|
2486
|
+
let extractedImage;
|
|
2206
2487
|
if (typeof sanitizedRaw === "object" && sanitizedRaw !== null) {
|
|
2207
2488
|
const d = sanitizedRaw;
|
|
2208
2489
|
if (payload.hookType === "post_tool_use" || payload.hookType === "post_tool_failure") {
|
|
@@ -2211,7 +2492,13 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
2211
2492
|
raw.toolOutput = d["tool_output"] || d["error"];
|
|
2212
2493
|
}
|
|
2213
2494
|
if (payload.hookType === "prompt_submit") raw.userPrompt = d["prompt"];
|
|
2495
|
+
extractedImage = extractImage(sanitizedRaw);
|
|
2496
|
+
if (extractedImage) raw.modality = raw.toolInput || raw.toolOutput || raw.userPrompt ? "mixed" : "image";
|
|
2497
|
+
} else if (typeof sanitizedRaw === "string") {
|
|
2498
|
+
extractedImage = extractImage(sanitizedRaw);
|
|
2499
|
+
if (extractedImage) raw.modality = "image";
|
|
2214
2500
|
}
|
|
2501
|
+
const pendingImageData = extractedImage;
|
|
2215
2502
|
return withKeyedLock(`obs:${payload.sessionId}`, async () => {
|
|
2216
2503
|
if (maxObservationsPerSession && maxObservationsPerSession > 0) {
|
|
2217
2504
|
if ((await kv.list(KV.observations(payload.sessionId))).length >= maxObservationsPerSession) return {
|
|
@@ -2219,7 +2506,29 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
2219
2506
|
error: `Session observation limit reached (${maxObservationsPerSession})`
|
|
2220
2507
|
};
|
|
2221
2508
|
}
|
|
2222
|
-
|
|
2509
|
+
if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) {
|
|
2510
|
+
const { saveImageToDisk } = await import("./image-store-DGvZMMrI.mjs");
|
|
2511
|
+
const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
|
|
2512
|
+
raw.imageData = filePath;
|
|
2513
|
+
const { incrementImageRef } = await import("./image-refs-CESf9ndJ.mjs");
|
|
2514
|
+
await incrementImageRef(kv, filePath);
|
|
2515
|
+
sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: bytesWritten });
|
|
2516
|
+
if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] === "true") sdk.triggerVoid("mem::vision-embed", {
|
|
2517
|
+
imageRef: filePath,
|
|
2518
|
+
sessionId: payload.sessionId,
|
|
2519
|
+
observationId: obsId
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2522
|
+
try {
|
|
2523
|
+
await kv.set(KV.observations(payload.sessionId), obsId, raw);
|
|
2524
|
+
} catch (error) {
|
|
2525
|
+
if (raw.imageData) {
|
|
2526
|
+
const { deleteImage } = await import("./image-store-DGvZMMrI.mjs");
|
|
2527
|
+
const { deletedBytes } = await deleteImage(raw.imageData);
|
|
2528
|
+
if (deletedBytes > 0) sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
|
|
2529
|
+
}
|
|
2530
|
+
throw error;
|
|
2531
|
+
}
|
|
2223
2532
|
if (dedupMap && dedupHash) dedupMap.record(dedupHash);
|
|
2224
2533
|
await sdk.trigger({
|
|
2225
2534
|
function_id: "stream::set",
|
|
@@ -2249,15 +2558,26 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
2249
2558
|
action: TriggerAction.Void()
|
|
2250
2559
|
});
|
|
2251
2560
|
const session = await kv.get(KV.sessions, payload.sessionId);
|
|
2252
|
-
if (session)
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2561
|
+
if (session) {
|
|
2562
|
+
const updates = [{
|
|
2563
|
+
type: "set",
|
|
2564
|
+
path: "updatedAt",
|
|
2565
|
+
value: (/* @__PURE__ */ new Date()).toISOString()
|
|
2566
|
+
}, {
|
|
2567
|
+
type: "set",
|
|
2568
|
+
path: "observationCount",
|
|
2569
|
+
value: (session.observationCount || 0) + 1
|
|
2570
|
+
}];
|
|
2571
|
+
if (!session.firstPrompt && typeof raw.userPrompt === "string") {
|
|
2572
|
+
const trimmed = raw.userPrompt.replace(/\s+/g, " ").trim();
|
|
2573
|
+
if (trimmed.length > 0) updates.push({
|
|
2574
|
+
type: "set",
|
|
2575
|
+
path: "firstPrompt",
|
|
2576
|
+
value: trimmed.slice(0, 200)
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
await kv.update(KV.sessions, payload.sessionId, updates);
|
|
2580
|
+
}
|
|
2261
2581
|
if (isAutoCompressEnabled()) await sdk.trigger({
|
|
2262
2582
|
function_id: "mem::compress",
|
|
2263
2583
|
payload: {
|
|
@@ -2309,64 +2629,777 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
2309
2629
|
}
|
|
2310
2630
|
|
|
2311
2631
|
//#endregion
|
|
2312
|
-
//#region src/
|
|
2313
|
-
const
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2632
|
+
//#region src/functions/image-quota-cleanup.ts
|
|
2633
|
+
const GRACE_PERIOD_MS = 3e4;
|
|
2634
|
+
function registerImageQuotaCleanup(sdk, kv) {
|
|
2635
|
+
sdk.registerFunction("mem::image-quota-cleanup", async () => {
|
|
2636
|
+
const now = Date.now();
|
|
2637
|
+
return withKeyedLock("system:cleanupLock", async () => {
|
|
2638
|
+
let totalSize = 0;
|
|
2639
|
+
const fileStats = [];
|
|
2640
|
+
try {
|
|
2641
|
+
const files = await readdir(IMAGES_DIR);
|
|
2642
|
+
for (const file of files) {
|
|
2643
|
+
if (file.startsWith(".")) continue;
|
|
2644
|
+
const filePath = join(IMAGES_DIR, file);
|
|
2645
|
+
const s = await stat(filePath);
|
|
2646
|
+
if (s.isFile()) {
|
|
2647
|
+
fileStats.push({
|
|
2648
|
+
filePath,
|
|
2649
|
+
size: s.size,
|
|
2650
|
+
mtimeMs: s.mtimeMs
|
|
2651
|
+
});
|
|
2652
|
+
totalSize += s.size;
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
} catch {
|
|
2656
|
+
return {
|
|
2657
|
+
success: true,
|
|
2658
|
+
evicted: 0,
|
|
2659
|
+
freedBytes: 0
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
const limit = getMaxBytes();
|
|
2663
|
+
if (totalSize <= limit) return {
|
|
2664
|
+
success: true,
|
|
2665
|
+
evicted: 0,
|
|
2666
|
+
freedBytes: 0,
|
|
2667
|
+
underQuota: true
|
|
2668
|
+
};
|
|
2669
|
+
fileStats.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
2670
|
+
let totalToFree = totalSize - limit;
|
|
2671
|
+
let evicted = 0;
|
|
2672
|
+
let freedBytes = 0;
|
|
2673
|
+
for (const f of fileStats) {
|
|
2674
|
+
if (totalToFree <= 0) break;
|
|
2675
|
+
if (now - f.mtimeMs < GRACE_PERIOD_MS) continue;
|
|
2676
|
+
await withKeyedLock(`imgRef:${f.filePath}`, async () => {
|
|
2677
|
+
let refCount;
|
|
2678
|
+
try {
|
|
2679
|
+
refCount = await getImageRefCount(kv, f.filePath);
|
|
2680
|
+
} catch (err) {
|
|
2681
|
+
logger.error("Failed to read refCount; skipping eviction", {
|
|
2682
|
+
filePath: f.filePath,
|
|
2683
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2684
|
+
});
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2687
|
+
if (refCount > 0) return;
|
|
2688
|
+
const { deletedBytes } = await deleteImage(f.filePath);
|
|
2689
|
+
if (deletedBytes > 0) {
|
|
2690
|
+
sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
|
|
2691
|
+
totalToFree -= deletedBytes;
|
|
2692
|
+
freedBytes += deletedBytes;
|
|
2693
|
+
evicted++;
|
|
2694
|
+
}
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
if (evicted > 0) {
|
|
2698
|
+
const freedMb = (freedBytes / (1024 * 1024)).toFixed(1);
|
|
2699
|
+
logger.info("Image quota cleanup complete", {
|
|
2700
|
+
evicted,
|
|
2701
|
+
freedMb
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
return {
|
|
2705
|
+
success: true,
|
|
2706
|
+
evicted,
|
|
2707
|
+
freedBytes
|
|
2708
|
+
};
|
|
2709
|
+
});
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2334
2712
|
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2713
|
+
//#endregion
|
|
2714
|
+
//#region src/functions/audit.ts
|
|
2715
|
+
async function recordAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
|
|
2716
|
+
const entry = {
|
|
2717
|
+
id: generateId("aud"),
|
|
2718
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2719
|
+
operation,
|
|
2720
|
+
userId,
|
|
2721
|
+
functionId,
|
|
2722
|
+
targetIds,
|
|
2723
|
+
details,
|
|
2724
|
+
qualityScore
|
|
2725
|
+
};
|
|
2726
|
+
await kv.set(KV.audit, entry.id, entry);
|
|
2727
|
+
return entry;
|
|
2728
|
+
}
|
|
2729
|
+
async function safeAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
|
|
2730
|
+
try {
|
|
2731
|
+
await recordAudit(kv, operation, functionId, targetIds, details, qualityScore, userId);
|
|
2732
|
+
} catch (err) {
|
|
2733
|
+
try {
|
|
2734
|
+
logger.warn("audit write failed", {
|
|
2735
|
+
functionId,
|
|
2736
|
+
operation,
|
|
2737
|
+
targetIds,
|
|
2738
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2739
|
+
});
|
|
2740
|
+
} catch {}
|
|
2351
2741
|
}
|
|
2352
|
-
if (observation.userPrompt) parts.push(`User prompt:\n${truncate$1(observation.userPrompt, 2e3)}`);
|
|
2353
|
-
return parts.join("\n\n");
|
|
2354
2742
|
}
|
|
2355
|
-
function
|
|
2356
|
-
|
|
2743
|
+
async function queryAudit(kv, filter) {
|
|
2744
|
+
let entries = [...await kv.list(KV.audit)].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
2745
|
+
if (filter?.operation) entries = entries.filter((e) => e.operation === filter.operation);
|
|
2746
|
+
if (filter?.dateFrom) {
|
|
2747
|
+
const from = new Date(filter.dateFrom).getTime();
|
|
2748
|
+
if (Number.isNaN(from)) throw new Error(`Invalid dateFrom: ${filter.dateFrom}`);
|
|
2749
|
+
entries = entries.filter((e) => new Date(e.timestamp).getTime() >= from);
|
|
2750
|
+
}
|
|
2751
|
+
if (filter?.dateTo) {
|
|
2752
|
+
const to = new Date(filter.dateTo).getTime();
|
|
2753
|
+
if (Number.isNaN(to)) throw new Error(`Invalid dateTo: ${filter.dateTo}`);
|
|
2754
|
+
entries = entries.filter((e) => new Date(e.timestamp).getTime() <= to);
|
|
2755
|
+
}
|
|
2756
|
+
return entries.slice(0, filter?.limit || 100);
|
|
2357
2757
|
}
|
|
2358
2758
|
|
|
2359
2759
|
//#endregion
|
|
2360
|
-
//#region src/
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
}
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2760
|
+
//#region src/functions/vision-search.ts
|
|
2761
|
+
function registerVisionSearchFunctions(sdk, kv, imageProvider) {
|
|
2762
|
+
sdk.registerFunction("mem::vision-embed", async (data) => {
|
|
2763
|
+
if (!imageProvider?.embedImage) return {
|
|
2764
|
+
success: false,
|
|
2765
|
+
error: "image embeddings disabled (set AGENTMEMORY_IMAGE_EMBEDDINGS=true)"
|
|
2766
|
+
};
|
|
2767
|
+
if (!data?.imageRef || typeof data.imageRef !== "string") return {
|
|
2768
|
+
success: false,
|
|
2769
|
+
error: "imageRef required"
|
|
2770
|
+
};
|
|
2771
|
+
if (!isManagedImagePath(data.imageRef)) return {
|
|
2772
|
+
success: false,
|
|
2773
|
+
error: "imageRef must point to a file under the managed image store"
|
|
2774
|
+
};
|
|
2775
|
+
const refCount = await kv.get(KV.imageRefs, data.imageRef);
|
|
2776
|
+
if (!refCount || Number(refCount) < 1) return {
|
|
2777
|
+
success: false,
|
|
2778
|
+
error: "imageRef not registered in mem:image-refs"
|
|
2779
|
+
};
|
|
2780
|
+
try {
|
|
2781
|
+
const vec = await imageProvider.embedImage(data.imageRef);
|
|
2782
|
+
const stored = {
|
|
2783
|
+
imageRef: data.imageRef,
|
|
2784
|
+
vector: Array.from(vec),
|
|
2785
|
+
modelName: imageProvider.name,
|
|
2786
|
+
dimensions: imageProvider.dimensions,
|
|
2787
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2788
|
+
sessionId: data.sessionId,
|
|
2789
|
+
observationId: data.observationId
|
|
2790
|
+
};
|
|
2791
|
+
await kv.set(KV.imageEmbeddings, data.imageRef, stored);
|
|
2792
|
+
await recordAudit(kv, "vision_embed", "mem::vision-embed", [data.imageRef], {
|
|
2793
|
+
modelName: imageProvider.name,
|
|
2794
|
+
dimensions: stored.dimensions,
|
|
2795
|
+
sessionId: data.sessionId,
|
|
2796
|
+
observationId: data.observationId
|
|
2797
|
+
});
|
|
2798
|
+
return {
|
|
2799
|
+
success: true,
|
|
2800
|
+
imageRef: data.imageRef,
|
|
2801
|
+
dimensions: stored.dimensions
|
|
2802
|
+
};
|
|
2803
|
+
} catch (err) {
|
|
2804
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2805
|
+
logger.warn("vision-embed failed", {
|
|
2806
|
+
imageRef: data.imageRef,
|
|
2807
|
+
error: msg
|
|
2808
|
+
});
|
|
2809
|
+
return {
|
|
2810
|
+
success: false,
|
|
2811
|
+
error: msg
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
});
|
|
2815
|
+
sdk.registerFunction("mem::vision-search", async (data) => {
|
|
2816
|
+
if (!imageProvider?.embedImage) return {
|
|
2817
|
+
success: false,
|
|
2818
|
+
error: "image embeddings disabled (set AGENTMEMORY_IMAGE_EMBEDDINGS=true)"
|
|
2819
|
+
};
|
|
2820
|
+
const requestedTopK = typeof data?.topK === "number" && Number.isFinite(data.topK) ? Math.trunc(data.topK) : 10;
|
|
2821
|
+
const topK = Math.min(50, Math.max(1, requestedTopK));
|
|
2822
|
+
let queryVec = null;
|
|
2823
|
+
try {
|
|
2824
|
+
if (data?.queryText) queryVec = await imageProvider.embed(data.queryText);
|
|
2825
|
+
else if (data?.queryImageBase64) {
|
|
2826
|
+
const b64 = data.queryImageBase64.startsWith("data:") ? data.queryImageBase64 : `data:image/png;base64,${data.queryImageBase64}`;
|
|
2827
|
+
queryVec = await imageProvider.embedImage(b64);
|
|
2828
|
+
} else if (data?.queryImageRef) {
|
|
2829
|
+
if (!isManagedImagePath(data.queryImageRef)) return {
|
|
2830
|
+
success: false,
|
|
2831
|
+
error: "queryImageRef must point to a file under the managed image store"
|
|
2832
|
+
};
|
|
2833
|
+
const refCount = await kv.get(KV.imageRefs, data.queryImageRef);
|
|
2834
|
+
if (!refCount || Number(refCount) < 1) return {
|
|
2835
|
+
success: false,
|
|
2836
|
+
error: "queryImageRef not registered in mem:image-refs"
|
|
2837
|
+
};
|
|
2838
|
+
queryVec = await imageProvider.embedImage(data.queryImageRef);
|
|
2839
|
+
} else return {
|
|
2840
|
+
success: false,
|
|
2841
|
+
error: "queryText, queryImageRef, or queryImageBase64 required"
|
|
2842
|
+
};
|
|
2843
|
+
} catch (err) {
|
|
2844
|
+
return {
|
|
2845
|
+
success: false,
|
|
2846
|
+
error: `query embed failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2849
|
+
if (!queryVec) return {
|
|
2850
|
+
success: false,
|
|
2851
|
+
error: "failed to build query vector"
|
|
2852
|
+
};
|
|
2853
|
+
const stored = await kv.list(KV.imageEmbeddings);
|
|
2854
|
+
const scored = (data?.sessionId ? stored.filter((s) => s.sessionId === data.sessionId) : stored).map((s) => ({
|
|
2855
|
+
imageRef: s.imageRef,
|
|
2856
|
+
score: cosine(queryVec, s.vector),
|
|
2857
|
+
sessionId: s.sessionId,
|
|
2858
|
+
observationId: s.observationId,
|
|
2859
|
+
updatedAt: s.updatedAt
|
|
2860
|
+
}));
|
|
2861
|
+
scored.sort((a, b) => b.score - a.score);
|
|
2862
|
+
return {
|
|
2863
|
+
success: true,
|
|
2864
|
+
results: scored.slice(0, topK),
|
|
2865
|
+
total: scored.length
|
|
2866
|
+
};
|
|
2867
|
+
});
|
|
2868
|
+
}
|
|
2869
|
+
function cosine(a, b) {
|
|
2870
|
+
if (a.length !== b.length) return 0;
|
|
2871
|
+
let dot = 0;
|
|
2872
|
+
let normA = 0;
|
|
2873
|
+
let normB = 0;
|
|
2874
|
+
for (let i = 0; i < a.length; i++) {
|
|
2875
|
+
dot += a[i] * b[i];
|
|
2876
|
+
normA += a[i] * a[i];
|
|
2877
|
+
normB += b[i] * b[i];
|
|
2878
|
+
}
|
|
2879
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
2880
|
+
return denom === 0 ? 0 : dot / denom;
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
//#endregion
|
|
2884
|
+
//#region src/functions/slots.ts
|
|
2885
|
+
const DEFAULT_SIZE_LIMIT = 2e3;
|
|
2886
|
+
const DEFAULT_SLOTS = [
|
|
2887
|
+
{
|
|
2888
|
+
label: "persona",
|
|
2889
|
+
content: "",
|
|
2890
|
+
sizeLimit: 1e3,
|
|
2891
|
+
description: "How the agent should see itself: role, tone, behavioural guidelines.",
|
|
2892
|
+
pinned: true,
|
|
2893
|
+
readOnly: false,
|
|
2894
|
+
scope: "global"
|
|
2895
|
+
},
|
|
2896
|
+
{
|
|
2897
|
+
label: "user_preferences",
|
|
2898
|
+
content: "",
|
|
2899
|
+
sizeLimit: 2e3,
|
|
2900
|
+
description: "Coding style, tool preferences, naming conventions, and other habits the user wants preserved across sessions.",
|
|
2901
|
+
pinned: true,
|
|
2902
|
+
readOnly: false,
|
|
2903
|
+
scope: "global"
|
|
2904
|
+
},
|
|
2905
|
+
{
|
|
2906
|
+
label: "tool_guidelines",
|
|
2907
|
+
content: "",
|
|
2908
|
+
sizeLimit: 1500,
|
|
2909
|
+
description: "Rules the agent should follow when picking or sequencing tools (e.g. prefer X over Y, never run Z without confirmation).",
|
|
2910
|
+
pinned: true,
|
|
2911
|
+
readOnly: false,
|
|
2912
|
+
scope: "global"
|
|
2913
|
+
},
|
|
2914
|
+
{
|
|
2915
|
+
label: "project_context",
|
|
2916
|
+
content: "",
|
|
2917
|
+
sizeLimit: 3e3,
|
|
2918
|
+
description: "Architecture decisions, codebase conventions, build/test commands, and cross-cutting constraints for the current project.",
|
|
2919
|
+
pinned: true,
|
|
2920
|
+
readOnly: false,
|
|
2921
|
+
scope: "project"
|
|
2922
|
+
},
|
|
2923
|
+
{
|
|
2924
|
+
label: "guidance",
|
|
2925
|
+
content: "",
|
|
2926
|
+
sizeLimit: 1500,
|
|
2927
|
+
description: "Active advice for the next session: what to focus on, what to avoid, open risks.",
|
|
2928
|
+
pinned: true,
|
|
2929
|
+
readOnly: false,
|
|
2930
|
+
scope: "project"
|
|
2931
|
+
},
|
|
2932
|
+
{
|
|
2933
|
+
label: "pending_items",
|
|
2934
|
+
content: "",
|
|
2935
|
+
sizeLimit: 2e3,
|
|
2936
|
+
description: "Unfinished work, explicit TODOs, and promises made but not yet delivered.",
|
|
2937
|
+
pinned: true,
|
|
2938
|
+
readOnly: false,
|
|
2939
|
+
scope: "project"
|
|
2940
|
+
},
|
|
2941
|
+
{
|
|
2942
|
+
label: "session_patterns",
|
|
2943
|
+
content: "",
|
|
2944
|
+
sizeLimit: 1500,
|
|
2945
|
+
description: "Recurring behaviours and common struggles observed across recent sessions.",
|
|
2946
|
+
pinned: false,
|
|
2947
|
+
readOnly: false,
|
|
2948
|
+
scope: "project"
|
|
2949
|
+
},
|
|
2950
|
+
{
|
|
2951
|
+
label: "self_notes",
|
|
2952
|
+
content: "",
|
|
2953
|
+
sizeLimit: 1500,
|
|
2954
|
+
description: "Free-form notes the agent keeps for itself: hypotheses, dead ends, things to revisit.",
|
|
2955
|
+
pinned: false,
|
|
2956
|
+
readOnly: false,
|
|
2957
|
+
scope: "project"
|
|
2958
|
+
}
|
|
2959
|
+
];
|
|
2960
|
+
function isSlotsEnabled() {
|
|
2961
|
+
return process.env["AGENTMEMORY_SLOTS"] === "true";
|
|
2962
|
+
}
|
|
2963
|
+
function isReflectEnabled() {
|
|
2964
|
+
return process.env["AGENTMEMORY_REFLECT"] === "true";
|
|
2965
|
+
}
|
|
2966
|
+
function scopeKv(scope) {
|
|
2967
|
+
return scope === "global" ? KV.globalSlots : KV.slots;
|
|
2968
|
+
}
|
|
2969
|
+
function nowIso() {
|
|
2970
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2971
|
+
}
|
|
2972
|
+
function validateLabel(label) {
|
|
2973
|
+
if (typeof label !== "string") return null;
|
|
2974
|
+
const trimmed = label.trim();
|
|
2975
|
+
if (!trimmed || trimmed.length > 64) return null;
|
|
2976
|
+
if (!/^[a-z][a-z0-9_]*$/.test(trimmed)) return null;
|
|
2977
|
+
return trimmed;
|
|
2978
|
+
}
|
|
2979
|
+
async function readSlot(kv, label) {
|
|
2980
|
+
const project = await kv.get(KV.slots, label);
|
|
2981
|
+
if (project) return {
|
|
2982
|
+
slot: project,
|
|
2983
|
+
scope: "project"
|
|
2984
|
+
};
|
|
2985
|
+
const global = await kv.get(KV.globalSlots, label);
|
|
2986
|
+
if (global) return {
|
|
2987
|
+
slot: global,
|
|
2988
|
+
scope: "global"
|
|
2989
|
+
};
|
|
2990
|
+
return {
|
|
2991
|
+
slot: null,
|
|
2992
|
+
scope: "project"
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
async function readSlotInScope(kv, label, scope) {
|
|
2996
|
+
return kv.get(scopeKv(scope), label);
|
|
2997
|
+
}
|
|
2998
|
+
function validateScope(raw) {
|
|
2999
|
+
if (raw === void 0 || raw === null) return "project";
|
|
3000
|
+
if (raw === "project" || raw === "global") return raw;
|
|
3001
|
+
return null;
|
|
3002
|
+
}
|
|
3003
|
+
function validateSizeLimit(raw) {
|
|
3004
|
+
if (raw === void 0 || raw === null) return DEFAULT_SIZE_LIMIT;
|
|
3005
|
+
if (typeof raw !== "number") return null;
|
|
3006
|
+
if (!Number.isInteger(raw) || raw < 1 || raw > 2e4) return null;
|
|
3007
|
+
return raw;
|
|
3008
|
+
}
|
|
3009
|
+
async function seedDefaults(kv) {
|
|
3010
|
+
const ts = nowIso();
|
|
3011
|
+
for (const tmpl of DEFAULT_SLOTS) {
|
|
3012
|
+
const target = scopeKv(tmpl.scope);
|
|
3013
|
+
if (await kv.get(target, tmpl.label)) continue;
|
|
3014
|
+
const slot = {
|
|
3015
|
+
...tmpl,
|
|
3016
|
+
createdAt: ts,
|
|
3017
|
+
updatedAt: ts
|
|
3018
|
+
};
|
|
3019
|
+
await kv.set(target, tmpl.label, slot);
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
function registerSlotsFunctions(sdk, kv) {
|
|
3023
|
+
seedDefaults(kv).catch((err) => {
|
|
3024
|
+
logger.warn("slot defaults seed failed", { error: err instanceof Error ? err.message : String(err) });
|
|
3025
|
+
});
|
|
3026
|
+
sdk.registerFunction("mem::slot-list", async () => {
|
|
3027
|
+
const [project, global] = await Promise.all([kv.list(KV.slots), kv.list(KV.globalSlots)]);
|
|
3028
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3029
|
+
for (const s of global) merged.set(s.label, s);
|
|
3030
|
+
for (const s of project) merged.set(s.label, s);
|
|
3031
|
+
return {
|
|
3032
|
+
success: true,
|
|
3033
|
+
slots: Array.from(merged.values()).sort((a, b) => a.label.localeCompare(b.label))
|
|
3034
|
+
};
|
|
3035
|
+
});
|
|
3036
|
+
sdk.registerFunction("mem::slot-get", async (data) => {
|
|
3037
|
+
const label = validateLabel(data?.label);
|
|
3038
|
+
if (!label) return {
|
|
3039
|
+
success: false,
|
|
3040
|
+
error: "label required (lowercase, starts with letter, [a-z0-9_])"
|
|
3041
|
+
};
|
|
3042
|
+
const { slot, scope } = await readSlot(kv, label);
|
|
3043
|
+
if (!slot) return {
|
|
3044
|
+
success: false,
|
|
3045
|
+
error: "slot not found"
|
|
3046
|
+
};
|
|
3047
|
+
return {
|
|
3048
|
+
success: true,
|
|
3049
|
+
slot,
|
|
3050
|
+
scope
|
|
3051
|
+
};
|
|
3052
|
+
});
|
|
3053
|
+
sdk.registerFunction("mem::slot-create", async (data) => {
|
|
3054
|
+
const label = validateLabel(data?.label);
|
|
3055
|
+
if (!label) return {
|
|
3056
|
+
success: false,
|
|
3057
|
+
error: "label required (lowercase, starts with letter, [a-z0-9_])"
|
|
3058
|
+
};
|
|
3059
|
+
const scope = validateScope(data?.scope);
|
|
3060
|
+
if (!scope) return {
|
|
3061
|
+
success: false,
|
|
3062
|
+
error: "scope must be 'project' or 'global'"
|
|
3063
|
+
};
|
|
3064
|
+
const sizeLimit = validateSizeLimit(data?.sizeLimit);
|
|
3065
|
+
if (sizeLimit === null) return {
|
|
3066
|
+
success: false,
|
|
3067
|
+
error: "sizeLimit must be an integer between 1 and 20000"
|
|
3068
|
+
};
|
|
3069
|
+
const content = typeof data?.content === "string" ? data.content : "";
|
|
3070
|
+
if (content.length > sizeLimit) return {
|
|
3071
|
+
success: false,
|
|
3072
|
+
error: `content exceeds sizeLimit (${content.length} > ${sizeLimit})`
|
|
3073
|
+
};
|
|
3074
|
+
const description = typeof data?.description === "string" ? data.description : "";
|
|
3075
|
+
const pinned = typeof data?.pinned === "boolean" ? data.pinned : true;
|
|
3076
|
+
return withKeyedLock(`slot:${label}`, async () => {
|
|
3077
|
+
if (await readSlotInScope(kv, label, scope)) return {
|
|
3078
|
+
success: false,
|
|
3079
|
+
error: `slot already exists in ${scope} scope`
|
|
3080
|
+
};
|
|
3081
|
+
const ts = nowIso();
|
|
3082
|
+
const slot = {
|
|
3083
|
+
label,
|
|
3084
|
+
content,
|
|
3085
|
+
sizeLimit,
|
|
3086
|
+
description,
|
|
3087
|
+
pinned,
|
|
3088
|
+
readOnly: false,
|
|
3089
|
+
scope,
|
|
3090
|
+
createdAt: ts,
|
|
3091
|
+
updatedAt: ts
|
|
3092
|
+
};
|
|
3093
|
+
await kv.set(scopeKv(scope), label, slot);
|
|
3094
|
+
await recordAudit(kv, "slot_create", "mem::slot-create", [label], {
|
|
3095
|
+
scope,
|
|
3096
|
+
sizeLimit: slot.sizeLimit,
|
|
3097
|
+
pinned: slot.pinned
|
|
3098
|
+
});
|
|
3099
|
+
return {
|
|
3100
|
+
success: true,
|
|
3101
|
+
slot
|
|
3102
|
+
};
|
|
3103
|
+
});
|
|
3104
|
+
});
|
|
3105
|
+
sdk.registerFunction("mem::slot-append", async (data) => {
|
|
3106
|
+
const label = validateLabel(data?.label);
|
|
3107
|
+
if (!label) return {
|
|
3108
|
+
success: false,
|
|
3109
|
+
error: "label required"
|
|
3110
|
+
};
|
|
3111
|
+
const text = typeof data?.text === "string" ? data.text : "";
|
|
3112
|
+
if (!text) return {
|
|
3113
|
+
success: false,
|
|
3114
|
+
error: "text required"
|
|
3115
|
+
};
|
|
3116
|
+
return withKeyedLock(`slot:${label}`, async () => {
|
|
3117
|
+
const { slot, scope } = await readSlot(kv, label);
|
|
3118
|
+
if (!slot) return {
|
|
3119
|
+
success: false,
|
|
3120
|
+
error: "slot not found (use mem::slot-create first)"
|
|
3121
|
+
};
|
|
3122
|
+
if (slot.readOnly) return {
|
|
3123
|
+
success: false,
|
|
3124
|
+
error: "slot is read-only"
|
|
3125
|
+
};
|
|
3126
|
+
const sep = slot.content && !slot.content.endsWith("\n") ? "\n" : "";
|
|
3127
|
+
const next = `${slot.content}${sep}${text}`;
|
|
3128
|
+
if (next.length > slot.sizeLimit) return {
|
|
3129
|
+
success: false,
|
|
3130
|
+
error: `append would exceed sizeLimit (${next.length} > ${slot.sizeLimit}). Use mem::slot-replace to compact first.`,
|
|
3131
|
+
currentSize: slot.content.length,
|
|
3132
|
+
sizeLimit: slot.sizeLimit
|
|
3133
|
+
};
|
|
3134
|
+
const updated = {
|
|
3135
|
+
...slot,
|
|
3136
|
+
content: next,
|
|
3137
|
+
updatedAt: nowIso()
|
|
3138
|
+
};
|
|
3139
|
+
await kv.set(scopeKv(scope), label, updated);
|
|
3140
|
+
await recordAudit(kv, "slot_append", "mem::slot-append", [label], {
|
|
3141
|
+
scope,
|
|
3142
|
+
added: text.length,
|
|
3143
|
+
total: next.length
|
|
3144
|
+
});
|
|
3145
|
+
return {
|
|
3146
|
+
success: true,
|
|
3147
|
+
slot: updated,
|
|
3148
|
+
size: next.length
|
|
3149
|
+
};
|
|
3150
|
+
});
|
|
3151
|
+
});
|
|
3152
|
+
sdk.registerFunction("mem::slot-replace", async (data) => {
|
|
3153
|
+
const label = validateLabel(data?.label);
|
|
3154
|
+
if (!label) return {
|
|
3155
|
+
success: false,
|
|
3156
|
+
error: "label required"
|
|
3157
|
+
};
|
|
3158
|
+
if (typeof data?.content !== "string") return {
|
|
3159
|
+
success: false,
|
|
3160
|
+
error: "content required (string)"
|
|
3161
|
+
};
|
|
3162
|
+
return withKeyedLock(`slot:${label}`, async () => {
|
|
3163
|
+
const { slot, scope } = await readSlot(kv, label);
|
|
3164
|
+
if (!slot) return {
|
|
3165
|
+
success: false,
|
|
3166
|
+
error: "slot not found (use mem::slot-create first)"
|
|
3167
|
+
};
|
|
3168
|
+
if (slot.readOnly) return {
|
|
3169
|
+
success: false,
|
|
3170
|
+
error: "slot is read-only"
|
|
3171
|
+
};
|
|
3172
|
+
if (data.content.length > slot.sizeLimit) return {
|
|
3173
|
+
success: false,
|
|
3174
|
+
error: `content exceeds sizeLimit (${data.content.length} > ${slot.sizeLimit})`,
|
|
3175
|
+
sizeLimit: slot.sizeLimit
|
|
3176
|
+
};
|
|
3177
|
+
const updated = {
|
|
3178
|
+
...slot,
|
|
3179
|
+
content: data.content,
|
|
3180
|
+
updatedAt: nowIso()
|
|
3181
|
+
};
|
|
3182
|
+
await kv.set(scopeKv(scope), label, updated);
|
|
3183
|
+
await recordAudit(kv, "slot_replace", "mem::slot-replace", [label], {
|
|
3184
|
+
scope,
|
|
3185
|
+
before: slot.content.length,
|
|
3186
|
+
after: data.content.length
|
|
3187
|
+
});
|
|
3188
|
+
return {
|
|
3189
|
+
success: true,
|
|
3190
|
+
slot: updated,
|
|
3191
|
+
size: data.content.length
|
|
3192
|
+
};
|
|
3193
|
+
});
|
|
3194
|
+
});
|
|
3195
|
+
sdk.registerFunction("mem::slot-delete", async (data) => {
|
|
3196
|
+
const label = validateLabel(data?.label);
|
|
3197
|
+
if (!label) return {
|
|
3198
|
+
success: false,
|
|
3199
|
+
error: "label required"
|
|
3200
|
+
};
|
|
3201
|
+
return withKeyedLock(`slot:${label}`, async () => {
|
|
3202
|
+
const { slot, scope } = await readSlot(kv, label);
|
|
3203
|
+
if (!slot) return {
|
|
3204
|
+
success: false,
|
|
3205
|
+
error: "slot not found"
|
|
3206
|
+
};
|
|
3207
|
+
if (slot.readOnly) return {
|
|
3208
|
+
success: false,
|
|
3209
|
+
error: "slot is read-only"
|
|
3210
|
+
};
|
|
3211
|
+
await kv.delete(scopeKv(scope), label);
|
|
3212
|
+
await recordAudit(kv, "slot_delete", "mem::slot-delete", [label], {
|
|
3213
|
+
scope,
|
|
3214
|
+
size: slot.content.length
|
|
3215
|
+
});
|
|
3216
|
+
return { success: true };
|
|
3217
|
+
});
|
|
3218
|
+
});
|
|
3219
|
+
sdk.registerFunction("mem::slot-reflect", async (data) => {
|
|
3220
|
+
if (!data?.sessionId || typeof data.sessionId !== "string") return {
|
|
3221
|
+
success: false,
|
|
3222
|
+
error: "sessionId required"
|
|
3223
|
+
};
|
|
3224
|
+
const max = typeof data.maxObservations === "number" && Number.isInteger(data.maxObservations) && data.maxObservations > 0 ? Math.min(200, data.maxObservations) : 50;
|
|
3225
|
+
const observations = await kv.list(KV.observations(data.sessionId));
|
|
3226
|
+
if (observations.length === 0) return {
|
|
3227
|
+
success: true,
|
|
3228
|
+
applied: 0,
|
|
3229
|
+
reason: "no observations for session"
|
|
3230
|
+
};
|
|
3231
|
+
const recent = observations.slice().sort((a, b) => (b.timestamp || "").localeCompare(a.timestamp || "")).slice(0, max);
|
|
3232
|
+
const pendingLines = [];
|
|
3233
|
+
const patternCounts = /* @__PURE__ */ new Map();
|
|
3234
|
+
const files = /* @__PURE__ */ new Set();
|
|
3235
|
+
for (const obs of recent) {
|
|
3236
|
+
const title = (obs.title || "").toLowerCase();
|
|
3237
|
+
if ((obs.narrative || "").toLowerCase().includes("todo") || title.includes("todo")) pendingLines.push(`- ${obs.title || obs.id}`);
|
|
3238
|
+
if (obs.type === "error") patternCounts.set("errors", (patternCounts.get("errors") ?? 0) + 1);
|
|
3239
|
+
if (obs.type === "command_run") patternCounts.set("commands", (patternCounts.get("commands") ?? 0) + 1);
|
|
3240
|
+
if (obs.files) for (const f of obs.files) files.add(f);
|
|
3241
|
+
}
|
|
3242
|
+
let applied = 0;
|
|
3243
|
+
if (pendingLines.length > 0) {
|
|
3244
|
+
if (await withKeyedLock(`slot:pending_items`, async () => {
|
|
3245
|
+
const { slot, scope } = await readSlot(kv, "pending_items");
|
|
3246
|
+
if (!slot) return false;
|
|
3247
|
+
const already = new Set(slot.content.split("\n"));
|
|
3248
|
+
const fresh = pendingLines.filter((line) => !already.has(line));
|
|
3249
|
+
if (fresh.length === 0) return false;
|
|
3250
|
+
const sep = slot.content && !slot.content.endsWith("\n") ? "\n" : "";
|
|
3251
|
+
const next = `${slot.content}${sep}${fresh.join("\n")}`;
|
|
3252
|
+
const truncated = next.length > slot.sizeLimit ? next.slice(next.length - slot.sizeLimit) : next;
|
|
3253
|
+
await kv.set(scopeKv(scope), "pending_items", {
|
|
3254
|
+
...slot,
|
|
3255
|
+
content: truncated,
|
|
3256
|
+
updatedAt: nowIso()
|
|
3257
|
+
});
|
|
3258
|
+
return true;
|
|
3259
|
+
})) applied++;
|
|
3260
|
+
}
|
|
3261
|
+
if (patternCounts.size > 0) {
|
|
3262
|
+
if (await withKeyedLock(`slot:session_patterns`, async () => {
|
|
3263
|
+
const { slot, scope } = await readSlot(kv, "session_patterns");
|
|
3264
|
+
if (!slot) return false;
|
|
3265
|
+
const summary = [`last reflection: ${nowIso()}`, ...Array.from(patternCounts.entries()).map(([kind, count]) => `- ${kind}: ${count} in last ${recent.length} observations`)].join("\n");
|
|
3266
|
+
const next = summary.length > slot.sizeLimit ? summary.slice(0, slot.sizeLimit) : summary;
|
|
3267
|
+
await kv.set(scopeKv(scope), "session_patterns", {
|
|
3268
|
+
...slot,
|
|
3269
|
+
content: next,
|
|
3270
|
+
updatedAt: nowIso()
|
|
3271
|
+
});
|
|
3272
|
+
return true;
|
|
3273
|
+
})) applied++;
|
|
3274
|
+
}
|
|
3275
|
+
if (files.size > 0) {
|
|
3276
|
+
if (await withKeyedLock(`slot:project_context`, async () => {
|
|
3277
|
+
const { slot, scope } = await readSlot(kv, "project_context");
|
|
3278
|
+
if (!slot) return false;
|
|
3279
|
+
const already = slot.content;
|
|
3280
|
+
const fresh = Array.from(files).filter((f) => !already.includes(f)).slice(0, 20);
|
|
3281
|
+
if (fresh.length === 0) return false;
|
|
3282
|
+
const header = already.length === 0 ? "Files touched in recent sessions:" : "";
|
|
3283
|
+
const nextRaw = `${already}${already && !already.endsWith("\n") ? "\n" : ""}${header ? header + "\n" : ""}${fresh.map((f) => `- ${f}`).join("\n")}`;
|
|
3284
|
+
const next = nextRaw.length > slot.sizeLimit ? nextRaw.slice(nextRaw.length - slot.sizeLimit) : nextRaw;
|
|
3285
|
+
await kv.set(scopeKv(scope), "project_context", {
|
|
3286
|
+
...slot,
|
|
3287
|
+
content: next,
|
|
3288
|
+
updatedAt: nowIso()
|
|
3289
|
+
});
|
|
3290
|
+
return true;
|
|
3291
|
+
})) applied++;
|
|
3292
|
+
}
|
|
3293
|
+
if (applied > 0) await recordAudit(kv, "slot_reflect", "mem::slot-reflect", [data.sessionId], {
|
|
3294
|
+
observationCount: recent.length,
|
|
3295
|
+
slotsUpdated: applied
|
|
3296
|
+
});
|
|
3297
|
+
return {
|
|
3298
|
+
success: true,
|
|
3299
|
+
applied,
|
|
3300
|
+
observationsReviewed: recent.length
|
|
3301
|
+
};
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
//#endregion
|
|
3306
|
+
//#region src/functions/disk-size-manager.ts
|
|
3307
|
+
const DISK_SIZE_KEY = "system:currentDiskSize";
|
|
3308
|
+
function registerDiskSizeManager(sdk, kv) {
|
|
3309
|
+
sdk.registerFunction("mem::disk-size-delta", async (data) => {
|
|
3310
|
+
if (typeof data?.deltaBytes !== "number" || !isFinite(data.deltaBytes)) return {
|
|
3311
|
+
success: false,
|
|
3312
|
+
error: "deltaBytes must be a finite number"
|
|
3313
|
+
};
|
|
3314
|
+
return withKeyedLock(DISK_SIZE_KEY, async () => {
|
|
3315
|
+
let newTotal = (await kv.get(KV.state, DISK_SIZE_KEY) || 0) + data.deltaBytes;
|
|
3316
|
+
if (newTotal < 0) newTotal = 0;
|
|
3317
|
+
await kv.set(KV.state, DISK_SIZE_KEY, newTotal);
|
|
3318
|
+
if (data.deltaBytes > 0 && newTotal > getMaxBytes()) {
|
|
3319
|
+
sdk.triggerVoid("mem::image-quota-cleanup", {});
|
|
3320
|
+
logger.info("Disk quota exceeded, cleanup triggered", {
|
|
3321
|
+
currentBytes: newTotal,
|
|
3322
|
+
maxBytes: getMaxBytes()
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
return {
|
|
3326
|
+
success: true,
|
|
3327
|
+
currentTotal: newTotal
|
|
3328
|
+
};
|
|
3329
|
+
});
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
//#endregion
|
|
3334
|
+
//#region src/prompts/compression.ts
|
|
3335
|
+
const COMPRESSION_SYSTEM = `You are a memory compression engine for an AI coding agent. Your job is to extract the essential information from a tool usage observation and compress it into structured data.
|
|
3336
|
+
|
|
3337
|
+
Output EXACTLY this XML format with no additional text:
|
|
3338
|
+
|
|
3339
|
+
<observation>
|
|
3340
|
+
<type>one of: file_read, file_write, file_edit, command_run, search, web_fetch, conversation, error, decision, discovery, subagent, notification, task, other</type>
|
|
3341
|
+
<title>Short descriptive title (max 80 chars)</title>
|
|
3342
|
+
<subtitle>One-line context (optional)</subtitle>
|
|
3343
|
+
<facts>
|
|
3344
|
+
<fact>Specific factual detail 1</fact>
|
|
3345
|
+
<fact>Specific factual detail 2</fact>
|
|
3346
|
+
</facts>
|
|
3347
|
+
<narrative>2-3 sentence summary of what happened and why it matters</narrative>
|
|
3348
|
+
<concepts>
|
|
3349
|
+
<concept>technical concept or pattern</concept>
|
|
3350
|
+
</concepts>
|
|
3351
|
+
<files>
|
|
3352
|
+
<file>path/to/file</file>
|
|
3353
|
+
</files>
|
|
3354
|
+
<importance>1-10 scale, 10 being critical architectural decision</importance>
|
|
3355
|
+
</observation>
|
|
3356
|
+
|
|
3357
|
+
Rules:
|
|
3358
|
+
- Be concise but preserve ALL technically relevant details
|
|
3359
|
+
- File paths must be exact
|
|
3360
|
+
- Importance: 1-3 for routine reads, 4-6 for edits/commands, 7-9 for architectural decisions, 10 for breaking changes
|
|
3361
|
+
- Concepts should be reusable search terms (e.g., "React hooks", "SQL migration", "auth middleware")
|
|
3362
|
+
- Strip any secrets, tokens, or credentials from the output`;
|
|
3363
|
+
function buildCompressionPrompt(observation) {
|
|
3364
|
+
const parts = [`Timestamp: ${observation.timestamp}`, `Hook: ${observation.hookType}`];
|
|
3365
|
+
if (observation.toolName) parts.push(`Tool: ${observation.toolName}`);
|
|
3366
|
+
if (observation.toolInput) {
|
|
3367
|
+
const input = typeof observation.toolInput === "string" ? observation.toolInput : JSON.stringify(observation.toolInput, null, 2);
|
|
3368
|
+
parts.push(`Input:\n${truncate$1(input, 4e3)}`);
|
|
3369
|
+
}
|
|
3370
|
+
if (observation.toolOutput) {
|
|
3371
|
+
const output = typeof observation.toolOutput === "string" ? observation.toolOutput : JSON.stringify(observation.toolOutput, null, 2);
|
|
3372
|
+
parts.push(`Output:\n${truncate$1(output, 4e3)}`);
|
|
3373
|
+
}
|
|
3374
|
+
if (observation.userPrompt) parts.push(`User prompt:\n${truncate$1(observation.userPrompt, 2e3)}`);
|
|
3375
|
+
return parts.join("\n\n");
|
|
3376
|
+
}
|
|
3377
|
+
function truncate$1(s, max) {
|
|
3378
|
+
return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s;
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
//#endregion
|
|
3382
|
+
//#region src/prompts/vision.ts
|
|
3383
|
+
const VISION_DESCRIPTION_PROMPT = `Describe what this image shows in the context of software development. Extract:
|
|
3384
|
+
- What type of image this is (screenshot, diagram, mockup, terminal output, error, etc.)
|
|
3385
|
+
- Key entities visible (files, components, UI elements, error messages)
|
|
3386
|
+
- Relationships or flow shown
|
|
3387
|
+
- Any decisions, errors, or state visible
|
|
3388
|
+
- Text content visible in the image
|
|
3389
|
+
|
|
3390
|
+
Be concise but preserve all technically relevant details. Output plain text, no XML.`;
|
|
3391
|
+
|
|
3392
|
+
//#endregion
|
|
3393
|
+
//#region src/prompts/xml.ts
|
|
3394
|
+
const VALID_TAG = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
|
|
3395
|
+
function getXmlTag(xml, tag) {
|
|
3396
|
+
if (!VALID_TAG.test(tag)) return "";
|
|
3397
|
+
const match = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
|
|
3398
|
+
return match ? match[1].trim() : "";
|
|
3399
|
+
}
|
|
3400
|
+
function getXmlChildren(xml, parentTag, childTag) {
|
|
3401
|
+
if (!VALID_TAG.test(parentTag) || !VALID_TAG.test(childTag)) return [];
|
|
3402
|
+
const parentMatch = xml.match(new RegExp(`<${parentTag}>([\\s\\S]*?)</${parentTag}>`));
|
|
2370
3403
|
if (!parentMatch) return [];
|
|
2371
3404
|
const items = [];
|
|
2372
3405
|
const re = new RegExp(`<${childTag}>([\\s\\S]*?)</${childTag}>`, "g");
|
|
@@ -2589,6 +3622,7 @@ const VALID_TYPES$1 = new Set([
|
|
|
2589
3622
|
"subagent",
|
|
2590
3623
|
"notification",
|
|
2591
3624
|
"task",
|
|
3625
|
+
"image",
|
|
2592
3626
|
"other"
|
|
2593
3627
|
]);
|
|
2594
3628
|
function parseCompressionXml(xml) {
|
|
@@ -2609,11 +3643,32 @@ function parseCompressionXml(xml) {
|
|
|
2609
3643
|
function registerCompressFunction(sdk, kv, provider, metricsStore) {
|
|
2610
3644
|
sdk.registerFunction("mem::compress", async (data) => {
|
|
2611
3645
|
const startMs = Date.now();
|
|
3646
|
+
let imageDescription;
|
|
3647
|
+
const hasImage = data.raw.modality === "image" || data.raw.modality === "mixed";
|
|
3648
|
+
if (hasImage && data.raw.imageData && provider.describeImage) try {
|
|
3649
|
+
let base64Data = data.raw.imageData;
|
|
3650
|
+
let mimeType = "image/png";
|
|
3651
|
+
if (!data.raw.imageData.startsWith("/9j/") && !data.raw.imageData.startsWith("iVBOR")) {
|
|
3652
|
+
if (!isManagedImagePath(data.raw.imageData)) throw new Error(`Refusing to read image outside managed store: ${data.raw.imageData}`);
|
|
3653
|
+
base64Data = readFileSync(data.raw.imageData).toString("base64");
|
|
3654
|
+
if (data.raw.imageData.endsWith(".jpg") || data.raw.imageData.endsWith(".jpeg")) mimeType = "image/jpeg";
|
|
3655
|
+
else if (data.raw.imageData.endsWith(".webp")) mimeType = "image/webp";
|
|
3656
|
+
else if (data.raw.imageData.endsWith(".gif")) mimeType = "image/gif";
|
|
3657
|
+
}
|
|
3658
|
+
imageDescription = await provider.describeImage(base64Data, mimeType, VISION_DESCRIPTION_PROMPT);
|
|
3659
|
+
logger.info("Image described by vision model", { obsId: data.observationId });
|
|
3660
|
+
} catch (err) {
|
|
3661
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3662
|
+
logger.warn("Vision model call failed, falling back to text-only compression", {
|
|
3663
|
+
obsId: data.observationId,
|
|
3664
|
+
error: msg
|
|
3665
|
+
});
|
|
3666
|
+
}
|
|
2612
3667
|
const prompt = buildCompressionPrompt({
|
|
2613
3668
|
hookType: data.raw.hookType,
|
|
2614
3669
|
toolName: data.raw.toolName,
|
|
2615
3670
|
toolInput: data.raw.toolInput,
|
|
2616
|
-
toolOutput: data.raw.toolOutput,
|
|
3671
|
+
toolOutput: imageDescription ? `[Image Description]: ${imageDescription}\n\n${data.raw.toolOutput ?? ""}` : data.raw.toolOutput,
|
|
2617
3672
|
userPrompt: data.raw.userPrompt,
|
|
2618
3673
|
timestamp: data.raw.timestamp
|
|
2619
3674
|
});
|
|
@@ -2650,7 +3705,10 @@ function registerCompressFunction(sdk, kv, provider, metricsStore) {
|
|
|
2650
3705
|
sessionId: data.sessionId,
|
|
2651
3706
|
timestamp: data.raw.timestamp,
|
|
2652
3707
|
...parsed,
|
|
2653
|
-
confidence: qualityScore / 100
|
|
3708
|
+
confidence: qualityScore / 100,
|
|
3709
|
+
...hasImage ? { modality: data.raw.modality } : {},
|
|
3710
|
+
...imageDescription ? { imageDescription } : {},
|
|
3711
|
+
...data.raw.imageData ? { imageRef: data.raw.imageData } : {}
|
|
2654
3712
|
};
|
|
2655
3713
|
await kv.set(KV.observations(data.sessionId), data.observationId, compressed);
|
|
2656
3714
|
getSearchIndex().add(compressed);
|
|
@@ -2844,52 +3902,6 @@ function buildSummaryPrompt(observations) {
|
|
|
2844
3902
|
return `Session observations (${observations.length} total):\n\n${lines.join("\n\n---\n\n")}`;
|
|
2845
3903
|
}
|
|
2846
3904
|
|
|
2847
|
-
//#endregion
|
|
2848
|
-
//#region src/functions/audit.ts
|
|
2849
|
-
async function recordAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
|
|
2850
|
-
const entry = {
|
|
2851
|
-
id: generateId("aud"),
|
|
2852
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2853
|
-
operation,
|
|
2854
|
-
userId,
|
|
2855
|
-
functionId,
|
|
2856
|
-
targetIds,
|
|
2857
|
-
details,
|
|
2858
|
-
qualityScore
|
|
2859
|
-
};
|
|
2860
|
-
await kv.set(KV.audit, entry.id, entry);
|
|
2861
|
-
return entry;
|
|
2862
|
-
}
|
|
2863
|
-
async function safeAudit(kv, operation, functionId, targetIds, details = {}, qualityScore, userId) {
|
|
2864
|
-
try {
|
|
2865
|
-
await recordAudit(kv, operation, functionId, targetIds, details, qualityScore, userId);
|
|
2866
|
-
} catch (err) {
|
|
2867
|
-
try {
|
|
2868
|
-
logger.warn("audit write failed", {
|
|
2869
|
-
functionId,
|
|
2870
|
-
operation,
|
|
2871
|
-
targetIds,
|
|
2872
|
-
error: err instanceof Error ? err.message : String(err)
|
|
2873
|
-
});
|
|
2874
|
-
} catch {}
|
|
2875
|
-
}
|
|
2876
|
-
}
|
|
2877
|
-
async function queryAudit(kv, filter) {
|
|
2878
|
-
let entries = [...await kv.list(KV.audit)].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
2879
|
-
if (filter?.operation) entries = entries.filter((e) => e.operation === filter.operation);
|
|
2880
|
-
if (filter?.dateFrom) {
|
|
2881
|
-
const from = new Date(filter.dateFrom).getTime();
|
|
2882
|
-
if (Number.isNaN(from)) throw new Error(`Invalid dateFrom: ${filter.dateFrom}`);
|
|
2883
|
-
entries = entries.filter((e) => new Date(e.timestamp).getTime() >= from);
|
|
2884
|
-
}
|
|
2885
|
-
if (filter?.dateTo) {
|
|
2886
|
-
const to = new Date(filter.dateTo).getTime();
|
|
2887
|
-
if (Number.isNaN(to)) throw new Error(`Invalid dateTo: ${filter.dateTo}`);
|
|
2888
|
-
entries = entries.filter((e) => new Date(e.timestamp).getTime() <= to);
|
|
2889
|
-
}
|
|
2890
|
-
return entries.slice(0, filter?.limit || 100);
|
|
2891
|
-
}
|
|
2892
|
-
|
|
2893
3905
|
//#endregion
|
|
2894
3906
|
//#region src/functions/summarize.ts
|
|
2895
3907
|
function parseSummaryXml(xml, sessionId, project, obsCount) {
|
|
@@ -2931,9 +3943,33 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
|
|
|
2931
3943
|
error: "no_observations"
|
|
2932
3944
|
};
|
|
2933
3945
|
}
|
|
3946
|
+
if (provider.name === "noop") {
|
|
3947
|
+
logger.info("Summarize skipped — no LLM provider configured", { sessionId });
|
|
3948
|
+
return {
|
|
3949
|
+
success: false,
|
|
3950
|
+
error: "no_provider",
|
|
3951
|
+
reason: "No LLM provider key set; Summarize is a no-op. Set ANTHROPIC_API_KEY (or GEMINI/OPENROUTER/MINIMAX) in ~/.agentmemory/.env to enable."
|
|
3952
|
+
};
|
|
3953
|
+
}
|
|
2934
3954
|
try {
|
|
2935
3955
|
const prompt = buildSummaryPrompt(compressed);
|
|
2936
|
-
const
|
|
3956
|
+
const response = await provider.summarize(SUMMARY_SYSTEM, prompt);
|
|
3957
|
+
if (!response || !response.trim()) {
|
|
3958
|
+
const latencyMs = Date.now() - startMs;
|
|
3959
|
+
if (metricsStore) await metricsStore.record("mem::summarize", latencyMs, false);
|
|
3960
|
+
logger.warn("Empty provider response on summarize", {
|
|
3961
|
+
sessionId,
|
|
3962
|
+
provider: provider.name,
|
|
3963
|
+
promptBytes: prompt.length,
|
|
3964
|
+
systemBytes: SUMMARY_SYSTEM.length,
|
|
3965
|
+
observationCount: compressed.length
|
|
3966
|
+
});
|
|
3967
|
+
return {
|
|
3968
|
+
success: false,
|
|
3969
|
+
error: "empty_provider_response"
|
|
3970
|
+
};
|
|
3971
|
+
}
|
|
3972
|
+
const summary = parseSummaryXml(response, sessionId, session.project, compressed.length);
|
|
2937
3973
|
if (!summary) {
|
|
2938
3974
|
const latencyMs = Date.now() - startMs;
|
|
2939
3975
|
if (metricsStore) await metricsStore.record("mem::summarize", latencyMs, false);
|
|
@@ -3520,14 +4556,20 @@ function registerRememberFunction(sdk, kv) {
|
|
|
3520
4556
|
const deletedMemoryIds = [];
|
|
3521
4557
|
const deletedObservationIds = [];
|
|
3522
4558
|
let deletedSession = false;
|
|
4559
|
+
const { decrementImageRef } = await import("./image-refs-CESf9ndJ.mjs");
|
|
3523
4560
|
if (data.memoryId) {
|
|
4561
|
+
const mem = await kv.get(KV.memories, data.memoryId);
|
|
3524
4562
|
await kv.delete(KV.memories, data.memoryId);
|
|
4563
|
+
if (mem?.imageRef) await decrementImageRef(kv, sdk, mem.imageRef);
|
|
3525
4564
|
await deleteAccessLog(kv, data.memoryId);
|
|
3526
4565
|
deletedMemoryIds.push(data.memoryId);
|
|
3527
4566
|
deleted++;
|
|
3528
4567
|
}
|
|
3529
4568
|
if (data.sessionId && data.observationIds && data.observationIds.length > 0) for (const obsId of data.observationIds) {
|
|
4569
|
+
const obs = await kv.get(KV.observations(data.sessionId), obsId);
|
|
3530
4570
|
await kv.delete(KV.observations(data.sessionId), obsId);
|
|
4571
|
+
if (obs?.imageData) await decrementImageRef(kv, sdk, obs.imageData);
|
|
4572
|
+
if (obs?.imageRef && obs.imageRef !== obs.imageData) await decrementImageRef(kv, sdk, obs.imageRef);
|
|
3531
4573
|
deletedObservationIds.push(obsId);
|
|
3532
4574
|
deleted++;
|
|
3533
4575
|
}
|
|
@@ -3535,6 +4577,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
3535
4577
|
const observations = await kv.list(KV.observations(data.sessionId));
|
|
3536
4578
|
for (const obs of observations) {
|
|
3537
4579
|
await kv.delete(KV.observations(data.sessionId), obs.id);
|
|
4580
|
+
if (obs.imageData) await decrementImageRef(kv, sdk, obs.imageData);
|
|
4581
|
+
if (obs.imageRef && obs.imageRef !== obs.imageData) await decrementImageRef(kv, sdk, obs.imageRef);
|
|
3538
4582
|
deletedObservationIds.push(obs.id);
|
|
3539
4583
|
deleted++;
|
|
3540
4584
|
}
|
|
@@ -3571,6 +4615,7 @@ const DEFAULTS$1 = {
|
|
|
3571
4615
|
function registerEvictFunction(sdk, kv) {
|
|
3572
4616
|
sdk.registerFunction("mem::evict", async (data) => {
|
|
3573
4617
|
const dryRun = data?.dryRun ?? false;
|
|
4618
|
+
const { decrementImageRef } = await import("./image-refs-CESf9ndJ.mjs");
|
|
3574
4619
|
const configOverride = await kv.get(KV.config, "eviction").catch(() => null);
|
|
3575
4620
|
const cfg = {
|
|
3576
4621
|
...DEFAULTS$1,
|
|
@@ -3629,6 +4674,8 @@ function registerEvictFunction(sdk, kv) {
|
|
|
3629
4674
|
});
|
|
3630
4675
|
continue;
|
|
3631
4676
|
}
|
|
4677
|
+
if (o.imageData) await decrementImageRef(kv, sdk, o.imageData);
|
|
4678
|
+
if (o.imageRef && o.imageRef !== o.imageData) await decrementImageRef(kv, sdk, o.imageRef);
|
|
3632
4679
|
await recordAudit(kv, "delete", "mem::evict", [o.id], {
|
|
3633
4680
|
resource: "observation",
|
|
3634
4681
|
reason: "low_importance_old_observation",
|
|
@@ -3658,6 +4705,8 @@ function registerEvictFunction(sdk, kv) {
|
|
|
3658
4705
|
});
|
|
3659
4706
|
continue;
|
|
3660
4707
|
}
|
|
4708
|
+
if (o.imageData) await decrementImageRef(kv, sdk, o.imageData);
|
|
4709
|
+
if (o.imageRef && o.imageRef !== o.imageData) await decrementImageRef(kv, sdk, o.imageRef);
|
|
3661
4710
|
await recordAudit(kv, "delete", "mem::evict", [o.id], {
|
|
3662
4711
|
resource: "observation",
|
|
3663
4712
|
reason: "project_observation_cap",
|
|
@@ -3687,6 +4736,7 @@ function registerEvictFunction(sdk, kv) {
|
|
|
3687
4736
|
});
|
|
3688
4737
|
continue;
|
|
3689
4738
|
}
|
|
4739
|
+
if (mem.imageRef) await decrementImageRef(kv, sdk, mem.imageRef);
|
|
3690
4740
|
await recordAudit(kv, "delete", "mem::evict", [mem.id], {
|
|
3691
4741
|
resource: "memory",
|
|
3692
4742
|
reason: "expired_memory",
|
|
@@ -3710,6 +4760,7 @@ function registerEvictFunction(sdk, kv) {
|
|
|
3710
4760
|
});
|
|
3711
4761
|
continue;
|
|
3712
4762
|
}
|
|
4763
|
+
if (mem.imageRef) await decrementImageRef(kv, sdk, mem.imageRef);
|
|
3713
4764
|
await recordAudit(kv, "delete", "mem::evict", [mem.id], {
|
|
3714
4765
|
resource: "memory",
|
|
3715
4766
|
reason: "old_non_latest_memory",
|
|
@@ -4167,6 +5218,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
4167
5218
|
sdk.registerFunction("mem::auto-forget", async (data) => {
|
|
4168
5219
|
const dryRun = data?.dryRun ?? false;
|
|
4169
5220
|
const now = Date.now();
|
|
5221
|
+
const { decrementImageRef } = await import("./image-refs-CESf9ndJ.mjs");
|
|
4170
5222
|
const result = {
|
|
4171
5223
|
ttlExpired: [],
|
|
4172
5224
|
contradictions: [],
|
|
@@ -4180,6 +5232,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
4180
5232
|
result.ttlExpired.push(mem.id);
|
|
4181
5233
|
deletedIds.add(mem.id);
|
|
4182
5234
|
if (!dryRun) {
|
|
5235
|
+
if (mem.imageRef) await decrementImageRef(kv, sdk, mem.imageRef);
|
|
4183
5236
|
await kv.delete(KV.memories, mem.id);
|
|
4184
5237
|
await recordAudit(kv, "delete", "mem::auto-forget", [mem.id], {
|
|
4185
5238
|
resource: "memory",
|
|
@@ -4248,13 +5301,23 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
4248
5301
|
if (now - new Date(obs.timestamp).getTime() > 180 * MS_PER_DAY && (obs.importance ?? 5) <= 2) {
|
|
4249
5302
|
result.lowValueObs.push(obs.id);
|
|
4250
5303
|
if (!dryRun) {
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
}
|
|
5304
|
+
let deletedOk = false;
|
|
5305
|
+
try {
|
|
5306
|
+
await kv.delete(KV.observations(sessions[i].id), obs.id);
|
|
5307
|
+
deletedOk = true;
|
|
5308
|
+
} catch {
|
|
5309
|
+
deletedOk = false;
|
|
5310
|
+
}
|
|
5311
|
+
if (deletedOk) {
|
|
5312
|
+
if (obs.imageData) await decrementImageRef(kv, sdk, obs.imageData);
|
|
5313
|
+
if (obs.imageRef && obs.imageRef !== obs.imageData) await decrementImageRef(kv, sdk, obs.imageRef);
|
|
5314
|
+
await recordAudit(kv, "delete", "mem::auto-forget", [obs.id], {
|
|
5315
|
+
resource: "observation",
|
|
5316
|
+
reason: "auto-forget low-value observation",
|
|
5317
|
+
sessionId: sessions[i].id,
|
|
5318
|
+
timestamp: obs.timestamp
|
|
5319
|
+
});
|
|
5320
|
+
}
|
|
4258
5321
|
}
|
|
4259
5322
|
}
|
|
4260
5323
|
}
|
|
@@ -4385,7 +5448,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
4385
5448
|
"0.8.12",
|
|
4386
5449
|
"0.8.13",
|
|
4387
5450
|
"0.9.0",
|
|
4388
|
-
"0.9.1"
|
|
5451
|
+
"0.9.1",
|
|
5452
|
+
"0.9.2",
|
|
5453
|
+
"0.9.3"
|
|
4389
5454
|
]).has(importData.version)) return {
|
|
4390
5455
|
success: false,
|
|
4391
5456
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -10742,6 +11807,7 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10742
11807
|
const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
|
|
10743
11808
|
const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
|
|
10744
11809
|
const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
|
|
11810
|
+
const { decrementImageRef } = await import("./image-refs-CESf9ndJ.mjs");
|
|
10745
11811
|
const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
|
|
10746
11812
|
if (data?.dryRun) return {
|
|
10747
11813
|
success: true,
|
|
@@ -10773,6 +11839,8 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10773
11839
|
resolvedSource = "semantic";
|
|
10774
11840
|
}
|
|
10775
11841
|
if (!scope || !resolvedSource) continue;
|
|
11842
|
+
const mem = await kv.get(scope, candidate.memoryId);
|
|
11843
|
+
if (mem && mem.imageRef) await decrementImageRef(kv, sdk, mem.imageRef);
|
|
10776
11844
|
await kv.delete(scope, candidate.memoryId);
|
|
10777
11845
|
await kv.delete(KV.retentionScores, candidate.memoryId);
|
|
10778
11846
|
await deleteAccessLog(kv, candidate.memoryId);
|
|
@@ -11210,6 +12278,92 @@ function rawFromCompressed(obs) {
|
|
|
11210
12278
|
}
|
|
11211
12279
|
};
|
|
11212
12280
|
}
|
|
12281
|
+
const LESSON_PATTERNS = [/\b(always|never|don'?t|do not|make sure|remember to|note:|caveat:|warning:)\b[^.\n]{10,200}[.!\n]/gi, /\b(prefer|avoid)\s[^.\n]{10,200}[.!\n]/gi];
|
|
12282
|
+
async function deriveCrystalAndLessons(kv, sessionId, project, rawObs, compressed, firstPrompt) {
|
|
12283
|
+
if (rawObs.length === 0) return;
|
|
12284
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12285
|
+
const files = /* @__PURE__ */ new Set();
|
|
12286
|
+
const tools = /* @__PURE__ */ new Set();
|
|
12287
|
+
for (const c of compressed) {
|
|
12288
|
+
for (const f of c.files || []) files.add(f);
|
|
12289
|
+
if (c.type && c.type !== "conversation" && c.title) tools.add(c.title);
|
|
12290
|
+
}
|
|
12291
|
+
const assistantTexts = [];
|
|
12292
|
+
const userPrompts = [];
|
|
12293
|
+
for (const r of rawObs) {
|
|
12294
|
+
if (typeof r.assistantResponse === "string" && r.assistantResponse.trim()) assistantTexts.push(r.assistantResponse);
|
|
12295
|
+
if (typeof r.userPrompt === "string" && r.userPrompt.trim()) userPrompts.push(r.userPrompt);
|
|
12296
|
+
}
|
|
12297
|
+
const lessonMatches = /* @__PURE__ */ new Map();
|
|
12298
|
+
for (const text of assistantTexts.concat(userPrompts).slice(0, 200)) for (const pat of LESSON_PATTERNS) {
|
|
12299
|
+
pat.lastIndex = 0;
|
|
12300
|
+
let m;
|
|
12301
|
+
while ((m = pat.exec(text)) !== null && lessonMatches.size < 40) {
|
|
12302
|
+
const snippet = m[0].replace(/\s+/g, " ").trim();
|
|
12303
|
+
if (snippet.length >= 20 && snippet.length <= 220) {
|
|
12304
|
+
const key = snippet.toLowerCase();
|
|
12305
|
+
if (!lessonMatches.has(key)) lessonMatches.set(key, snippet);
|
|
12306
|
+
}
|
|
12307
|
+
}
|
|
12308
|
+
}
|
|
12309
|
+
const lessonEntries = Array.from(lessonMatches.values()).slice(0, 20);
|
|
12310
|
+
const lessonIds = [];
|
|
12311
|
+
for (const content of lessonEntries) {
|
|
12312
|
+
const lessonId = fingerprintId("lesson", content.trim().toLowerCase());
|
|
12313
|
+
try {
|
|
12314
|
+
const existing = await kv.get(KV.lessons, lessonId);
|
|
12315
|
+
if (existing) {
|
|
12316
|
+
const existingSources = existing.sourceIds || [];
|
|
12317
|
+
const mergedSources = existingSources.includes(sessionId) ? existingSources : [...existingSources, sessionId];
|
|
12318
|
+
const existingTags = existing.tags || [];
|
|
12319
|
+
const mergedTags = existingTags.includes("auto-import") ? existingTags : [...existingTags, "auto-import"];
|
|
12320
|
+
const merged = {
|
|
12321
|
+
...existing,
|
|
12322
|
+
sourceIds: mergedSources,
|
|
12323
|
+
tags: mergedTags,
|
|
12324
|
+
reinforcements: (existing.reinforcements || 0) + 1,
|
|
12325
|
+
updatedAt: createdAt,
|
|
12326
|
+
lastReinforcedAt: createdAt
|
|
12327
|
+
};
|
|
12328
|
+
await kv.set(KV.lessons, lessonId, merged);
|
|
12329
|
+
} else {
|
|
12330
|
+
const lesson = {
|
|
12331
|
+
id: lessonId,
|
|
12332
|
+
content,
|
|
12333
|
+
context: firstPrompt || project,
|
|
12334
|
+
confidence: .4,
|
|
12335
|
+
reinforcements: 0,
|
|
12336
|
+
source: "consolidation",
|
|
12337
|
+
sourceIds: [sessionId],
|
|
12338
|
+
project,
|
|
12339
|
+
tags: ["auto-import"],
|
|
12340
|
+
createdAt,
|
|
12341
|
+
updatedAt: createdAt,
|
|
12342
|
+
decayRate: .05
|
|
12343
|
+
};
|
|
12344
|
+
await kv.set(KV.lessons, lessonId, lesson);
|
|
12345
|
+
}
|
|
12346
|
+
lessonIds.push(lessonId);
|
|
12347
|
+
} catch {}
|
|
12348
|
+
}
|
|
12349
|
+
const crystalId = fingerprintId("crystal", sessionId);
|
|
12350
|
+
const narrativePreview = firstPrompt ? firstPrompt.slice(0, 300) : compressed.slice(0, 5).map((c) => c.narrative || c.title).filter(Boolean).join(" · ").slice(0, 300);
|
|
12351
|
+
try {
|
|
12352
|
+
const existingCrystal = await kv.get(KV.crystals, crystalId);
|
|
12353
|
+
const crystal = {
|
|
12354
|
+
id: crystalId,
|
|
12355
|
+
narrative: narrativePreview || `Session ${sessionId.slice(0, 12)} (${rawObs.length} observations)`,
|
|
12356
|
+
keyOutcomes: Array.from(tools).slice(0, 8),
|
|
12357
|
+
filesAffected: Array.from(files).slice(0, 20),
|
|
12358
|
+
lessons: lessonIds,
|
|
12359
|
+
sourceActionIds: existingCrystal?.sourceActionIds ?? [],
|
|
12360
|
+
sessionId,
|
|
12361
|
+
project,
|
|
12362
|
+
createdAt: existingCrystal?.createdAt ?? createdAt
|
|
12363
|
+
};
|
|
12364
|
+
await kv.set(KV.crystals, crystalId, crystal);
|
|
12365
|
+
} catch {}
|
|
12366
|
+
}
|
|
11213
12367
|
function isRawShape(o) {
|
|
11214
12368
|
if (!o || typeof o !== "object") return false;
|
|
11215
12369
|
return typeof o.hookType === "string";
|
|
@@ -11320,6 +12474,8 @@ function registerReplayFunctions(sdk, kv) {
|
|
|
11320
12474
|
}
|
|
11321
12475
|
const parsed = parseJsonlText(text, generateId("sess"));
|
|
11322
12476
|
if (parsed.observations.length === 0) continue;
|
|
12477
|
+
const firstPromptObs = parsed.observations.find((o) => typeof o.userPrompt === "string" && o.userPrompt.trim().length > 0);
|
|
12478
|
+
const firstPrompt = firstPromptObs?.userPrompt ? firstPromptObs.userPrompt.replace(/\s+/g, " ").trim().slice(0, 200) : void 0;
|
|
11323
12479
|
const existing = await kv.get(KV.sessions, parsed.sessionId);
|
|
11324
12480
|
if (existing) {
|
|
11325
12481
|
existing.observationCount = (existing.observationCount || 0) + parsed.observations.length;
|
|
@@ -11327,6 +12483,7 @@ function registerReplayFunctions(sdk, kv) {
|
|
|
11327
12483
|
if (existing.status === "active") existing.status = "completed";
|
|
11328
12484
|
const existingTags = existing.tags || [];
|
|
11329
12485
|
if (!existingTags.includes("jsonl-import")) existing.tags = [...existingTags, "jsonl-import"];
|
|
12486
|
+
if (!existing.firstPrompt && firstPrompt) existing.firstPrompt = firstPrompt;
|
|
11330
12487
|
await kv.set(KV.sessions, existing.id, existing);
|
|
11331
12488
|
} else {
|
|
11332
12489
|
const session = {
|
|
@@ -11337,13 +12494,22 @@ function registerReplayFunctions(sdk, kv) {
|
|
|
11337
12494
|
endedAt: parsed.endedAt,
|
|
11338
12495
|
status: "completed",
|
|
11339
12496
|
observationCount: parsed.observations.length,
|
|
11340
|
-
tags: ["jsonl-import"]
|
|
12497
|
+
tags: ["jsonl-import"],
|
|
12498
|
+
firstPrompt
|
|
11341
12499
|
};
|
|
11342
12500
|
await kv.set(KV.sessions, session.id, session);
|
|
11343
12501
|
}
|
|
11344
|
-
|
|
12502
|
+
const searchIndex = getSearchIndex();
|
|
12503
|
+
const compressed = [];
|
|
12504
|
+
await Promise.all(parsed.observations.map(async (obs) => {
|
|
12505
|
+
const synthetic = buildSyntheticCompression(obs);
|
|
12506
|
+
compressed.push(synthetic);
|
|
12507
|
+
await kv.set(KV.observations(parsed.sessionId), obs.id, synthetic);
|
|
12508
|
+
searchIndex.add(synthetic);
|
|
12509
|
+
}));
|
|
11345
12510
|
observationCount += parsed.observations.length;
|
|
11346
12511
|
sessionIds.push(parsed.sessionId);
|
|
12512
|
+
await deriveCrystalAndLessons(kv, parsed.sessionId, parsed.project, parsed.observations, compressed, firstPrompt);
|
|
11347
12513
|
}
|
|
11348
12514
|
await safeAudit(kv, "import", "mem::replay::import-jsonl", sessionIds, {
|
|
11349
12515
|
source: "jsonl",
|
|
@@ -11377,6 +12543,7 @@ function evaluateHealth(snapshot, config = {}) {
|
|
|
11377
12543
|
...config
|
|
11378
12544
|
};
|
|
11379
12545
|
const alerts = [];
|
|
12546
|
+
const notes = [];
|
|
11380
12547
|
let critical = false;
|
|
11381
12548
|
let degraded = false;
|
|
11382
12549
|
if (snapshot.connectionState === "disconnected" || snapshot.connectionState === "failed") {
|
|
@@ -11410,10 +12577,11 @@ function evaluateHealth(snapshot, config = {}) {
|
|
|
11410
12577
|
} else if (memPercent > cfg.memoryWarnPercent && rssAboveFloor) {
|
|
11411
12578
|
alerts.push(`memory_warn_${Math.round(memPercent)}%_rss${memMb}mb`);
|
|
11412
12579
|
degraded = true;
|
|
11413
|
-
} else if (memPercent > cfg.memoryWarnPercent)
|
|
12580
|
+
} else if (memPercent > cfg.memoryWarnPercent) notes.push(`memory_heap_tight_${Math.round(memPercent)}%_rss${memMb}mb`);
|
|
11414
12581
|
return {
|
|
11415
12582
|
status: critical ? "critical" : degraded ? "degraded" : "healthy",
|
|
11416
|
-
alerts
|
|
12583
|
+
alerts,
|
|
12584
|
+
notes
|
|
11417
12585
|
};
|
|
11418
12586
|
}
|
|
11419
12587
|
|
|
@@ -11490,6 +12658,7 @@ function registerHealthMonitor(sdk, kv) {
|
|
|
11490
12658
|
const evaluated = evaluateHealth(snapshot);
|
|
11491
12659
|
snapshot.status = evaluated.status;
|
|
11492
12660
|
snapshot.alerts = evaluated.alerts;
|
|
12661
|
+
snapshot.notes = evaluated.notes;
|
|
11493
12662
|
await kv.set(KV.health, "latest", snapshot).catch(() => {});
|
|
11494
12663
|
return snapshot;
|
|
11495
12664
|
}
|
|
@@ -11579,6 +12748,28 @@ function requireConfiguredSecret(secret, feature) {
|
|
|
11579
12748
|
body: { error: `${feature} requires AGENTMEMORY_SECRET` }
|
|
11580
12749
|
};
|
|
11581
12750
|
}
|
|
12751
|
+
function flagDisabledResponse(opts) {
|
|
12752
|
+
return {
|
|
12753
|
+
status_code: 503,
|
|
12754
|
+
body: opts
|
|
12755
|
+
};
|
|
12756
|
+
}
|
|
12757
|
+
function graphDisabledResponse() {
|
|
12758
|
+
return flagDisabledResponse({
|
|
12759
|
+
error: "Knowledge graph not enabled",
|
|
12760
|
+
flag: "GRAPH_EXTRACTION_ENABLED",
|
|
12761
|
+
enableHow: "Set GRAPH_EXTRACTION_ENABLED=true and restart. Requires an LLM provider key.",
|
|
12762
|
+
docsHref: "https://github.com/rohitg00/agentmemory#knowledge-graph"
|
|
12763
|
+
});
|
|
12764
|
+
}
|
|
12765
|
+
function consolidationDisabledResponse() {
|
|
12766
|
+
return flagDisabledResponse({
|
|
12767
|
+
error: "Consolidation pipeline not enabled",
|
|
12768
|
+
flag: "CONSOLIDATION_ENABLED",
|
|
12769
|
+
enableHow: "Set CONSOLIDATION_ENABLED=true and restart. Requires an LLM provider key.",
|
|
12770
|
+
docsHref: "https://github.com/rohitg00/agentmemory#consolidation"
|
|
12771
|
+
});
|
|
12772
|
+
}
|
|
11582
12773
|
function asNonEmptyString$1(value) {
|
|
11583
12774
|
if (typeof value !== "string") return null;
|
|
11584
12775
|
const trimmed = value.trim();
|
|
@@ -11630,6 +12821,78 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
11630
12821
|
http_method: "GET"
|
|
11631
12822
|
}
|
|
11632
12823
|
});
|
|
12824
|
+
sdk.registerFunction("api::config-flags", async (req) => {
|
|
12825
|
+
const authErr = checkAuth(req, secret);
|
|
12826
|
+
if (authErr) return authErr;
|
|
12827
|
+
const env = process.env;
|
|
12828
|
+
return {
|
|
12829
|
+
status_code: 200,
|
|
12830
|
+
body: {
|
|
12831
|
+
version: VERSION,
|
|
12832
|
+
provider: env["ANTHROPIC_API_KEY"] || env["GEMINI_API_KEY"] || env["OPENROUTER_API_KEY"] || env["MINIMAX_API_KEY"] ? "llm" : "noop",
|
|
12833
|
+
embeddingProvider: detectEmbeddingProvider() ? "embeddings" : "none",
|
|
12834
|
+
flags: [
|
|
12835
|
+
{
|
|
12836
|
+
key: "GRAPH_EXTRACTION_ENABLED",
|
|
12837
|
+
label: "Knowledge graph extraction",
|
|
12838
|
+
enabled: isGraphExtractionEnabled(),
|
|
12839
|
+
default: false,
|
|
12840
|
+
affects: ["Graph", "Dashboard"],
|
|
12841
|
+
needsLlm: true,
|
|
12842
|
+
description: "Extracts entities and relations from observations into a knowledge graph.",
|
|
12843
|
+
enableHow: "Set GRAPH_EXTRACTION_ENABLED=true and provide an LLM key, then restart.",
|
|
12844
|
+
docsHref: "https://github.com/rohitg00/agentmemory#knowledge-graph"
|
|
12845
|
+
},
|
|
12846
|
+
{
|
|
12847
|
+
key: "CONSOLIDATION_ENABLED",
|
|
12848
|
+
label: "Memory consolidation",
|
|
12849
|
+
enabled: isConsolidationEnabled(),
|
|
12850
|
+
default: false,
|
|
12851
|
+
affects: [
|
|
12852
|
+
"Dashboard",
|
|
12853
|
+
"Memories",
|
|
12854
|
+
"Crystals"
|
|
12855
|
+
],
|
|
12856
|
+
needsLlm: true,
|
|
12857
|
+
description: "Periodically summarizes sessions into semantic facts + procedures.",
|
|
12858
|
+
enableHow: "Set CONSOLIDATION_ENABLED=true and provide an LLM key, then restart.",
|
|
12859
|
+
docsHref: "https://github.com/rohitg00/agentmemory#consolidation"
|
|
12860
|
+
},
|
|
12861
|
+
{
|
|
12862
|
+
key: "AGENTMEMORY_AUTO_COMPRESS",
|
|
12863
|
+
label: "LLM-powered observation compression",
|
|
12864
|
+
enabled: isAutoCompressEnabled(),
|
|
12865
|
+
default: false,
|
|
12866
|
+
affects: ["Memories", "Timeline"],
|
|
12867
|
+
needsLlm: true,
|
|
12868
|
+
description: "Every observation is compressed by the LLM for richer summaries (costs tokens). OFF uses zero-LLM synthetic compression.",
|
|
12869
|
+
enableHow: "Set AGENTMEMORY_AUTO_COMPRESS=true and provide an LLM key.",
|
|
12870
|
+
docsHref: "https://github.com/rohitg00/agentmemory/issues/138"
|
|
12871
|
+
},
|
|
12872
|
+
{
|
|
12873
|
+
key: "AGENTMEMORY_INJECT_CONTEXT",
|
|
12874
|
+
label: "In-conversation context injection",
|
|
12875
|
+
enabled: isContextInjectionEnabled(),
|
|
12876
|
+
default: false,
|
|
12877
|
+
affects: ["Hooks"],
|
|
12878
|
+
needsLlm: false,
|
|
12879
|
+
description: "Hooks write recalled context into Claude Code's conversation. OFF captures in the background without injecting.",
|
|
12880
|
+
enableHow: "Set AGENTMEMORY_INJECT_CONTEXT=true and restart.",
|
|
12881
|
+
docsHref: "https://github.com/rohitg00/agentmemory/issues/143"
|
|
12882
|
+
}
|
|
12883
|
+
]
|
|
12884
|
+
}
|
|
12885
|
+
};
|
|
12886
|
+
});
|
|
12887
|
+
sdk.registerTrigger({
|
|
12888
|
+
type: "http",
|
|
12889
|
+
function_id: "api::config-flags",
|
|
12890
|
+
config: {
|
|
12891
|
+
api_path: "/agentmemory/config/flags",
|
|
12892
|
+
http_method: "GET",
|
|
12893
|
+
middleware_function_ids: ["middleware::api-auth"]
|
|
12894
|
+
}
|
|
12895
|
+
});
|
|
11633
12896
|
sdk.registerFunction("api::health", async (req) => {
|
|
11634
12897
|
const health = await getLatestHealth(kv);
|
|
11635
12898
|
const functionMetrics = metricsStore ? await metricsStore.getAll() : [];
|
|
@@ -11833,9 +13096,14 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
11833
13096
|
sdk.registerFunction("api::replay::sessions", async (req) => {
|
|
11834
13097
|
const authErr = checkAuth(req, secret);
|
|
11835
13098
|
if (authErr) return authErr;
|
|
13099
|
+
const sessions = await kv.list(KV.sessions);
|
|
13100
|
+
sessions.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
|
|
11836
13101
|
return {
|
|
11837
13102
|
status_code: 200,
|
|
11838
|
-
body:
|
|
13103
|
+
body: {
|
|
13104
|
+
success: true,
|
|
13105
|
+
sessions
|
|
13106
|
+
}
|
|
11839
13107
|
};
|
|
11840
13108
|
});
|
|
11841
13109
|
sdk.registerTrigger({
|
|
@@ -12445,10 +13713,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
12445
13713
|
})
|
|
12446
13714
|
};
|
|
12447
13715
|
} catch {
|
|
12448
|
-
return
|
|
12449
|
-
status_code: 404,
|
|
12450
|
-
body: { error: "Knowledge graph not enabled" }
|
|
12451
|
-
};
|
|
13716
|
+
return graphDisabledResponse();
|
|
12452
13717
|
}
|
|
12453
13718
|
});
|
|
12454
13719
|
sdk.registerTrigger({
|
|
@@ -12471,10 +13736,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
12471
13736
|
})
|
|
12472
13737
|
};
|
|
12473
13738
|
} catch {
|
|
12474
|
-
return
|
|
12475
|
-
status_code: 404,
|
|
12476
|
-
body: { error: "Knowledge graph not enabled" }
|
|
12477
|
-
};
|
|
13739
|
+
return graphDisabledResponse();
|
|
12478
13740
|
}
|
|
12479
13741
|
});
|
|
12480
13742
|
sdk.registerTrigger({
|
|
@@ -12501,10 +13763,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
12501
13763
|
})
|
|
12502
13764
|
};
|
|
12503
13765
|
} catch {
|
|
12504
|
-
return
|
|
12505
|
-
status_code: 404,
|
|
12506
|
-
body: { error: "Knowledge graph not enabled" }
|
|
12507
|
-
};
|
|
13766
|
+
return graphDisabledResponse();
|
|
12508
13767
|
}
|
|
12509
13768
|
});
|
|
12510
13769
|
sdk.registerTrigger({
|
|
@@ -12527,10 +13786,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
12527
13786
|
})
|
|
12528
13787
|
};
|
|
12529
13788
|
} catch {
|
|
12530
|
-
return
|
|
12531
|
-
status_code: 404,
|
|
12532
|
-
body: { error: "Consolidation pipeline not enabled" }
|
|
12533
|
-
};
|
|
13789
|
+
return consolidationDisabledResponse();
|
|
12534
13790
|
}
|
|
12535
13791
|
});
|
|
12536
13792
|
sdk.registerTrigger({
|
|
@@ -12630,13 +13886,16 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
12630
13886
|
const parsedLimit = parseOptionalInt(req.query_params?.["limit"]);
|
|
12631
13887
|
return {
|
|
12632
13888
|
status_code: 200,
|
|
12633
|
-
body:
|
|
12634
|
-
|
|
12635
|
-
|
|
12636
|
-
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
13889
|
+
body: {
|
|
13890
|
+
entries: await sdk.trigger({
|
|
13891
|
+
function_id: "mem::audit-query",
|
|
13892
|
+
payload: {
|
|
13893
|
+
operation: req.query_params?.["operation"],
|
|
13894
|
+
limit: parsedLimit ?? 50
|
|
13895
|
+
}
|
|
13896
|
+
}),
|
|
13897
|
+
success: true
|
|
13898
|
+
}
|
|
12640
13899
|
};
|
|
12641
13900
|
});
|
|
12642
13901
|
sdk.registerTrigger({
|
|
@@ -12683,157 +13942,489 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
12683
13942
|
});
|
|
12684
13943
|
sdk.registerTrigger({
|
|
12685
13944
|
type: "http",
|
|
12686
|
-
function_id: "api::governance-bulk",
|
|
13945
|
+
function_id: "api::governance-bulk",
|
|
13946
|
+
config: {
|
|
13947
|
+
api_path: "/agentmemory/governance/bulk-delete",
|
|
13948
|
+
http_method: "POST"
|
|
13949
|
+
}
|
|
13950
|
+
});
|
|
13951
|
+
sdk.registerFunction("api::snapshots", async (req) => {
|
|
13952
|
+
const authErr = checkAuth(req, secret);
|
|
13953
|
+
if (authErr) return authErr;
|
|
13954
|
+
try {
|
|
13955
|
+
return {
|
|
13956
|
+
status_code: 200,
|
|
13957
|
+
body: await sdk.trigger({
|
|
13958
|
+
function_id: "mem::snapshot-list",
|
|
13959
|
+
payload: {}
|
|
13960
|
+
})
|
|
13961
|
+
};
|
|
13962
|
+
} catch {
|
|
13963
|
+
return {
|
|
13964
|
+
status_code: 404,
|
|
13965
|
+
body: { error: "Snapshots not enabled" }
|
|
13966
|
+
};
|
|
13967
|
+
}
|
|
13968
|
+
});
|
|
13969
|
+
sdk.registerTrigger({
|
|
13970
|
+
type: "http",
|
|
13971
|
+
function_id: "api::snapshots",
|
|
13972
|
+
config: {
|
|
13973
|
+
api_path: "/agentmemory/snapshots",
|
|
13974
|
+
http_method: "GET"
|
|
13975
|
+
}
|
|
13976
|
+
});
|
|
13977
|
+
sdk.registerFunction("api::snapshot-create", async (req) => {
|
|
13978
|
+
const authErr = checkAuth(req, secret);
|
|
13979
|
+
if (authErr) return authErr;
|
|
13980
|
+
try {
|
|
13981
|
+
return {
|
|
13982
|
+
status_code: 201,
|
|
13983
|
+
body: await sdk.trigger({
|
|
13984
|
+
function_id: "mem::snapshot-create",
|
|
13985
|
+
payload: req.body || {}
|
|
13986
|
+
})
|
|
13987
|
+
};
|
|
13988
|
+
} catch {
|
|
13989
|
+
return {
|
|
13990
|
+
status_code: 404,
|
|
13991
|
+
body: { error: "Snapshots not enabled" }
|
|
13992
|
+
};
|
|
13993
|
+
}
|
|
13994
|
+
});
|
|
13995
|
+
sdk.registerTrigger({
|
|
13996
|
+
type: "http",
|
|
13997
|
+
function_id: "api::snapshot-create",
|
|
13998
|
+
config: {
|
|
13999
|
+
api_path: "/agentmemory/snapshot/create",
|
|
14000
|
+
http_method: "POST"
|
|
14001
|
+
}
|
|
14002
|
+
});
|
|
14003
|
+
sdk.registerFunction("api::snapshot-restore", async (req) => {
|
|
14004
|
+
const authErr = checkAuth(req, secret);
|
|
14005
|
+
if (authErr) return authErr;
|
|
14006
|
+
if (!req.body?.commitHash) return {
|
|
14007
|
+
status_code: 400,
|
|
14008
|
+
body: { error: "commitHash is required" }
|
|
14009
|
+
};
|
|
14010
|
+
try {
|
|
14011
|
+
return {
|
|
14012
|
+
status_code: 200,
|
|
14013
|
+
body: await sdk.trigger({
|
|
14014
|
+
function_id: "mem::snapshot-restore",
|
|
14015
|
+
payload: req.body
|
|
14016
|
+
})
|
|
14017
|
+
};
|
|
14018
|
+
} catch {
|
|
14019
|
+
return {
|
|
14020
|
+
status_code: 404,
|
|
14021
|
+
body: { error: "Snapshots not enabled" }
|
|
14022
|
+
};
|
|
14023
|
+
}
|
|
14024
|
+
});
|
|
14025
|
+
sdk.registerTrigger({
|
|
14026
|
+
type: "http",
|
|
14027
|
+
function_id: "api::snapshot-restore",
|
|
14028
|
+
config: {
|
|
14029
|
+
api_path: "/agentmemory/snapshot/restore",
|
|
14030
|
+
http_method: "POST"
|
|
14031
|
+
}
|
|
14032
|
+
});
|
|
14033
|
+
sdk.registerFunction("api::memories", async (req) => {
|
|
14034
|
+
const authErr = checkAuth(req, secret);
|
|
14035
|
+
if (authErr) return authErr;
|
|
14036
|
+
const memories = await kv.list(KV.memories);
|
|
14037
|
+
return {
|
|
14038
|
+
status_code: 200,
|
|
14039
|
+
body: { memories: req.query_params?.["latest"] === "true" ? memories.filter((m) => m.isLatest) : memories }
|
|
14040
|
+
};
|
|
14041
|
+
});
|
|
14042
|
+
sdk.registerTrigger({
|
|
14043
|
+
type: "http",
|
|
14044
|
+
function_id: "api::memories",
|
|
14045
|
+
config: {
|
|
14046
|
+
api_path: "/agentmemory/memories",
|
|
14047
|
+
http_method: "GET"
|
|
14048
|
+
}
|
|
14049
|
+
});
|
|
14050
|
+
sdk.registerFunction("api::semantic-list", async (req) => {
|
|
14051
|
+
const authErr = checkAuth(req, secret);
|
|
14052
|
+
if (authErr) return authErr;
|
|
14053
|
+
return {
|
|
14054
|
+
status_code: 200,
|
|
14055
|
+
body: { semantic: await kv.list(KV.semantic) }
|
|
14056
|
+
};
|
|
14057
|
+
});
|
|
14058
|
+
sdk.registerTrigger({
|
|
14059
|
+
type: "http",
|
|
14060
|
+
function_id: "api::semantic-list",
|
|
14061
|
+
config: {
|
|
14062
|
+
api_path: "/agentmemory/semantic",
|
|
14063
|
+
http_method: "GET"
|
|
14064
|
+
}
|
|
14065
|
+
});
|
|
14066
|
+
sdk.registerFunction("api::procedural-list", async (req) => {
|
|
14067
|
+
const authErr = checkAuth(req, secret);
|
|
14068
|
+
if (authErr) return authErr;
|
|
14069
|
+
return {
|
|
14070
|
+
status_code: 200,
|
|
14071
|
+
body: { procedural: await kv.list(KV.procedural) }
|
|
14072
|
+
};
|
|
14073
|
+
});
|
|
14074
|
+
sdk.registerTrigger({
|
|
14075
|
+
type: "http",
|
|
14076
|
+
function_id: "api::procedural-list",
|
|
14077
|
+
config: {
|
|
14078
|
+
api_path: "/agentmemory/procedural",
|
|
14079
|
+
http_method: "GET"
|
|
14080
|
+
}
|
|
14081
|
+
});
|
|
14082
|
+
sdk.registerFunction("api::relations-list", async (req) => {
|
|
14083
|
+
const authErr = checkAuth(req, secret);
|
|
14084
|
+
if (authErr) return authErr;
|
|
14085
|
+
return {
|
|
14086
|
+
status_code: 200,
|
|
14087
|
+
body: { relations: await kv.list(KV.relations) }
|
|
14088
|
+
};
|
|
14089
|
+
});
|
|
14090
|
+
sdk.registerTrigger({
|
|
14091
|
+
type: "http",
|
|
14092
|
+
function_id: "api::relations-list",
|
|
14093
|
+
config: {
|
|
14094
|
+
api_path: "/agentmemory/relations",
|
|
14095
|
+
http_method: "GET"
|
|
14096
|
+
}
|
|
14097
|
+
});
|
|
14098
|
+
sdk.registerFunction("api::vision-search", async (req) => {
|
|
14099
|
+
const authErr = checkAuth(req, secret);
|
|
14100
|
+
if (authErr) return authErr;
|
|
14101
|
+
const body = req.body ?? {};
|
|
14102
|
+
const queryText = asNonEmptyString$1(body["queryText"]);
|
|
14103
|
+
const queryImageRef = asNonEmptyString$1(body["queryImageRef"]);
|
|
14104
|
+
const queryImageBase64 = asNonEmptyString$1(body["queryImageBase64"]);
|
|
14105
|
+
const sessionId = asNonEmptyString$1(body["sessionId"]);
|
|
14106
|
+
if (!queryText && !queryImageRef && !queryImageBase64) return {
|
|
14107
|
+
status_code: 400,
|
|
14108
|
+
body: { error: "queryText, queryImageRef, or queryImageBase64 required" }
|
|
14109
|
+
};
|
|
14110
|
+
const topKParsed = parseOptionalPositiveInt(body["topK"]);
|
|
14111
|
+
if (topKParsed === null) return {
|
|
14112
|
+
status_code: 400,
|
|
14113
|
+
body: { error: "topK must be a positive integer" }
|
|
14114
|
+
};
|
|
14115
|
+
const payload = {};
|
|
14116
|
+
if (queryText) payload["queryText"] = queryText;
|
|
14117
|
+
if (queryImageRef) payload["queryImageRef"] = queryImageRef;
|
|
14118
|
+
if (queryImageBase64) payload["queryImageBase64"] = queryImageBase64;
|
|
14119
|
+
if (sessionId) payload["sessionId"] = sessionId;
|
|
14120
|
+
if (topKParsed !== void 0) payload["topK"] = Math.min(50, topKParsed);
|
|
14121
|
+
const result = await sdk.trigger({
|
|
14122
|
+
function_id: "mem::vision-search",
|
|
14123
|
+
payload
|
|
14124
|
+
});
|
|
14125
|
+
const resp = result;
|
|
14126
|
+
if (resp?.success === false) return {
|
|
14127
|
+
status_code: resp.error?.includes("disabled") ? 503 : 400,
|
|
14128
|
+
body: resp
|
|
14129
|
+
};
|
|
14130
|
+
return {
|
|
14131
|
+
status_code: 200,
|
|
14132
|
+
body: result
|
|
14133
|
+
};
|
|
14134
|
+
});
|
|
14135
|
+
sdk.registerTrigger({
|
|
14136
|
+
type: "http",
|
|
14137
|
+
function_id: "api::vision-search",
|
|
14138
|
+
config: {
|
|
14139
|
+
api_path: "/agentmemory/vision-search",
|
|
14140
|
+
http_method: "POST"
|
|
14141
|
+
}
|
|
14142
|
+
});
|
|
14143
|
+
sdk.registerFunction("api::vision-embed", async (req) => {
|
|
14144
|
+
const authErr = checkAuth(req, secret);
|
|
14145
|
+
if (authErr) return authErr;
|
|
14146
|
+
const body = req.body ?? {};
|
|
14147
|
+
const imageRef = asNonEmptyString$1(body["imageRef"]);
|
|
14148
|
+
const sessionId = asNonEmptyString$1(body["sessionId"]);
|
|
14149
|
+
const observationId = asNonEmptyString$1(body["observationId"]);
|
|
14150
|
+
if (!imageRef) return {
|
|
14151
|
+
status_code: 400,
|
|
14152
|
+
body: { error: "imageRef is required" }
|
|
14153
|
+
};
|
|
14154
|
+
const payload = { imageRef };
|
|
14155
|
+
if (sessionId) payload["sessionId"] = sessionId;
|
|
14156
|
+
if (observationId) payload["observationId"] = observationId;
|
|
14157
|
+
const result = await sdk.trigger({
|
|
14158
|
+
function_id: "mem::vision-embed",
|
|
14159
|
+
payload
|
|
14160
|
+
});
|
|
14161
|
+
const resp = result;
|
|
14162
|
+
if (resp?.success === false) return {
|
|
14163
|
+
status_code: resp.error?.includes("disabled") ? 503 : 400,
|
|
14164
|
+
body: resp
|
|
14165
|
+
};
|
|
14166
|
+
return {
|
|
14167
|
+
status_code: 200,
|
|
14168
|
+
body: result
|
|
14169
|
+
};
|
|
14170
|
+
});
|
|
14171
|
+
sdk.registerTrigger({
|
|
14172
|
+
type: "http",
|
|
14173
|
+
function_id: "api::vision-embed",
|
|
12687
14174
|
config: {
|
|
12688
|
-
api_path: "/agentmemory/
|
|
14175
|
+
api_path: "/agentmemory/vision-embed",
|
|
12689
14176
|
http_method: "POST"
|
|
12690
14177
|
}
|
|
12691
14178
|
});
|
|
12692
|
-
sdk.registerFunction("api::
|
|
14179
|
+
sdk.registerFunction("api::slot-list", async (req) => {
|
|
12693
14180
|
const authErr = checkAuth(req, secret);
|
|
12694
14181
|
if (authErr) return authErr;
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
};
|
|
12703
|
-
} catch {
|
|
12704
|
-
return {
|
|
12705
|
-
status_code: 404,
|
|
12706
|
-
body: { error: "Snapshots not enabled" }
|
|
12707
|
-
};
|
|
12708
|
-
}
|
|
14182
|
+
return {
|
|
14183
|
+
status_code: 200,
|
|
14184
|
+
body: await sdk.trigger({
|
|
14185
|
+
function_id: "mem::slot-list",
|
|
14186
|
+
payload: {}
|
|
14187
|
+
})
|
|
14188
|
+
};
|
|
12709
14189
|
});
|
|
12710
14190
|
sdk.registerTrigger({
|
|
12711
14191
|
type: "http",
|
|
12712
|
-
function_id: "api::
|
|
14192
|
+
function_id: "api::slot-list",
|
|
12713
14193
|
config: {
|
|
12714
|
-
api_path: "/agentmemory/
|
|
14194
|
+
api_path: "/agentmemory/slots",
|
|
12715
14195
|
http_method: "GET"
|
|
12716
14196
|
}
|
|
12717
14197
|
});
|
|
12718
|
-
sdk.registerFunction("api::
|
|
14198
|
+
sdk.registerFunction("api::slot-get", async (req) => {
|
|
12719
14199
|
const authErr = checkAuth(req, secret);
|
|
12720
14200
|
if (authErr) return authErr;
|
|
12721
|
-
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
}
|
|
12729
|
-
}
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
|
|
12733
|
-
|
|
12734
|
-
}
|
|
14201
|
+
const label = asNonEmptyString$1(req.query_params?.["label"]);
|
|
14202
|
+
if (!label) return {
|
|
14203
|
+
status_code: 400,
|
|
14204
|
+
body: { error: "label query param required" }
|
|
14205
|
+
};
|
|
14206
|
+
const result = await sdk.trigger({
|
|
14207
|
+
function_id: "mem::slot-get",
|
|
14208
|
+
payload: { label }
|
|
14209
|
+
});
|
|
14210
|
+
const resp = result;
|
|
14211
|
+
if (resp?.success === false) return {
|
|
14212
|
+
status_code: resp.error?.includes("not found") ? 404 : 400,
|
|
14213
|
+
body: resp
|
|
14214
|
+
};
|
|
14215
|
+
return {
|
|
14216
|
+
status_code: 200,
|
|
14217
|
+
body: result
|
|
14218
|
+
};
|
|
12735
14219
|
});
|
|
12736
14220
|
sdk.registerTrigger({
|
|
12737
14221
|
type: "http",
|
|
12738
|
-
function_id: "api::
|
|
14222
|
+
function_id: "api::slot-get",
|
|
12739
14223
|
config: {
|
|
12740
|
-
api_path: "/agentmemory/
|
|
12741
|
-
http_method: "
|
|
14224
|
+
api_path: "/agentmemory/slot",
|
|
14225
|
+
http_method: "GET"
|
|
12742
14226
|
}
|
|
12743
14227
|
});
|
|
12744
|
-
sdk.registerFunction("api::
|
|
14228
|
+
sdk.registerFunction("api::slot-create", async (req) => {
|
|
12745
14229
|
const authErr = checkAuth(req, secret);
|
|
12746
14230
|
if (authErr) return authErr;
|
|
12747
|
-
|
|
14231
|
+
const body = req.body ?? {};
|
|
14232
|
+
const label = asNonEmptyString$1(body["label"]);
|
|
14233
|
+
if (!label) return {
|
|
12748
14234
|
status_code: 400,
|
|
12749
|
-
body: { error: "
|
|
14235
|
+
body: { error: "label required" }
|
|
14236
|
+
};
|
|
14237
|
+
if (body["content"] !== void 0 && typeof body["content"] !== "string") return {
|
|
14238
|
+
status_code: 400,
|
|
14239
|
+
body: { error: "content must be a string" }
|
|
14240
|
+
};
|
|
14241
|
+
if (body["description"] !== void 0 && typeof body["description"] !== "string") return {
|
|
14242
|
+
status_code: 400,
|
|
14243
|
+
body: { error: "description must be a string" }
|
|
14244
|
+
};
|
|
14245
|
+
if (body["pinned"] !== void 0 && typeof body["pinned"] !== "boolean") return {
|
|
14246
|
+
status_code: 400,
|
|
14247
|
+
body: { error: "pinned must be a boolean" }
|
|
14248
|
+
};
|
|
14249
|
+
if (body["scope"] !== void 0 && body["scope"] !== "project" && body["scope"] !== "global") return {
|
|
14250
|
+
status_code: 400,
|
|
14251
|
+
body: { error: "scope must be 'project' or 'global'" }
|
|
14252
|
+
};
|
|
14253
|
+
const sizeLimit = parseOptionalPositiveInt(body["sizeLimit"]);
|
|
14254
|
+
if (sizeLimit === null) return {
|
|
14255
|
+
status_code: 400,
|
|
14256
|
+
body: { error: "sizeLimit must be a positive integer" }
|
|
14257
|
+
};
|
|
14258
|
+
if (sizeLimit !== void 0 && sizeLimit > 2e4) return {
|
|
14259
|
+
status_code: 400,
|
|
14260
|
+
body: { error: "sizeLimit must be <= 20000" }
|
|
14261
|
+
};
|
|
14262
|
+
const payload = { label };
|
|
14263
|
+
if (typeof body["content"] === "string") payload["content"] = body["content"];
|
|
14264
|
+
if (typeof body["description"] === "string") payload["description"] = body["description"];
|
|
14265
|
+
if (sizeLimit !== void 0) payload["sizeLimit"] = sizeLimit;
|
|
14266
|
+
if (typeof body["pinned"] === "boolean") payload["pinned"] = body["pinned"];
|
|
14267
|
+
if (body["scope"] === "project" || body["scope"] === "global") payload["scope"] = body["scope"];
|
|
14268
|
+
const result = await sdk.trigger({
|
|
14269
|
+
function_id: "mem::slot-create",
|
|
14270
|
+
payload
|
|
14271
|
+
});
|
|
14272
|
+
const resp = result;
|
|
14273
|
+
if (resp?.success === false) return {
|
|
14274
|
+
status_code: resp.error?.includes("exists") ? 409 : 400,
|
|
14275
|
+
body: resp
|
|
14276
|
+
};
|
|
14277
|
+
return {
|
|
14278
|
+
status_code: 201,
|
|
14279
|
+
body: result
|
|
12750
14280
|
};
|
|
12751
|
-
try {
|
|
12752
|
-
return {
|
|
12753
|
-
status_code: 200,
|
|
12754
|
-
body: await sdk.trigger({
|
|
12755
|
-
function_id: "mem::snapshot-restore",
|
|
12756
|
-
payload: req.body
|
|
12757
|
-
})
|
|
12758
|
-
};
|
|
12759
|
-
} catch {
|
|
12760
|
-
return {
|
|
12761
|
-
status_code: 404,
|
|
12762
|
-
body: { error: "Snapshots not enabled" }
|
|
12763
|
-
};
|
|
12764
|
-
}
|
|
12765
14281
|
});
|
|
12766
14282
|
sdk.registerTrigger({
|
|
12767
14283
|
type: "http",
|
|
12768
|
-
function_id: "api::
|
|
14284
|
+
function_id: "api::slot-create",
|
|
12769
14285
|
config: {
|
|
12770
|
-
api_path: "/agentmemory/
|
|
14286
|
+
api_path: "/agentmemory/slot",
|
|
12771
14287
|
http_method: "POST"
|
|
12772
14288
|
}
|
|
12773
14289
|
});
|
|
12774
|
-
sdk.registerFunction("api::
|
|
14290
|
+
sdk.registerFunction("api::slot-append", async (req) => {
|
|
12775
14291
|
const authErr = checkAuth(req, secret);
|
|
12776
14292
|
if (authErr) return authErr;
|
|
12777
|
-
const
|
|
14293
|
+
const body = req.body ?? {};
|
|
14294
|
+
const label = asNonEmptyString$1(body["label"]);
|
|
14295
|
+
const text = typeof body["text"] === "string" ? body["text"] : null;
|
|
14296
|
+
if (!label || !text) return {
|
|
14297
|
+
status_code: 400,
|
|
14298
|
+
body: { error: "label and text required" }
|
|
14299
|
+
};
|
|
14300
|
+
const result = await sdk.trigger({
|
|
14301
|
+
function_id: "mem::slot-append",
|
|
14302
|
+
payload: {
|
|
14303
|
+
label,
|
|
14304
|
+
text
|
|
14305
|
+
}
|
|
14306
|
+
});
|
|
14307
|
+
const resp = result;
|
|
14308
|
+
if (resp?.success === false) {
|
|
14309
|
+
const notFound = resp.error?.includes("not found");
|
|
14310
|
+
const overLimit = resp.error?.includes("exceed");
|
|
14311
|
+
return {
|
|
14312
|
+
status_code: notFound ? 404 : overLimit ? 413 : 400,
|
|
14313
|
+
body: resp
|
|
14314
|
+
};
|
|
14315
|
+
}
|
|
12778
14316
|
return {
|
|
12779
14317
|
status_code: 200,
|
|
12780
|
-
body:
|
|
14318
|
+
body: result
|
|
12781
14319
|
};
|
|
12782
14320
|
});
|
|
12783
14321
|
sdk.registerTrigger({
|
|
12784
14322
|
type: "http",
|
|
12785
|
-
function_id: "api::
|
|
14323
|
+
function_id: "api::slot-append",
|
|
12786
14324
|
config: {
|
|
12787
|
-
api_path: "/agentmemory/
|
|
12788
|
-
http_method: "
|
|
14325
|
+
api_path: "/agentmemory/slot/append",
|
|
14326
|
+
http_method: "POST"
|
|
12789
14327
|
}
|
|
12790
14328
|
});
|
|
12791
|
-
sdk.registerFunction("api::
|
|
14329
|
+
sdk.registerFunction("api::slot-replace", async (req) => {
|
|
12792
14330
|
const authErr = checkAuth(req, secret);
|
|
12793
14331
|
if (authErr) return authErr;
|
|
14332
|
+
const body = req.body ?? {};
|
|
14333
|
+
const label = asNonEmptyString$1(body["label"]);
|
|
14334
|
+
const content = body["content"];
|
|
14335
|
+
if (!label || typeof content !== "string") return {
|
|
14336
|
+
status_code: 400,
|
|
14337
|
+
body: { error: "label and content (string) required" }
|
|
14338
|
+
};
|
|
14339
|
+
const result = await sdk.trigger({
|
|
14340
|
+
function_id: "mem::slot-replace",
|
|
14341
|
+
payload: {
|
|
14342
|
+
label,
|
|
14343
|
+
content
|
|
14344
|
+
}
|
|
14345
|
+
});
|
|
14346
|
+
const resp = result;
|
|
14347
|
+
if (resp?.success === false) {
|
|
14348
|
+
const notFound = resp.error?.includes("not found");
|
|
14349
|
+
const overLimit = resp.error?.includes("exceed");
|
|
14350
|
+
return {
|
|
14351
|
+
status_code: notFound ? 404 : overLimit ? 413 : 400,
|
|
14352
|
+
body: resp
|
|
14353
|
+
};
|
|
14354
|
+
}
|
|
12794
14355
|
return {
|
|
12795
14356
|
status_code: 200,
|
|
12796
|
-
body:
|
|
14357
|
+
body: result
|
|
12797
14358
|
};
|
|
12798
14359
|
});
|
|
12799
14360
|
sdk.registerTrigger({
|
|
12800
14361
|
type: "http",
|
|
12801
|
-
function_id: "api::
|
|
14362
|
+
function_id: "api::slot-replace",
|
|
12802
14363
|
config: {
|
|
12803
|
-
api_path: "/agentmemory/
|
|
12804
|
-
http_method: "
|
|
14364
|
+
api_path: "/agentmemory/slot/replace",
|
|
14365
|
+
http_method: "POST"
|
|
12805
14366
|
}
|
|
12806
14367
|
});
|
|
12807
|
-
sdk.registerFunction("api::
|
|
14368
|
+
sdk.registerFunction("api::slot-delete", async (req) => {
|
|
12808
14369
|
const authErr = checkAuth(req, secret);
|
|
12809
14370
|
if (authErr) return authErr;
|
|
14371
|
+
const label = asNonEmptyString$1(req.query_params?.["label"]);
|
|
14372
|
+
if (!label) return {
|
|
14373
|
+
status_code: 400,
|
|
14374
|
+
body: { error: "label query param required" }
|
|
14375
|
+
};
|
|
14376
|
+
const result = await sdk.trigger({
|
|
14377
|
+
function_id: "mem::slot-delete",
|
|
14378
|
+
payload: { label }
|
|
14379
|
+
});
|
|
14380
|
+
const resp = result;
|
|
14381
|
+
if (resp?.success === false) return {
|
|
14382
|
+
status_code: resp.error?.includes("not found") ? 404 : 400,
|
|
14383
|
+
body: resp
|
|
14384
|
+
};
|
|
12810
14385
|
return {
|
|
12811
14386
|
status_code: 200,
|
|
12812
|
-
body:
|
|
14387
|
+
body: result
|
|
12813
14388
|
};
|
|
12814
14389
|
});
|
|
12815
14390
|
sdk.registerTrigger({
|
|
12816
14391
|
type: "http",
|
|
12817
|
-
function_id: "api::
|
|
14392
|
+
function_id: "api::slot-delete",
|
|
12818
14393
|
config: {
|
|
12819
|
-
api_path: "/agentmemory/
|
|
12820
|
-
http_method: "
|
|
14394
|
+
api_path: "/agentmemory/slot",
|
|
14395
|
+
http_method: "DELETE"
|
|
12821
14396
|
}
|
|
12822
14397
|
});
|
|
12823
|
-
sdk.registerFunction("api::
|
|
14398
|
+
sdk.registerFunction("api::slot-reflect", async (req) => {
|
|
12824
14399
|
const authErr = checkAuth(req, secret);
|
|
12825
14400
|
if (authErr) return authErr;
|
|
14401
|
+
const body = req.body ?? {};
|
|
14402
|
+
const sessionId = asNonEmptyString$1(body["sessionId"]);
|
|
14403
|
+
if (!sessionId) return {
|
|
14404
|
+
status_code: 400,
|
|
14405
|
+
body: { error: "sessionId required" }
|
|
14406
|
+
};
|
|
14407
|
+
const maxObservations = parseOptionalPositiveInt(body["maxObservations"]);
|
|
14408
|
+
if (maxObservations === null) return {
|
|
14409
|
+
status_code: 400,
|
|
14410
|
+
body: { error: "maxObservations must be a positive integer" }
|
|
14411
|
+
};
|
|
14412
|
+
const payload = { sessionId };
|
|
14413
|
+
if (maxObservations !== void 0) payload["maxObservations"] = maxObservations;
|
|
12826
14414
|
return {
|
|
12827
14415
|
status_code: 200,
|
|
12828
|
-
body:
|
|
14416
|
+
body: await sdk.trigger({
|
|
14417
|
+
function_id: "mem::slot-reflect",
|
|
14418
|
+
payload
|
|
14419
|
+
})
|
|
12829
14420
|
};
|
|
12830
14421
|
});
|
|
12831
14422
|
sdk.registerTrigger({
|
|
12832
14423
|
type: "http",
|
|
12833
|
-
function_id: "api::
|
|
14424
|
+
function_id: "api::slot-reflect",
|
|
12834
14425
|
config: {
|
|
12835
|
-
api_path: "/agentmemory/
|
|
12836
|
-
http_method: "
|
|
14426
|
+
api_path: "/agentmemory/slot/reflect",
|
|
14427
|
+
http_method: "POST"
|
|
12837
14428
|
}
|
|
12838
14429
|
});
|
|
12839
14430
|
sdk.registerFunction("api::action-create", async (req) => {
|
|
@@ -13070,9 +14661,9 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
13070
14661
|
sdk.registerFunction("api::routine-create", async (req) => {
|
|
13071
14662
|
const authErr = checkAuth(req, secret);
|
|
13072
14663
|
if (authErr) return authErr;
|
|
13073
|
-
if (!req.body?.name) return {
|
|
14664
|
+
if (!req.body?.name || !req.body?.steps) return {
|
|
13074
14665
|
status_code: 400,
|
|
13075
|
-
body: { error: "name
|
|
14666
|
+
body: { error: "name and steps are required" }
|
|
13076
14667
|
};
|
|
13077
14668
|
return {
|
|
13078
14669
|
status_code: 201,
|
|
@@ -14321,10 +15912,21 @@ function registerEventTriggers(sdk, kv) {
|
|
|
14321
15912
|
function_id: "event::observation",
|
|
14322
15913
|
config: { topic: "agentmemory.observation" }
|
|
14323
15914
|
});
|
|
14324
|
-
sdk.registerFunction("event::session::stopped", async (data) =>
|
|
14325
|
-
|
|
14326
|
-
|
|
14327
|
-
|
|
15915
|
+
sdk.registerFunction("event::session::stopped", async (data) => {
|
|
15916
|
+
const summary = await sdk.trigger({
|
|
15917
|
+
function_id: "mem::summarize",
|
|
15918
|
+
payload: data
|
|
15919
|
+
});
|
|
15920
|
+
if (isReflectEnabled()) try {
|
|
15921
|
+
sdk.triggerVoid("mem::slot-reflect", { sessionId: data.sessionId });
|
|
15922
|
+
} catch (err) {
|
|
15923
|
+
logger.warn("slot-reflect triggerVoid failed", {
|
|
15924
|
+
sessionId: data.sessionId,
|
|
15925
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15926
|
+
});
|
|
15927
|
+
}
|
|
15928
|
+
return summary;
|
|
15929
|
+
});
|
|
14328
15930
|
sdk.registerTrigger({
|
|
14329
15931
|
type: "durable:subscriber",
|
|
14330
15932
|
function_id: "event::session::stopped",
|
|
@@ -14576,6 +16178,34 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
14576
16178
|
}] }
|
|
14577
16179
|
};
|
|
14578
16180
|
}
|
|
16181
|
+
case "memory_vision_search": {
|
|
16182
|
+
const queryText = typeof args.queryText === "string" ? args.queryText : void 0;
|
|
16183
|
+
const queryImageRef = typeof args.queryImageRef === "string" ? args.queryImageRef : void 0;
|
|
16184
|
+
const queryImageBase64 = typeof args.queryImageBase64 === "string" ? args.queryImageBase64 : void 0;
|
|
16185
|
+
if (!queryText && !queryImageRef && !queryImageBase64) return {
|
|
16186
|
+
status_code: 400,
|
|
16187
|
+
body: { error: "queryText, queryImageRef, or queryImageBase64 required" }
|
|
16188
|
+
};
|
|
16189
|
+
const topK = Math.max(1, Math.min(50, asNumber(args.topK, 10) ?? 10));
|
|
16190
|
+
const sessionId = typeof args.sessionId === "string" ? args.sessionId : void 0;
|
|
16191
|
+
const result = await sdk.trigger({
|
|
16192
|
+
function_id: "mem::vision-search",
|
|
16193
|
+
payload: {
|
|
16194
|
+
queryText,
|
|
16195
|
+
queryImageRef,
|
|
16196
|
+
queryImageBase64,
|
|
16197
|
+
topK,
|
|
16198
|
+
sessionId
|
|
16199
|
+
}
|
|
16200
|
+
});
|
|
16201
|
+
return {
|
|
16202
|
+
status_code: 200,
|
|
16203
|
+
body: { content: [{
|
|
16204
|
+
type: "text",
|
|
16205
|
+
text: JSON.stringify(result, null, 2)
|
|
16206
|
+
}] }
|
|
16207
|
+
};
|
|
16208
|
+
}
|
|
14579
16209
|
case "memory_timeline": {
|
|
14580
16210
|
if (typeof args.anchor !== "string" || !args.anchor.trim()) return {
|
|
14581
16211
|
status_code: 400,
|
|
@@ -15441,6 +17071,123 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
15441
17071
|
}] }
|
|
15442
17072
|
};
|
|
15443
17073
|
}
|
|
17074
|
+
case "memory_slot_list": {
|
|
17075
|
+
const result = await sdk.trigger({
|
|
17076
|
+
function_id: "mem::slot-list",
|
|
17077
|
+
payload: {}
|
|
17078
|
+
});
|
|
17079
|
+
return {
|
|
17080
|
+
status_code: 200,
|
|
17081
|
+
body: { content: [{
|
|
17082
|
+
type: "text",
|
|
17083
|
+
text: JSON.stringify(result, null, 2)
|
|
17084
|
+
}] }
|
|
17085
|
+
};
|
|
17086
|
+
}
|
|
17087
|
+
case "memory_slot_get": {
|
|
17088
|
+
const label = asNonEmptyString(args.label);
|
|
17089
|
+
if (!label) return {
|
|
17090
|
+
status_code: 400,
|
|
17091
|
+
body: { error: "label required" }
|
|
17092
|
+
};
|
|
17093
|
+
const result = await sdk.trigger({
|
|
17094
|
+
function_id: "mem::slot-get",
|
|
17095
|
+
payload: { label }
|
|
17096
|
+
});
|
|
17097
|
+
return {
|
|
17098
|
+
status_code: 200,
|
|
17099
|
+
body: { content: [{
|
|
17100
|
+
type: "text",
|
|
17101
|
+
text: JSON.stringify(result, null, 2)
|
|
17102
|
+
}] }
|
|
17103
|
+
};
|
|
17104
|
+
}
|
|
17105
|
+
case "memory_slot_create": {
|
|
17106
|
+
const label = asNonEmptyString(args.label);
|
|
17107
|
+
if (!label) return {
|
|
17108
|
+
status_code: 400,
|
|
17109
|
+
body: { error: "label required" }
|
|
17110
|
+
};
|
|
17111
|
+
const payload = { label };
|
|
17112
|
+
if (typeof args.content === "string") payload.content = args.content;
|
|
17113
|
+
if (typeof args.description === "string") payload.description = args.description;
|
|
17114
|
+
if (typeof args.sizeLimit === "number") payload.sizeLimit = args.sizeLimit;
|
|
17115
|
+
if (args.pinned === false || args.pinned === "false") payload.pinned = false;
|
|
17116
|
+
else if (args.pinned === true || args.pinned === "true") payload.pinned = true;
|
|
17117
|
+
if (args.scope === "global" || args.scope === "project") payload.scope = args.scope;
|
|
17118
|
+
const result = await sdk.trigger({
|
|
17119
|
+
function_id: "mem::slot-create",
|
|
17120
|
+
payload
|
|
17121
|
+
});
|
|
17122
|
+
return {
|
|
17123
|
+
status_code: 200,
|
|
17124
|
+
body: { content: [{
|
|
17125
|
+
type: "text",
|
|
17126
|
+
text: JSON.stringify(result, null, 2)
|
|
17127
|
+
}] }
|
|
17128
|
+
};
|
|
17129
|
+
}
|
|
17130
|
+
case "memory_slot_append": {
|
|
17131
|
+
const label = asNonEmptyString(args.label);
|
|
17132
|
+
const text = typeof args.text === "string" ? args.text : null;
|
|
17133
|
+
if (!label || !text) return {
|
|
17134
|
+
status_code: 400,
|
|
17135
|
+
body: { error: "label and text required" }
|
|
17136
|
+
};
|
|
17137
|
+
const result = await sdk.trigger({
|
|
17138
|
+
function_id: "mem::slot-append",
|
|
17139
|
+
payload: {
|
|
17140
|
+
label,
|
|
17141
|
+
text
|
|
17142
|
+
}
|
|
17143
|
+
});
|
|
17144
|
+
return {
|
|
17145
|
+
status_code: 200,
|
|
17146
|
+
body: { content: [{
|
|
17147
|
+
type: "text",
|
|
17148
|
+
text: JSON.stringify(result, null, 2)
|
|
17149
|
+
}] }
|
|
17150
|
+
};
|
|
17151
|
+
}
|
|
17152
|
+
case "memory_slot_replace": {
|
|
17153
|
+
const label = asNonEmptyString(args.label);
|
|
17154
|
+
if (!label || typeof args.content !== "string") return {
|
|
17155
|
+
status_code: 400,
|
|
17156
|
+
body: { error: "label and content (string) required" }
|
|
17157
|
+
};
|
|
17158
|
+
const result = await sdk.trigger({
|
|
17159
|
+
function_id: "mem::slot-replace",
|
|
17160
|
+
payload: {
|
|
17161
|
+
label,
|
|
17162
|
+
content: args.content
|
|
17163
|
+
}
|
|
17164
|
+
});
|
|
17165
|
+
return {
|
|
17166
|
+
status_code: 200,
|
|
17167
|
+
body: { content: [{
|
|
17168
|
+
type: "text",
|
|
17169
|
+
text: JSON.stringify(result, null, 2)
|
|
17170
|
+
}] }
|
|
17171
|
+
};
|
|
17172
|
+
}
|
|
17173
|
+
case "memory_slot_delete": {
|
|
17174
|
+
const label = asNonEmptyString(args.label);
|
|
17175
|
+
if (!label) return {
|
|
17176
|
+
status_code: 400,
|
|
17177
|
+
body: { error: "label required" }
|
|
17178
|
+
};
|
|
17179
|
+
const result = await sdk.trigger({
|
|
17180
|
+
function_id: "mem::slot-delete",
|
|
17181
|
+
payload: { label }
|
|
17182
|
+
});
|
|
17183
|
+
return {
|
|
17184
|
+
status_code: 200,
|
|
17185
|
+
body: { content: [{
|
|
17186
|
+
type: "text",
|
|
17187
|
+
text: JSON.stringify(result, null, 2)
|
|
17188
|
+
}] }
|
|
17189
|
+
};
|
|
17190
|
+
}
|
|
15444
17191
|
default: return {
|
|
15445
17192
|
status_code: 400,
|
|
15446
17193
|
body: { error: `Unknown tool: ${name}` }
|
|
@@ -16095,11 +17842,13 @@ async function main() {
|
|
|
16095
17842
|
const fallbackConfig = loadFallbackConfig();
|
|
16096
17843
|
const provider = fallbackConfig.providers.length > 0 ? createFallbackProvider(config.provider, fallbackConfig) : createProvider(config.provider);
|
|
16097
17844
|
const embeddingProvider = createEmbeddingProvider();
|
|
17845
|
+
const imageEmbeddingProvider = createImageEmbeddingProvider();
|
|
16098
17846
|
console.log(`[agentmemory] Starting worker v${VERSION}...`);
|
|
16099
17847
|
console.log(`[agentmemory] Engine: ${config.engineUrl}`);
|
|
16100
17848
|
console.log(`[agentmemory] Provider: ${config.provider.provider} (${config.provider.model})`);
|
|
16101
17849
|
if (embeddingProvider) console.log(`[agentmemory] Embedding provider: ${embeddingProvider.name} (${embeddingProvider.dimensions} dims)`);
|
|
16102
17850
|
else console.log(`[agentmemory] Embedding provider: none (BM25-only mode)`);
|
|
17851
|
+
if (imageEmbeddingProvider) console.log(`[agentmemory] Image embedding provider: ${imageEmbeddingProvider.name} (${imageEmbeddingProvider.dimensions} dims) — vision-search active`);
|
|
16103
17852
|
console.log(`[agentmemory] REST API: http://localhost:${config.restPort}/agentmemory/*`);
|
|
16104
17853
|
console.log(`[agentmemory] Streams: ws://localhost:${config.streamsPort}`);
|
|
16105
17854
|
const sdk = registerWorker(config.engineUrl, {
|
|
@@ -16118,6 +17867,10 @@ async function main() {
|
|
|
16118
17867
|
initMetrics(hasGetMeter(sdk) ? sdk.getMeter.bind(sdk) : void 0);
|
|
16119
17868
|
registerPrivacyFunction(sdk);
|
|
16120
17869
|
registerObserveFunction(sdk, kv, dedupMap, config.maxObservationsPerSession);
|
|
17870
|
+
registerImageQuotaCleanup(sdk, kv);
|
|
17871
|
+
registerVisionSearchFunctions(sdk, kv, imageEmbeddingProvider);
|
|
17872
|
+
if (isSlotsEnabled()) registerSlotsFunctions(sdk, kv);
|
|
17873
|
+
registerDiskSizeManager(sdk, kv);
|
|
16121
17874
|
registerCompressFunction(sdk, kv, provider, metricsStore);
|
|
16122
17875
|
registerSearchFunction(sdk, kv);
|
|
16123
17876
|
registerContextFunction(sdk, kv, config.tokenBudget);
|
|
@@ -16184,6 +17937,7 @@ async function main() {
|
|
|
16184
17937
|
registerReplayFunctions(sdk, kv);
|
|
16185
17938
|
console.log(`[agentmemory] v0.6 advanced retrieval: sliding-window, query-expansion, temporal-graph, retention-scoring`);
|
|
16186
17939
|
console.log(`[agentmemory] Orchestration layer: actions, frontier, leases, routines, signals, checkpoints, flow-compress, mesh, branch-aware, sentinels, sketches, crystallize, diagnostics, facets`);
|
|
17940
|
+
if (isSlotsEnabled()) console.log(`[agentmemory] Slots: enabled (pinned editable memory). Reflect on Stop hook: ${isReflectEnabled() ? "on" : "off"}`);
|
|
16187
17941
|
const snapshotConfig = loadSnapshotConfig();
|
|
16188
17942
|
if (snapshotConfig.enabled) {
|
|
16189
17943
|
registerSnapshotFunction(sdk, kv, snapshotConfig.dir);
|
|
@@ -16221,7 +17975,7 @@ async function main() {
|
|
|
16221
17975
|
}
|
|
16222
17976
|
}
|
|
16223
17977
|
console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
16224
|
-
console.log(`[agentmemory] Endpoints: 107 REST +
|
|
17978
|
+
console.log(`[agentmemory] Endpoints: 107 REST + ${getAllTools().length} MCP tools + 6 MCP resources + 3 MCP prompts`);
|
|
16225
17979
|
const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
|
|
16226
17980
|
const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
|
|
16227
17981
|
const consolidationIntervalMs = parseInt(process.env.CONSOLIDATION_INTERVAL_MS || "7200000", 10);
|
|
@@ -16287,5 +18041,5 @@ main().catch((err) => {
|
|
|
16287
18041
|
});
|
|
16288
18042
|
|
|
16289
18043
|
//#endregion
|
|
16290
|
-
export {
|
|
16291
|
-
//# sourceMappingURL=src-
|
|
18044
|
+
export { deleteImage as a, saveImageToDisk as c, IMAGES_DIR as i, touchImage as l, getImageRefCount as n, getMaxBytes as o, incrementImageRef as r, isManagedImagePath as s, decrementImageRef as t };
|
|
18045
|
+
//# sourceMappingURL=src-3Snd7D3T.mjs.map
|