@agentmemory/agentmemory 0.9.0 → 0.9.2
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 +42 -15
- package/dist/cli.mjs +60 -25
- 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-Dq5wcV-a.mjs +3 -0
- package/dist/image-store-BLOkD0xV.mjs +3 -0
- package/dist/index.mjs +2054 -144
- package/dist/index.mjs.map +1 -1
- package/dist/{src-B3pEsBSb.mjs → src-tmuZyobT.mjs} +1974 -253
- package/dist/src-tmuZyobT.mjs.map +1 -0
- package/dist/{standalone-DXc-BEqr.mjs → standalone-BiwX0rdC.mjs} +2 -2
- package/dist/{standalone-DXc-BEqr.mjs.map → standalone-BiwX0rdC.mjs.map} +1 -1
- package/dist/standalone.mjs +136 -2
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-DXIK5CxQ.mjs → tools-registry-CHH84gIQ.mjs} +166 -12
- package/dist/tools-registry-CHH84gIQ.mjs.map +1 -0
- package/dist/viewer/index.html +249 -62
- 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-B3pEsBSb.mjs.map +0 -1
- package/dist/tools-registry-DXIK5CxQ.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-CHH84gIQ.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-BLOkD0xV.mjs");
|
|
2511
|
+
const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
|
|
2512
|
+
raw.imageData = filePath;
|
|
2513
|
+
const { incrementImageRef } = await import("./image-refs-Dq5wcV-a.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-BLOkD0xV.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-Dq5wcV-a.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-Dq5wcV-a.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-Dq5wcV-a.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
|
}
|
|
@@ -4384,7 +5447,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
4384
5447
|
"0.8.11",
|
|
4385
5448
|
"0.8.12",
|
|
4386
5449
|
"0.8.13",
|
|
4387
|
-
"0.9.0"
|
|
5450
|
+
"0.9.0",
|
|
5451
|
+
"0.9.1",
|
|
5452
|
+
"0.9.2"
|
|
4388
5453
|
]).has(importData.version)) return {
|
|
4389
5454
|
success: false,
|
|
4390
5455
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -10741,6 +11806,7 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10741
11806
|
const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
|
|
10742
11807
|
const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
|
|
10743
11808
|
const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
|
|
11809
|
+
const { decrementImageRef } = await import("./image-refs-Dq5wcV-a.mjs");
|
|
10744
11810
|
const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
|
|
10745
11811
|
if (data?.dryRun) return {
|
|
10746
11812
|
success: true,
|
|
@@ -10772,6 +11838,8 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10772
11838
|
resolvedSource = "semantic";
|
|
10773
11839
|
}
|
|
10774
11840
|
if (!scope || !resolvedSource) continue;
|
|
11841
|
+
const mem = await kv.get(scope, candidate.memoryId);
|
|
11842
|
+
if (mem && mem.imageRef) await decrementImageRef(kv, sdk, mem.imageRef);
|
|
10775
11843
|
await kv.delete(scope, candidate.memoryId);
|
|
10776
11844
|
await kv.delete(KV.retentionScores, candidate.memoryId);
|
|
10777
11845
|
await deleteAccessLog(kv, candidate.memoryId);
|
|
@@ -11209,6 +12277,92 @@ function rawFromCompressed(obs) {
|
|
|
11209
12277
|
}
|
|
11210
12278
|
};
|
|
11211
12279
|
}
|
|
12280
|
+
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];
|
|
12281
|
+
async function deriveCrystalAndLessons(kv, sessionId, project, rawObs, compressed, firstPrompt) {
|
|
12282
|
+
if (rawObs.length === 0) return;
|
|
12283
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12284
|
+
const files = /* @__PURE__ */ new Set();
|
|
12285
|
+
const tools = /* @__PURE__ */ new Set();
|
|
12286
|
+
for (const c of compressed) {
|
|
12287
|
+
for (const f of c.files || []) files.add(f);
|
|
12288
|
+
if (c.type && c.type !== "conversation" && c.title) tools.add(c.title);
|
|
12289
|
+
}
|
|
12290
|
+
const assistantTexts = [];
|
|
12291
|
+
const userPrompts = [];
|
|
12292
|
+
for (const r of rawObs) {
|
|
12293
|
+
if (typeof r.assistantResponse === "string" && r.assistantResponse.trim()) assistantTexts.push(r.assistantResponse);
|
|
12294
|
+
if (typeof r.userPrompt === "string" && r.userPrompt.trim()) userPrompts.push(r.userPrompt);
|
|
12295
|
+
}
|
|
12296
|
+
const lessonMatches = /* @__PURE__ */ new Map();
|
|
12297
|
+
for (const text of assistantTexts.concat(userPrompts).slice(0, 200)) for (const pat of LESSON_PATTERNS) {
|
|
12298
|
+
pat.lastIndex = 0;
|
|
12299
|
+
let m;
|
|
12300
|
+
while ((m = pat.exec(text)) !== null && lessonMatches.size < 40) {
|
|
12301
|
+
const snippet = m[0].replace(/\s+/g, " ").trim();
|
|
12302
|
+
if (snippet.length >= 20 && snippet.length <= 220) {
|
|
12303
|
+
const key = snippet.toLowerCase();
|
|
12304
|
+
if (!lessonMatches.has(key)) lessonMatches.set(key, snippet);
|
|
12305
|
+
}
|
|
12306
|
+
}
|
|
12307
|
+
}
|
|
12308
|
+
const lessonEntries = Array.from(lessonMatches.values()).slice(0, 20);
|
|
12309
|
+
const lessonIds = [];
|
|
12310
|
+
for (const content of lessonEntries) {
|
|
12311
|
+
const lessonId = fingerprintId("lesson", content.trim().toLowerCase());
|
|
12312
|
+
try {
|
|
12313
|
+
const existing = await kv.get(KV.lessons, lessonId);
|
|
12314
|
+
if (existing) {
|
|
12315
|
+
const existingSources = existing.sourceIds || [];
|
|
12316
|
+
const mergedSources = existingSources.includes(sessionId) ? existingSources : [...existingSources, sessionId];
|
|
12317
|
+
const existingTags = existing.tags || [];
|
|
12318
|
+
const mergedTags = existingTags.includes("auto-import") ? existingTags : [...existingTags, "auto-import"];
|
|
12319
|
+
const merged = {
|
|
12320
|
+
...existing,
|
|
12321
|
+
sourceIds: mergedSources,
|
|
12322
|
+
tags: mergedTags,
|
|
12323
|
+
reinforcements: (existing.reinforcements || 0) + 1,
|
|
12324
|
+
updatedAt: createdAt,
|
|
12325
|
+
lastReinforcedAt: createdAt
|
|
12326
|
+
};
|
|
12327
|
+
await kv.set(KV.lessons, lessonId, merged);
|
|
12328
|
+
} else {
|
|
12329
|
+
const lesson = {
|
|
12330
|
+
id: lessonId,
|
|
12331
|
+
content,
|
|
12332
|
+
context: firstPrompt || project,
|
|
12333
|
+
confidence: .4,
|
|
12334
|
+
reinforcements: 0,
|
|
12335
|
+
source: "consolidation",
|
|
12336
|
+
sourceIds: [sessionId],
|
|
12337
|
+
project,
|
|
12338
|
+
tags: ["auto-import"],
|
|
12339
|
+
createdAt,
|
|
12340
|
+
updatedAt: createdAt,
|
|
12341
|
+
decayRate: .05
|
|
12342
|
+
};
|
|
12343
|
+
await kv.set(KV.lessons, lessonId, lesson);
|
|
12344
|
+
}
|
|
12345
|
+
lessonIds.push(lessonId);
|
|
12346
|
+
} catch {}
|
|
12347
|
+
}
|
|
12348
|
+
const crystalId = fingerprintId("crystal", sessionId);
|
|
12349
|
+
const narrativePreview = firstPrompt ? firstPrompt.slice(0, 300) : compressed.slice(0, 5).map((c) => c.narrative || c.title).filter(Boolean).join(" · ").slice(0, 300);
|
|
12350
|
+
try {
|
|
12351
|
+
const existingCrystal = await kv.get(KV.crystals, crystalId);
|
|
12352
|
+
const crystal = {
|
|
12353
|
+
id: crystalId,
|
|
12354
|
+
narrative: narrativePreview || `Session ${sessionId.slice(0, 12)} (${rawObs.length} observations)`,
|
|
12355
|
+
keyOutcomes: Array.from(tools).slice(0, 8),
|
|
12356
|
+
filesAffected: Array.from(files).slice(0, 20),
|
|
12357
|
+
lessons: lessonIds,
|
|
12358
|
+
sourceActionIds: existingCrystal?.sourceActionIds ?? [],
|
|
12359
|
+
sessionId,
|
|
12360
|
+
project,
|
|
12361
|
+
createdAt: existingCrystal?.createdAt ?? createdAt
|
|
12362
|
+
};
|
|
12363
|
+
await kv.set(KV.crystals, crystalId, crystal);
|
|
12364
|
+
} catch {}
|
|
12365
|
+
}
|
|
11212
12366
|
function isRawShape(o) {
|
|
11213
12367
|
if (!o || typeof o !== "object") return false;
|
|
11214
12368
|
return typeof o.hookType === "string";
|
|
@@ -11319,6 +12473,8 @@ function registerReplayFunctions(sdk, kv) {
|
|
|
11319
12473
|
}
|
|
11320
12474
|
const parsed = parseJsonlText(text, generateId("sess"));
|
|
11321
12475
|
if (parsed.observations.length === 0) continue;
|
|
12476
|
+
const firstPromptObs = parsed.observations.find((o) => typeof o.userPrompt === "string" && o.userPrompt.trim().length > 0);
|
|
12477
|
+
const firstPrompt = firstPromptObs?.userPrompt ? firstPromptObs.userPrompt.replace(/\s+/g, " ").trim().slice(0, 200) : void 0;
|
|
11322
12478
|
const existing = await kv.get(KV.sessions, parsed.sessionId);
|
|
11323
12479
|
if (existing) {
|
|
11324
12480
|
existing.observationCount = (existing.observationCount || 0) + parsed.observations.length;
|
|
@@ -11326,6 +12482,7 @@ function registerReplayFunctions(sdk, kv) {
|
|
|
11326
12482
|
if (existing.status === "active") existing.status = "completed";
|
|
11327
12483
|
const existingTags = existing.tags || [];
|
|
11328
12484
|
if (!existingTags.includes("jsonl-import")) existing.tags = [...existingTags, "jsonl-import"];
|
|
12485
|
+
if (!existing.firstPrompt && firstPrompt) existing.firstPrompt = firstPrompt;
|
|
11329
12486
|
await kv.set(KV.sessions, existing.id, existing);
|
|
11330
12487
|
} else {
|
|
11331
12488
|
const session = {
|
|
@@ -11336,13 +12493,22 @@ function registerReplayFunctions(sdk, kv) {
|
|
|
11336
12493
|
endedAt: parsed.endedAt,
|
|
11337
12494
|
status: "completed",
|
|
11338
12495
|
observationCount: parsed.observations.length,
|
|
11339
|
-
tags: ["jsonl-import"]
|
|
12496
|
+
tags: ["jsonl-import"],
|
|
12497
|
+
firstPrompt
|
|
11340
12498
|
};
|
|
11341
12499
|
await kv.set(KV.sessions, session.id, session);
|
|
11342
12500
|
}
|
|
11343
|
-
|
|
12501
|
+
const searchIndex = getSearchIndex();
|
|
12502
|
+
const compressed = [];
|
|
12503
|
+
await Promise.all(parsed.observations.map(async (obs) => {
|
|
12504
|
+
const synthetic = buildSyntheticCompression(obs);
|
|
12505
|
+
compressed.push(synthetic);
|
|
12506
|
+
await kv.set(KV.observations(parsed.sessionId), obs.id, synthetic);
|
|
12507
|
+
searchIndex.add(synthetic);
|
|
12508
|
+
}));
|
|
11344
12509
|
observationCount += parsed.observations.length;
|
|
11345
12510
|
sessionIds.push(parsed.sessionId);
|
|
12511
|
+
await deriveCrystalAndLessons(kv, parsed.sessionId, parsed.project, parsed.observations, compressed, firstPrompt);
|
|
11346
12512
|
}
|
|
11347
12513
|
await safeAudit(kv, "import", "mem::replay::import-jsonl", sessionIds, {
|
|
11348
12514
|
source: "jsonl",
|
|
@@ -11376,6 +12542,7 @@ function evaluateHealth(snapshot, config = {}) {
|
|
|
11376
12542
|
...config
|
|
11377
12543
|
};
|
|
11378
12544
|
const alerts = [];
|
|
12545
|
+
const notes = [];
|
|
11379
12546
|
let critical = false;
|
|
11380
12547
|
let degraded = false;
|
|
11381
12548
|
if (snapshot.connectionState === "disconnected" || snapshot.connectionState === "failed") {
|
|
@@ -11409,10 +12576,11 @@ function evaluateHealth(snapshot, config = {}) {
|
|
|
11409
12576
|
} else if (memPercent > cfg.memoryWarnPercent && rssAboveFloor) {
|
|
11410
12577
|
alerts.push(`memory_warn_${Math.round(memPercent)}%_rss${memMb}mb`);
|
|
11411
12578
|
degraded = true;
|
|
11412
|
-
} else if (memPercent > cfg.memoryWarnPercent)
|
|
12579
|
+
} else if (memPercent > cfg.memoryWarnPercent) notes.push(`memory_heap_tight_${Math.round(memPercent)}%_rss${memMb}mb`);
|
|
11413
12580
|
return {
|
|
11414
12581
|
status: critical ? "critical" : degraded ? "degraded" : "healthy",
|
|
11415
|
-
alerts
|
|
12582
|
+
alerts,
|
|
12583
|
+
notes
|
|
11416
12584
|
};
|
|
11417
12585
|
}
|
|
11418
12586
|
|
|
@@ -11489,6 +12657,7 @@ function registerHealthMonitor(sdk, kv) {
|
|
|
11489
12657
|
const evaluated = evaluateHealth(snapshot);
|
|
11490
12658
|
snapshot.status = evaluated.status;
|
|
11491
12659
|
snapshot.alerts = evaluated.alerts;
|
|
12660
|
+
snapshot.notes = evaluated.notes;
|
|
11492
12661
|
await kv.set(KV.health, "latest", snapshot).catch(() => {});
|
|
11493
12662
|
return snapshot;
|
|
11494
12663
|
}
|
|
@@ -11531,6 +12700,7 @@ function buildViewerCsp(nonce) {
|
|
|
11531
12700
|
|
|
11532
12701
|
//#endregion
|
|
11533
12702
|
//#region src/viewer/document.ts
|
|
12703
|
+
const VIEWER_VERSION_PLACEHOLDER = "__AGENTMEMORY_VERSION__";
|
|
11534
12704
|
function loadViewerTemplate() {
|
|
11535
12705
|
const base = dirname(fileURLToPath(import.meta.url));
|
|
11536
12706
|
const candidates = [
|
|
@@ -11549,7 +12719,7 @@ function renderViewerDocument() {
|
|
|
11549
12719
|
const nonce = createViewerNonce();
|
|
11550
12720
|
return {
|
|
11551
12721
|
found: true,
|
|
11552
|
-
html: template.replaceAll(VIEWER_NONCE_PLACEHOLDER, nonce),
|
|
12722
|
+
html: template.replaceAll(VIEWER_NONCE_PLACEHOLDER, nonce).replaceAll(VIEWER_VERSION_PLACEHOLDER, VERSION),
|
|
11553
12723
|
csp: buildViewerCsp(nonce)
|
|
11554
12724
|
};
|
|
11555
12725
|
}
|
|
@@ -11831,9 +13001,14 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
11831
13001
|
sdk.registerFunction("api::replay::sessions", async (req) => {
|
|
11832
13002
|
const authErr = checkAuth(req, secret);
|
|
11833
13003
|
if (authErr) return authErr;
|
|
13004
|
+
const sessions = await kv.list(KV.sessions);
|
|
13005
|
+
sessions.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
|
|
11834
13006
|
return {
|
|
11835
13007
|
status_code: 200,
|
|
11836
|
-
body:
|
|
13008
|
+
body: {
|
|
13009
|
+
success: true,
|
|
13010
|
+
sessions
|
|
13011
|
+
}
|
|
11837
13012
|
};
|
|
11838
13013
|
});
|
|
11839
13014
|
sdk.registerTrigger({
|
|
@@ -12616,174 +13791,557 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
12616
13791
|
});
|
|
12617
13792
|
sdk.registerTrigger({
|
|
12618
13793
|
type: "http",
|
|
12619
|
-
function_id: "api::team-profile",
|
|
13794
|
+
function_id: "api::team-profile",
|
|
13795
|
+
config: {
|
|
13796
|
+
api_path: "/agentmemory/team/profile",
|
|
13797
|
+
http_method: "GET"
|
|
13798
|
+
}
|
|
13799
|
+
});
|
|
13800
|
+
sdk.registerFunction("api::audit", async (req) => {
|
|
13801
|
+
const authErr = checkAuth(req, secret);
|
|
13802
|
+
if (authErr) return authErr;
|
|
13803
|
+
const parsedLimit = parseOptionalInt(req.query_params?.["limit"]);
|
|
13804
|
+
return {
|
|
13805
|
+
status_code: 200,
|
|
13806
|
+
body: {
|
|
13807
|
+
entries: await sdk.trigger({
|
|
13808
|
+
function_id: "mem::audit-query",
|
|
13809
|
+
payload: {
|
|
13810
|
+
operation: req.query_params?.["operation"],
|
|
13811
|
+
limit: parsedLimit ?? 50
|
|
13812
|
+
}
|
|
13813
|
+
}),
|
|
13814
|
+
success: true
|
|
13815
|
+
}
|
|
13816
|
+
};
|
|
13817
|
+
});
|
|
13818
|
+
sdk.registerTrigger({
|
|
13819
|
+
type: "http",
|
|
13820
|
+
function_id: "api::audit",
|
|
13821
|
+
config: {
|
|
13822
|
+
api_path: "/agentmemory/audit",
|
|
13823
|
+
http_method: "GET"
|
|
13824
|
+
}
|
|
13825
|
+
});
|
|
13826
|
+
sdk.registerFunction("api::governance-delete", async (req) => {
|
|
13827
|
+
const authErr = checkAuth(req, secret);
|
|
13828
|
+
if (authErr) return authErr;
|
|
13829
|
+
if (!req.body?.memoryIds || !Array.isArray(req.body.memoryIds)) return {
|
|
13830
|
+
status_code: 400,
|
|
13831
|
+
body: { error: "memoryIds array is required" }
|
|
13832
|
+
};
|
|
13833
|
+
return {
|
|
13834
|
+
status_code: 200,
|
|
13835
|
+
body: await sdk.trigger({
|
|
13836
|
+
function_id: "mem::governance-delete",
|
|
13837
|
+
payload: req.body
|
|
13838
|
+
})
|
|
13839
|
+
};
|
|
13840
|
+
});
|
|
13841
|
+
sdk.registerTrigger({
|
|
13842
|
+
type: "http",
|
|
13843
|
+
function_id: "api::governance-delete",
|
|
13844
|
+
config: {
|
|
13845
|
+
api_path: "/agentmemory/governance/memories",
|
|
13846
|
+
http_method: "DELETE"
|
|
13847
|
+
}
|
|
13848
|
+
});
|
|
13849
|
+
sdk.registerFunction("api::governance-bulk", async (req) => {
|
|
13850
|
+
const authErr = checkAuth(req, secret);
|
|
13851
|
+
if (authErr) return authErr;
|
|
13852
|
+
return {
|
|
13853
|
+
status_code: 200,
|
|
13854
|
+
body: await sdk.trigger({
|
|
13855
|
+
function_id: "mem::governance-bulk",
|
|
13856
|
+
payload: req.body || {}
|
|
13857
|
+
})
|
|
13858
|
+
};
|
|
13859
|
+
});
|
|
13860
|
+
sdk.registerTrigger({
|
|
13861
|
+
type: "http",
|
|
13862
|
+
function_id: "api::governance-bulk",
|
|
13863
|
+
config: {
|
|
13864
|
+
api_path: "/agentmemory/governance/bulk-delete",
|
|
13865
|
+
http_method: "POST"
|
|
13866
|
+
}
|
|
13867
|
+
});
|
|
13868
|
+
sdk.registerFunction("api::snapshots", async (req) => {
|
|
13869
|
+
const authErr = checkAuth(req, secret);
|
|
13870
|
+
if (authErr) return authErr;
|
|
13871
|
+
try {
|
|
13872
|
+
return {
|
|
13873
|
+
status_code: 200,
|
|
13874
|
+
body: await sdk.trigger({
|
|
13875
|
+
function_id: "mem::snapshot-list",
|
|
13876
|
+
payload: {}
|
|
13877
|
+
})
|
|
13878
|
+
};
|
|
13879
|
+
} catch {
|
|
13880
|
+
return {
|
|
13881
|
+
status_code: 404,
|
|
13882
|
+
body: { error: "Snapshots not enabled" }
|
|
13883
|
+
};
|
|
13884
|
+
}
|
|
13885
|
+
});
|
|
13886
|
+
sdk.registerTrigger({
|
|
13887
|
+
type: "http",
|
|
13888
|
+
function_id: "api::snapshots",
|
|
13889
|
+
config: {
|
|
13890
|
+
api_path: "/agentmemory/snapshots",
|
|
13891
|
+
http_method: "GET"
|
|
13892
|
+
}
|
|
13893
|
+
});
|
|
13894
|
+
sdk.registerFunction("api::snapshot-create", async (req) => {
|
|
13895
|
+
const authErr = checkAuth(req, secret);
|
|
13896
|
+
if (authErr) return authErr;
|
|
13897
|
+
try {
|
|
13898
|
+
return {
|
|
13899
|
+
status_code: 201,
|
|
13900
|
+
body: await sdk.trigger({
|
|
13901
|
+
function_id: "mem::snapshot-create",
|
|
13902
|
+
payload: req.body || {}
|
|
13903
|
+
})
|
|
13904
|
+
};
|
|
13905
|
+
} catch {
|
|
13906
|
+
return {
|
|
13907
|
+
status_code: 404,
|
|
13908
|
+
body: { error: "Snapshots not enabled" }
|
|
13909
|
+
};
|
|
13910
|
+
}
|
|
13911
|
+
});
|
|
13912
|
+
sdk.registerTrigger({
|
|
13913
|
+
type: "http",
|
|
13914
|
+
function_id: "api::snapshot-create",
|
|
13915
|
+
config: {
|
|
13916
|
+
api_path: "/agentmemory/snapshot/create",
|
|
13917
|
+
http_method: "POST"
|
|
13918
|
+
}
|
|
13919
|
+
});
|
|
13920
|
+
sdk.registerFunction("api::snapshot-restore", async (req) => {
|
|
13921
|
+
const authErr = checkAuth(req, secret);
|
|
13922
|
+
if (authErr) return authErr;
|
|
13923
|
+
if (!req.body?.commitHash) return {
|
|
13924
|
+
status_code: 400,
|
|
13925
|
+
body: { error: "commitHash is required" }
|
|
13926
|
+
};
|
|
13927
|
+
try {
|
|
13928
|
+
return {
|
|
13929
|
+
status_code: 200,
|
|
13930
|
+
body: await sdk.trigger({
|
|
13931
|
+
function_id: "mem::snapshot-restore",
|
|
13932
|
+
payload: req.body
|
|
13933
|
+
})
|
|
13934
|
+
};
|
|
13935
|
+
} catch {
|
|
13936
|
+
return {
|
|
13937
|
+
status_code: 404,
|
|
13938
|
+
body: { error: "Snapshots not enabled" }
|
|
13939
|
+
};
|
|
13940
|
+
}
|
|
13941
|
+
});
|
|
13942
|
+
sdk.registerTrigger({
|
|
13943
|
+
type: "http",
|
|
13944
|
+
function_id: "api::snapshot-restore",
|
|
13945
|
+
config: {
|
|
13946
|
+
api_path: "/agentmemory/snapshot/restore",
|
|
13947
|
+
http_method: "POST"
|
|
13948
|
+
}
|
|
13949
|
+
});
|
|
13950
|
+
sdk.registerFunction("api::memories", async (req) => {
|
|
13951
|
+
const authErr = checkAuth(req, secret);
|
|
13952
|
+
if (authErr) return authErr;
|
|
13953
|
+
const memories = await kv.list(KV.memories);
|
|
13954
|
+
return {
|
|
13955
|
+
status_code: 200,
|
|
13956
|
+
body: { memories: req.query_params?.["latest"] === "true" ? memories.filter((m) => m.isLatest) : memories }
|
|
13957
|
+
};
|
|
13958
|
+
});
|
|
13959
|
+
sdk.registerTrigger({
|
|
13960
|
+
type: "http",
|
|
13961
|
+
function_id: "api::memories",
|
|
13962
|
+
config: {
|
|
13963
|
+
api_path: "/agentmemory/memories",
|
|
13964
|
+
http_method: "GET"
|
|
13965
|
+
}
|
|
13966
|
+
});
|
|
13967
|
+
sdk.registerFunction("api::semantic-list", async (req) => {
|
|
13968
|
+
const authErr = checkAuth(req, secret);
|
|
13969
|
+
if (authErr) return authErr;
|
|
13970
|
+
return {
|
|
13971
|
+
status_code: 200,
|
|
13972
|
+
body: { semantic: await kv.list(KV.semantic) }
|
|
13973
|
+
};
|
|
13974
|
+
});
|
|
13975
|
+
sdk.registerTrigger({
|
|
13976
|
+
type: "http",
|
|
13977
|
+
function_id: "api::semantic-list",
|
|
13978
|
+
config: {
|
|
13979
|
+
api_path: "/agentmemory/semantic",
|
|
13980
|
+
http_method: "GET"
|
|
13981
|
+
}
|
|
13982
|
+
});
|
|
13983
|
+
sdk.registerFunction("api::procedural-list", async (req) => {
|
|
13984
|
+
const authErr = checkAuth(req, secret);
|
|
13985
|
+
if (authErr) return authErr;
|
|
13986
|
+
return {
|
|
13987
|
+
status_code: 200,
|
|
13988
|
+
body: { procedural: await kv.list(KV.procedural) }
|
|
13989
|
+
};
|
|
13990
|
+
});
|
|
13991
|
+
sdk.registerTrigger({
|
|
13992
|
+
type: "http",
|
|
13993
|
+
function_id: "api::procedural-list",
|
|
13994
|
+
config: {
|
|
13995
|
+
api_path: "/agentmemory/procedural",
|
|
13996
|
+
http_method: "GET"
|
|
13997
|
+
}
|
|
13998
|
+
});
|
|
13999
|
+
sdk.registerFunction("api::relations-list", async (req) => {
|
|
14000
|
+
const authErr = checkAuth(req, secret);
|
|
14001
|
+
if (authErr) return authErr;
|
|
14002
|
+
return {
|
|
14003
|
+
status_code: 200,
|
|
14004
|
+
body: { relations: await kv.list(KV.relations) }
|
|
14005
|
+
};
|
|
14006
|
+
});
|
|
14007
|
+
sdk.registerTrigger({
|
|
14008
|
+
type: "http",
|
|
14009
|
+
function_id: "api::relations-list",
|
|
14010
|
+
config: {
|
|
14011
|
+
api_path: "/agentmemory/relations",
|
|
14012
|
+
http_method: "GET"
|
|
14013
|
+
}
|
|
14014
|
+
});
|
|
14015
|
+
sdk.registerFunction("api::vision-search", async (req) => {
|
|
14016
|
+
const authErr = checkAuth(req, secret);
|
|
14017
|
+
if (authErr) return authErr;
|
|
14018
|
+
const body = req.body ?? {};
|
|
14019
|
+
const queryText = asNonEmptyString$1(body["queryText"]);
|
|
14020
|
+
const queryImageRef = asNonEmptyString$1(body["queryImageRef"]);
|
|
14021
|
+
const queryImageBase64 = asNonEmptyString$1(body["queryImageBase64"]);
|
|
14022
|
+
const sessionId = asNonEmptyString$1(body["sessionId"]);
|
|
14023
|
+
if (!queryText && !queryImageRef && !queryImageBase64) return {
|
|
14024
|
+
status_code: 400,
|
|
14025
|
+
body: { error: "queryText, queryImageRef, or queryImageBase64 required" }
|
|
14026
|
+
};
|
|
14027
|
+
const topKParsed = parseOptionalPositiveInt(body["topK"]);
|
|
14028
|
+
if (topKParsed === null) return {
|
|
14029
|
+
status_code: 400,
|
|
14030
|
+
body: { error: "topK must be a positive integer" }
|
|
14031
|
+
};
|
|
14032
|
+
const payload = {};
|
|
14033
|
+
if (queryText) payload["queryText"] = queryText;
|
|
14034
|
+
if (queryImageRef) payload["queryImageRef"] = queryImageRef;
|
|
14035
|
+
if (queryImageBase64) payload["queryImageBase64"] = queryImageBase64;
|
|
14036
|
+
if (sessionId) payload["sessionId"] = sessionId;
|
|
14037
|
+
if (topKParsed !== void 0) payload["topK"] = Math.min(50, topKParsed);
|
|
14038
|
+
const result = await sdk.trigger({
|
|
14039
|
+
function_id: "mem::vision-search",
|
|
14040
|
+
payload
|
|
14041
|
+
});
|
|
14042
|
+
const resp = result;
|
|
14043
|
+
if (resp?.success === false) return {
|
|
14044
|
+
status_code: resp.error?.includes("disabled") ? 503 : 400,
|
|
14045
|
+
body: resp
|
|
14046
|
+
};
|
|
14047
|
+
return {
|
|
14048
|
+
status_code: 200,
|
|
14049
|
+
body: result
|
|
14050
|
+
};
|
|
14051
|
+
});
|
|
14052
|
+
sdk.registerTrigger({
|
|
14053
|
+
type: "http",
|
|
14054
|
+
function_id: "api::vision-search",
|
|
14055
|
+
config: {
|
|
14056
|
+
api_path: "/agentmemory/vision-search",
|
|
14057
|
+
http_method: "POST"
|
|
14058
|
+
}
|
|
14059
|
+
});
|
|
14060
|
+
sdk.registerFunction("api::vision-embed", async (req) => {
|
|
14061
|
+
const authErr = checkAuth(req, secret);
|
|
14062
|
+
if (authErr) return authErr;
|
|
14063
|
+
const body = req.body ?? {};
|
|
14064
|
+
const imageRef = asNonEmptyString$1(body["imageRef"]);
|
|
14065
|
+
const sessionId = asNonEmptyString$1(body["sessionId"]);
|
|
14066
|
+
const observationId = asNonEmptyString$1(body["observationId"]);
|
|
14067
|
+
if (!imageRef) return {
|
|
14068
|
+
status_code: 400,
|
|
14069
|
+
body: { error: "imageRef is required" }
|
|
14070
|
+
};
|
|
14071
|
+
const payload = { imageRef };
|
|
14072
|
+
if (sessionId) payload["sessionId"] = sessionId;
|
|
14073
|
+
if (observationId) payload["observationId"] = observationId;
|
|
14074
|
+
const result = await sdk.trigger({
|
|
14075
|
+
function_id: "mem::vision-embed",
|
|
14076
|
+
payload
|
|
14077
|
+
});
|
|
14078
|
+
const resp = result;
|
|
14079
|
+
if (resp?.success === false) return {
|
|
14080
|
+
status_code: resp.error?.includes("disabled") ? 503 : 400,
|
|
14081
|
+
body: resp
|
|
14082
|
+
};
|
|
14083
|
+
return {
|
|
14084
|
+
status_code: 200,
|
|
14085
|
+
body: result
|
|
14086
|
+
};
|
|
14087
|
+
});
|
|
14088
|
+
sdk.registerTrigger({
|
|
14089
|
+
type: "http",
|
|
14090
|
+
function_id: "api::vision-embed",
|
|
12620
14091
|
config: {
|
|
12621
|
-
api_path: "/agentmemory/
|
|
12622
|
-
http_method: "
|
|
14092
|
+
api_path: "/agentmemory/vision-embed",
|
|
14093
|
+
http_method: "POST"
|
|
12623
14094
|
}
|
|
12624
14095
|
});
|
|
12625
|
-
sdk.registerFunction("api::
|
|
14096
|
+
sdk.registerFunction("api::slot-list", async (req) => {
|
|
12626
14097
|
const authErr = checkAuth(req, secret);
|
|
12627
14098
|
if (authErr) return authErr;
|
|
12628
|
-
const parsedLimit = parseOptionalInt(req.query_params?.["limit"]);
|
|
12629
14099
|
return {
|
|
12630
14100
|
status_code: 200,
|
|
12631
14101
|
body: await sdk.trigger({
|
|
12632
|
-
function_id: "mem::
|
|
12633
|
-
payload: {
|
|
12634
|
-
operation: req.query_params?.["operation"],
|
|
12635
|
-
limit: parsedLimit ?? 50
|
|
12636
|
-
}
|
|
14102
|
+
function_id: "mem::slot-list",
|
|
14103
|
+
payload: {}
|
|
12637
14104
|
})
|
|
12638
14105
|
};
|
|
12639
14106
|
});
|
|
12640
14107
|
sdk.registerTrigger({
|
|
12641
14108
|
type: "http",
|
|
12642
|
-
function_id: "api::
|
|
14109
|
+
function_id: "api::slot-list",
|
|
12643
14110
|
config: {
|
|
12644
|
-
api_path: "/agentmemory/
|
|
14111
|
+
api_path: "/agentmemory/slots",
|
|
12645
14112
|
http_method: "GET"
|
|
12646
14113
|
}
|
|
12647
14114
|
});
|
|
12648
|
-
sdk.registerFunction("api::
|
|
14115
|
+
sdk.registerFunction("api::slot-get", async (req) => {
|
|
12649
14116
|
const authErr = checkAuth(req, secret);
|
|
12650
14117
|
if (authErr) return authErr;
|
|
12651
|
-
|
|
14118
|
+
const label = asNonEmptyString$1(req.query_params?.["label"]);
|
|
14119
|
+
if (!label) return {
|
|
12652
14120
|
status_code: 400,
|
|
12653
|
-
body: { error: "
|
|
14121
|
+
body: { error: "label query param required" }
|
|
14122
|
+
};
|
|
14123
|
+
const result = await sdk.trigger({
|
|
14124
|
+
function_id: "mem::slot-get",
|
|
14125
|
+
payload: { label }
|
|
14126
|
+
});
|
|
14127
|
+
const resp = result;
|
|
14128
|
+
if (resp?.success === false) return {
|
|
14129
|
+
status_code: resp.error?.includes("not found") ? 404 : 400,
|
|
14130
|
+
body: resp
|
|
12654
14131
|
};
|
|
12655
14132
|
return {
|
|
12656
14133
|
status_code: 200,
|
|
12657
|
-
body:
|
|
12658
|
-
function_id: "mem::governance-delete",
|
|
12659
|
-
payload: req.body
|
|
12660
|
-
})
|
|
14134
|
+
body: result
|
|
12661
14135
|
};
|
|
12662
14136
|
});
|
|
12663
14137
|
sdk.registerTrigger({
|
|
12664
14138
|
type: "http",
|
|
12665
|
-
function_id: "api::
|
|
14139
|
+
function_id: "api::slot-get",
|
|
12666
14140
|
config: {
|
|
12667
|
-
api_path: "/agentmemory/
|
|
12668
|
-
http_method: "
|
|
14141
|
+
api_path: "/agentmemory/slot",
|
|
14142
|
+
http_method: "GET"
|
|
12669
14143
|
}
|
|
12670
14144
|
});
|
|
12671
|
-
sdk.registerFunction("api::
|
|
14145
|
+
sdk.registerFunction("api::slot-create", async (req) => {
|
|
12672
14146
|
const authErr = checkAuth(req, secret);
|
|
12673
14147
|
if (authErr) return authErr;
|
|
14148
|
+
const body = req.body ?? {};
|
|
14149
|
+
const label = asNonEmptyString$1(body["label"]);
|
|
14150
|
+
if (!label) return {
|
|
14151
|
+
status_code: 400,
|
|
14152
|
+
body: { error: "label required" }
|
|
14153
|
+
};
|
|
14154
|
+
if (body["content"] !== void 0 && typeof body["content"] !== "string") return {
|
|
14155
|
+
status_code: 400,
|
|
14156
|
+
body: { error: "content must be a string" }
|
|
14157
|
+
};
|
|
14158
|
+
if (body["description"] !== void 0 && typeof body["description"] !== "string") return {
|
|
14159
|
+
status_code: 400,
|
|
14160
|
+
body: { error: "description must be a string" }
|
|
14161
|
+
};
|
|
14162
|
+
if (body["pinned"] !== void 0 && typeof body["pinned"] !== "boolean") return {
|
|
14163
|
+
status_code: 400,
|
|
14164
|
+
body: { error: "pinned must be a boolean" }
|
|
14165
|
+
};
|
|
14166
|
+
if (body["scope"] !== void 0 && body["scope"] !== "project" && body["scope"] !== "global") return {
|
|
14167
|
+
status_code: 400,
|
|
14168
|
+
body: { error: "scope must be 'project' or 'global'" }
|
|
14169
|
+
};
|
|
14170
|
+
const sizeLimit = parseOptionalPositiveInt(body["sizeLimit"]);
|
|
14171
|
+
if (sizeLimit === null) return {
|
|
14172
|
+
status_code: 400,
|
|
14173
|
+
body: { error: "sizeLimit must be a positive integer" }
|
|
14174
|
+
};
|
|
14175
|
+
if (sizeLimit !== void 0 && sizeLimit > 2e4) return {
|
|
14176
|
+
status_code: 400,
|
|
14177
|
+
body: { error: "sizeLimit must be <= 20000" }
|
|
14178
|
+
};
|
|
14179
|
+
const payload = { label };
|
|
14180
|
+
if (typeof body["content"] === "string") payload["content"] = body["content"];
|
|
14181
|
+
if (typeof body["description"] === "string") payload["description"] = body["description"];
|
|
14182
|
+
if (sizeLimit !== void 0) payload["sizeLimit"] = sizeLimit;
|
|
14183
|
+
if (typeof body["pinned"] === "boolean") payload["pinned"] = body["pinned"];
|
|
14184
|
+
if (body["scope"] === "project" || body["scope"] === "global") payload["scope"] = body["scope"];
|
|
14185
|
+
const result = await sdk.trigger({
|
|
14186
|
+
function_id: "mem::slot-create",
|
|
14187
|
+
payload
|
|
14188
|
+
});
|
|
14189
|
+
const resp = result;
|
|
14190
|
+
if (resp?.success === false) return {
|
|
14191
|
+
status_code: resp.error?.includes("exists") ? 409 : 400,
|
|
14192
|
+
body: resp
|
|
14193
|
+
};
|
|
12674
14194
|
return {
|
|
12675
|
-
status_code:
|
|
12676
|
-
body:
|
|
12677
|
-
function_id: "mem::governance-bulk",
|
|
12678
|
-
payload: req.body || {}
|
|
12679
|
-
})
|
|
14195
|
+
status_code: 201,
|
|
14196
|
+
body: result
|
|
12680
14197
|
};
|
|
12681
14198
|
});
|
|
12682
14199
|
sdk.registerTrigger({
|
|
12683
14200
|
type: "http",
|
|
12684
|
-
function_id: "api::
|
|
14201
|
+
function_id: "api::slot-create",
|
|
12685
14202
|
config: {
|
|
12686
|
-
api_path: "/agentmemory/
|
|
14203
|
+
api_path: "/agentmemory/slot",
|
|
12687
14204
|
http_method: "POST"
|
|
12688
14205
|
}
|
|
12689
14206
|
});
|
|
12690
|
-
sdk.registerFunction("api::
|
|
14207
|
+
sdk.registerFunction("api::slot-append", async (req) => {
|
|
12691
14208
|
const authErr = checkAuth(req, secret);
|
|
12692
14209
|
if (authErr) return authErr;
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
14210
|
+
const body = req.body ?? {};
|
|
14211
|
+
const label = asNonEmptyString$1(body["label"]);
|
|
14212
|
+
const text = typeof body["text"] === "string" ? body["text"] : null;
|
|
14213
|
+
if (!label || !text) return {
|
|
14214
|
+
status_code: 400,
|
|
14215
|
+
body: { error: "label and text required" }
|
|
14216
|
+
};
|
|
14217
|
+
const result = await sdk.trigger({
|
|
14218
|
+
function_id: "mem::slot-append",
|
|
14219
|
+
payload: {
|
|
14220
|
+
label,
|
|
14221
|
+
text
|
|
14222
|
+
}
|
|
14223
|
+
});
|
|
14224
|
+
const resp = result;
|
|
14225
|
+
if (resp?.success === false) {
|
|
14226
|
+
const notFound = resp.error?.includes("not found");
|
|
14227
|
+
const overLimit = resp.error?.includes("exceed");
|
|
12702
14228
|
return {
|
|
12703
|
-
status_code: 404,
|
|
12704
|
-
body:
|
|
14229
|
+
status_code: notFound ? 404 : overLimit ? 413 : 400,
|
|
14230
|
+
body: resp
|
|
12705
14231
|
};
|
|
12706
14232
|
}
|
|
14233
|
+
return {
|
|
14234
|
+
status_code: 200,
|
|
14235
|
+
body: result
|
|
14236
|
+
};
|
|
12707
14237
|
});
|
|
12708
14238
|
sdk.registerTrigger({
|
|
12709
14239
|
type: "http",
|
|
12710
|
-
function_id: "api::
|
|
14240
|
+
function_id: "api::slot-append",
|
|
12711
14241
|
config: {
|
|
12712
|
-
api_path: "/agentmemory/
|
|
12713
|
-
http_method: "
|
|
14242
|
+
api_path: "/agentmemory/slot/append",
|
|
14243
|
+
http_method: "POST"
|
|
12714
14244
|
}
|
|
12715
14245
|
});
|
|
12716
|
-
sdk.registerFunction("api::
|
|
14246
|
+
sdk.registerFunction("api::slot-replace", async (req) => {
|
|
12717
14247
|
const authErr = checkAuth(req, secret);
|
|
12718
14248
|
if (authErr) return authErr;
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
14249
|
+
const body = req.body ?? {};
|
|
14250
|
+
const label = asNonEmptyString$1(body["label"]);
|
|
14251
|
+
const content = body["content"];
|
|
14252
|
+
if (!label || typeof content !== "string") return {
|
|
14253
|
+
status_code: 400,
|
|
14254
|
+
body: { error: "label and content (string) required" }
|
|
14255
|
+
};
|
|
14256
|
+
const result = await sdk.trigger({
|
|
14257
|
+
function_id: "mem::slot-replace",
|
|
14258
|
+
payload: {
|
|
14259
|
+
label,
|
|
14260
|
+
content
|
|
14261
|
+
}
|
|
14262
|
+
});
|
|
14263
|
+
const resp = result;
|
|
14264
|
+
if (resp?.success === false) {
|
|
14265
|
+
const notFound = resp.error?.includes("not found");
|
|
14266
|
+
const overLimit = resp.error?.includes("exceed");
|
|
12728
14267
|
return {
|
|
12729
|
-
status_code: 404,
|
|
12730
|
-
body:
|
|
14268
|
+
status_code: notFound ? 404 : overLimit ? 413 : 400,
|
|
14269
|
+
body: resp
|
|
12731
14270
|
};
|
|
12732
14271
|
}
|
|
14272
|
+
return {
|
|
14273
|
+
status_code: 200,
|
|
14274
|
+
body: result
|
|
14275
|
+
};
|
|
12733
14276
|
});
|
|
12734
14277
|
sdk.registerTrigger({
|
|
12735
14278
|
type: "http",
|
|
12736
|
-
function_id: "api::
|
|
14279
|
+
function_id: "api::slot-replace",
|
|
12737
14280
|
config: {
|
|
12738
|
-
api_path: "/agentmemory/
|
|
14281
|
+
api_path: "/agentmemory/slot/replace",
|
|
12739
14282
|
http_method: "POST"
|
|
12740
14283
|
}
|
|
12741
14284
|
});
|
|
12742
|
-
sdk.registerFunction("api::
|
|
14285
|
+
sdk.registerFunction("api::slot-delete", async (req) => {
|
|
12743
14286
|
const authErr = checkAuth(req, secret);
|
|
12744
14287
|
if (authErr) return authErr;
|
|
12745
|
-
|
|
14288
|
+
const label = asNonEmptyString$1(req.query_params?.["label"]);
|
|
14289
|
+
if (!label) return {
|
|
12746
14290
|
status_code: 400,
|
|
12747
|
-
body: { error: "
|
|
14291
|
+
body: { error: "label query param required" }
|
|
14292
|
+
};
|
|
14293
|
+
const result = await sdk.trigger({
|
|
14294
|
+
function_id: "mem::slot-delete",
|
|
14295
|
+
payload: { label }
|
|
14296
|
+
});
|
|
14297
|
+
const resp = result;
|
|
14298
|
+
if (resp?.success === false) return {
|
|
14299
|
+
status_code: resp.error?.includes("not found") ? 404 : 400,
|
|
14300
|
+
body: resp
|
|
14301
|
+
};
|
|
14302
|
+
return {
|
|
14303
|
+
status_code: 200,
|
|
14304
|
+
body: result
|
|
12748
14305
|
};
|
|
12749
|
-
try {
|
|
12750
|
-
return {
|
|
12751
|
-
status_code: 200,
|
|
12752
|
-
body: await sdk.trigger({
|
|
12753
|
-
function_id: "mem::snapshot-restore",
|
|
12754
|
-
payload: req.body
|
|
12755
|
-
})
|
|
12756
|
-
};
|
|
12757
|
-
} catch {
|
|
12758
|
-
return {
|
|
12759
|
-
status_code: 404,
|
|
12760
|
-
body: { error: "Snapshots not enabled" }
|
|
12761
|
-
};
|
|
12762
|
-
}
|
|
12763
14306
|
});
|
|
12764
14307
|
sdk.registerTrigger({
|
|
12765
14308
|
type: "http",
|
|
12766
|
-
function_id: "api::
|
|
14309
|
+
function_id: "api::slot-delete",
|
|
12767
14310
|
config: {
|
|
12768
|
-
api_path: "/agentmemory/
|
|
12769
|
-
http_method: "
|
|
14311
|
+
api_path: "/agentmemory/slot",
|
|
14312
|
+
http_method: "DELETE"
|
|
12770
14313
|
}
|
|
12771
14314
|
});
|
|
12772
|
-
sdk.registerFunction("api::
|
|
14315
|
+
sdk.registerFunction("api::slot-reflect", async (req) => {
|
|
12773
14316
|
const authErr = checkAuth(req, secret);
|
|
12774
14317
|
if (authErr) return authErr;
|
|
12775
|
-
const
|
|
14318
|
+
const body = req.body ?? {};
|
|
14319
|
+
const sessionId = asNonEmptyString$1(body["sessionId"]);
|
|
14320
|
+
if (!sessionId) return {
|
|
14321
|
+
status_code: 400,
|
|
14322
|
+
body: { error: "sessionId required" }
|
|
14323
|
+
};
|
|
14324
|
+
const maxObservations = parseOptionalPositiveInt(body["maxObservations"]);
|
|
14325
|
+
if (maxObservations === null) return {
|
|
14326
|
+
status_code: 400,
|
|
14327
|
+
body: { error: "maxObservations must be a positive integer" }
|
|
14328
|
+
};
|
|
14329
|
+
const payload = { sessionId };
|
|
14330
|
+
if (maxObservations !== void 0) payload["maxObservations"] = maxObservations;
|
|
12776
14331
|
return {
|
|
12777
14332
|
status_code: 200,
|
|
12778
|
-
body:
|
|
14333
|
+
body: await sdk.trigger({
|
|
14334
|
+
function_id: "mem::slot-reflect",
|
|
14335
|
+
payload
|
|
14336
|
+
})
|
|
12779
14337
|
};
|
|
12780
14338
|
});
|
|
12781
14339
|
sdk.registerTrigger({
|
|
12782
14340
|
type: "http",
|
|
12783
|
-
function_id: "api::
|
|
14341
|
+
function_id: "api::slot-reflect",
|
|
12784
14342
|
config: {
|
|
12785
|
-
api_path: "/agentmemory/
|
|
12786
|
-
http_method: "
|
|
14343
|
+
api_path: "/agentmemory/slot/reflect",
|
|
14344
|
+
http_method: "POST"
|
|
12787
14345
|
}
|
|
12788
14346
|
});
|
|
12789
14347
|
sdk.registerFunction("api::action-create", async (req) => {
|
|
@@ -13020,9 +14578,9 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
13020
14578
|
sdk.registerFunction("api::routine-create", async (req) => {
|
|
13021
14579
|
const authErr = checkAuth(req, secret);
|
|
13022
14580
|
if (authErr) return authErr;
|
|
13023
|
-
if (!req.body?.name) return {
|
|
14581
|
+
if (!req.body?.name || !req.body?.steps) return {
|
|
13024
14582
|
status_code: 400,
|
|
13025
|
-
body: { error: "name
|
|
14583
|
+
body: { error: "name and steps are required" }
|
|
13026
14584
|
};
|
|
13027
14585
|
return {
|
|
13028
14586
|
status_code: 201,
|
|
@@ -14271,10 +15829,21 @@ function registerEventTriggers(sdk, kv) {
|
|
|
14271
15829
|
function_id: "event::observation",
|
|
14272
15830
|
config: { topic: "agentmemory.observation" }
|
|
14273
15831
|
});
|
|
14274
|
-
sdk.registerFunction("event::session::stopped", async (data) =>
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
15832
|
+
sdk.registerFunction("event::session::stopped", async (data) => {
|
|
15833
|
+
const summary = await sdk.trigger({
|
|
15834
|
+
function_id: "mem::summarize",
|
|
15835
|
+
payload: data
|
|
15836
|
+
});
|
|
15837
|
+
if (isReflectEnabled()) try {
|
|
15838
|
+
sdk.triggerVoid("mem::slot-reflect", { sessionId: data.sessionId });
|
|
15839
|
+
} catch (err) {
|
|
15840
|
+
logger.warn("slot-reflect triggerVoid failed", {
|
|
15841
|
+
sessionId: data.sessionId,
|
|
15842
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15843
|
+
});
|
|
15844
|
+
}
|
|
15845
|
+
return summary;
|
|
15846
|
+
});
|
|
14278
15847
|
sdk.registerTrigger({
|
|
14279
15848
|
type: "durable:subscriber",
|
|
14280
15849
|
function_id: "event::session::stopped",
|
|
@@ -14526,6 +16095,34 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
14526
16095
|
}] }
|
|
14527
16096
|
};
|
|
14528
16097
|
}
|
|
16098
|
+
case "memory_vision_search": {
|
|
16099
|
+
const queryText = typeof args.queryText === "string" ? args.queryText : void 0;
|
|
16100
|
+
const queryImageRef = typeof args.queryImageRef === "string" ? args.queryImageRef : void 0;
|
|
16101
|
+
const queryImageBase64 = typeof args.queryImageBase64 === "string" ? args.queryImageBase64 : void 0;
|
|
16102
|
+
if (!queryText && !queryImageRef && !queryImageBase64) return {
|
|
16103
|
+
status_code: 400,
|
|
16104
|
+
body: { error: "queryText, queryImageRef, or queryImageBase64 required" }
|
|
16105
|
+
};
|
|
16106
|
+
const topK = Math.max(1, Math.min(50, asNumber(args.topK, 10) ?? 10));
|
|
16107
|
+
const sessionId = typeof args.sessionId === "string" ? args.sessionId : void 0;
|
|
16108
|
+
const result = await sdk.trigger({
|
|
16109
|
+
function_id: "mem::vision-search",
|
|
16110
|
+
payload: {
|
|
16111
|
+
queryText,
|
|
16112
|
+
queryImageRef,
|
|
16113
|
+
queryImageBase64,
|
|
16114
|
+
topK,
|
|
16115
|
+
sessionId
|
|
16116
|
+
}
|
|
16117
|
+
});
|
|
16118
|
+
return {
|
|
16119
|
+
status_code: 200,
|
|
16120
|
+
body: { content: [{
|
|
16121
|
+
type: "text",
|
|
16122
|
+
text: JSON.stringify(result, null, 2)
|
|
16123
|
+
}] }
|
|
16124
|
+
};
|
|
16125
|
+
}
|
|
14529
16126
|
case "memory_timeline": {
|
|
14530
16127
|
if (typeof args.anchor !== "string" || !args.anchor.trim()) return {
|
|
14531
16128
|
status_code: 400,
|
|
@@ -15391,6 +16988,123 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
15391
16988
|
}] }
|
|
15392
16989
|
};
|
|
15393
16990
|
}
|
|
16991
|
+
case "memory_slot_list": {
|
|
16992
|
+
const result = await sdk.trigger({
|
|
16993
|
+
function_id: "mem::slot-list",
|
|
16994
|
+
payload: {}
|
|
16995
|
+
});
|
|
16996
|
+
return {
|
|
16997
|
+
status_code: 200,
|
|
16998
|
+
body: { content: [{
|
|
16999
|
+
type: "text",
|
|
17000
|
+
text: JSON.stringify(result, null, 2)
|
|
17001
|
+
}] }
|
|
17002
|
+
};
|
|
17003
|
+
}
|
|
17004
|
+
case "memory_slot_get": {
|
|
17005
|
+
const label = asNonEmptyString(args.label);
|
|
17006
|
+
if (!label) return {
|
|
17007
|
+
status_code: 400,
|
|
17008
|
+
body: { error: "label required" }
|
|
17009
|
+
};
|
|
17010
|
+
const result = await sdk.trigger({
|
|
17011
|
+
function_id: "mem::slot-get",
|
|
17012
|
+
payload: { label }
|
|
17013
|
+
});
|
|
17014
|
+
return {
|
|
17015
|
+
status_code: 200,
|
|
17016
|
+
body: { content: [{
|
|
17017
|
+
type: "text",
|
|
17018
|
+
text: JSON.stringify(result, null, 2)
|
|
17019
|
+
}] }
|
|
17020
|
+
};
|
|
17021
|
+
}
|
|
17022
|
+
case "memory_slot_create": {
|
|
17023
|
+
const label = asNonEmptyString(args.label);
|
|
17024
|
+
if (!label) return {
|
|
17025
|
+
status_code: 400,
|
|
17026
|
+
body: { error: "label required" }
|
|
17027
|
+
};
|
|
17028
|
+
const payload = { label };
|
|
17029
|
+
if (typeof args.content === "string") payload.content = args.content;
|
|
17030
|
+
if (typeof args.description === "string") payload.description = args.description;
|
|
17031
|
+
if (typeof args.sizeLimit === "number") payload.sizeLimit = args.sizeLimit;
|
|
17032
|
+
if (args.pinned === false || args.pinned === "false") payload.pinned = false;
|
|
17033
|
+
else if (args.pinned === true || args.pinned === "true") payload.pinned = true;
|
|
17034
|
+
if (args.scope === "global" || args.scope === "project") payload.scope = args.scope;
|
|
17035
|
+
const result = await sdk.trigger({
|
|
17036
|
+
function_id: "mem::slot-create",
|
|
17037
|
+
payload
|
|
17038
|
+
});
|
|
17039
|
+
return {
|
|
17040
|
+
status_code: 200,
|
|
17041
|
+
body: { content: [{
|
|
17042
|
+
type: "text",
|
|
17043
|
+
text: JSON.stringify(result, null, 2)
|
|
17044
|
+
}] }
|
|
17045
|
+
};
|
|
17046
|
+
}
|
|
17047
|
+
case "memory_slot_append": {
|
|
17048
|
+
const label = asNonEmptyString(args.label);
|
|
17049
|
+
const text = typeof args.text === "string" ? args.text : null;
|
|
17050
|
+
if (!label || !text) return {
|
|
17051
|
+
status_code: 400,
|
|
17052
|
+
body: { error: "label and text required" }
|
|
17053
|
+
};
|
|
17054
|
+
const result = await sdk.trigger({
|
|
17055
|
+
function_id: "mem::slot-append",
|
|
17056
|
+
payload: {
|
|
17057
|
+
label,
|
|
17058
|
+
text
|
|
17059
|
+
}
|
|
17060
|
+
});
|
|
17061
|
+
return {
|
|
17062
|
+
status_code: 200,
|
|
17063
|
+
body: { content: [{
|
|
17064
|
+
type: "text",
|
|
17065
|
+
text: JSON.stringify(result, null, 2)
|
|
17066
|
+
}] }
|
|
17067
|
+
};
|
|
17068
|
+
}
|
|
17069
|
+
case "memory_slot_replace": {
|
|
17070
|
+
const label = asNonEmptyString(args.label);
|
|
17071
|
+
if (!label || typeof args.content !== "string") return {
|
|
17072
|
+
status_code: 400,
|
|
17073
|
+
body: { error: "label and content (string) required" }
|
|
17074
|
+
};
|
|
17075
|
+
const result = await sdk.trigger({
|
|
17076
|
+
function_id: "mem::slot-replace",
|
|
17077
|
+
payload: {
|
|
17078
|
+
label,
|
|
17079
|
+
content: args.content
|
|
17080
|
+
}
|
|
17081
|
+
});
|
|
17082
|
+
return {
|
|
17083
|
+
status_code: 200,
|
|
17084
|
+
body: { content: [{
|
|
17085
|
+
type: "text",
|
|
17086
|
+
text: JSON.stringify(result, null, 2)
|
|
17087
|
+
}] }
|
|
17088
|
+
};
|
|
17089
|
+
}
|
|
17090
|
+
case "memory_slot_delete": {
|
|
17091
|
+
const label = asNonEmptyString(args.label);
|
|
17092
|
+
if (!label) return {
|
|
17093
|
+
status_code: 400,
|
|
17094
|
+
body: { error: "label required" }
|
|
17095
|
+
};
|
|
17096
|
+
const result = await sdk.trigger({
|
|
17097
|
+
function_id: "mem::slot-delete",
|
|
17098
|
+
payload: { label }
|
|
17099
|
+
});
|
|
17100
|
+
return {
|
|
17101
|
+
status_code: 200,
|
|
17102
|
+
body: { content: [{
|
|
17103
|
+
type: "text",
|
|
17104
|
+
text: JSON.stringify(result, null, 2)
|
|
17105
|
+
}] }
|
|
17106
|
+
};
|
|
17107
|
+
}
|
|
15394
17108
|
default: return {
|
|
15395
17109
|
status_code: 400,
|
|
15396
17110
|
body: { error: `Unknown tool: ${name}` }
|
|
@@ -16045,11 +17759,13 @@ async function main() {
|
|
|
16045
17759
|
const fallbackConfig = loadFallbackConfig();
|
|
16046
17760
|
const provider = fallbackConfig.providers.length > 0 ? createFallbackProvider(config.provider, fallbackConfig) : createProvider(config.provider);
|
|
16047
17761
|
const embeddingProvider = createEmbeddingProvider();
|
|
17762
|
+
const imageEmbeddingProvider = createImageEmbeddingProvider();
|
|
16048
17763
|
console.log(`[agentmemory] Starting worker v${VERSION}...`);
|
|
16049
17764
|
console.log(`[agentmemory] Engine: ${config.engineUrl}`);
|
|
16050
17765
|
console.log(`[agentmemory] Provider: ${config.provider.provider} (${config.provider.model})`);
|
|
16051
17766
|
if (embeddingProvider) console.log(`[agentmemory] Embedding provider: ${embeddingProvider.name} (${embeddingProvider.dimensions} dims)`);
|
|
16052
17767
|
else console.log(`[agentmemory] Embedding provider: none (BM25-only mode)`);
|
|
17768
|
+
if (imageEmbeddingProvider) console.log(`[agentmemory] Image embedding provider: ${imageEmbeddingProvider.name} (${imageEmbeddingProvider.dimensions} dims) — vision-search active`);
|
|
16053
17769
|
console.log(`[agentmemory] REST API: http://localhost:${config.restPort}/agentmemory/*`);
|
|
16054
17770
|
console.log(`[agentmemory] Streams: ws://localhost:${config.streamsPort}`);
|
|
16055
17771
|
const sdk = registerWorker(config.engineUrl, {
|
|
@@ -16068,6 +17784,10 @@ async function main() {
|
|
|
16068
17784
|
initMetrics(hasGetMeter(sdk) ? sdk.getMeter.bind(sdk) : void 0);
|
|
16069
17785
|
registerPrivacyFunction(sdk);
|
|
16070
17786
|
registerObserveFunction(sdk, kv, dedupMap, config.maxObservationsPerSession);
|
|
17787
|
+
registerImageQuotaCleanup(sdk, kv);
|
|
17788
|
+
registerVisionSearchFunctions(sdk, kv, imageEmbeddingProvider);
|
|
17789
|
+
if (isSlotsEnabled()) registerSlotsFunctions(sdk, kv);
|
|
17790
|
+
registerDiskSizeManager(sdk, kv);
|
|
16071
17791
|
registerCompressFunction(sdk, kv, provider, metricsStore);
|
|
16072
17792
|
registerSearchFunction(sdk, kv);
|
|
16073
17793
|
registerContextFunction(sdk, kv, config.tokenBudget);
|
|
@@ -16134,6 +17854,7 @@ async function main() {
|
|
|
16134
17854
|
registerReplayFunctions(sdk, kv);
|
|
16135
17855
|
console.log(`[agentmemory] v0.6 advanced retrieval: sliding-window, query-expansion, temporal-graph, retention-scoring`);
|
|
16136
17856
|
console.log(`[agentmemory] Orchestration layer: actions, frontier, leases, routines, signals, checkpoints, flow-compress, mesh, branch-aware, sentinels, sketches, crystallize, diagnostics, facets`);
|
|
17857
|
+
if (isSlotsEnabled()) console.log(`[agentmemory] Slots: enabled (pinned editable memory). Reflect on Stop hook: ${isReflectEnabled() ? "on" : "off"}`);
|
|
16137
17858
|
const snapshotConfig = loadSnapshotConfig();
|
|
16138
17859
|
if (snapshotConfig.enabled) {
|
|
16139
17860
|
registerSnapshotFunction(sdk, kv, snapshotConfig.dir);
|
|
@@ -16171,7 +17892,7 @@ async function main() {
|
|
|
16171
17892
|
}
|
|
16172
17893
|
}
|
|
16173
17894
|
console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
16174
|
-
console.log(`[agentmemory] Endpoints: 107 REST +
|
|
17895
|
+
console.log(`[agentmemory] Endpoints: 107 REST + ${getAllTools().length} MCP tools + 6 MCP resources + 3 MCP prompts`);
|
|
16175
17896
|
const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
|
|
16176
17897
|
const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
|
|
16177
17898
|
const consolidationIntervalMs = parseInt(process.env.CONSOLIDATION_INTERVAL_MS || "7200000", 10);
|
|
@@ -16237,5 +17958,5 @@ main().catch((err) => {
|
|
|
16237
17958
|
});
|
|
16238
17959
|
|
|
16239
17960
|
//#endregion
|
|
16240
|
-
export {
|
|
16241
|
-
//# sourceMappingURL=src-
|
|
17961
|
+
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 };
|
|
17962
|
+
//# sourceMappingURL=src-tmuZyobT.mjs.map
|