@agentmemory/agentmemory 0.9.21 → 0.9.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +7 -2
- package/README.md +288 -33
- package/dist/cli.d.mts +5 -1
- package/dist/cli.d.mts.map +1 -0
- package/dist/cli.mjs +128 -703
- package/dist/cli.mjs.map +1 -1
- package/dist/connect-Cf9bmBqO.mjs +1020 -0
- package/dist/connect-Cf9bmBqO.mjs.map +1 -0
- package/dist/hooks/notification.mjs +46 -21
- package/dist/hooks/notification.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +47 -21
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +57 -22
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +26 -2
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +19 -12
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/prompt-submit.mjs +39 -16
- package/dist/hooks/prompt-submit.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +26 -33
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +28 -3
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +14 -9
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-start.mjs +31 -4
- package/dist/hooks/subagent-start.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +45 -20
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +44 -21
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/iii-config.docker.yaml +3 -2
- package/dist/iii-config.yaml +11 -2
- package/dist/{image-refs-R3tin9MR.mjs → image-refs-CJS5B9Gq.mjs} +2 -2
- package/dist/{image-refs-R3tin9MR.mjs.map → image-refs-CJS5B9Gq.mjs.map} +1 -1
- package/dist/{image-store-DyrKZKqZ.mjs → image-store-CdE0amb1.mjs} +1 -1
- package/dist/index.mjs +866 -380
- package/dist/index.mjs.map +1 -1
- package/dist/logger-xlVlvCWX.mjs +43 -0
- package/dist/logger-xlVlvCWX.mjs.map +1 -0
- package/dist/schema-BkALl7Z_.mjs +74 -0
- package/dist/schema-BkALl7Z_.mjs.map +1 -0
- package/dist/{src-D5arboxc.mjs → src-DvS3bhMe.mjs} +844 -395
- package/dist/src-DvS3bhMe.mjs.map +1 -0
- package/dist/{standalone-C7BgzzIN.mjs → standalone-DHQcPX_g.mjs} +107 -14
- package/dist/standalone-DHQcPX_g.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +108 -12
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-CRTWUFw9.mjs → tools-registry-DJizX9Az.mjs} +51 -12
- package/dist/tools-registry-DJizX9Az.mjs.map +1 -0
- package/dist/version-BPfyI4Kc.mjs +6 -0
- package/dist/version-BPfyI4Kc.mjs.map +1 -0
- package/dist/viewer/index.html +85 -10
- package/iii-config.docker.yaml +3 -2
- package/iii-config.yaml +11 -2
- package/package.json +6 -4
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/.mcp.copilot.json +15 -0
- package/plugin/.mcp.json +3 -2
- package/plugin/hooks/hooks.copilot.json +72 -0
- package/plugin/opencode/agentmemory-capture.ts +34 -9
- package/plugin/plugin.json +15 -0
- package/plugin/scripts/diagnostics.d.mts +17 -0
- package/plugin/scripts/diagnostics.d.mts.map +1 -0
- package/plugin/scripts/diagnostics.mjs.map +1 -0
- package/plugin/scripts/notification.mjs +46 -21
- package/plugin/scripts/notification.mjs.map +1 -1
- package/plugin/scripts/post-tool-failure.mjs +47 -21
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +57 -22
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +26 -2
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +19 -12
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/prompt-submit.mjs +39 -16
- package/plugin/scripts/prompt-submit.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +26 -33
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +28 -3
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +14 -9
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-start.mjs +31 -4
- package/plugin/scripts/subagent-start.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +45 -20
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +44 -21
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/dist/src-D5arboxc.mjs.map +0 -1
- package/dist/standalone-C7BgzzIN.mjs.map +0 -1
- package/dist/tools-registry-CRTWUFw9.mjs.map +0 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./schema-BkALl7Z_.mjs";
|
|
2
|
+
import { n as logger, t as bootLog } from "./logger-xlVlvCWX.mjs";
|
|
3
|
+
import { t as VERSION } from "./version-BPfyI4Kc.mjs";
|
|
4
|
+
import { a as isManagedImagePath, getImageRefCount, i as getMaxBytes, n as IMAGES_DIR, r as deleteImage, t as withKeyedLock } from "./image-refs-CJS5B9Gq.mjs";
|
|
5
|
+
import { _ as loadEmbeddingConfig, a as getAgentId, b as loadTeamConfig, d as isConsolidationEnabled, f as isContextInjectionEnabled, g as loadConfig, h as loadClaudeBridgeConfig, i as detectLlmProviderKind, l as isAgentScopeIsolated, m as isGraphExtractionEnabled, n as getVisibleTools, o as getConsolidationDecayDays, p as isDropStaleIndexEnabled, r as detectEmbeddingProvider, s as getEnvVar, t as getAllTools, u as isAutoCompressEnabled, v as loadFallbackConfig, y as loadSnapshotConfig } from "./tools-registry-DJizX9Az.mjs";
|
|
4
6
|
import { createRequire } from "node:module";
|
|
5
7
|
import { execFile } from "node:child_process";
|
|
6
|
-
import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { constants, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
9
|
import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
8
10
|
import { fileURLToPath } from "node:url";
|
|
9
11
|
import { homedir } from "node:os";
|
|
@@ -229,13 +231,25 @@ function v1AzureUrl(baseUrl, path) {
|
|
|
229
231
|
url.pathname = `${url.pathname.replace(/\/?openai(?:\/v1)?\/?$/, "").replace(/\/+$/, "")}/openai/v1/${route}`;
|
|
230
232
|
return url.toString();
|
|
231
233
|
}
|
|
234
|
+
function appendOpenAIRoute(baseUrl, route) {
|
|
235
|
+
const trimmedBase = baseUrl.replace(/\/+$/, "");
|
|
236
|
+
const cleanRoute = route.startsWith("/") ? route : `/${route}`;
|
|
237
|
+
let pathname;
|
|
238
|
+
try {
|
|
239
|
+
pathname = new URL(trimmedBase).pathname.replace(/\/+$/, "");
|
|
240
|
+
} catch {
|
|
241
|
+
return `${trimmedBase}/v1${cleanRoute}`;
|
|
242
|
+
}
|
|
243
|
+
if (pathname === "" || pathname === "/") return `${trimmedBase}/v1${cleanRoute}`;
|
|
244
|
+
return `${trimmedBase}${cleanRoute}`;
|
|
245
|
+
}
|
|
232
246
|
function buildChatUrl(baseUrl, isAzure, azureApiVersion) {
|
|
233
247
|
if (isAzure) return azureStyleOf(baseUrl) === "legacy" ? legacyAzureUrl(baseUrl, "/chat/completions", azureApiVersion) : v1AzureUrl(baseUrl, "/chat/completions");
|
|
234
|
-
return
|
|
248
|
+
return appendOpenAIRoute(baseUrl, "/chat/completions");
|
|
235
249
|
}
|
|
236
250
|
function buildEmbeddingUrl(baseUrl, isAzure, azureApiVersion) {
|
|
237
251
|
if (isAzure) return azureStyleOf(baseUrl) === "legacy" ? legacyAzureUrl(baseUrl, "/embeddings", azureApiVersion) : v1AzureUrl(baseUrl, "/embeddings");
|
|
238
|
-
return
|
|
252
|
+
return appendOpenAIRoute(baseUrl, "/embeddings");
|
|
239
253
|
}
|
|
240
254
|
function buildAuthHeaders(apiKey, isAzure) {
|
|
241
255
|
if (isAzure) return {
|
|
@@ -317,6 +331,7 @@ var OpenAIProvider = class {
|
|
|
317
331
|
const body = {
|
|
318
332
|
model: this.model,
|
|
319
333
|
max_tokens: this.maxTokens,
|
|
334
|
+
stream: false,
|
|
320
335
|
messages: [{
|
|
321
336
|
role: "system",
|
|
322
337
|
content: systemPrompt
|
|
@@ -345,7 +360,7 @@ var OpenAIProvider = class {
|
|
|
345
360
|
const message = data.choices?.[0]?.message;
|
|
346
361
|
const content = message?.content;
|
|
347
362
|
if (content) return content;
|
|
348
|
-
const reasoning = message?.reasoning;
|
|
363
|
+
const reasoning = message?.reasoning ?? message?.reasoning_content;
|
|
349
364
|
if (reasoning) return reasoning;
|
|
350
365
|
throw new Error(`OpenAI returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`);
|
|
351
366
|
}
|
|
@@ -624,16 +639,30 @@ function resolveDimensions(model, override) {
|
|
|
624
639
|
* `api-key` header instead of `Authorization: Bearer`.
|
|
625
640
|
*
|
|
626
641
|
* Required env vars:
|
|
627
|
-
* OPENAI_API_KEY
|
|
642
|
+
* OPENAI_API_KEY — API key (fallback for OPENAI_EMBEDDING_API_KEY)
|
|
628
643
|
*
|
|
629
644
|
* Optional:
|
|
630
|
-
* OPENAI_BASE_URL
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
645
|
+
* OPENAI_BASE_URL — base URL without path (default: https://api.openai.com).
|
|
646
|
+
* Azure: https://<resource>.openai.azure.com/openai/deployments/<deployment>
|
|
647
|
+
* OPENAI_EMBEDDING_BASE_URL — embedding-specific base URL override (defaults
|
|
648
|
+
* to OPENAI_BASE_URL). Lets operators run
|
|
649
|
+
* embeddings on a separate endpoint from chat —
|
|
650
|
+
* e.g. local Ollama / LM Studio / llama.cpp /
|
|
651
|
+
* vLLM at http://localhost:1234 for unlimited
|
|
652
|
+
* free embeddings, while keeping chat
|
|
653
|
+
* completions on a rate-limited but high-quality
|
|
654
|
+
* hosted provider. Azure detection runs on
|
|
655
|
+
* whichever URL ends up selected.
|
|
656
|
+
* OPENAI_EMBEDDING_API_KEY — separate API key for the embedding endpoint
|
|
657
|
+
* (defaults to OPENAI_API_KEY). Useful when the
|
|
658
|
+
* embedding endpoint requires a different key
|
|
659
|
+
* or no key at all (set to e.g. "local" for
|
|
660
|
+
* endpoints that ignore Authorization).
|
|
661
|
+
* OPENAI_API_VERSION — Azure api-version query param (default: 2024-08-01-preview)
|
|
662
|
+
* OPENAI_EMBEDDING_MODEL — model name (default: text-embedding-3-small)
|
|
663
|
+
* OPENAI_EMBEDDING_DIMENSIONS — override reported dimensions (required for
|
|
664
|
+
* custom / self-hosted models not in the
|
|
665
|
+
* MODEL_DIMENSIONS table above)
|
|
637
666
|
*/
|
|
638
667
|
var OpenAIEmbeddingProvider = class {
|
|
639
668
|
name = "openai";
|
|
@@ -644,9 +673,9 @@ var OpenAIEmbeddingProvider = class {
|
|
|
644
673
|
isAzure;
|
|
645
674
|
azureApiVersion;
|
|
646
675
|
constructor(apiKey) {
|
|
647
|
-
this.apiKey = apiKey || getEnvVar("OPENAI_API_KEY") || "";
|
|
648
|
-
if (!this.apiKey) throw new Error("
|
|
649
|
-
this.baseUrl = normalizeBaseUrl(getEnvVar("OPENAI_BASE_URL"));
|
|
676
|
+
this.apiKey = apiKey || getEnvVar("OPENAI_EMBEDDING_API_KEY") || getEnvVar("OPENAI_API_KEY") || "";
|
|
677
|
+
if (!this.apiKey) throw new Error("API key is required (via constructor, OPENAI_EMBEDDING_API_KEY, or OPENAI_API_KEY)");
|
|
678
|
+
this.baseUrl = normalizeBaseUrl(getEnvVar("OPENAI_EMBEDDING_BASE_URL") || getEnvVar("OPENAI_BASE_URL"));
|
|
650
679
|
this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
|
|
651
680
|
this.dimensions = resolveDimensions(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
|
|
652
681
|
this.isAzure = detectAzure(this.baseUrl);
|
|
@@ -1023,10 +1052,11 @@ var StateKV = class {
|
|
|
1023
1052
|
//#endregion
|
|
1024
1053
|
//#region src/state/vector-index.ts
|
|
1025
1054
|
function float32ToBase64(arr) {
|
|
1026
|
-
return Buffer.from(arr.buffer).toString("base64");
|
|
1055
|
+
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
|
|
1027
1056
|
}
|
|
1028
1057
|
function base64ToFloat32(b64) {
|
|
1029
|
-
|
|
1058
|
+
const buf = Buffer.from(b64, "base64");
|
|
1059
|
+
return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / Float32Array.BYTES_PER_ELEMENT);
|
|
1030
1060
|
}
|
|
1031
1061
|
function cosineSimilarity(a, b) {
|
|
1032
1062
|
if (a.length !== b.length) return 0;
|
|
@@ -1147,7 +1177,7 @@ var VectorIndex = class VectorIndex {
|
|
|
1147
1177
|
function memoryToObservation(memory) {
|
|
1148
1178
|
return {
|
|
1149
1179
|
id: memory.id,
|
|
1150
|
-
sessionId: memory.sessionIds[0] ?? "memory",
|
|
1180
|
+
sessionId: memory.sessionIds?.[0] ?? "memory",
|
|
1151
1181
|
timestamp: memory.createdAt,
|
|
1152
1182
|
type: "decision",
|
|
1153
1183
|
title: memory.title,
|
|
@@ -2182,6 +2212,24 @@ var SearchIndex = class SearchIndex {
|
|
|
2182
2212
|
has(id) {
|
|
2183
2213
|
return this.entries.has(id);
|
|
2184
2214
|
}
|
|
2215
|
+
remove(id) {
|
|
2216
|
+
const entry = this.entries.get(id);
|
|
2217
|
+
if (!entry) return;
|
|
2218
|
+
const termFreq = this.docTermCounts.get(id);
|
|
2219
|
+
if (termFreq) {
|
|
2220
|
+
for (const term of termFreq.keys()) {
|
|
2221
|
+
const postingList = this.invertedIndex.get(term);
|
|
2222
|
+
if (postingList) {
|
|
2223
|
+
postingList.delete(id);
|
|
2224
|
+
if (postingList.size === 0) this.invertedIndex.delete(term);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
this.docTermCounts.delete(id);
|
|
2228
|
+
}
|
|
2229
|
+
this.totalDocLength = Math.max(0, this.totalDocLength - entry.termCount);
|
|
2230
|
+
this.entries.delete(id);
|
|
2231
|
+
this.sortedTerms = null;
|
|
2232
|
+
}
|
|
2185
2233
|
search(query, limit = 20) {
|
|
2186
2234
|
const rawTerms = this.tokenize(query.toLowerCase());
|
|
2187
2235
|
if (rawTerms.length === 0) return [];
|
|
@@ -2521,6 +2569,7 @@ function buildSyntheticCompression(raw) {
|
|
|
2521
2569
|
};
|
|
2522
2570
|
if (raw.modality) result.modality = raw.modality;
|
|
2523
2571
|
if (raw.imageData) result.imageData = raw.imageData;
|
|
2572
|
+
if (raw.agentId) result.agentId = raw.agentId;
|
|
2524
2573
|
return result;
|
|
2525
2574
|
}
|
|
2526
2575
|
|
|
@@ -2610,6 +2659,16 @@ function setVectorIndex(idx) {
|
|
|
2610
2659
|
function setEmbeddingProvider(provider) {
|
|
2611
2660
|
currentEmbeddingProvider = provider;
|
|
2612
2661
|
}
|
|
2662
|
+
function vectorIndexRemove(id) {
|
|
2663
|
+
vectorIndex?.remove(id);
|
|
2664
|
+
}
|
|
2665
|
+
let indexPersistence = null;
|
|
2666
|
+
function setIndexPersistence(p) {
|
|
2667
|
+
indexPersistence = p;
|
|
2668
|
+
}
|
|
2669
|
+
async function flushIndexSave() {
|
|
2670
|
+
await indexPersistence?.save();
|
|
2671
|
+
}
|
|
2613
2672
|
const EMBED_MAX_CHARS = 16e3;
|
|
2614
2673
|
function clipEmbedInput(text) {
|
|
2615
2674
|
if (text.length <= EMBED_MAX_CHARS) return text;
|
|
@@ -2739,7 +2798,7 @@ async function rebuildIndex(kv) {
|
|
|
2739
2798
|
idx.add(memoryToObservation(memory));
|
|
2740
2799
|
await enqueue({
|
|
2741
2800
|
id: memory.id,
|
|
2742
|
-
sessionId: memory.sessionIds[0] ?? "memory",
|
|
2801
|
+
sessionId: memory.sessionIds?.[0] ?? "memory",
|
|
2743
2802
|
text: memory.title + " " + memory.content,
|
|
2744
2803
|
context: {
|
|
2745
2804
|
kind: "memory",
|
|
@@ -2798,8 +2857,8 @@ function registerSearchFunction(sdk, kv) {
|
|
|
2798
2857
|
if (!Number.isInteger(data.limit) || data.limit < 1) throw new Error("mem::search: limit must be a positive integer");
|
|
2799
2858
|
effectiveLimit = Math.min(data.limit, MAX_LIMIT);
|
|
2800
2859
|
}
|
|
2801
|
-
const projectFilter = typeof data.project === "string" && data.project.length > 0 ? data.project : void 0;
|
|
2802
|
-
const cwdFilter = typeof data.cwd === "string" && data.cwd.length > 0 ? data.cwd : void 0;
|
|
2860
|
+
const projectFilter = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
|
|
2861
|
+
const cwdFilter = typeof data.cwd === "string" && data.cwd.trim().length > 0 ? data.cwd.trim() : void 0;
|
|
2803
2862
|
const format = typeof data.format === "string" ? data.format : "full";
|
|
2804
2863
|
if (![
|
|
2805
2864
|
"full",
|
|
@@ -2825,14 +2884,25 @@ function registerSearchFunction(sdk, kv) {
|
|
|
2825
2884
|
sessionCache.set(sessionId, s ?? null);
|
|
2826
2885
|
return s ?? null;
|
|
2827
2886
|
};
|
|
2887
|
+
const memoryProjectCache = /* @__PURE__ */ new Map();
|
|
2888
|
+
const loadMemoryProject = async (obsId) => {
|
|
2889
|
+
if (memoryProjectCache.has(obsId)) return memoryProjectCache.get(obsId);
|
|
2890
|
+
const proj = (await kv.get(KV.memories, obsId).catch(() => null))?.project ?? null;
|
|
2891
|
+
memoryProjectCache.set(obsId, proj);
|
|
2892
|
+
return proj;
|
|
2893
|
+
};
|
|
2828
2894
|
const candidates = [];
|
|
2829
2895
|
for (const r of results) {
|
|
2830
2896
|
if (candidates.length >= effectiveLimit) break;
|
|
2831
2897
|
if (filtering) {
|
|
2832
2898
|
const s = await loadSession(r.sessionId);
|
|
2833
|
-
if (
|
|
2834
|
-
|
|
2835
|
-
|
|
2899
|
+
if (s) {
|
|
2900
|
+
if (projectFilter && s.project !== projectFilter) continue;
|
|
2901
|
+
if (cwdFilter && s.cwd !== cwdFilter) continue;
|
|
2902
|
+
} else if (projectFilter) {
|
|
2903
|
+
const memProject = await loadMemoryProject(r.obsId);
|
|
2904
|
+
if (memProject !== null && memProject !== projectFilter) continue;
|
|
2905
|
+
}
|
|
2836
2906
|
}
|
|
2837
2907
|
candidates.push(r);
|
|
2838
2908
|
}
|
|
@@ -3004,11 +3074,14 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3004
3074
|
error: `Session observation limit reached (${maxObservationsPerSession})`
|
|
3005
3075
|
};
|
|
3006
3076
|
}
|
|
3077
|
+
const existingSession = await kv.get(KV.sessions, payload.sessionId);
|
|
3078
|
+
const inheritedAgentId = existingSession ? existingSession.agentId : getAgentId();
|
|
3079
|
+
if (inheritedAgentId) raw.agentId = inheritedAgentId;
|
|
3007
3080
|
if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) {
|
|
3008
|
-
const { saveImageToDisk } = await import("./image-store-
|
|
3081
|
+
const { saveImageToDisk } = await import("./image-store-CdE0amb1.mjs");
|
|
3009
3082
|
const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
|
|
3010
3083
|
raw.imageData = filePath;
|
|
3011
|
-
const { incrementImageRef } = await import("./image-refs-
|
|
3084
|
+
const { incrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
|
|
3012
3085
|
await incrementImageRef(kv, filePath);
|
|
3013
3086
|
sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: bytesWritten });
|
|
3014
3087
|
if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] === "true") sdk.triggerVoid("mem::vision-embed", {
|
|
@@ -3021,7 +3094,7 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3021
3094
|
await kv.set(KV.observations(payload.sessionId), obsId, raw);
|
|
3022
3095
|
} catch (error) {
|
|
3023
3096
|
if (raw.imageData) {
|
|
3024
|
-
const { deleteImage } = await import("./image-store-
|
|
3097
|
+
const { deleteImage } = await import("./image-store-CdE0amb1.mjs");
|
|
3025
3098
|
const { deletedBytes } = await deleteImage(raw.imageData);
|
|
3026
3099
|
if (deletedBytes > 0) sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
|
|
3027
3100
|
}
|
|
@@ -3055,7 +3128,7 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3055
3128
|
},
|
|
3056
3129
|
action: TriggerAction.Void()
|
|
3057
3130
|
});
|
|
3058
|
-
const session =
|
|
3131
|
+
const session = existingSession;
|
|
3059
3132
|
if (session) {
|
|
3060
3133
|
const updates = [{
|
|
3061
3134
|
type: "set",
|
|
@@ -3075,6 +3148,20 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3075
3148
|
});
|
|
3076
3149
|
}
|
|
3077
3150
|
await kv.update(KV.sessions, payload.sessionId, updates);
|
|
3151
|
+
} else if (typeof payload.project === "string" && payload.project.trim().length > 0 && typeof payload.cwd === "string" && payload.cwd.trim().length > 0) {
|
|
3152
|
+
const trimmedPrompt = typeof raw.userPrompt === "string" ? raw.userPrompt.replace(/\s+/g, " ").trim().slice(0, 200) : void 0;
|
|
3153
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
3154
|
+
await kv.set(KV.sessions, payload.sessionId, {
|
|
3155
|
+
id: payload.sessionId,
|
|
3156
|
+
project: payload.project,
|
|
3157
|
+
cwd: payload.cwd,
|
|
3158
|
+
startedAt: payload.timestamp ?? ts,
|
|
3159
|
+
updatedAt: ts,
|
|
3160
|
+
status: "active",
|
|
3161
|
+
observationCount: 1,
|
|
3162
|
+
...inheritedAgentId ? { agentId: inheritedAgentId } : {},
|
|
3163
|
+
...trimmedPrompt && trimmedPrompt.length > 0 ? { firstPrompt: trimmedPrompt } : {}
|
|
3164
|
+
});
|
|
3078
3165
|
}
|
|
3079
3166
|
if (isAutoCompressEnabled()) await sdk.trigger({
|
|
3080
3167
|
function_id: "mem::compress",
|
|
@@ -3460,10 +3547,10 @@ const DEFAULT_SLOTS = [
|
|
|
3460
3547
|
}
|
|
3461
3548
|
];
|
|
3462
3549
|
function isSlotsEnabled() {
|
|
3463
|
-
return
|
|
3550
|
+
return getEnvVar("AGENTMEMORY_SLOTS") === "true";
|
|
3464
3551
|
}
|
|
3465
3552
|
function isReflectEnabled() {
|
|
3466
|
-
return
|
|
3553
|
+
return getEnvVar("AGENTMEMORY_REFLECT") === "true";
|
|
3467
3554
|
}
|
|
3468
3555
|
function scopeKv(scope) {
|
|
3469
3556
|
return scope === "global" ? KV.globalSlots : KV.slots;
|
|
@@ -4227,7 +4314,8 @@ function registerCompressFunction(sdk, kv, provider, metricsStore) {
|
|
|
4227
4314
|
confidence: qualityScore / 100,
|
|
4228
4315
|
...hasImage ? { modality: data.raw.modality } : {},
|
|
4229
4316
|
...imageDescription ? { imageDescription } : {},
|
|
4230
|
-
...data.raw.imageData ? { imageRef: data.raw.imageData } : {}
|
|
4317
|
+
...data.raw.imageData ? { imageRef: data.raw.imageData } : {},
|
|
4318
|
+
...data.raw.agentId ? { agentId: data.raw.agentId } : {}
|
|
4231
4319
|
};
|
|
4232
4320
|
await kv.set(KV.observations(data.sessionId), data.observationId, compressed);
|
|
4233
4321
|
try {
|
|
@@ -4727,8 +4815,78 @@ function isAllowedPath(dbPath) {
|
|
|
4727
4815
|
const resolved = resolve(dbPath);
|
|
4728
4816
|
return ALLOWED_DIRS.some((dir) => resolved.startsWith(dir + "/"));
|
|
4729
4817
|
}
|
|
4818
|
+
async function inferMemoryProjects(kv, dryRun = false) {
|
|
4819
|
+
const memories = await kv.list(KV.memories);
|
|
4820
|
+
const sessionCache = /* @__PURE__ */ new Map();
|
|
4821
|
+
const loadSession = async (sid) => {
|
|
4822
|
+
if (sessionCache.has(sid)) return sessionCache.get(sid);
|
|
4823
|
+
const s = await kv.get(KV.sessions, sid).catch(() => null);
|
|
4824
|
+
sessionCache.set(sid, s);
|
|
4825
|
+
return s;
|
|
4826
|
+
};
|
|
4827
|
+
let updated = 0;
|
|
4828
|
+
let skipped = 0;
|
|
4829
|
+
let ambiguous = 0;
|
|
4830
|
+
for (const memory of memories) {
|
|
4831
|
+
if (memory.project) {
|
|
4832
|
+
skipped++;
|
|
4833
|
+
continue;
|
|
4834
|
+
}
|
|
4835
|
+
const sessionIds = memory.sessionIds ?? [];
|
|
4836
|
+
if (sessionIds.length === 0) {
|
|
4837
|
+
ambiguous++;
|
|
4838
|
+
continue;
|
|
4839
|
+
}
|
|
4840
|
+
const projects = [];
|
|
4841
|
+
for (const sid of sessionIds) {
|
|
4842
|
+
const session = await loadSession(sid);
|
|
4843
|
+
if (session?.project) projects.push(session.project);
|
|
4844
|
+
}
|
|
4845
|
+
if (projects.length === 0) {
|
|
4846
|
+
ambiguous++;
|
|
4847
|
+
continue;
|
|
4848
|
+
}
|
|
4849
|
+
const freq = /* @__PURE__ */ new Map();
|
|
4850
|
+
for (const p of projects) freq.set(p, (freq.get(p) ?? 0) + 1);
|
|
4851
|
+
const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1]);
|
|
4852
|
+
const [topProject, topCount] = sorted[0];
|
|
4853
|
+
if (topCount <= projects.length / 2 && sorted.length > 1) {
|
|
4854
|
+
ambiguous++;
|
|
4855
|
+
continue;
|
|
4856
|
+
}
|
|
4857
|
+
if (!dryRun) {
|
|
4858
|
+
memory.project = topProject;
|
|
4859
|
+
await kv.set(KV.memories, memory.id, memory);
|
|
4860
|
+
}
|
|
4861
|
+
updated++;
|
|
4862
|
+
}
|
|
4863
|
+
logger.info("inferMemoryProjects complete", {
|
|
4864
|
+
updated,
|
|
4865
|
+
skipped,
|
|
4866
|
+
ambiguous,
|
|
4867
|
+
dryRun
|
|
4868
|
+
});
|
|
4869
|
+
return {
|
|
4870
|
+
updated,
|
|
4871
|
+
skipped,
|
|
4872
|
+
ambiguous
|
|
4873
|
+
};
|
|
4874
|
+
}
|
|
4730
4875
|
function registerMigrateFunction(sdk, kv) {
|
|
4731
4876
|
sdk.registerFunction("mem::migrate", async (data) => {
|
|
4877
|
+
if (data.step === "infer-memory-projects") {
|
|
4878
|
+
const dryRun = data.dryRun ?? false;
|
|
4879
|
+
logger.info("Migration step: infer-memory-projects", { dryRun });
|
|
4880
|
+
return {
|
|
4881
|
+
success: true,
|
|
4882
|
+
step: "infer-memory-projects",
|
|
4883
|
+
...await inferMemoryProjects(kv, dryRun)
|
|
4884
|
+
};
|
|
4885
|
+
}
|
|
4886
|
+
if (!data.dbPath) return {
|
|
4887
|
+
success: false,
|
|
4888
|
+
error: "Either step or dbPath is required"
|
|
4889
|
+
};
|
|
4732
4890
|
logger.info("Migration started", { dbPath: data.dbPath });
|
|
4733
4891
|
if (!isAllowedPath(data.dbPath)) return {
|
|
4734
4892
|
success: false,
|
|
@@ -5004,9 +5162,10 @@ function registerConsolidateFunction(sdk, kv, provider) {
|
|
|
5004
5162
|
llmCallCount++;
|
|
5005
5163
|
const parsed = parseMemoryXml(response, sessionIds);
|
|
5006
5164
|
if (!parsed) continue;
|
|
5007
|
-
const existingMatch = existingMemories.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase());
|
|
5008
5165
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5009
5166
|
const obsIds = [...new Set(top.map((o) => o.id))];
|
|
5167
|
+
const scopedProject = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
|
|
5168
|
+
const existingMatch = existingMemories.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase() && (!scopedProject || !m.project || m.project === scopedProject));
|
|
5010
5169
|
if (existingMatch) {
|
|
5011
5170
|
existingMatch.isLatest = false;
|
|
5012
5171
|
await kv.set(KV.memories, existingMatch.id, existingMatch);
|
|
@@ -5023,7 +5182,8 @@ function registerConsolidateFunction(sdk, kv, provider) {
|
|
|
5023
5182
|
parentId: existingMatch.id,
|
|
5024
5183
|
supersedes: [existingMatch.id, ...existingMatch.supersedes || []],
|
|
5025
5184
|
sourceObservationIds: obsIds,
|
|
5026
|
-
isLatest: true
|
|
5185
|
+
isLatest: true,
|
|
5186
|
+
...scopedProject !== void 0 && { project: scopedProject }
|
|
5027
5187
|
};
|
|
5028
5188
|
await kv.set(KV.memories, evolved.id, evolved);
|
|
5029
5189
|
await recordAudit(kv, "evolve", "mem::consolidate", [evolved.id], {
|
|
@@ -5042,7 +5202,8 @@ function registerConsolidateFunction(sdk, kv, provider) {
|
|
|
5042
5202
|
...parsed,
|
|
5043
5203
|
sourceObservationIds: obsIds,
|
|
5044
5204
|
version: 1,
|
|
5045
|
-
isLatest: true
|
|
5205
|
+
isLatest: true,
|
|
5206
|
+
...scopedProject !== void 0 && { project: scopedProject }
|
|
5046
5207
|
};
|
|
5047
5208
|
await kv.set(KV.memories, memory.id, memory);
|
|
5048
5209
|
await recordAudit(kv, "remember", "mem::consolidate", [memory.id], {
|
|
@@ -5183,6 +5344,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5183
5344
|
"fact"
|
|
5184
5345
|
]).has(data.type || "") ? data.type : "fact";
|
|
5185
5346
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5347
|
+
const project = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
|
|
5186
5348
|
return withKeyedLock("mem:remember", async () => {
|
|
5187
5349
|
const existingMemories = await kv.list(KV.memories);
|
|
5188
5350
|
let supersededId;
|
|
@@ -5191,6 +5353,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5191
5353
|
const lowerContent = data.content.toLowerCase();
|
|
5192
5354
|
for (const existing of existingMemories) {
|
|
5193
5355
|
if (existing.isLatest === false) continue;
|
|
5356
|
+
if (project && existing.project && existing.project !== project) continue;
|
|
5194
5357
|
if (jaccardSimilarity(lowerContent, existing.content.toLowerCase()) > .7) {
|
|
5195
5358
|
supersededId = existing.id;
|
|
5196
5359
|
supersededVersion = existing.version ?? 1;
|
|
@@ -5198,6 +5361,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5198
5361
|
break;
|
|
5199
5362
|
}
|
|
5200
5363
|
}
|
|
5364
|
+
const callAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim().slice(0, 128) : getAgentId();
|
|
5201
5365
|
const memory = {
|
|
5202
5366
|
id: generateId("mem"),
|
|
5203
5367
|
createdAt: now,
|
|
@@ -5213,7 +5377,9 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5213
5377
|
parentId: supersededId,
|
|
5214
5378
|
supersedes: supersededId ? [supersededId] : [],
|
|
5215
5379
|
sourceObservationIds: (data.sourceObservationIds || []).filter((id) => typeof id === "string" && id.length > 0),
|
|
5216
|
-
isLatest: true
|
|
5380
|
+
isLatest: true,
|
|
5381
|
+
...callAgentId ? { agentId: callAgentId } : {},
|
|
5382
|
+
...project !== void 0 && { project }
|
|
5217
5383
|
};
|
|
5218
5384
|
if (data.ttlDays && typeof data.ttlDays === "number" && data.ttlDays > 0) memory.forgetAfter = new Date(Date.now() + data.ttlDays * 864e5).toISOString();
|
|
5219
5385
|
if (supersededMemory) {
|
|
@@ -5229,7 +5395,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5229
5395
|
error: err instanceof Error ? err.message : String(err)
|
|
5230
5396
|
});
|
|
5231
5397
|
}
|
|
5232
|
-
await vectorIndexAddGuarded(memory.id, memory.sessionIds[0] ?? "memory", memory.title + " " + memory.content, {
|
|
5398
|
+
await vectorIndexAddGuarded(memory.id, memory.sessionIds?.[0] ?? "memory", memory.title + " " + memory.content, {
|
|
5233
5399
|
kind: "memory",
|
|
5234
5400
|
logId: memory.id
|
|
5235
5401
|
});
|
|
@@ -5240,7 +5406,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5240
5406
|
});
|
|
5241
5407
|
logger.info("Memory saved", {
|
|
5242
5408
|
memId: memory.id,
|
|
5243
|
-
type: memory.type
|
|
5409
|
+
type: memory.type,
|
|
5410
|
+
project: memory.project
|
|
5244
5411
|
});
|
|
5245
5412
|
return {
|
|
5246
5413
|
success: true,
|
|
@@ -5253,12 +5420,14 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5253
5420
|
const deletedMemoryIds = [];
|
|
5254
5421
|
const deletedObservationIds = [];
|
|
5255
5422
|
let deletedSession = false;
|
|
5256
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
5423
|
+
const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
|
|
5257
5424
|
if (data.memoryId) {
|
|
5258
5425
|
const mem = await kv.get(KV.memories, data.memoryId);
|
|
5259
5426
|
await kv.delete(KV.memories, data.memoryId);
|
|
5260
5427
|
if (mem?.imageRef) await decrementImageRef(kv, sdk, mem.imageRef);
|
|
5261
5428
|
await deleteAccessLog(kv, data.memoryId);
|
|
5429
|
+
getSearchIndex().remove(data.memoryId);
|
|
5430
|
+
vectorIndexRemove(data.memoryId);
|
|
5262
5431
|
deletedMemoryIds.push(data.memoryId);
|
|
5263
5432
|
deleted++;
|
|
5264
5433
|
}
|
|
@@ -5267,6 +5436,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5267
5436
|
await kv.delete(KV.observations(data.sessionId), obsId);
|
|
5268
5437
|
if (obs?.imageData) await decrementImageRef(kv, sdk, obs.imageData);
|
|
5269
5438
|
if (obs?.imageRef && obs.imageRef !== obs.imageData) await decrementImageRef(kv, sdk, obs.imageRef);
|
|
5439
|
+
getSearchIndex().remove(obsId);
|
|
5440
|
+
vectorIndexRemove(obsId);
|
|
5270
5441
|
deletedObservationIds.push(obsId);
|
|
5271
5442
|
deleted++;
|
|
5272
5443
|
}
|
|
@@ -5276,6 +5447,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5276
5447
|
await kv.delete(KV.observations(data.sessionId), obs.id);
|
|
5277
5448
|
if (obs.imageData) await decrementImageRef(kv, sdk, obs.imageData);
|
|
5278
5449
|
if (obs.imageRef && obs.imageRef !== obs.imageData) await decrementImageRef(kv, sdk, obs.imageRef);
|
|
5450
|
+
getSearchIndex().remove(obs.id);
|
|
5451
|
+
vectorIndexRemove(obs.id);
|
|
5279
5452
|
deletedObservationIds.push(obs.id);
|
|
5280
5453
|
deleted++;
|
|
5281
5454
|
}
|
|
@@ -5284,14 +5457,17 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5284
5457
|
deletedSession = true;
|
|
5285
5458
|
deleted += 2;
|
|
5286
5459
|
}
|
|
5287
|
-
if (deleted > 0)
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5460
|
+
if (deleted > 0) {
|
|
5461
|
+
await flushIndexSave();
|
|
5462
|
+
await recordAudit(kv, "forget", "mem::forget", [...deletedMemoryIds, ...deletedObservationIds], {
|
|
5463
|
+
sessionId: data.sessionId,
|
|
5464
|
+
deleted,
|
|
5465
|
+
memoriesDeleted: deletedMemoryIds.length,
|
|
5466
|
+
observationsDeleted: deletedObservationIds.length,
|
|
5467
|
+
sessionDeleted: deletedSession,
|
|
5468
|
+
reason: "user-initiated forget"
|
|
5469
|
+
});
|
|
5470
|
+
}
|
|
5295
5471
|
logger.info("Memory forgotten", { deleted });
|
|
5296
5472
|
return {
|
|
5297
5473
|
success: true,
|
|
@@ -5352,7 +5528,7 @@ async function runRecoveredSessionConsolidation(sdk) {
|
|
|
5352
5528
|
function registerEvictFunction(sdk, kv) {
|
|
5353
5529
|
sdk.registerFunction("mem::evict", async (data) => {
|
|
5354
5530
|
const dryRun = data?.dryRun ?? false;
|
|
5355
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
5531
|
+
const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
|
|
5356
5532
|
const configOverride = await kv.get(KV.config, "eviction").catch(() => null);
|
|
5357
5533
|
const cfg = {
|
|
5358
5534
|
...DEFAULTS$1,
|
|
@@ -5806,6 +5982,9 @@ async function findByKeyword(kv, keyword, project) {
|
|
|
5806
5982
|
const LESSON_CONTENT_PREVIEW_CHARS = 240;
|
|
5807
5983
|
function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
5808
5984
|
sdk.registerFunction("mem::smart-search", async (data) => {
|
|
5985
|
+
const isolated = isAgentScopeIsolated();
|
|
5986
|
+
const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
|
|
5987
|
+
const filterAgentId = explicitAgentId === "*" ? void 0 : explicitAgentId ?? (isolated ? getAgentId() : void 0);
|
|
5809
5988
|
if (data.expandIds && data.expandIds.length > 0) {
|
|
5810
5989
|
const raw = data.expandIds.slice(0, 20);
|
|
5811
5990
|
const items = raw.map((entry) => {
|
|
@@ -5826,17 +6005,19 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
5826
6005
|
observation: obs
|
|
5827
6006
|
} : null)));
|
|
5828
6007
|
for (const r of results) if (r) expanded.push(r);
|
|
5829
|
-
|
|
6008
|
+
const scoped = filterAgentId ? expanded.filter((e) => e.observation.agentId === filterAgentId) : expanded;
|
|
6009
|
+
recordAccessBatch(kv, scoped.map((e) => e.observation.id));
|
|
5830
6010
|
const truncated = data.expandIds.length > raw.length;
|
|
5831
6011
|
logger.info("Smart search expanded", {
|
|
5832
6012
|
requested: data.expandIds.length,
|
|
5833
6013
|
attempted: raw.length,
|
|
5834
|
-
returned:
|
|
6014
|
+
returned: scoped.length,
|
|
6015
|
+
filteredOutOfScope: expanded.length - scoped.length,
|
|
5835
6016
|
truncated
|
|
5836
6017
|
});
|
|
5837
6018
|
return {
|
|
5838
6019
|
mode: "expanded",
|
|
5839
|
-
results:
|
|
6020
|
+
results: scoped,
|
|
5840
6021
|
truncated
|
|
5841
6022
|
};
|
|
5842
6023
|
}
|
|
@@ -5848,8 +6029,9 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
5848
6029
|
const limit = Math.max(1, Math.min(data.limit ?? 20, 100));
|
|
5849
6030
|
const lessonLimit = Math.min(limit, 10);
|
|
5850
6031
|
const includeLessons = data.includeLessons !== false;
|
|
5851
|
-
const
|
|
5852
|
-
const
|
|
6032
|
+
const overFetchLimit = filterAgentId ? Math.min(limit * 3, 300) : limit;
|
|
6033
|
+
const [hybridResults, lessons] = await Promise.all([searchFn(data.query, overFetchLimit), includeLessons ? recallLessons(sdk, data.query, lessonLimit, data.project) : Promise.resolve([])]);
|
|
6034
|
+
const compact = (filterAgentId ? hybridResults.filter((r) => r.observation.agentId === filterAgentId).slice(0, limit) : hybridResults.slice(0, limit)).map((r) => ({
|
|
5853
6035
|
obsId: r.observation.id,
|
|
5854
6036
|
sessionId: r.sessionId,
|
|
5855
6037
|
title: r.observation.title,
|
|
@@ -6006,7 +6188,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6006
6188
|
sdk.registerFunction("mem::auto-forget", async (data) => {
|
|
6007
6189
|
const dryRun = data?.dryRun ?? false;
|
|
6008
6190
|
const now = Date.now();
|
|
6009
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
6191
|
+
const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
|
|
6010
6192
|
const result = {
|
|
6011
6193
|
ttlExpired: [],
|
|
6012
6194
|
contradictions: [],
|
|
@@ -6028,6 +6210,8 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6028
6210
|
timestamp: mem.forgetAfter
|
|
6029
6211
|
});
|
|
6030
6212
|
await deleteAccessLog(kv, mem.id);
|
|
6213
|
+
getSearchIndex().remove(mem.id);
|
|
6214
|
+
vectorIndexRemove(mem.id);
|
|
6031
6215
|
}
|
|
6032
6216
|
}
|
|
6033
6217
|
}
|
|
@@ -6105,10 +6289,13 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6105
6289
|
sessionId: sessions[i].id,
|
|
6106
6290
|
timestamp: obs.timestamp
|
|
6107
6291
|
});
|
|
6292
|
+
getSearchIndex().remove(obs.id);
|
|
6293
|
+
vectorIndexRemove(obs.id);
|
|
6108
6294
|
}
|
|
6109
6295
|
}
|
|
6110
6296
|
}
|
|
6111
6297
|
}
|
|
6298
|
+
if (!dryRun && (result.ttlExpired.length > 0 || result.lowValueObs.length > 0)) await flushIndexSave();
|
|
6112
6299
|
logger.info("Auto-forget complete", {
|
|
6113
6300
|
ttlExpired: result.ttlExpired.length,
|
|
6114
6301
|
contradictions: result.contradictions.length,
|
|
@@ -6256,7 +6443,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
6256
6443
|
"0.9.18",
|
|
6257
6444
|
"0.9.19",
|
|
6258
6445
|
"0.9.20",
|
|
6259
|
-
"0.9.21"
|
|
6446
|
+
"0.9.21",
|
|
6447
|
+
"0.9.22",
|
|
6448
|
+
"0.9.23"
|
|
6260
6449
|
]).has(importData.version)) return {
|
|
6261
6450
|
success: false,
|
|
6262
6451
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -6379,6 +6568,7 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
6379
6568
|
continue;
|
|
6380
6569
|
}
|
|
6381
6570
|
}
|
|
6571
|
+
if (!Array.isArray(memory.sessionIds)) memory.sessionIds = [];
|
|
6382
6572
|
await kv.set(KV.memories, memory.id, memory);
|
|
6383
6573
|
stats.memories++;
|
|
6384
6574
|
}
|
|
@@ -6582,6 +6772,7 @@ function escapeXml(s) {
|
|
|
6582
6772
|
}
|
|
6583
6773
|
function registerEnrichFunction(sdk, kv) {
|
|
6584
6774
|
sdk.registerFunction("mem::enrich", async (data) => {
|
|
6775
|
+
const project = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0;
|
|
6585
6776
|
const parts = [];
|
|
6586
6777
|
const fileContextPromise = sdk.trigger({
|
|
6587
6778
|
function_id: "mem::file-context",
|
|
@@ -6595,10 +6786,11 @@ function registerEnrichFunction(sdk, kv) {
|
|
|
6595
6786
|
function_id: "mem::search",
|
|
6596
6787
|
payload: {
|
|
6597
6788
|
query: searchQueries.join(" "),
|
|
6598
|
-
limit: 5
|
|
6789
|
+
limit: 5,
|
|
6790
|
+
...project !== void 0 && { project }
|
|
6599
6791
|
}
|
|
6600
6792
|
}).catch(() => ({ results: [] })) : Promise.resolve({ results: [] });
|
|
6601
|
-
const bugMemoriesPromise = kv.list(KV.memories).then((memories) => memories.filter((m) => m.type === "bug" && m.isLatest && m.files.some((f) => data.files.some((df) => f.includes(df) || df.includes(f)))).sort((a, b) => new Date(b.updatedAt || b.createdAt).getTime() - new Date(a.updatedAt || a.createdAt).getTime())).catch(() => []);
|
|
6793
|
+
const bugMemoriesPromise = kv.list(KV.memories).then((memories) => memories.filter((m) => m.type === "bug" && m.isLatest && (!project || !m.project || m.project === project) && m.files.some((f) => data.files.some((df) => f.includes(df) || df.includes(f)))).sort((a, b) => new Date(b.updatedAt || b.createdAt).getTime() - new Date(a.updatedAt || a.createdAt).getTime())).catch(() => []);
|
|
6602
6794
|
const [fileContext, searchResult, bugMemories] = await Promise.all([
|
|
6603
6795
|
fileContextPromise,
|
|
6604
6796
|
searchPromise,
|
|
@@ -6621,6 +6813,7 @@ function registerEnrichFunction(sdk, kv) {
|
|
|
6621
6813
|
}
|
|
6622
6814
|
logger.info("Enrichment completed", {
|
|
6623
6815
|
sessionId: data.sessionId,
|
|
6816
|
+
project,
|
|
6624
6817
|
fileCount: data.files.length,
|
|
6625
6818
|
contextLength: context.length,
|
|
6626
6819
|
truncated
|
|
@@ -6778,16 +6971,24 @@ function buildGraphExtractionPrompt(observations) {
|
|
|
6778
6971
|
|
|
6779
6972
|
//#endregion
|
|
6780
6973
|
//#region src/functions/graph.ts
|
|
6974
|
+
function parseAttrs(raw) {
|
|
6975
|
+
const attrs = {};
|
|
6976
|
+
const attrRegex = /([A-Za-z_][\w:-]*)="([^"]*)"/g;
|
|
6977
|
+
let m;
|
|
6978
|
+
while ((m = attrRegex.exec(raw)) !== null) attrs[m[1]] = m[2];
|
|
6979
|
+
return attrs;
|
|
6980
|
+
}
|
|
6781
6981
|
function parseGraphXml(xml, observationIds) {
|
|
6782
6982
|
const nodes = [];
|
|
6783
6983
|
const edges = [];
|
|
6784
6984
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6785
|
-
const
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
const
|
|
6789
|
-
const
|
|
6790
|
-
const
|
|
6985
|
+
const entitySelfClose = /<entity\b([^>]*?)\/>/g;
|
|
6986
|
+
const entityWithBody = /<entity\b([^>]*[^/])>([\s\S]*?)<\/entity>/g;
|
|
6987
|
+
const addEntity = (rawAttrs, propsBlock = "") => {
|
|
6988
|
+
const attrs = parseAttrs(rawAttrs);
|
|
6989
|
+
const type = attrs["type"];
|
|
6990
|
+
const name = attrs["name"];
|
|
6991
|
+
if (!type || !name) return;
|
|
6791
6992
|
const properties = {};
|
|
6792
6993
|
const propRegex = /<property\s+key="([^"]+)">([^<]*)<\/property>/g;
|
|
6793
6994
|
let propMatch;
|
|
@@ -6800,17 +7001,23 @@ function parseGraphXml(xml, observationIds) {
|
|
|
6800
7001
|
sourceObservationIds: observationIds,
|
|
6801
7002
|
createdAt: now
|
|
6802
7003
|
});
|
|
6803
|
-
}
|
|
6804
|
-
|
|
7004
|
+
};
|
|
7005
|
+
let match;
|
|
7006
|
+
while ((match = entitySelfClose.exec(xml)) !== null) addEntity(match[1]);
|
|
7007
|
+
while ((match = entityWithBody.exec(xml)) !== null) addEntity(match[1], match[2]);
|
|
7008
|
+
const relRegex = /<relationship\b([^>]*?)\/>/g;
|
|
6805
7009
|
while ((match = relRegex.exec(xml)) !== null) {
|
|
6806
|
-
const
|
|
6807
|
-
const
|
|
6808
|
-
const
|
|
6809
|
-
const
|
|
6810
|
-
|
|
7010
|
+
const attrs = parseAttrs(match[1]);
|
|
7011
|
+
const type = attrs["type"];
|
|
7012
|
+
const sourceName = attrs["source"];
|
|
7013
|
+
const targetName = attrs["target"];
|
|
7014
|
+
if (!type || !sourceName || !targetName) continue;
|
|
7015
|
+
const parsedWeight = parseFloat(attrs["weight"] ?? "");
|
|
7016
|
+
const weight = Number.isFinite(parsedWeight) ? parsedWeight : .5;
|
|
6811
7017
|
const sourceNode = nodes.find((n) => n.name === sourceName);
|
|
6812
7018
|
const targetNode = nodes.find((n) => n.name === targetName);
|
|
6813
|
-
if (sourceNode
|
|
7019
|
+
if (!sourceNode || !targetNode) continue;
|
|
7020
|
+
edges.push({
|
|
6814
7021
|
id: generateId("ge"),
|
|
6815
7022
|
type,
|
|
6816
7023
|
sourceNodeId: sourceNode.id,
|
|
@@ -7024,7 +7231,7 @@ function registerConsolidationPipelineFunction(sdk, kv, provider) {
|
|
|
7024
7231
|
if (!data?.force && !isConsolidationEnabled()) return {
|
|
7025
7232
|
success: false,
|
|
7026
7233
|
skipped: true,
|
|
7027
|
-
reason: "CONSOLIDATION_ENABLED
|
|
7234
|
+
reason: "Consolidation disabled: set CONSOLIDATION_ENABLED=true or configure an LLM provider (ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY / GEMINI_API_KEY / GOOGLE_API_KEY / MINIMAX_API_KEY / OPENAI_BASE_URL / AGENTMEMORY_PROVIDER=agent-sdk)"
|
|
7028
7235
|
};
|
|
7029
7236
|
const tier = data?.tier || "all";
|
|
7030
7237
|
const decayDays = getConsolidationDecayDays();
|
|
@@ -7315,8 +7522,11 @@ function registerGovernanceFunction(sdk, kv) {
|
|
|
7315
7522
|
for (const id of data.memoryIds) if (await kv.get(KV.memories, id)) {
|
|
7316
7523
|
await kv.delete(KV.memories, id);
|
|
7317
7524
|
await deleteAccessLog(kv, id);
|
|
7525
|
+
getSearchIndex().remove(id);
|
|
7526
|
+
vectorIndexRemove(id);
|
|
7318
7527
|
deleted++;
|
|
7319
7528
|
}
|
|
7529
|
+
if (deleted > 0) await flushIndexSave();
|
|
7320
7530
|
await recordAudit(kv, "delete", "mem::governance-delete", data.memoryIds, {
|
|
7321
7531
|
reason: data.reason || "manual deletion",
|
|
7322
7532
|
deleted
|
|
@@ -7369,6 +7579,8 @@ function registerGovernanceFunction(sdk, kv) {
|
|
|
7369
7579
|
(await Promise.allSettled(batch.map(async (mem) => {
|
|
7370
7580
|
await kv.delete(KV.memories, mem.id);
|
|
7371
7581
|
await deleteAccessLog(kv, mem.id);
|
|
7582
|
+
getSearchIndex().remove(mem.id);
|
|
7583
|
+
vectorIndexRemove(mem.id);
|
|
7372
7584
|
}))).forEach((result, j) => {
|
|
7373
7585
|
const mem = batch[j];
|
|
7374
7586
|
if (result.status === "fulfilled") successfulIds.push(mem.id);
|
|
@@ -7384,6 +7596,7 @@ function registerGovernanceFunction(sdk, kv) {
|
|
|
7384
7596
|
}
|
|
7385
7597
|
});
|
|
7386
7598
|
}
|
|
7599
|
+
if (successfulIds.length > 0) await flushIndexSave();
|
|
7387
7600
|
await safeAudit(kv, "delete", "mem::governance-bulk", successfulIds, {
|
|
7388
7601
|
filter: data,
|
|
7389
7602
|
deleted: successfulIds.length,
|
|
@@ -10188,11 +10401,34 @@ function registerDiagnosticsFunction(sdk, kv) {
|
|
|
10188
10401
|
});
|
|
10189
10402
|
memoryIssues++;
|
|
10190
10403
|
}
|
|
10404
|
+
const latestMemories = memories.filter((m) => m.isLatest);
|
|
10405
|
+
const unscopedCount = latestMemories.filter((m) => !m.project).length;
|
|
10406
|
+
if (unscopedCount === 0) checks.push({
|
|
10407
|
+
name: "memory-project-coverage",
|
|
10408
|
+
category: "memories",
|
|
10409
|
+
status: "pass",
|
|
10410
|
+
message: `All ${latestMemories.length} latest memories have a project scope`,
|
|
10411
|
+
fixable: false
|
|
10412
|
+
});
|
|
10413
|
+
else if (unscopedCount <= 10) checks.push({
|
|
10414
|
+
name: "memory-project-coverage",
|
|
10415
|
+
category: "memories",
|
|
10416
|
+
status: "warn",
|
|
10417
|
+
message: `${unscopedCount} of ${latestMemories.length} latest memories have no project scope — run POST /agentmemory/migrate {"step":"infer-memory-projects"} to backfill`,
|
|
10418
|
+
fixable: true
|
|
10419
|
+
});
|
|
10420
|
+
else checks.push({
|
|
10421
|
+
name: "memory-project-coverage",
|
|
10422
|
+
category: "memories",
|
|
10423
|
+
status: "fail",
|
|
10424
|
+
message: `${unscopedCount} of ${latestMemories.length} latest memories have no project scope — run POST /agentmemory/migrate {"step":"infer-memory-projects"} to backfill`,
|
|
10425
|
+
fixable: true
|
|
10426
|
+
});
|
|
10191
10427
|
if (memoryIssues === 0) checks.push({
|
|
10192
10428
|
name: "memories-ok",
|
|
10193
10429
|
category: "memories",
|
|
10194
10430
|
status: "pass",
|
|
10195
|
-
message: `All ${memories.length} memories are consistent`,
|
|
10431
|
+
message: `All ${memories.length} memories are structurally consistent`,
|
|
10196
10432
|
fixable: false
|
|
10197
10433
|
});
|
|
10198
10434
|
}
|
|
@@ -12746,7 +12982,7 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
12746
12982
|
const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
|
|
12747
12983
|
const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
|
|
12748
12984
|
const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
|
|
12749
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
12985
|
+
const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
|
|
12750
12986
|
const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
|
|
12751
12987
|
if (data?.dryRun) return {
|
|
12752
12988
|
success: true,
|
|
@@ -12783,6 +13019,8 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
12783
13019
|
await kv.delete(scope, candidate.memoryId);
|
|
12784
13020
|
await kv.delete(KV.retentionScores, candidate.memoryId);
|
|
12785
13021
|
await deleteAccessLog(kv, candidate.memoryId);
|
|
13022
|
+
getSearchIndex().remove(candidate.memoryId);
|
|
13023
|
+
vectorIndexRemove(candidate.memoryId);
|
|
12786
13024
|
evicted++;
|
|
12787
13025
|
evictedIds.push(candidate.memoryId);
|
|
12788
13026
|
if (resolvedSource === "semantic") evictedSemantic++;
|
|
@@ -12790,13 +13028,16 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
12790
13028
|
} catch {
|
|
12791
13029
|
continue;
|
|
12792
13030
|
}
|
|
12793
|
-
if (evicted > 0)
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
13031
|
+
if (evicted > 0) {
|
|
13032
|
+
await flushIndexSave();
|
|
13033
|
+
await recordAudit(kv, "delete", "mem::retention-evict", evictedIds, {
|
|
13034
|
+
threshold,
|
|
13035
|
+
evicted,
|
|
13036
|
+
evictedEpisodic,
|
|
13037
|
+
evictedSemantic,
|
|
13038
|
+
reason: "retention score below threshold"
|
|
13039
|
+
});
|
|
13040
|
+
}
|
|
12800
13041
|
logger.info("Retention-based eviction complete", {
|
|
12801
13042
|
evicted,
|
|
12802
13043
|
evictedEpisodic,
|
|
@@ -13701,122 +13942,355 @@ function renderViewerDocument() {
|
|
|
13701
13942
|
}
|
|
13702
13943
|
|
|
13703
13944
|
//#endregion
|
|
13704
|
-
//#region src/
|
|
13705
|
-
function
|
|
13706
|
-
|
|
13707
|
-
const
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13712
|
-
const
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
body: { error: "unauthorized" }
|
|
13716
|
-
};
|
|
13945
|
+
//#region src/viewer/server.ts
|
|
13946
|
+
function loadViewerFavicon() {
|
|
13947
|
+
const base = dirname(fileURLToPath(import.meta.url));
|
|
13948
|
+
const candidates = [
|
|
13949
|
+
join(base, "..", "src", "viewer", "favicon.svg"),
|
|
13950
|
+
join(base, "..", "viewer", "favicon.svg"),
|
|
13951
|
+
join(base, "viewer", "favicon.svg")
|
|
13952
|
+
];
|
|
13953
|
+
for (const path of candidates) try {
|
|
13954
|
+
return readFileSync(path);
|
|
13955
|
+
} catch {}
|
|
13717
13956
|
return null;
|
|
13718
13957
|
}
|
|
13719
|
-
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
13724
|
-
|
|
13958
|
+
const ALLOWED_ORIGINS = (process.env.VIEWER_ALLOWED_ORIGINS || "http://localhost:3111,http://localhost:3113,http://127.0.0.1:3111,http://127.0.0.1:3113").split(",").map((o) => o.trim());
|
|
13959
|
+
const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
|
|
13960
|
+
function buildAllowedHosts(origins, listenPort) {
|
|
13961
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
13962
|
+
for (const o of origins) try {
|
|
13963
|
+
const parsed = new URL(o);
|
|
13964
|
+
if (parsed.host) hosts.add(parsed.host.toLowerCase());
|
|
13965
|
+
} catch {}
|
|
13966
|
+
hosts.add(`localhost:${listenPort}`);
|
|
13967
|
+
hosts.add(`127.0.0.1:${listenPort}`);
|
|
13968
|
+
hosts.add(`[::1]:${listenPort}`);
|
|
13969
|
+
for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h);
|
|
13970
|
+
return hosts;
|
|
13725
13971
|
}
|
|
13726
|
-
function
|
|
13972
|
+
function isHostAllowed(headerHost, allowed) {
|
|
13973
|
+
if (typeof headerHost !== "string") return false;
|
|
13974
|
+
const lower = headerHost.toLowerCase().trim();
|
|
13975
|
+
if (!lower) return false;
|
|
13976
|
+
return allowed.has(lower);
|
|
13977
|
+
}
|
|
13978
|
+
function corsHeaders(req) {
|
|
13979
|
+
const origin = req.headers.origin || "";
|
|
13980
|
+
const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
|
|
13727
13981
|
return {
|
|
13728
|
-
|
|
13729
|
-
|
|
13982
|
+
"Access-Control-Allow-Origin": allowed,
|
|
13983
|
+
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
13984
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
13985
|
+
Vary: "Origin"
|
|
13730
13986
|
};
|
|
13731
13987
|
}
|
|
13732
|
-
function
|
|
13733
|
-
|
|
13734
|
-
|
|
13735
|
-
|
|
13736
|
-
|
|
13737
|
-
|
|
13988
|
+
function json(res, status, data, req) {
|
|
13989
|
+
const body = JSON.stringify(data);
|
|
13990
|
+
const cors = req ? corsHeaders(req) : {
|
|
13991
|
+
"Access-Control-Allow-Origin": ALLOWED_ORIGINS[0],
|
|
13992
|
+
Vary: "Origin"
|
|
13993
|
+
};
|
|
13994
|
+
res.writeHead(status, {
|
|
13995
|
+
...cors,
|
|
13996
|
+
"Content-Type": "application/json"
|
|
13738
13997
|
});
|
|
13998
|
+
res.end(body);
|
|
13739
13999
|
}
|
|
13740
|
-
function
|
|
13741
|
-
return
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
13745
|
-
|
|
14000
|
+
function readBody(req) {
|
|
14001
|
+
return new Promise((resolve, reject) => {
|
|
14002
|
+
let data = "";
|
|
14003
|
+
let size = 0;
|
|
14004
|
+
req.on("data", (chunk) => {
|
|
14005
|
+
size += chunk.length;
|
|
14006
|
+
if (size > 1e6) {
|
|
14007
|
+
req.destroy();
|
|
14008
|
+
reject(/* @__PURE__ */ new Error("too large"));
|
|
14009
|
+
return;
|
|
14010
|
+
}
|
|
14011
|
+
data += chunk.toString();
|
|
14012
|
+
});
|
|
14013
|
+
req.on("end", () => resolve(data));
|
|
14014
|
+
req.on("error", reject);
|
|
13746
14015
|
});
|
|
13747
14016
|
}
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
|
|
13751
|
-
|
|
13752
|
-
|
|
13753
|
-
function parseOptionalFiniteNumber(value) {
|
|
13754
|
-
if (value === void 0 || value === null) return void 0;
|
|
13755
|
-
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
13756
|
-
if (typeof value === "string") {
|
|
13757
|
-
const trimmed = value.trim();
|
|
13758
|
-
if (!trimmed) return void 0;
|
|
13759
|
-
const parsed = Number(trimmed);
|
|
13760
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
13761
|
-
}
|
|
13762
|
-
return null;
|
|
14017
|
+
const MAX_VIEWER_PORT_RETRIES = 10;
|
|
14018
|
+
let boundViewerPort = null;
|
|
14019
|
+
let viewerSkipped = false;
|
|
14020
|
+
function getBoundViewerPort() {
|
|
14021
|
+
return boundViewerPort;
|
|
13763
14022
|
}
|
|
13764
|
-
function
|
|
13765
|
-
|
|
13766
|
-
if (parsed === void 0 || parsed === null) return parsed;
|
|
13767
|
-
if (!Number.isInteger(parsed) || parsed < 1) return null;
|
|
13768
|
-
return parsed;
|
|
14023
|
+
function getViewerSkipped() {
|
|
14024
|
+
return viewerSkipped;
|
|
13769
14025
|
}
|
|
13770
|
-
function
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
}
|
|
13781
|
-
};
|
|
13782
|
-
return { action: "continue" };
|
|
13783
|
-
});
|
|
13784
|
-
sdk.registerFunction("api::liveness", async () => ({
|
|
13785
|
-
status_code: 200,
|
|
13786
|
-
body: {
|
|
13787
|
-
status: "ok",
|
|
13788
|
-
service: "agentmemory"
|
|
14026
|
+
function startViewerServer(port, _kv, _sdk, secret, restPort) {
|
|
14027
|
+
boundViewerPort = null;
|
|
14028
|
+
viewerSkipped = false;
|
|
14029
|
+
const resolvedRestPort = restPort ?? port - 2;
|
|
14030
|
+
const requestedPort = port;
|
|
14031
|
+
let allowedHosts = null;
|
|
14032
|
+
const server = createServer(async (req, res) => {
|
|
14033
|
+
if (!allowedHosts) {
|
|
14034
|
+
const addr = server.address();
|
|
14035
|
+
allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port);
|
|
13789
14036
|
}
|
|
13790
|
-
|
|
13791
|
-
|
|
13792
|
-
|
|
13793
|
-
|
|
13794
|
-
config: {
|
|
13795
|
-
api_path: "/agentmemory/livez",
|
|
13796
|
-
http_method: "GET"
|
|
14037
|
+
if (!isHostAllowed(req.headers.host, allowedHosts)) {
|
|
14038
|
+
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
14039
|
+
res.end("forbidden host");
|
|
14040
|
+
return;
|
|
13797
14041
|
}
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
const
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
14042
|
+
const raw = req.url || "/";
|
|
14043
|
+
const qIdx = raw.indexOf("?");
|
|
14044
|
+
const pathname = qIdx >= 0 ? raw.slice(0, qIdx) : raw;
|
|
14045
|
+
const qs = qIdx >= 0 ? raw.slice(qIdx + 1) : "";
|
|
14046
|
+
const method = req.method || "GET";
|
|
14047
|
+
if (method === "OPTIONS") {
|
|
14048
|
+
res.writeHead(204, {
|
|
14049
|
+
...corsHeaders(req),
|
|
14050
|
+
"Access-Control-Max-Age": "86400"
|
|
14051
|
+
});
|
|
14052
|
+
res.end();
|
|
14053
|
+
return;
|
|
14054
|
+
}
|
|
14055
|
+
if (method === "GET" && (pathname === "/" || pathname === "/viewer" || pathname === "/agentmemory/viewer")) {
|
|
14056
|
+
const rendered = renderViewerDocument();
|
|
14057
|
+
if (rendered.found) {
|
|
14058
|
+
res.writeHead(200, {
|
|
14059
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
14060
|
+
"Content-Security-Policy": rendered.csp,
|
|
14061
|
+
"Cache-Control": "no-cache"
|
|
14062
|
+
});
|
|
14063
|
+
res.end(rendered.html);
|
|
14064
|
+
return;
|
|
14065
|
+
}
|
|
14066
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
14067
|
+
res.end("viewer not found");
|
|
14068
|
+
return;
|
|
14069
|
+
}
|
|
14070
|
+
if (method === "GET" && pathname === "/favicon.svg") {
|
|
14071
|
+
const favicon = loadViewerFavicon();
|
|
14072
|
+
if (favicon) {
|
|
14073
|
+
res.writeHead(200, {
|
|
14074
|
+
"Content-Type": "image/svg+xml",
|
|
14075
|
+
"Cache-Control": "public, max-age=3600"
|
|
14076
|
+
});
|
|
14077
|
+
res.end(favicon);
|
|
14078
|
+
return;
|
|
14079
|
+
}
|
|
14080
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
14081
|
+
res.end("favicon not found");
|
|
14082
|
+
return;
|
|
14083
|
+
}
|
|
14084
|
+
try {
|
|
14085
|
+
await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret);
|
|
14086
|
+
} catch (err) {
|
|
14087
|
+
console.error(`[viewer] proxy error on ${method} ${pathname}:`, err);
|
|
14088
|
+
json(res, 502, { error: "upstream error" }, req);
|
|
14089
|
+
}
|
|
14090
|
+
});
|
|
14091
|
+
let attempt = 0;
|
|
14092
|
+
let currentPort = requestedPort;
|
|
14093
|
+
const tryListen = () => {
|
|
14094
|
+
server.listen(currentPort, "127.0.0.1");
|
|
14095
|
+
};
|
|
14096
|
+
server.on("listening", () => {
|
|
14097
|
+
const addr = server.address();
|
|
14098
|
+
boundViewerPort = addr && typeof addr === "object" && "port" in addr ? addr.port : currentPort;
|
|
14099
|
+
viewerSkipped = false;
|
|
14100
|
+
if (currentPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${currentPort}`);
|
|
14101
|
+
else console.log(`[agentmemory] Viewer started on http://localhost:${currentPort} (fallback from ${requestedPort})`);
|
|
14102
|
+
});
|
|
14103
|
+
server.on("error", (err) => {
|
|
14104
|
+
if (err.code === "EADDRINUSE" && attempt < MAX_VIEWER_PORT_RETRIES) {
|
|
14105
|
+
attempt++;
|
|
14106
|
+
currentPort = requestedPort + attempt;
|
|
14107
|
+
setImmediate(tryListen);
|
|
14108
|
+
return;
|
|
14109
|
+
}
|
|
14110
|
+
if (err.code === "EADDRINUSE") {
|
|
14111
|
+
boundViewerPort = null;
|
|
14112
|
+
viewerSkipped = true;
|
|
14113
|
+
console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
|
|
14114
|
+
} else {
|
|
14115
|
+
boundViewerPort = null;
|
|
14116
|
+
viewerSkipped = true;
|
|
14117
|
+
console.error(`[agentmemory] Viewer error:`, err.message);
|
|
14118
|
+
}
|
|
14119
|
+
});
|
|
14120
|
+
tryListen();
|
|
14121
|
+
return server;
|
|
14122
|
+
}
|
|
14123
|
+
async function proxyToRestApi(restPort, pathname, qs, method, req, res, secret) {
|
|
14124
|
+
const upstreamUrl = `http://127.0.0.1:${restPort}${pathname.startsWith("/agentmemory/") ? pathname : `/agentmemory${pathname.startsWith("/") ? pathname : "/" + pathname}`}${qs ? "?" + qs : ""}`;
|
|
14125
|
+
const headers = {};
|
|
14126
|
+
if (secret) headers["Authorization"] = `Bearer ${secret}`;
|
|
14127
|
+
const ct = req.headers["content-type"];
|
|
14128
|
+
if (ct) headers["Content-Type"] = ct;
|
|
14129
|
+
let body;
|
|
14130
|
+
if (method === "POST" || method === "PUT" || method === "DELETE" || method === "PATCH") body = await readBody(req);
|
|
14131
|
+
const controller = new AbortController();
|
|
14132
|
+
const fetchTimeout = setTimeout(() => controller.abort(), 1e4);
|
|
14133
|
+
let upstream;
|
|
14134
|
+
try {
|
|
14135
|
+
upstream = await fetch(upstreamUrl, {
|
|
14136
|
+
method,
|
|
14137
|
+
headers,
|
|
14138
|
+
body: body || void 0,
|
|
14139
|
+
signal: controller.signal
|
|
14140
|
+
});
|
|
14141
|
+
clearTimeout(fetchTimeout);
|
|
14142
|
+
} catch (err) {
|
|
14143
|
+
clearTimeout(fetchTimeout);
|
|
14144
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
14145
|
+
json(res, 504, { error: "upstream timeout" }, req);
|
|
14146
|
+
return;
|
|
14147
|
+
}
|
|
14148
|
+
throw err;
|
|
14149
|
+
}
|
|
14150
|
+
const cors = corsHeaders(req);
|
|
14151
|
+
const responseBody = await upstream.text();
|
|
14152
|
+
const responseHeaders = { ...cors };
|
|
14153
|
+
const upstreamCt = upstream.headers.get("content-type");
|
|
14154
|
+
if (upstreamCt) responseHeaders["Content-Type"] = upstreamCt;
|
|
14155
|
+
res.writeHead(upstream.status, responseHeaders);
|
|
14156
|
+
res.end(responseBody);
|
|
14157
|
+
}
|
|
14158
|
+
|
|
14159
|
+
//#endregion
|
|
14160
|
+
//#region src/triggers/api.ts
|
|
14161
|
+
function parseOptionalInt(raw) {
|
|
14162
|
+
if (raw === void 0 || raw === null || raw === "") return void 0;
|
|
14163
|
+
const n = typeof raw === "number" ? raw : parseInt(String(raw), 10);
|
|
14164
|
+
return Number.isFinite(n) ? n : void 0;
|
|
14165
|
+
}
|
|
14166
|
+
function checkAuth(req, secret) {
|
|
14167
|
+
if (!secret) return null;
|
|
14168
|
+
const auth = req.headers?.["authorization"] || req.headers?.["Authorization"];
|
|
14169
|
+
if (typeof auth !== "string" || !timingSafeCompare(auth, `Bearer ${secret}`)) return {
|
|
14170
|
+
status_code: 401,
|
|
14171
|
+
body: { error: "unauthorized" }
|
|
14172
|
+
};
|
|
14173
|
+
return null;
|
|
14174
|
+
}
|
|
14175
|
+
function requireConfiguredSecret(secret, feature) {
|
|
14176
|
+
if (secret) return null;
|
|
14177
|
+
return {
|
|
14178
|
+
status_code: 503,
|
|
14179
|
+
body: { error: `${feature} requires AGENTMEMORY_SECRET` }
|
|
14180
|
+
};
|
|
14181
|
+
}
|
|
14182
|
+
function flagDisabledResponse(opts) {
|
|
14183
|
+
return {
|
|
14184
|
+
status_code: 503,
|
|
14185
|
+
body: opts
|
|
14186
|
+
};
|
|
14187
|
+
}
|
|
14188
|
+
function graphDisabledResponse() {
|
|
14189
|
+
return flagDisabledResponse({
|
|
14190
|
+
error: "Knowledge graph not enabled",
|
|
14191
|
+
flag: "GRAPH_EXTRACTION_ENABLED",
|
|
14192
|
+
enableHow: "Set GRAPH_EXTRACTION_ENABLED=true and restart. Requires an LLM provider key.",
|
|
14193
|
+
docsHref: "https://github.com/rohitg00/agentmemory#knowledge-graph"
|
|
14194
|
+
});
|
|
14195
|
+
}
|
|
14196
|
+
function consolidationDisabledResponse() {
|
|
14197
|
+
return flagDisabledResponse({
|
|
14198
|
+
error: "Consolidation pipeline not enabled",
|
|
14199
|
+
flag: "CONSOLIDATION_ENABLED",
|
|
14200
|
+
enableHow: "Set CONSOLIDATION_ENABLED=true and restart. Requires an LLM provider key.",
|
|
14201
|
+
docsHref: "https://github.com/rohitg00/agentmemory#consolidation"
|
|
14202
|
+
});
|
|
14203
|
+
}
|
|
14204
|
+
function slotsDisabledResponse() {
|
|
14205
|
+
return flagDisabledResponse({
|
|
14206
|
+
error: "Memory slots not enabled",
|
|
14207
|
+
flag: "AGENTMEMORY_SLOTS",
|
|
14208
|
+
enableHow: "Set AGENTMEMORY_SLOTS=true (in ~/.agentmemory/.env or the shell) and restart.",
|
|
14209
|
+
docsHref: "https://github.com/rohitg00/agentmemory#memory-slots"
|
|
14210
|
+
});
|
|
14211
|
+
}
|
|
14212
|
+
function reflectDisabledResponse() {
|
|
14213
|
+
return flagDisabledResponse({
|
|
14214
|
+
error: "Slot reflection not enabled",
|
|
14215
|
+
flag: "AGENTMEMORY_REFLECT",
|
|
14216
|
+
enableHow: "Set AGENTMEMORY_REFLECT=true (in ~/.agentmemory/.env or the shell) and restart. Requires AGENTMEMORY_SLOTS=true.",
|
|
14217
|
+
docsHref: "https://github.com/rohitg00/agentmemory#memory-slots"
|
|
14218
|
+
});
|
|
14219
|
+
}
|
|
14220
|
+
function asNonEmptyString$1(value) {
|
|
14221
|
+
if (typeof value !== "string") return null;
|
|
14222
|
+
const trimmed = value.trim();
|
|
14223
|
+
return trimmed ? trimmed : null;
|
|
14224
|
+
}
|
|
14225
|
+
function parseOptionalFiniteNumber(value) {
|
|
14226
|
+
if (value === void 0 || value === null) return void 0;
|
|
14227
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
14228
|
+
if (typeof value === "string") {
|
|
14229
|
+
const trimmed = value.trim();
|
|
14230
|
+
if (!trimmed) return void 0;
|
|
14231
|
+
const parsed = Number(trimmed);
|
|
14232
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
14233
|
+
}
|
|
14234
|
+
return null;
|
|
14235
|
+
}
|
|
14236
|
+
function parseOptionalPositiveInt(value) {
|
|
14237
|
+
const parsed = parseOptionalFiniteNumber(value);
|
|
14238
|
+
if (parsed === void 0 || parsed === null) return parsed;
|
|
14239
|
+
if (!Number.isInteger(parsed) || parsed < 1) return null;
|
|
14240
|
+
return parsed;
|
|
14241
|
+
}
|
|
14242
|
+
function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
14243
|
+
sdk.registerFunction("middleware::api-auth", async (input) => {
|
|
14244
|
+
if (!secret) return { action: "continue" };
|
|
14245
|
+
const headers = input?.request?.headers || {};
|
|
14246
|
+
const auth = headers["authorization"] || headers["Authorization"];
|
|
14247
|
+
if (typeof auth !== "string" || !timingSafeCompare(auth, `Bearer ${secret}`)) return {
|
|
14248
|
+
action: "respond",
|
|
14249
|
+
response: {
|
|
14250
|
+
status_code: 401,
|
|
14251
|
+
body: { error: "unauthorized" }
|
|
14252
|
+
}
|
|
14253
|
+
};
|
|
14254
|
+
return { action: "continue" };
|
|
14255
|
+
});
|
|
14256
|
+
sdk.registerFunction("api::liveness", async () => ({
|
|
14257
|
+
status_code: 200,
|
|
14258
|
+
body: {
|
|
14259
|
+
status: "ok",
|
|
14260
|
+
service: "agentmemory",
|
|
14261
|
+
viewerPort: getBoundViewerPort(),
|
|
14262
|
+
viewerSkipped: getViewerSkipped()
|
|
14263
|
+
}
|
|
14264
|
+
}));
|
|
14265
|
+
sdk.registerTrigger({
|
|
14266
|
+
type: "http",
|
|
14267
|
+
function_id: "api::liveness",
|
|
14268
|
+
config: {
|
|
14269
|
+
api_path: "/agentmemory/livez",
|
|
14270
|
+
http_method: "GET"
|
|
14271
|
+
}
|
|
14272
|
+
});
|
|
14273
|
+
sdk.registerFunction("api::config-flags", async (req) => {
|
|
14274
|
+
const authErr = checkAuth(req, secret);
|
|
14275
|
+
if (authErr) return authErr;
|
|
14276
|
+
return {
|
|
14277
|
+
status_code: 200,
|
|
14278
|
+
body: {
|
|
14279
|
+
version: VERSION,
|
|
14280
|
+
provider: detectLlmProviderKind(),
|
|
14281
|
+
embeddingProvider: detectEmbeddingProvider() ? "embeddings" : "none",
|
|
14282
|
+
flags: [
|
|
14283
|
+
{
|
|
14284
|
+
key: "GRAPH_EXTRACTION_ENABLED",
|
|
14285
|
+
label: "Knowledge graph extraction",
|
|
14286
|
+
enabled: isGraphExtractionEnabled(),
|
|
14287
|
+
default: false,
|
|
14288
|
+
affects: ["Graph", "Dashboard"],
|
|
14289
|
+
needsLlm: true,
|
|
14290
|
+
description: "Extracts entities and relations from observations into a knowledge graph.",
|
|
14291
|
+
enableHow: "Set GRAPH_EXTRACTION_ENABLED=true and provide an LLM key, then restart.",
|
|
14292
|
+
docsHref: "https://github.com/rohitg00/agentmemory#knowledge-graph"
|
|
14293
|
+
},
|
|
13820
14294
|
{
|
|
13821
14295
|
key: "CONSOLIDATION_ENABLED",
|
|
13822
14296
|
label: "Memory consolidation",
|
|
@@ -13880,7 +14354,9 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
13880
14354
|
version: VERSION,
|
|
13881
14355
|
health: health || null,
|
|
13882
14356
|
functionMetrics,
|
|
13883
|
-
circuitBreaker
|
|
14357
|
+
circuitBreaker,
|
|
14358
|
+
viewerPort: getBoundViewerPort(),
|
|
14359
|
+
viewerSkipped: getViewerSkipped()
|
|
13884
14360
|
}
|
|
13885
14361
|
};
|
|
13886
14362
|
});
|
|
@@ -14134,6 +14610,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14134
14610
|
body: { error: "sessionId, project, and cwd are required non-empty strings" }
|
|
14135
14611
|
};
|
|
14136
14612
|
const title = typeof body.title === "string" ? body.title.trim() : void 0;
|
|
14613
|
+
const agentId = (typeof body.agentId === "string" && body.agentId.trim().length > 0 ? body.agentId.trim().slice(0, 128) : void 0) ?? getAgentId();
|
|
14137
14614
|
const session = {
|
|
14138
14615
|
id: sessionId,
|
|
14139
14616
|
project,
|
|
@@ -14142,7 +14619,8 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14142
14619
|
status: "active",
|
|
14143
14620
|
observationCount: 0,
|
|
14144
14621
|
...title ? { summary: title.slice(0, 200) } : {},
|
|
14145
|
-
...title ? { firstPrompt: title.slice(0, 200) } : {}
|
|
14622
|
+
...title ? { firstPrompt: title.slice(0, 200) } : {},
|
|
14623
|
+
...agentId ? { agentId } : {}
|
|
14146
14624
|
};
|
|
14147
14625
|
await kv.set(KV.sessions, sessionId, session);
|
|
14148
14626
|
return {
|
|
@@ -14183,6 +14661,14 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14183
14661
|
path: "status",
|
|
14184
14662
|
value: "completed"
|
|
14185
14663
|
}]);
|
|
14664
|
+
try {
|
|
14665
|
+
sdk.triggerVoid("event::session::stopped", { sessionId });
|
|
14666
|
+
} catch (err) {
|
|
14667
|
+
logger.warn("event::session::stopped triggerVoid failed", {
|
|
14668
|
+
sessionId,
|
|
14669
|
+
error: err instanceof Error ? err.message : String(err)
|
|
14670
|
+
});
|
|
14671
|
+
}
|
|
14186
14672
|
return {
|
|
14187
14673
|
status_code: 200,
|
|
14188
14674
|
body: { success: true }
|
|
@@ -14329,9 +14815,13 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14329
14815
|
sdk.registerFunction("api::sessions", async (req) => {
|
|
14330
14816
|
const authErr = checkAuth(req, secret);
|
|
14331
14817
|
if (authErr) return authErr;
|
|
14818
|
+
const sessions = await kv.list(KV.sessions);
|
|
14819
|
+
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
14820
|
+
const wildcardAgent = normalizedAgentId === "*";
|
|
14821
|
+
const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
14332
14822
|
return {
|
|
14333
14823
|
status_code: 200,
|
|
14334
|
-
body: { sessions:
|
|
14824
|
+
body: { sessions: filterAgentId ? sessions.filter((s) => s.agentId === filterAgentId) : sessions }
|
|
14335
14825
|
};
|
|
14336
14826
|
});
|
|
14337
14827
|
sdk.registerTrigger({
|
|
@@ -14350,9 +14840,13 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14350
14840
|
status_code: 400,
|
|
14351
14841
|
body: { error: "sessionId required" }
|
|
14352
14842
|
};
|
|
14843
|
+
const observations = await kv.list(KV.observations(sessionId));
|
|
14844
|
+
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
14845
|
+
const wildcardAgent = normalizedAgentId === "*";
|
|
14846
|
+
const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
14353
14847
|
return {
|
|
14354
14848
|
status_code: 200,
|
|
14355
|
-
body: { observations:
|
|
14849
|
+
body: { observations: filterAgentId ? observations.filter((o) => o.agentId === filterAgentId) : observations }
|
|
14356
14850
|
};
|
|
14357
14851
|
});
|
|
14358
14852
|
sdk.registerTrigger({
|
|
@@ -14393,11 +14887,21 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14393
14887
|
status_code: 400,
|
|
14394
14888
|
body: { error: "terms must be an array of strings" }
|
|
14395
14889
|
};
|
|
14890
|
+
if (req.body.project !== void 0 && (typeof req.body.project !== "string" || !req.body.project.trim())) return {
|
|
14891
|
+
status_code: 400,
|
|
14892
|
+
body: { error: "project must be a non-empty string" }
|
|
14893
|
+
};
|
|
14396
14894
|
return {
|
|
14397
14895
|
status_code: 200,
|
|
14398
14896
|
body: await sdk.trigger({
|
|
14399
14897
|
function_id: "mem::enrich",
|
|
14400
|
-
payload:
|
|
14898
|
+
payload: {
|
|
14899
|
+
sessionId: req.body.sessionId,
|
|
14900
|
+
files: req.body.files,
|
|
14901
|
+
...req.body.terms !== void 0 && { terms: req.body.terms },
|
|
14902
|
+
...req.body.toolName !== void 0 && { toolName: req.body.toolName },
|
|
14903
|
+
...req.body.project !== void 0 && { project: req.body.project }
|
|
14904
|
+
}
|
|
14401
14905
|
})
|
|
14402
14906
|
};
|
|
14403
14907
|
});
|
|
@@ -14416,11 +14920,23 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14416
14920
|
status_code: 400,
|
|
14417
14921
|
body: { error: "content is required" }
|
|
14418
14922
|
};
|
|
14923
|
+
if (req.body.project !== void 0 && (typeof req.body.project !== "string" || !req.body.project.trim())) return {
|
|
14924
|
+
status_code: 400,
|
|
14925
|
+
body: { error: "project must be a non-empty string" }
|
|
14926
|
+
};
|
|
14419
14927
|
return {
|
|
14420
14928
|
status_code: 201,
|
|
14421
14929
|
body: await sdk.trigger({
|
|
14422
14930
|
function_id: "mem::remember",
|
|
14423
|
-
payload:
|
|
14931
|
+
payload: {
|
|
14932
|
+
content: req.body.content,
|
|
14933
|
+
...req.body.type !== void 0 && { type: req.body.type },
|
|
14934
|
+
...req.body.concepts !== void 0 && { concepts: req.body.concepts },
|
|
14935
|
+
...req.body.files !== void 0 && { files: req.body.files },
|
|
14936
|
+
...req.body.ttlDays !== void 0 && { ttlDays: req.body.ttlDays },
|
|
14937
|
+
...req.body.sourceObservationIds !== void 0 && { sourceObservationIds: req.body.sourceObservationIds },
|
|
14938
|
+
...req.body.project !== void 0 && { project: req.body.project }
|
|
14939
|
+
}
|
|
14424
14940
|
})
|
|
14425
14941
|
};
|
|
14426
14942
|
});
|
|
@@ -14515,15 +15031,21 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14515
15031
|
sdk.registerFunction("api::migrate", async (req) => {
|
|
14516
15032
|
const authErr = checkAuth(req, secret);
|
|
14517
15033
|
if (authErr) return authErr;
|
|
14518
|
-
|
|
15034
|
+
const hasStep = typeof req.body?.step === "string" && req.body.step.trim().length > 0;
|
|
15035
|
+
const hasDbPath = typeof req.body?.dbPath === "string" && req.body.dbPath.trim().length > 0;
|
|
15036
|
+
if (!hasStep && !hasDbPath) return {
|
|
14519
15037
|
status_code: 400,
|
|
14520
|
-
body: { error: "dbPath is required" }
|
|
15038
|
+
body: { error: "Either step (string) or dbPath (string) is required" }
|
|
14521
15039
|
};
|
|
14522
15040
|
return {
|
|
14523
15041
|
status_code: 200,
|
|
14524
15042
|
body: await sdk.trigger({
|
|
14525
15043
|
function_id: "mem::migrate",
|
|
14526
|
-
payload:
|
|
15044
|
+
payload: {
|
|
15045
|
+
...req.body.step !== void 0 && { step: req.body.step },
|
|
15046
|
+
...req.body.dbPath !== void 0 && { dbPath: req.body.dbPath },
|
|
15047
|
+
...req.body.dryRun !== void 0 && { dryRun: req.body.dryRun }
|
|
15048
|
+
}
|
|
14527
15049
|
})
|
|
14528
15050
|
};
|
|
14529
15051
|
});
|
|
@@ -14628,11 +15150,22 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14628
15150
|
sdk.registerFunction("api::export", async (req) => {
|
|
14629
15151
|
const authErr = checkAuth(req, secret);
|
|
14630
15152
|
if (authErr) return authErr;
|
|
15153
|
+
const rawMax = req.query_params?.["maxSessions"];
|
|
15154
|
+
const rawOffset = req.query_params?.["offset"];
|
|
15155
|
+
const payload = {};
|
|
15156
|
+
if (typeof rawMax === "string") {
|
|
15157
|
+
const n = Number(rawMax);
|
|
15158
|
+
if (Number.isInteger(n) && n > 0) payload.maxSessions = n;
|
|
15159
|
+
}
|
|
15160
|
+
if (typeof rawOffset === "string") {
|
|
15161
|
+
const n = Number(rawOffset);
|
|
15162
|
+
if (Number.isInteger(n) && n >= 0) payload.offset = n;
|
|
15163
|
+
}
|
|
14631
15164
|
return {
|
|
14632
15165
|
status_code: 200,
|
|
14633
15166
|
body: await sdk.trigger({
|
|
14634
15167
|
function_id: "mem::export",
|
|
14635
|
-
payload
|
|
15168
|
+
payload
|
|
14636
15169
|
})
|
|
14637
15170
|
};
|
|
14638
15171
|
});
|
|
@@ -14858,6 +15391,63 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14858
15391
|
http_method: "POST"
|
|
14859
15392
|
}
|
|
14860
15393
|
});
|
|
15394
|
+
sdk.registerFunction("api::graph-build", async (req) => {
|
|
15395
|
+
const authErr = checkAuth(req, secret);
|
|
15396
|
+
if (authErr) return authErr;
|
|
15397
|
+
const batchSize = Math.max(1, Math.min(100, Number(req.body?.batchSize) || 25));
|
|
15398
|
+
try {
|
|
15399
|
+
const sessions = await kv.list(KV.sessions);
|
|
15400
|
+
let totalNodes = 0;
|
|
15401
|
+
let totalEdges = 0;
|
|
15402
|
+
let batchesRun = 0;
|
|
15403
|
+
for (const session of sessions) {
|
|
15404
|
+
const sid = session?.id;
|
|
15405
|
+
if (typeof sid !== "string" || sid.length === 0) continue;
|
|
15406
|
+
const compressed = (await kv.list(KV.observations(sid))).filter((o) => o && typeof o.title === "string" && o.title.length > 0);
|
|
15407
|
+
if (compressed.length === 0) continue;
|
|
15408
|
+
for (let i = 0; i < compressed.length; i += batchSize) {
|
|
15409
|
+
const batch = compressed.slice(i, i + batchSize);
|
|
15410
|
+
try {
|
|
15411
|
+
const result = await sdk.trigger({
|
|
15412
|
+
function_id: "mem::graph-extract",
|
|
15413
|
+
payload: { observations: batch }
|
|
15414
|
+
});
|
|
15415
|
+
if (result?.success) {
|
|
15416
|
+
totalNodes += Number(result.nodesAdded) || 0;
|
|
15417
|
+
totalEdges += Number(result.edgesAdded) || 0;
|
|
15418
|
+
}
|
|
15419
|
+
batchesRun++;
|
|
15420
|
+
} catch (err) {
|
|
15421
|
+
logger.warn("graph-build batch failed", {
|
|
15422
|
+
sessionId: sid,
|
|
15423
|
+
batchIndex: Math.floor(i / batchSize),
|
|
15424
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15425
|
+
});
|
|
15426
|
+
}
|
|
15427
|
+
}
|
|
15428
|
+
}
|
|
15429
|
+
return {
|
|
15430
|
+
status_code: 200,
|
|
15431
|
+
body: {
|
|
15432
|
+
success: true,
|
|
15433
|
+
sessions: sessions.length,
|
|
15434
|
+
batches: batchesRun,
|
|
15435
|
+
nodes: totalNodes,
|
|
15436
|
+
edges: totalEdges
|
|
15437
|
+
}
|
|
15438
|
+
};
|
|
15439
|
+
} catch {
|
|
15440
|
+
return graphDisabledResponse();
|
|
15441
|
+
}
|
|
15442
|
+
});
|
|
15443
|
+
sdk.registerTrigger({
|
|
15444
|
+
type: "http",
|
|
15445
|
+
function_id: "api::graph-build",
|
|
15446
|
+
config: {
|
|
15447
|
+
api_path: "/agentmemory/graph/build",
|
|
15448
|
+
http_method: "POST"
|
|
15449
|
+
}
|
|
15450
|
+
});
|
|
14861
15451
|
sdk.registerFunction("api::consolidate-pipeline", async (req) => {
|
|
14862
15452
|
const authErr = checkAuth(req, secret);
|
|
14863
15453
|
if (authErr) return authErr;
|
|
@@ -15118,9 +15708,35 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15118
15708
|
const authErr = checkAuth(req, secret);
|
|
15119
15709
|
if (authErr) return authErr;
|
|
15120
15710
|
const memories = await kv.list(KV.memories);
|
|
15711
|
+
const latest = req.query_params?.["latest"] === "true";
|
|
15712
|
+
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
15713
|
+
const wildcardAgent = normalizedAgentId === "*";
|
|
15714
|
+
const explicitAgentId = normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0;
|
|
15715
|
+
const includeOrphans = req.query_params?.["includeOrphans"] === "true";
|
|
15716
|
+
const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
15717
|
+
let filtered = latest ? memories.filter((m) => m.isLatest) : memories;
|
|
15718
|
+
if (filterAgentId) filtered = filtered.filter((m) => m.agentId === filterAgentId || includeOrphans && m.agentId === void 0);
|
|
15719
|
+
if (req.query_params?.["count"] === "true") return {
|
|
15720
|
+
status_code: 200,
|
|
15721
|
+
body: {
|
|
15722
|
+
total: filtered.length,
|
|
15723
|
+
latestCount: filtered.filter((m) => m.isLatest).length
|
|
15724
|
+
}
|
|
15725
|
+
};
|
|
15726
|
+
const rawLimit = req.query_params?.["limit"];
|
|
15727
|
+
const rawOffset = req.query_params?.["offset"];
|
|
15728
|
+
const parsedLimit = typeof rawLimit === "string" ? Number(rawLimit) : NaN;
|
|
15729
|
+
const parsedOffset = typeof rawOffset === "string" ? Number(rawOffset) : NaN;
|
|
15730
|
+
const limit = Number.isInteger(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 5e3) : void 0;
|
|
15731
|
+
const offset = Number.isInteger(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0;
|
|
15121
15732
|
return {
|
|
15122
15733
|
status_code: 200,
|
|
15123
|
-
body: {
|
|
15734
|
+
body: {
|
|
15735
|
+
memories: limit !== void 0 ? filtered.slice(offset, offset + limit) : filtered,
|
|
15736
|
+
total: filtered.length,
|
|
15737
|
+
offset,
|
|
15738
|
+
limit: limit ?? null
|
|
15739
|
+
}
|
|
15124
15740
|
};
|
|
15125
15741
|
});
|
|
15126
15742
|
sdk.registerTrigger({
|
|
@@ -15289,6 +15905,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15289
15905
|
sdk.registerFunction("api::slot-list", async (req) => {
|
|
15290
15906
|
const authErr = checkAuth(req, secret);
|
|
15291
15907
|
if (authErr) return authErr;
|
|
15908
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
15292
15909
|
return {
|
|
15293
15910
|
status_code: 200,
|
|
15294
15911
|
body: await sdk.trigger({
|
|
@@ -15308,6 +15925,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15308
15925
|
sdk.registerFunction("api::slot-get", async (req) => {
|
|
15309
15926
|
const authErr = checkAuth(req, secret);
|
|
15310
15927
|
if (authErr) return authErr;
|
|
15928
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
15311
15929
|
const label = asNonEmptyString$1(req.query_params?.["label"]);
|
|
15312
15930
|
if (!label) return {
|
|
15313
15931
|
status_code: 400,
|
|
@@ -15338,6 +15956,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15338
15956
|
sdk.registerFunction("api::slot-create", async (req) => {
|
|
15339
15957
|
const authErr = checkAuth(req, secret);
|
|
15340
15958
|
if (authErr) return authErr;
|
|
15959
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
15341
15960
|
const body = req.body ?? {};
|
|
15342
15961
|
const label = asNonEmptyString$1(body["label"]);
|
|
15343
15962
|
if (!label) return {
|
|
@@ -15400,6 +16019,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15400
16019
|
sdk.registerFunction("api::slot-append", async (req) => {
|
|
15401
16020
|
const authErr = checkAuth(req, secret);
|
|
15402
16021
|
if (authErr) return authErr;
|
|
16022
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
15403
16023
|
const body = req.body ?? {};
|
|
15404
16024
|
const label = asNonEmptyString$1(body["label"]);
|
|
15405
16025
|
const text = typeof body["text"] === "string" ? body["text"] : null;
|
|
@@ -15439,6 +16059,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15439
16059
|
sdk.registerFunction("api::slot-replace", async (req) => {
|
|
15440
16060
|
const authErr = checkAuth(req, secret);
|
|
15441
16061
|
if (authErr) return authErr;
|
|
16062
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
15442
16063
|
const body = req.body ?? {};
|
|
15443
16064
|
const label = asNonEmptyString$1(body["label"]);
|
|
15444
16065
|
const content = body["content"];
|
|
@@ -15478,6 +16099,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15478
16099
|
sdk.registerFunction("api::slot-delete", async (req) => {
|
|
15479
16100
|
const authErr = checkAuth(req, secret);
|
|
15480
16101
|
if (authErr) return authErr;
|
|
16102
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
15481
16103
|
const label = asNonEmptyString$1(req.query_params?.["label"]);
|
|
15482
16104
|
if (!label) return {
|
|
15483
16105
|
status_code: 400,
|
|
@@ -15508,6 +16130,8 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15508
16130
|
sdk.registerFunction("api::slot-reflect", async (req) => {
|
|
15509
16131
|
const authErr = checkAuth(req, secret);
|
|
15510
16132
|
if (authErr) return authErr;
|
|
16133
|
+
if (!isSlotsEnabled()) return slotsDisabledResponse();
|
|
16134
|
+
if (!isReflectEnabled()) return reflectDisabledResponse();
|
|
15511
16135
|
const body = req.body ?? {};
|
|
15512
16136
|
const sessionId = asNonEmptyString$1(body["sessionId"]);
|
|
15513
16137
|
if (!sessionId) return {
|
|
@@ -17210,13 +17834,15 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
17210
17834
|
const type = args.type || "fact";
|
|
17211
17835
|
const concepts = typeof args.concepts === "string" ? args.concepts.split(",").map((c) => c.trim()).filter(Boolean) : [];
|
|
17212
17836
|
const files = typeof args.files === "string" ? args.files.split(",").map((f) => f.trim()).filter(Boolean) : [];
|
|
17837
|
+
const project = typeof args.project === "string" && args.project.trim().length > 0 ? args.project.trim() : void 0;
|
|
17213
17838
|
const result = await sdk.trigger({
|
|
17214
17839
|
function_id: "mem::remember",
|
|
17215
17840
|
payload: {
|
|
17216
17841
|
content: args.content,
|
|
17217
17842
|
type,
|
|
17218
17843
|
concepts,
|
|
17219
|
-
files
|
|
17844
|
+
files,
|
|
17845
|
+
...project !== void 0 && { project }
|
|
17220
17846
|
}
|
|
17221
17847
|
});
|
|
17222
17848
|
return {
|
|
@@ -18733,201 +19359,6 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
18733
19359
|
});
|
|
18734
19360
|
}
|
|
18735
19361
|
|
|
18736
|
-
//#endregion
|
|
18737
|
-
//#region src/viewer/server.ts
|
|
18738
|
-
function loadViewerFavicon() {
|
|
18739
|
-
const base = dirname(fileURLToPath(import.meta.url));
|
|
18740
|
-
const candidates = [
|
|
18741
|
-
join(base, "..", "src", "viewer", "favicon.svg"),
|
|
18742
|
-
join(base, "..", "viewer", "favicon.svg"),
|
|
18743
|
-
join(base, "viewer", "favicon.svg")
|
|
18744
|
-
];
|
|
18745
|
-
for (const path of candidates) try {
|
|
18746
|
-
return readFileSync(path);
|
|
18747
|
-
} catch {}
|
|
18748
|
-
return null;
|
|
18749
|
-
}
|
|
18750
|
-
const ALLOWED_ORIGINS = (process.env.VIEWER_ALLOWED_ORIGINS || "http://localhost:3111,http://localhost:3113,http://127.0.0.1:3111,http://127.0.0.1:3113").split(",").map((o) => o.trim());
|
|
18751
|
-
const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
|
|
18752
|
-
function buildAllowedHosts(origins, listenPort) {
|
|
18753
|
-
const hosts = /* @__PURE__ */ new Set();
|
|
18754
|
-
for (const o of origins) try {
|
|
18755
|
-
const parsed = new URL(o);
|
|
18756
|
-
if (parsed.host) hosts.add(parsed.host.toLowerCase());
|
|
18757
|
-
} catch {}
|
|
18758
|
-
hosts.add(`localhost:${listenPort}`);
|
|
18759
|
-
hosts.add(`127.0.0.1:${listenPort}`);
|
|
18760
|
-
hosts.add(`[::1]:${listenPort}`);
|
|
18761
|
-
for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h);
|
|
18762
|
-
return hosts;
|
|
18763
|
-
}
|
|
18764
|
-
function isHostAllowed(headerHost, allowed) {
|
|
18765
|
-
if (typeof headerHost !== "string") return false;
|
|
18766
|
-
const lower = headerHost.toLowerCase().trim();
|
|
18767
|
-
if (!lower) return false;
|
|
18768
|
-
return allowed.has(lower);
|
|
18769
|
-
}
|
|
18770
|
-
function corsHeaders(req) {
|
|
18771
|
-
const origin = req.headers.origin || "";
|
|
18772
|
-
const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
|
|
18773
|
-
return {
|
|
18774
|
-
"Access-Control-Allow-Origin": allowed,
|
|
18775
|
-
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
18776
|
-
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
18777
|
-
Vary: "Origin"
|
|
18778
|
-
};
|
|
18779
|
-
}
|
|
18780
|
-
function json(res, status, data, req) {
|
|
18781
|
-
const body = JSON.stringify(data);
|
|
18782
|
-
const cors = req ? corsHeaders(req) : {
|
|
18783
|
-
"Access-Control-Allow-Origin": ALLOWED_ORIGINS[0],
|
|
18784
|
-
Vary: "Origin"
|
|
18785
|
-
};
|
|
18786
|
-
res.writeHead(status, {
|
|
18787
|
-
...cors,
|
|
18788
|
-
"Content-Type": "application/json"
|
|
18789
|
-
});
|
|
18790
|
-
res.end(body);
|
|
18791
|
-
}
|
|
18792
|
-
function readBody(req) {
|
|
18793
|
-
return new Promise((resolve, reject) => {
|
|
18794
|
-
let data = "";
|
|
18795
|
-
let size = 0;
|
|
18796
|
-
req.on("data", (chunk) => {
|
|
18797
|
-
size += chunk.length;
|
|
18798
|
-
if (size > 1e6) {
|
|
18799
|
-
req.destroy();
|
|
18800
|
-
reject(/* @__PURE__ */ new Error("too large"));
|
|
18801
|
-
return;
|
|
18802
|
-
}
|
|
18803
|
-
data += chunk.toString();
|
|
18804
|
-
});
|
|
18805
|
-
req.on("end", () => resolve(data));
|
|
18806
|
-
req.on("error", reject);
|
|
18807
|
-
});
|
|
18808
|
-
}
|
|
18809
|
-
const MAX_VIEWER_PORT_RETRIES = 10;
|
|
18810
|
-
function startViewerServer(port, _kv, _sdk, secret, restPort) {
|
|
18811
|
-
const resolvedRestPort = restPort ?? port - 2;
|
|
18812
|
-
const requestedPort = port;
|
|
18813
|
-
let allowedHosts = null;
|
|
18814
|
-
const server = createServer(async (req, res) => {
|
|
18815
|
-
if (!allowedHosts) {
|
|
18816
|
-
const addr = server.address();
|
|
18817
|
-
allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port);
|
|
18818
|
-
}
|
|
18819
|
-
if (!isHostAllowed(req.headers.host, allowedHosts)) {
|
|
18820
|
-
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
18821
|
-
res.end("forbidden host");
|
|
18822
|
-
return;
|
|
18823
|
-
}
|
|
18824
|
-
const raw = req.url || "/";
|
|
18825
|
-
const qIdx = raw.indexOf("?");
|
|
18826
|
-
const pathname = qIdx >= 0 ? raw.slice(0, qIdx) : raw;
|
|
18827
|
-
const qs = qIdx >= 0 ? raw.slice(qIdx + 1) : "";
|
|
18828
|
-
const method = req.method || "GET";
|
|
18829
|
-
if (method === "OPTIONS") {
|
|
18830
|
-
res.writeHead(204, {
|
|
18831
|
-
...corsHeaders(req),
|
|
18832
|
-
"Access-Control-Max-Age": "86400"
|
|
18833
|
-
});
|
|
18834
|
-
res.end();
|
|
18835
|
-
return;
|
|
18836
|
-
}
|
|
18837
|
-
if (method === "GET" && (pathname === "/" || pathname === "/viewer" || pathname === "/agentmemory/viewer")) {
|
|
18838
|
-
const rendered = renderViewerDocument();
|
|
18839
|
-
if (rendered.found) {
|
|
18840
|
-
res.writeHead(200, {
|
|
18841
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
18842
|
-
"Content-Security-Policy": rendered.csp,
|
|
18843
|
-
"Cache-Control": "no-cache"
|
|
18844
|
-
});
|
|
18845
|
-
res.end(rendered.html);
|
|
18846
|
-
return;
|
|
18847
|
-
}
|
|
18848
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
18849
|
-
res.end("viewer not found");
|
|
18850
|
-
return;
|
|
18851
|
-
}
|
|
18852
|
-
if (method === "GET" && pathname === "/favicon.svg") {
|
|
18853
|
-
const favicon = loadViewerFavicon();
|
|
18854
|
-
if (favicon) {
|
|
18855
|
-
res.writeHead(200, {
|
|
18856
|
-
"Content-Type": "image/svg+xml",
|
|
18857
|
-
"Cache-Control": "public, max-age=3600"
|
|
18858
|
-
});
|
|
18859
|
-
res.end(favicon);
|
|
18860
|
-
return;
|
|
18861
|
-
}
|
|
18862
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
18863
|
-
res.end("favicon not found");
|
|
18864
|
-
return;
|
|
18865
|
-
}
|
|
18866
|
-
try {
|
|
18867
|
-
await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret);
|
|
18868
|
-
} catch (err) {
|
|
18869
|
-
console.error(`[viewer] proxy error on ${method} ${pathname}:`, err);
|
|
18870
|
-
json(res, 502, { error: "upstream error" }, req);
|
|
18871
|
-
}
|
|
18872
|
-
});
|
|
18873
|
-
let attempt = 0;
|
|
18874
|
-
let currentPort = requestedPort;
|
|
18875
|
-
const tryListen = () => {
|
|
18876
|
-
server.listen(currentPort, "127.0.0.1");
|
|
18877
|
-
};
|
|
18878
|
-
server.on("listening", () => {
|
|
18879
|
-
if (currentPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${currentPort}`);
|
|
18880
|
-
else console.log(`[agentmemory] Viewer started on http://localhost:${currentPort} (fallback from ${requestedPort})`);
|
|
18881
|
-
});
|
|
18882
|
-
server.on("error", (err) => {
|
|
18883
|
-
if (err.code === "EADDRINUSE" && attempt < MAX_VIEWER_PORT_RETRIES) {
|
|
18884
|
-
attempt++;
|
|
18885
|
-
currentPort = requestedPort + attempt;
|
|
18886
|
-
setImmediate(tryListen);
|
|
18887
|
-
return;
|
|
18888
|
-
}
|
|
18889
|
-
if (err.code === "EADDRINUSE") console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
|
|
18890
|
-
else console.error(`[agentmemory] Viewer error:`, err.message);
|
|
18891
|
-
});
|
|
18892
|
-
tryListen();
|
|
18893
|
-
return server;
|
|
18894
|
-
}
|
|
18895
|
-
async function proxyToRestApi(restPort, pathname, qs, method, req, res, secret) {
|
|
18896
|
-
const upstreamUrl = `http://127.0.0.1:${restPort}${pathname.startsWith("/agentmemory/") ? pathname : `/agentmemory${pathname.startsWith("/") ? pathname : "/" + pathname}`}${qs ? "?" + qs : ""}`;
|
|
18897
|
-
const headers = {};
|
|
18898
|
-
if (secret) headers["Authorization"] = `Bearer ${secret}`;
|
|
18899
|
-
const ct = req.headers["content-type"];
|
|
18900
|
-
if (ct) headers["Content-Type"] = ct;
|
|
18901
|
-
let body;
|
|
18902
|
-
if (method === "POST" || method === "PUT" || method === "DELETE" || method === "PATCH") body = await readBody(req);
|
|
18903
|
-
const controller = new AbortController();
|
|
18904
|
-
const fetchTimeout = setTimeout(() => controller.abort(), 1e4);
|
|
18905
|
-
let upstream;
|
|
18906
|
-
try {
|
|
18907
|
-
upstream = await fetch(upstreamUrl, {
|
|
18908
|
-
method,
|
|
18909
|
-
headers,
|
|
18910
|
-
body: body || void 0,
|
|
18911
|
-
signal: controller.signal
|
|
18912
|
-
});
|
|
18913
|
-
clearTimeout(fetchTimeout);
|
|
18914
|
-
} catch (err) {
|
|
18915
|
-
clearTimeout(fetchTimeout);
|
|
18916
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
18917
|
-
json(res, 504, { error: "upstream timeout" }, req);
|
|
18918
|
-
return;
|
|
18919
|
-
}
|
|
18920
|
-
throw err;
|
|
18921
|
-
}
|
|
18922
|
-
const cors = corsHeaders(req);
|
|
18923
|
-
const responseBody = await upstream.text();
|
|
18924
|
-
const responseHeaders = { ...cors };
|
|
18925
|
-
const upstreamCt = upstream.headers.get("content-type");
|
|
18926
|
-
if (upstreamCt) responseHeaders["Content-Type"] = upstreamCt;
|
|
18927
|
-
res.writeHead(upstream.status, responseHeaders);
|
|
18928
|
-
res.end(responseBody);
|
|
18929
|
-
}
|
|
18930
|
-
|
|
18931
19362
|
//#endregion
|
|
18932
19363
|
//#region src/eval/metrics-store.ts
|
|
18933
19364
|
var MetricsStore = class {
|
|
@@ -19065,6 +19496,21 @@ function initMetrics(getMeter) {
|
|
|
19065
19496
|
|
|
19066
19497
|
//#endregion
|
|
19067
19498
|
//#region src/index.ts
|
|
19499
|
+
function workerPidfilePath() {
|
|
19500
|
+
return join(homedir(), ".agentmemory", "worker.pid");
|
|
19501
|
+
}
|
|
19502
|
+
function writeWorkerPidfile() {
|
|
19503
|
+
try {
|
|
19504
|
+
const p = workerPidfilePath();
|
|
19505
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
19506
|
+
writeFileSync(p, `${process.pid}\n`, { encoding: "utf-8" });
|
|
19507
|
+
} catch {}
|
|
19508
|
+
}
|
|
19509
|
+
function clearWorkerPidfile() {
|
|
19510
|
+
try {
|
|
19511
|
+
unlinkSync(workerPidfilePath());
|
|
19512
|
+
} catch {}
|
|
19513
|
+
}
|
|
19068
19514
|
function hasGetMeter(sdk) {
|
|
19069
19515
|
return typeof sdk === "object" && sdk !== null && "getMeter" in sdk && typeof sdk.getMeter === "function";
|
|
19070
19516
|
}
|
|
@@ -19105,6 +19551,7 @@ async function main() {
|
|
|
19105
19551
|
framework: "iii-sdk"
|
|
19106
19552
|
}
|
|
19107
19553
|
});
|
|
19554
|
+
writeWorkerPidfile();
|
|
19108
19555
|
const kv = new StateKV(sdk);
|
|
19109
19556
|
const secret = getEnvVar("AGENTMEMORY_SECRET");
|
|
19110
19557
|
const metricsStore = new MetricsStore(kv);
|
|
@@ -19200,6 +19647,7 @@ async function main() {
|
|
|
19200
19647
|
registerMcpEndpoints(sdk, kv, secret);
|
|
19201
19648
|
const healthMonitor = registerHealthMonitor(sdk, kv);
|
|
19202
19649
|
const indexPersistence = new IndexPersistence(kv, bm25Index, vectorIndex);
|
|
19650
|
+
setIndexPersistence(indexPersistence);
|
|
19203
19651
|
const loaded = await indexPersistence.load().catch((err) => {
|
|
19204
19652
|
console.warn(`[agentmemory] Failed to load persisted index:`, err);
|
|
19205
19653
|
return null;
|
|
@@ -19241,7 +19689,7 @@ async function main() {
|
|
|
19241
19689
|
if (bm25Index.has(memory.id)) continue;
|
|
19242
19690
|
bm25Index.add({
|
|
19243
19691
|
id: memory.id,
|
|
19244
|
-
sessionId: memory.sessionIds[0] ?? "memory",
|
|
19692
|
+
sessionId: memory.sessionIds?.[0] ?? "memory",
|
|
19245
19693
|
timestamp: memory.createdAt,
|
|
19246
19694
|
type: "decision",
|
|
19247
19695
|
title: memory.title,
|
|
@@ -19261,7 +19709,7 @@ async function main() {
|
|
|
19261
19709
|
console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
|
|
19262
19710
|
}
|
|
19263
19711
|
bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
19264
|
-
bootLog(`REST API:
|
|
19712
|
+
bootLog(`REST API: 125 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
|
|
19265
19713
|
bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
|
|
19266
19714
|
const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
|
|
19267
19715
|
const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
|
|
@@ -19317,6 +19765,7 @@ async function main() {
|
|
|
19317
19765
|
console.warn(`[agentmemory] Failed to save index on shutdown:`, err);
|
|
19318
19766
|
});
|
|
19319
19767
|
await sdk.shutdown();
|
|
19768
|
+
clearWorkerPidfile();
|
|
19320
19769
|
process.exit(0);
|
|
19321
19770
|
};
|
|
19322
19771
|
process.on("SIGINT", shutdown);
|
|
@@ -19329,4 +19778,4 @@ main().catch((err) => {
|
|
|
19329
19778
|
|
|
19330
19779
|
//#endregion
|
|
19331
19780
|
export { };
|
|
19332
|
-
//# sourceMappingURL=src-
|
|
19781
|
+
//# sourceMappingURL=src-DvS3bhMe.mjs.map
|