@agentmemory/agentmemory 0.9.21 → 0.9.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -7
- package/dist/cli.d.mts +5 -1
- package/dist/cli.d.mts.map +1 -0
- package/dist/cli.mjs +103 -692
- package/dist/cli.mjs.map +1 -1
- package/dist/connect-BQQXpyDS.mjs +763 -0
- package/dist/connect-BQQXpyDS.mjs.map +1 -0
- package/dist/hooks/post-tool-use.mjs +1 -1
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/stop.mjs +8 -0
- package/dist/hooks/stop.mjs.map +1 -1
- 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 +450 -242
- 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-gpTAJuBy.mjs} +428 -243
- package/dist/src-gpTAJuBy.mjs.map +1 -0
- package/dist/{standalone-C7BgzzIN.mjs → standalone-C4i7ktpn.mjs} +18 -6
- package/dist/standalone-C4i7ktpn.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +15 -4
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-CRTWUFw9.mjs → tools-registry-B7Y6nJsr.mjs} +36 -11
- package/dist/tools-registry-B7Y6nJsr.mjs.map +1 -0
- package/dist/version-DvQMNbEH.mjs +6 -0
- package/dist/version-DvQMNbEH.mjs.map +1 -0
- package/dist/viewer/index.html +77 -9
- package/package.json +6 -4
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +3 -2
- package/plugin/opencode/agentmemory-capture.ts +34 -9
- 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/post-tool-use.mjs +1 -1
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +8 -0
- package/plugin/scripts/stop.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-DvQMNbEH.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-B7Y6nJsr.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
|
}
|
|
@@ -2182,6 +2197,24 @@ var SearchIndex = class SearchIndex {
|
|
|
2182
2197
|
has(id) {
|
|
2183
2198
|
return this.entries.has(id);
|
|
2184
2199
|
}
|
|
2200
|
+
remove(id) {
|
|
2201
|
+
const entry = this.entries.get(id);
|
|
2202
|
+
if (!entry) return;
|
|
2203
|
+
const termFreq = this.docTermCounts.get(id);
|
|
2204
|
+
if (termFreq) {
|
|
2205
|
+
for (const term of termFreq.keys()) {
|
|
2206
|
+
const postingList = this.invertedIndex.get(term);
|
|
2207
|
+
if (postingList) {
|
|
2208
|
+
postingList.delete(id);
|
|
2209
|
+
if (postingList.size === 0) this.invertedIndex.delete(term);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
this.docTermCounts.delete(id);
|
|
2213
|
+
}
|
|
2214
|
+
this.totalDocLength = Math.max(0, this.totalDocLength - entry.termCount);
|
|
2215
|
+
this.entries.delete(id);
|
|
2216
|
+
this.sortedTerms = null;
|
|
2217
|
+
}
|
|
2185
2218
|
search(query, limit = 20) {
|
|
2186
2219
|
const rawTerms = this.tokenize(query.toLowerCase());
|
|
2187
2220
|
if (rawTerms.length === 0) return [];
|
|
@@ -2521,6 +2554,7 @@ function buildSyntheticCompression(raw) {
|
|
|
2521
2554
|
};
|
|
2522
2555
|
if (raw.modality) result.modality = raw.modality;
|
|
2523
2556
|
if (raw.imageData) result.imageData = raw.imageData;
|
|
2557
|
+
if (raw.agentId) result.agentId = raw.agentId;
|
|
2524
2558
|
return result;
|
|
2525
2559
|
}
|
|
2526
2560
|
|
|
@@ -2610,6 +2644,16 @@ function setVectorIndex(idx) {
|
|
|
2610
2644
|
function setEmbeddingProvider(provider) {
|
|
2611
2645
|
currentEmbeddingProvider = provider;
|
|
2612
2646
|
}
|
|
2647
|
+
function vectorIndexRemove(id) {
|
|
2648
|
+
vectorIndex?.remove(id);
|
|
2649
|
+
}
|
|
2650
|
+
let indexPersistence = null;
|
|
2651
|
+
function setIndexPersistence(p) {
|
|
2652
|
+
indexPersistence = p;
|
|
2653
|
+
}
|
|
2654
|
+
async function flushIndexSave() {
|
|
2655
|
+
await indexPersistence?.save();
|
|
2656
|
+
}
|
|
2613
2657
|
const EMBED_MAX_CHARS = 16e3;
|
|
2614
2658
|
function clipEmbedInput(text) {
|
|
2615
2659
|
if (text.length <= EMBED_MAX_CHARS) return text;
|
|
@@ -3004,11 +3048,14 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3004
3048
|
error: `Session observation limit reached (${maxObservationsPerSession})`
|
|
3005
3049
|
};
|
|
3006
3050
|
}
|
|
3051
|
+
const existingSession = await kv.get(KV.sessions, payload.sessionId);
|
|
3052
|
+
const inheritedAgentId = existingSession ? existingSession.agentId : getAgentId();
|
|
3053
|
+
if (inheritedAgentId) raw.agentId = inheritedAgentId;
|
|
3007
3054
|
if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) {
|
|
3008
|
-
const { saveImageToDisk } = await import("./image-store-
|
|
3055
|
+
const { saveImageToDisk } = await import("./image-store-CdE0amb1.mjs");
|
|
3009
3056
|
const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
|
|
3010
3057
|
raw.imageData = filePath;
|
|
3011
|
-
const { incrementImageRef } = await import("./image-refs-
|
|
3058
|
+
const { incrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
|
|
3012
3059
|
await incrementImageRef(kv, filePath);
|
|
3013
3060
|
sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: bytesWritten });
|
|
3014
3061
|
if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] === "true") sdk.triggerVoid("mem::vision-embed", {
|
|
@@ -3021,7 +3068,7 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3021
3068
|
await kv.set(KV.observations(payload.sessionId), obsId, raw);
|
|
3022
3069
|
} catch (error) {
|
|
3023
3070
|
if (raw.imageData) {
|
|
3024
|
-
const { deleteImage } = await import("./image-store-
|
|
3071
|
+
const { deleteImage } = await import("./image-store-CdE0amb1.mjs");
|
|
3025
3072
|
const { deletedBytes } = await deleteImage(raw.imageData);
|
|
3026
3073
|
if (deletedBytes > 0) sdk.triggerVoid("mem::disk-size-delta", { deltaBytes: -deletedBytes });
|
|
3027
3074
|
}
|
|
@@ -3055,7 +3102,7 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3055
3102
|
},
|
|
3056
3103
|
action: TriggerAction.Void()
|
|
3057
3104
|
});
|
|
3058
|
-
const session =
|
|
3105
|
+
const session = existingSession;
|
|
3059
3106
|
if (session) {
|
|
3060
3107
|
const updates = [{
|
|
3061
3108
|
type: "set",
|
|
@@ -3075,6 +3122,20 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3075
3122
|
});
|
|
3076
3123
|
}
|
|
3077
3124
|
await kv.update(KV.sessions, payload.sessionId, updates);
|
|
3125
|
+
} else if (typeof payload.project === "string" && payload.project.trim().length > 0 && typeof payload.cwd === "string" && payload.cwd.trim().length > 0) {
|
|
3126
|
+
const trimmedPrompt = typeof raw.userPrompt === "string" ? raw.userPrompt.replace(/\s+/g, " ").trim().slice(0, 200) : void 0;
|
|
3127
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
3128
|
+
await kv.set(KV.sessions, payload.sessionId, {
|
|
3129
|
+
id: payload.sessionId,
|
|
3130
|
+
project: payload.project,
|
|
3131
|
+
cwd: payload.cwd,
|
|
3132
|
+
startedAt: payload.timestamp ?? ts,
|
|
3133
|
+
updatedAt: ts,
|
|
3134
|
+
status: "active",
|
|
3135
|
+
observationCount: 1,
|
|
3136
|
+
...inheritedAgentId ? { agentId: inheritedAgentId } : {},
|
|
3137
|
+
...trimmedPrompt && trimmedPrompt.length > 0 ? { firstPrompt: trimmedPrompt } : {}
|
|
3138
|
+
});
|
|
3078
3139
|
}
|
|
3079
3140
|
if (isAutoCompressEnabled()) await sdk.trigger({
|
|
3080
3141
|
function_id: "mem::compress",
|
|
@@ -4227,7 +4288,8 @@ function registerCompressFunction(sdk, kv, provider, metricsStore) {
|
|
|
4227
4288
|
confidence: qualityScore / 100,
|
|
4228
4289
|
...hasImage ? { modality: data.raw.modality } : {},
|
|
4229
4290
|
...imageDescription ? { imageDescription } : {},
|
|
4230
|
-
...data.raw.imageData ? { imageRef: data.raw.imageData } : {}
|
|
4291
|
+
...data.raw.imageData ? { imageRef: data.raw.imageData } : {},
|
|
4292
|
+
...data.raw.agentId ? { agentId: data.raw.agentId } : {}
|
|
4231
4293
|
};
|
|
4232
4294
|
await kv.set(KV.observations(data.sessionId), data.observationId, compressed);
|
|
4233
4295
|
try {
|
|
@@ -5198,6 +5260,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5198
5260
|
break;
|
|
5199
5261
|
}
|
|
5200
5262
|
}
|
|
5263
|
+
const callAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim().slice(0, 128) : getAgentId();
|
|
5201
5264
|
const memory = {
|
|
5202
5265
|
id: generateId("mem"),
|
|
5203
5266
|
createdAt: now,
|
|
@@ -5213,7 +5276,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5213
5276
|
parentId: supersededId,
|
|
5214
5277
|
supersedes: supersededId ? [supersededId] : [],
|
|
5215
5278
|
sourceObservationIds: (data.sourceObservationIds || []).filter((id) => typeof id === "string" && id.length > 0),
|
|
5216
|
-
isLatest: true
|
|
5279
|
+
isLatest: true,
|
|
5280
|
+
...callAgentId ? { agentId: callAgentId } : {}
|
|
5217
5281
|
};
|
|
5218
5282
|
if (data.ttlDays && typeof data.ttlDays === "number" && data.ttlDays > 0) memory.forgetAfter = new Date(Date.now() + data.ttlDays * 864e5).toISOString();
|
|
5219
5283
|
if (supersededMemory) {
|
|
@@ -5253,12 +5317,14 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5253
5317
|
const deletedMemoryIds = [];
|
|
5254
5318
|
const deletedObservationIds = [];
|
|
5255
5319
|
let deletedSession = false;
|
|
5256
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
5320
|
+
const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
|
|
5257
5321
|
if (data.memoryId) {
|
|
5258
5322
|
const mem = await kv.get(KV.memories, data.memoryId);
|
|
5259
5323
|
await kv.delete(KV.memories, data.memoryId);
|
|
5260
5324
|
if (mem?.imageRef) await decrementImageRef(kv, sdk, mem.imageRef);
|
|
5261
5325
|
await deleteAccessLog(kv, data.memoryId);
|
|
5326
|
+
getSearchIndex().remove(data.memoryId);
|
|
5327
|
+
vectorIndexRemove(data.memoryId);
|
|
5262
5328
|
deletedMemoryIds.push(data.memoryId);
|
|
5263
5329
|
deleted++;
|
|
5264
5330
|
}
|
|
@@ -5267,6 +5333,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5267
5333
|
await kv.delete(KV.observations(data.sessionId), obsId);
|
|
5268
5334
|
if (obs?.imageData) await decrementImageRef(kv, sdk, obs.imageData);
|
|
5269
5335
|
if (obs?.imageRef && obs.imageRef !== obs.imageData) await decrementImageRef(kv, sdk, obs.imageRef);
|
|
5336
|
+
getSearchIndex().remove(obsId);
|
|
5337
|
+
vectorIndexRemove(obsId);
|
|
5270
5338
|
deletedObservationIds.push(obsId);
|
|
5271
5339
|
deleted++;
|
|
5272
5340
|
}
|
|
@@ -5276,6 +5344,8 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5276
5344
|
await kv.delete(KV.observations(data.sessionId), obs.id);
|
|
5277
5345
|
if (obs.imageData) await decrementImageRef(kv, sdk, obs.imageData);
|
|
5278
5346
|
if (obs.imageRef && obs.imageRef !== obs.imageData) await decrementImageRef(kv, sdk, obs.imageRef);
|
|
5347
|
+
getSearchIndex().remove(obs.id);
|
|
5348
|
+
vectorIndexRemove(obs.id);
|
|
5279
5349
|
deletedObservationIds.push(obs.id);
|
|
5280
5350
|
deleted++;
|
|
5281
5351
|
}
|
|
@@ -5284,14 +5354,17 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5284
5354
|
deletedSession = true;
|
|
5285
5355
|
deleted += 2;
|
|
5286
5356
|
}
|
|
5287
|
-
if (deleted > 0)
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5357
|
+
if (deleted > 0) {
|
|
5358
|
+
await flushIndexSave();
|
|
5359
|
+
await recordAudit(kv, "forget", "mem::forget", [...deletedMemoryIds, ...deletedObservationIds], {
|
|
5360
|
+
sessionId: data.sessionId,
|
|
5361
|
+
deleted,
|
|
5362
|
+
memoriesDeleted: deletedMemoryIds.length,
|
|
5363
|
+
observationsDeleted: deletedObservationIds.length,
|
|
5364
|
+
sessionDeleted: deletedSession,
|
|
5365
|
+
reason: "user-initiated forget"
|
|
5366
|
+
});
|
|
5367
|
+
}
|
|
5295
5368
|
logger.info("Memory forgotten", { deleted });
|
|
5296
5369
|
return {
|
|
5297
5370
|
success: true,
|
|
@@ -5352,7 +5425,7 @@ async function runRecoveredSessionConsolidation(sdk) {
|
|
|
5352
5425
|
function registerEvictFunction(sdk, kv) {
|
|
5353
5426
|
sdk.registerFunction("mem::evict", async (data) => {
|
|
5354
5427
|
const dryRun = data?.dryRun ?? false;
|
|
5355
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
5428
|
+
const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
|
|
5356
5429
|
const configOverride = await kv.get(KV.config, "eviction").catch(() => null);
|
|
5357
5430
|
const cfg = {
|
|
5358
5431
|
...DEFAULTS$1,
|
|
@@ -5806,6 +5879,9 @@ async function findByKeyword(kv, keyword, project) {
|
|
|
5806
5879
|
const LESSON_CONTENT_PREVIEW_CHARS = 240;
|
|
5807
5880
|
function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
5808
5881
|
sdk.registerFunction("mem::smart-search", async (data) => {
|
|
5882
|
+
const isolated = isAgentScopeIsolated();
|
|
5883
|
+
const explicitAgentId = typeof data.agentId === "string" && data.agentId.trim().length > 0 ? data.agentId.trim() : void 0;
|
|
5884
|
+
const filterAgentId = explicitAgentId === "*" ? void 0 : explicitAgentId ?? (isolated ? getAgentId() : void 0);
|
|
5809
5885
|
if (data.expandIds && data.expandIds.length > 0) {
|
|
5810
5886
|
const raw = data.expandIds.slice(0, 20);
|
|
5811
5887
|
const items = raw.map((entry) => {
|
|
@@ -5826,17 +5902,19 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
5826
5902
|
observation: obs
|
|
5827
5903
|
} : null)));
|
|
5828
5904
|
for (const r of results) if (r) expanded.push(r);
|
|
5829
|
-
|
|
5905
|
+
const scoped = filterAgentId ? expanded.filter((e) => e.observation.agentId === filterAgentId) : expanded;
|
|
5906
|
+
recordAccessBatch(kv, scoped.map((e) => e.observation.id));
|
|
5830
5907
|
const truncated = data.expandIds.length > raw.length;
|
|
5831
5908
|
logger.info("Smart search expanded", {
|
|
5832
5909
|
requested: data.expandIds.length,
|
|
5833
5910
|
attempted: raw.length,
|
|
5834
|
-
returned:
|
|
5911
|
+
returned: scoped.length,
|
|
5912
|
+
filteredOutOfScope: expanded.length - scoped.length,
|
|
5835
5913
|
truncated
|
|
5836
5914
|
});
|
|
5837
5915
|
return {
|
|
5838
5916
|
mode: "expanded",
|
|
5839
|
-
results:
|
|
5917
|
+
results: scoped,
|
|
5840
5918
|
truncated
|
|
5841
5919
|
};
|
|
5842
5920
|
}
|
|
@@ -5848,8 +5926,9 @@ function registerSmartSearchFunction(sdk, kv, searchFn) {
|
|
|
5848
5926
|
const limit = Math.max(1, Math.min(data.limit ?? 20, 100));
|
|
5849
5927
|
const lessonLimit = Math.min(limit, 10);
|
|
5850
5928
|
const includeLessons = data.includeLessons !== false;
|
|
5851
|
-
const
|
|
5852
|
-
const
|
|
5929
|
+
const overFetchLimit = filterAgentId ? Math.min(limit * 3, 300) : limit;
|
|
5930
|
+
const [hybridResults, lessons] = await Promise.all([searchFn(data.query, overFetchLimit), includeLessons ? recallLessons(sdk, data.query, lessonLimit, data.project) : Promise.resolve([])]);
|
|
5931
|
+
const compact = (filterAgentId ? hybridResults.filter((r) => r.observation.agentId === filterAgentId).slice(0, limit) : hybridResults.slice(0, limit)).map((r) => ({
|
|
5853
5932
|
obsId: r.observation.id,
|
|
5854
5933
|
sessionId: r.sessionId,
|
|
5855
5934
|
title: r.observation.title,
|
|
@@ -6006,7 +6085,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6006
6085
|
sdk.registerFunction("mem::auto-forget", async (data) => {
|
|
6007
6086
|
const dryRun = data?.dryRun ?? false;
|
|
6008
6087
|
const now = Date.now();
|
|
6009
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
6088
|
+
const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
|
|
6010
6089
|
const result = {
|
|
6011
6090
|
ttlExpired: [],
|
|
6012
6091
|
contradictions: [],
|
|
@@ -6028,6 +6107,8 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6028
6107
|
timestamp: mem.forgetAfter
|
|
6029
6108
|
});
|
|
6030
6109
|
await deleteAccessLog(kv, mem.id);
|
|
6110
|
+
getSearchIndex().remove(mem.id);
|
|
6111
|
+
vectorIndexRemove(mem.id);
|
|
6031
6112
|
}
|
|
6032
6113
|
}
|
|
6033
6114
|
}
|
|
@@ -6105,10 +6186,13 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6105
6186
|
sessionId: sessions[i].id,
|
|
6106
6187
|
timestamp: obs.timestamp
|
|
6107
6188
|
});
|
|
6189
|
+
getSearchIndex().remove(obs.id);
|
|
6190
|
+
vectorIndexRemove(obs.id);
|
|
6108
6191
|
}
|
|
6109
6192
|
}
|
|
6110
6193
|
}
|
|
6111
6194
|
}
|
|
6195
|
+
if (!dryRun && (result.ttlExpired.length > 0 || result.lowValueObs.length > 0)) await flushIndexSave();
|
|
6112
6196
|
logger.info("Auto-forget complete", {
|
|
6113
6197
|
ttlExpired: result.ttlExpired.length,
|
|
6114
6198
|
contradictions: result.contradictions.length,
|
|
@@ -6256,7 +6340,8 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
6256
6340
|
"0.9.18",
|
|
6257
6341
|
"0.9.19",
|
|
6258
6342
|
"0.9.20",
|
|
6259
|
-
"0.9.21"
|
|
6343
|
+
"0.9.21",
|
|
6344
|
+
"0.9.22"
|
|
6260
6345
|
]).has(importData.version)) return {
|
|
6261
6346
|
success: false,
|
|
6262
6347
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -6782,12 +6867,12 @@ function parseGraphXml(xml, observationIds) {
|
|
|
6782
6867
|
const nodes = [];
|
|
6783
6868
|
const edges = [];
|
|
6784
6869
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6785
|
-
const entityRegex = /<entity\s+type="([^"]+)"\s+name="([^"]+)"[^>]
|
|
6870
|
+
const entityRegex = /<entity\s+type="([^"]+)"\s+name="([^"]+)"[^>]*?(?:\/>|>([\s\S]*?)<\/entity>)/g;
|
|
6786
6871
|
let match;
|
|
6787
6872
|
while ((match = entityRegex.exec(xml)) !== null) {
|
|
6788
6873
|
const type = match[1];
|
|
6789
6874
|
const name = match[2];
|
|
6790
|
-
const propsBlock = match[3];
|
|
6875
|
+
const propsBlock = match[3] ?? "";
|
|
6791
6876
|
const properties = {};
|
|
6792
6877
|
const propRegex = /<property\s+key="([^"]+)">([^<]*)<\/property>/g;
|
|
6793
6878
|
let propMatch;
|
|
@@ -7315,8 +7400,11 @@ function registerGovernanceFunction(sdk, kv) {
|
|
|
7315
7400
|
for (const id of data.memoryIds) if (await kv.get(KV.memories, id)) {
|
|
7316
7401
|
await kv.delete(KV.memories, id);
|
|
7317
7402
|
await deleteAccessLog(kv, id);
|
|
7403
|
+
getSearchIndex().remove(id);
|
|
7404
|
+
vectorIndexRemove(id);
|
|
7318
7405
|
deleted++;
|
|
7319
7406
|
}
|
|
7407
|
+
if (deleted > 0) await flushIndexSave();
|
|
7320
7408
|
await recordAudit(kv, "delete", "mem::governance-delete", data.memoryIds, {
|
|
7321
7409
|
reason: data.reason || "manual deletion",
|
|
7322
7410
|
deleted
|
|
@@ -7369,6 +7457,8 @@ function registerGovernanceFunction(sdk, kv) {
|
|
|
7369
7457
|
(await Promise.allSettled(batch.map(async (mem) => {
|
|
7370
7458
|
await kv.delete(KV.memories, mem.id);
|
|
7371
7459
|
await deleteAccessLog(kv, mem.id);
|
|
7460
|
+
getSearchIndex().remove(mem.id);
|
|
7461
|
+
vectorIndexRemove(mem.id);
|
|
7372
7462
|
}))).forEach((result, j) => {
|
|
7373
7463
|
const mem = batch[j];
|
|
7374
7464
|
if (result.status === "fulfilled") successfulIds.push(mem.id);
|
|
@@ -7384,6 +7474,7 @@ function registerGovernanceFunction(sdk, kv) {
|
|
|
7384
7474
|
}
|
|
7385
7475
|
});
|
|
7386
7476
|
}
|
|
7477
|
+
if (successfulIds.length > 0) await flushIndexSave();
|
|
7387
7478
|
await safeAudit(kv, "delete", "mem::governance-bulk", successfulIds, {
|
|
7388
7479
|
filter: data,
|
|
7389
7480
|
deleted: successfulIds.length,
|
|
@@ -12746,7 +12837,7 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
12746
12837
|
const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
|
|
12747
12838
|
const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
|
|
12748
12839
|
const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
|
|
12749
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
12840
|
+
const { decrementImageRef } = await import("./image-refs-CJS5B9Gq.mjs");
|
|
12750
12841
|
const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
|
|
12751
12842
|
if (data?.dryRun) return {
|
|
12752
12843
|
success: true,
|
|
@@ -12783,6 +12874,8 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
12783
12874
|
await kv.delete(scope, candidate.memoryId);
|
|
12784
12875
|
await kv.delete(KV.retentionScores, candidate.memoryId);
|
|
12785
12876
|
await deleteAccessLog(kv, candidate.memoryId);
|
|
12877
|
+
getSearchIndex().remove(candidate.memoryId);
|
|
12878
|
+
vectorIndexRemove(candidate.memoryId);
|
|
12786
12879
|
evicted++;
|
|
12787
12880
|
evictedIds.push(candidate.memoryId);
|
|
12788
12881
|
if (resolvedSource === "semantic") evictedSemantic++;
|
|
@@ -12790,13 +12883,16 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
12790
12883
|
} catch {
|
|
12791
12884
|
continue;
|
|
12792
12885
|
}
|
|
12793
|
-
if (evicted > 0)
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12886
|
+
if (evicted > 0) {
|
|
12887
|
+
await flushIndexSave();
|
|
12888
|
+
await recordAudit(kv, "delete", "mem::retention-evict", evictedIds, {
|
|
12889
|
+
threshold,
|
|
12890
|
+
evicted,
|
|
12891
|
+
evictedEpisodic,
|
|
12892
|
+
evictedSemantic,
|
|
12893
|
+
reason: "retention score below threshold"
|
|
12894
|
+
});
|
|
12895
|
+
}
|
|
12800
12896
|
logger.info("Retention-based eviction complete", {
|
|
12801
12897
|
evicted,
|
|
12802
12898
|
evictedEpisodic,
|
|
@@ -13700,6 +13796,221 @@ function renderViewerDocument() {
|
|
|
13700
13796
|
};
|
|
13701
13797
|
}
|
|
13702
13798
|
|
|
13799
|
+
//#endregion
|
|
13800
|
+
//#region src/viewer/server.ts
|
|
13801
|
+
function loadViewerFavicon() {
|
|
13802
|
+
const base = dirname(fileURLToPath(import.meta.url));
|
|
13803
|
+
const candidates = [
|
|
13804
|
+
join(base, "..", "src", "viewer", "favicon.svg"),
|
|
13805
|
+
join(base, "..", "viewer", "favicon.svg"),
|
|
13806
|
+
join(base, "viewer", "favicon.svg")
|
|
13807
|
+
];
|
|
13808
|
+
for (const path of candidates) try {
|
|
13809
|
+
return readFileSync(path);
|
|
13810
|
+
} catch {}
|
|
13811
|
+
return null;
|
|
13812
|
+
}
|
|
13813
|
+
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());
|
|
13814
|
+
const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
|
|
13815
|
+
function buildAllowedHosts(origins, listenPort) {
|
|
13816
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
13817
|
+
for (const o of origins) try {
|
|
13818
|
+
const parsed = new URL(o);
|
|
13819
|
+
if (parsed.host) hosts.add(parsed.host.toLowerCase());
|
|
13820
|
+
} catch {}
|
|
13821
|
+
hosts.add(`localhost:${listenPort}`);
|
|
13822
|
+
hosts.add(`127.0.0.1:${listenPort}`);
|
|
13823
|
+
hosts.add(`[::1]:${listenPort}`);
|
|
13824
|
+
for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h);
|
|
13825
|
+
return hosts;
|
|
13826
|
+
}
|
|
13827
|
+
function isHostAllowed(headerHost, allowed) {
|
|
13828
|
+
if (typeof headerHost !== "string") return false;
|
|
13829
|
+
const lower = headerHost.toLowerCase().trim();
|
|
13830
|
+
if (!lower) return false;
|
|
13831
|
+
return allowed.has(lower);
|
|
13832
|
+
}
|
|
13833
|
+
function corsHeaders(req) {
|
|
13834
|
+
const origin = req.headers.origin || "";
|
|
13835
|
+
const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
|
|
13836
|
+
return {
|
|
13837
|
+
"Access-Control-Allow-Origin": allowed,
|
|
13838
|
+
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
13839
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
13840
|
+
Vary: "Origin"
|
|
13841
|
+
};
|
|
13842
|
+
}
|
|
13843
|
+
function json(res, status, data, req) {
|
|
13844
|
+
const body = JSON.stringify(data);
|
|
13845
|
+
const cors = req ? corsHeaders(req) : {
|
|
13846
|
+
"Access-Control-Allow-Origin": ALLOWED_ORIGINS[0],
|
|
13847
|
+
Vary: "Origin"
|
|
13848
|
+
};
|
|
13849
|
+
res.writeHead(status, {
|
|
13850
|
+
...cors,
|
|
13851
|
+
"Content-Type": "application/json"
|
|
13852
|
+
});
|
|
13853
|
+
res.end(body);
|
|
13854
|
+
}
|
|
13855
|
+
function readBody(req) {
|
|
13856
|
+
return new Promise((resolve, reject) => {
|
|
13857
|
+
let data = "";
|
|
13858
|
+
let size = 0;
|
|
13859
|
+
req.on("data", (chunk) => {
|
|
13860
|
+
size += chunk.length;
|
|
13861
|
+
if (size > 1e6) {
|
|
13862
|
+
req.destroy();
|
|
13863
|
+
reject(/* @__PURE__ */ new Error("too large"));
|
|
13864
|
+
return;
|
|
13865
|
+
}
|
|
13866
|
+
data += chunk.toString();
|
|
13867
|
+
});
|
|
13868
|
+
req.on("end", () => resolve(data));
|
|
13869
|
+
req.on("error", reject);
|
|
13870
|
+
});
|
|
13871
|
+
}
|
|
13872
|
+
const MAX_VIEWER_PORT_RETRIES = 10;
|
|
13873
|
+
let boundViewerPort = null;
|
|
13874
|
+
let viewerSkipped = false;
|
|
13875
|
+
function getBoundViewerPort() {
|
|
13876
|
+
return boundViewerPort;
|
|
13877
|
+
}
|
|
13878
|
+
function getViewerSkipped() {
|
|
13879
|
+
return viewerSkipped;
|
|
13880
|
+
}
|
|
13881
|
+
function startViewerServer(port, _kv, _sdk, secret, restPort) {
|
|
13882
|
+
boundViewerPort = null;
|
|
13883
|
+
viewerSkipped = false;
|
|
13884
|
+
const resolvedRestPort = restPort ?? port - 2;
|
|
13885
|
+
const requestedPort = port;
|
|
13886
|
+
let allowedHosts = null;
|
|
13887
|
+
const server = createServer(async (req, res) => {
|
|
13888
|
+
if (!allowedHosts) {
|
|
13889
|
+
const addr = server.address();
|
|
13890
|
+
allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port);
|
|
13891
|
+
}
|
|
13892
|
+
if (!isHostAllowed(req.headers.host, allowedHosts)) {
|
|
13893
|
+
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
13894
|
+
res.end("forbidden host");
|
|
13895
|
+
return;
|
|
13896
|
+
}
|
|
13897
|
+
const raw = req.url || "/";
|
|
13898
|
+
const qIdx = raw.indexOf("?");
|
|
13899
|
+
const pathname = qIdx >= 0 ? raw.slice(0, qIdx) : raw;
|
|
13900
|
+
const qs = qIdx >= 0 ? raw.slice(qIdx + 1) : "";
|
|
13901
|
+
const method = req.method || "GET";
|
|
13902
|
+
if (method === "OPTIONS") {
|
|
13903
|
+
res.writeHead(204, {
|
|
13904
|
+
...corsHeaders(req),
|
|
13905
|
+
"Access-Control-Max-Age": "86400"
|
|
13906
|
+
});
|
|
13907
|
+
res.end();
|
|
13908
|
+
return;
|
|
13909
|
+
}
|
|
13910
|
+
if (method === "GET" && (pathname === "/" || pathname === "/viewer" || pathname === "/agentmemory/viewer")) {
|
|
13911
|
+
const rendered = renderViewerDocument();
|
|
13912
|
+
if (rendered.found) {
|
|
13913
|
+
res.writeHead(200, {
|
|
13914
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
13915
|
+
"Content-Security-Policy": rendered.csp,
|
|
13916
|
+
"Cache-Control": "no-cache"
|
|
13917
|
+
});
|
|
13918
|
+
res.end(rendered.html);
|
|
13919
|
+
return;
|
|
13920
|
+
}
|
|
13921
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
13922
|
+
res.end("viewer not found");
|
|
13923
|
+
return;
|
|
13924
|
+
}
|
|
13925
|
+
if (method === "GET" && pathname === "/favicon.svg") {
|
|
13926
|
+
const favicon = loadViewerFavicon();
|
|
13927
|
+
if (favicon) {
|
|
13928
|
+
res.writeHead(200, {
|
|
13929
|
+
"Content-Type": "image/svg+xml",
|
|
13930
|
+
"Cache-Control": "public, max-age=3600"
|
|
13931
|
+
});
|
|
13932
|
+
res.end(favicon);
|
|
13933
|
+
return;
|
|
13934
|
+
}
|
|
13935
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
13936
|
+
res.end("favicon not found");
|
|
13937
|
+
return;
|
|
13938
|
+
}
|
|
13939
|
+
try {
|
|
13940
|
+
await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret);
|
|
13941
|
+
} catch (err) {
|
|
13942
|
+
console.error(`[viewer] proxy error on ${method} ${pathname}:`, err);
|
|
13943
|
+
json(res, 502, { error: "upstream error" }, req);
|
|
13944
|
+
}
|
|
13945
|
+
});
|
|
13946
|
+
let attempt = 0;
|
|
13947
|
+
let currentPort = requestedPort;
|
|
13948
|
+
const tryListen = () => {
|
|
13949
|
+
server.listen(currentPort, "127.0.0.1");
|
|
13950
|
+
};
|
|
13951
|
+
server.on("listening", () => {
|
|
13952
|
+
const addr = server.address();
|
|
13953
|
+
boundViewerPort = addr && typeof addr === "object" && "port" in addr ? addr.port : currentPort;
|
|
13954
|
+
viewerSkipped = false;
|
|
13955
|
+
if (currentPort === requestedPort) console.log(`[agentmemory] Viewer: http://localhost:${currentPort}`);
|
|
13956
|
+
else console.log(`[agentmemory] Viewer started on http://localhost:${currentPort} (fallback from ${requestedPort})`);
|
|
13957
|
+
});
|
|
13958
|
+
server.on("error", (err) => {
|
|
13959
|
+
if (err.code === "EADDRINUSE" && attempt < MAX_VIEWER_PORT_RETRIES) {
|
|
13960
|
+
attempt++;
|
|
13961
|
+
currentPort = requestedPort + attempt;
|
|
13962
|
+
setImmediate(tryListen);
|
|
13963
|
+
return;
|
|
13964
|
+
}
|
|
13965
|
+
if (err.code === "EADDRINUSE") {
|
|
13966
|
+
boundViewerPort = null;
|
|
13967
|
+
viewerSkipped = true;
|
|
13968
|
+
console.warn(`[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`);
|
|
13969
|
+
} else {
|
|
13970
|
+
boundViewerPort = null;
|
|
13971
|
+
viewerSkipped = true;
|
|
13972
|
+
console.error(`[agentmemory] Viewer error:`, err.message);
|
|
13973
|
+
}
|
|
13974
|
+
});
|
|
13975
|
+
tryListen();
|
|
13976
|
+
return server;
|
|
13977
|
+
}
|
|
13978
|
+
async function proxyToRestApi(restPort, pathname, qs, method, req, res, secret) {
|
|
13979
|
+
const upstreamUrl = `http://127.0.0.1:${restPort}${pathname.startsWith("/agentmemory/") ? pathname : `/agentmemory${pathname.startsWith("/") ? pathname : "/" + pathname}`}${qs ? "?" + qs : ""}`;
|
|
13980
|
+
const headers = {};
|
|
13981
|
+
if (secret) headers["Authorization"] = `Bearer ${secret}`;
|
|
13982
|
+
const ct = req.headers["content-type"];
|
|
13983
|
+
if (ct) headers["Content-Type"] = ct;
|
|
13984
|
+
let body;
|
|
13985
|
+
if (method === "POST" || method === "PUT" || method === "DELETE" || method === "PATCH") body = await readBody(req);
|
|
13986
|
+
const controller = new AbortController();
|
|
13987
|
+
const fetchTimeout = setTimeout(() => controller.abort(), 1e4);
|
|
13988
|
+
let upstream;
|
|
13989
|
+
try {
|
|
13990
|
+
upstream = await fetch(upstreamUrl, {
|
|
13991
|
+
method,
|
|
13992
|
+
headers,
|
|
13993
|
+
body: body || void 0,
|
|
13994
|
+
signal: controller.signal
|
|
13995
|
+
});
|
|
13996
|
+
clearTimeout(fetchTimeout);
|
|
13997
|
+
} catch (err) {
|
|
13998
|
+
clearTimeout(fetchTimeout);
|
|
13999
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
14000
|
+
json(res, 504, { error: "upstream timeout" }, req);
|
|
14001
|
+
return;
|
|
14002
|
+
}
|
|
14003
|
+
throw err;
|
|
14004
|
+
}
|
|
14005
|
+
const cors = corsHeaders(req);
|
|
14006
|
+
const responseBody = await upstream.text();
|
|
14007
|
+
const responseHeaders = { ...cors };
|
|
14008
|
+
const upstreamCt = upstream.headers.get("content-type");
|
|
14009
|
+
if (upstreamCt) responseHeaders["Content-Type"] = upstreamCt;
|
|
14010
|
+
res.writeHead(upstream.status, responseHeaders);
|
|
14011
|
+
res.end(responseBody);
|
|
14012
|
+
}
|
|
14013
|
+
|
|
13703
14014
|
//#endregion
|
|
13704
14015
|
//#region src/triggers/api.ts
|
|
13705
14016
|
function parseOptionalInt(raw) {
|
|
@@ -13785,7 +14096,9 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
13785
14096
|
status_code: 200,
|
|
13786
14097
|
body: {
|
|
13787
14098
|
status: "ok",
|
|
13788
|
-
service: "agentmemory"
|
|
14099
|
+
service: "agentmemory",
|
|
14100
|
+
viewerPort: getBoundViewerPort(),
|
|
14101
|
+
viewerSkipped: getViewerSkipped()
|
|
13789
14102
|
}
|
|
13790
14103
|
}));
|
|
13791
14104
|
sdk.registerTrigger({
|
|
@@ -13880,7 +14193,9 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
13880
14193
|
version: VERSION,
|
|
13881
14194
|
health: health || null,
|
|
13882
14195
|
functionMetrics,
|
|
13883
|
-
circuitBreaker
|
|
14196
|
+
circuitBreaker,
|
|
14197
|
+
viewerPort: getBoundViewerPort(),
|
|
14198
|
+
viewerSkipped: getViewerSkipped()
|
|
13884
14199
|
}
|
|
13885
14200
|
};
|
|
13886
14201
|
});
|
|
@@ -14134,6 +14449,7 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14134
14449
|
body: { error: "sessionId, project, and cwd are required non-empty strings" }
|
|
14135
14450
|
};
|
|
14136
14451
|
const title = typeof body.title === "string" ? body.title.trim() : void 0;
|
|
14452
|
+
const agentId = (typeof body.agentId === "string" && body.agentId.trim().length > 0 ? body.agentId.trim().slice(0, 128) : void 0) ?? getAgentId();
|
|
14137
14453
|
const session = {
|
|
14138
14454
|
id: sessionId,
|
|
14139
14455
|
project,
|
|
@@ -14142,7 +14458,8 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14142
14458
|
status: "active",
|
|
14143
14459
|
observationCount: 0,
|
|
14144
14460
|
...title ? { summary: title.slice(0, 200) } : {},
|
|
14145
|
-
...title ? { firstPrompt: title.slice(0, 200) } : {}
|
|
14461
|
+
...title ? { firstPrompt: title.slice(0, 200) } : {},
|
|
14462
|
+
...agentId ? { agentId } : {}
|
|
14146
14463
|
};
|
|
14147
14464
|
await kv.set(KV.sessions, sessionId, session);
|
|
14148
14465
|
return {
|
|
@@ -14329,9 +14646,13 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14329
14646
|
sdk.registerFunction("api::sessions", async (req) => {
|
|
14330
14647
|
const authErr = checkAuth(req, secret);
|
|
14331
14648
|
if (authErr) return authErr;
|
|
14649
|
+
const sessions = await kv.list(KV.sessions);
|
|
14650
|
+
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
14651
|
+
const wildcardAgent = normalizedAgentId === "*";
|
|
14652
|
+
const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
14332
14653
|
return {
|
|
14333
14654
|
status_code: 200,
|
|
14334
|
-
body: { sessions:
|
|
14655
|
+
body: { sessions: filterAgentId ? sessions.filter((s) => s.agentId === filterAgentId) : sessions }
|
|
14335
14656
|
};
|
|
14336
14657
|
});
|
|
14337
14658
|
sdk.registerTrigger({
|
|
@@ -14350,9 +14671,13 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14350
14671
|
status_code: 400,
|
|
14351
14672
|
body: { error: "sessionId required" }
|
|
14352
14673
|
};
|
|
14674
|
+
const observations = await kv.list(KV.observations(sessionId));
|
|
14675
|
+
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
14676
|
+
const wildcardAgent = normalizedAgentId === "*";
|
|
14677
|
+
const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
14353
14678
|
return {
|
|
14354
14679
|
status_code: 200,
|
|
14355
|
-
body: { observations:
|
|
14680
|
+
body: { observations: filterAgentId ? observations.filter((o) => o.agentId === filterAgentId) : observations }
|
|
14356
14681
|
};
|
|
14357
14682
|
});
|
|
14358
14683
|
sdk.registerTrigger({
|
|
@@ -14628,11 +14953,22 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14628
14953
|
sdk.registerFunction("api::export", async (req) => {
|
|
14629
14954
|
const authErr = checkAuth(req, secret);
|
|
14630
14955
|
if (authErr) return authErr;
|
|
14956
|
+
const rawMax = req.query_params?.["maxSessions"];
|
|
14957
|
+
const rawOffset = req.query_params?.["offset"];
|
|
14958
|
+
const payload = {};
|
|
14959
|
+
if (typeof rawMax === "string") {
|
|
14960
|
+
const n = Number(rawMax);
|
|
14961
|
+
if (Number.isInteger(n) && n > 0) payload.maxSessions = n;
|
|
14962
|
+
}
|
|
14963
|
+
if (typeof rawOffset === "string") {
|
|
14964
|
+
const n = Number(rawOffset);
|
|
14965
|
+
if (Number.isInteger(n) && n >= 0) payload.offset = n;
|
|
14966
|
+
}
|
|
14631
14967
|
return {
|
|
14632
14968
|
status_code: 200,
|
|
14633
14969
|
body: await sdk.trigger({
|
|
14634
14970
|
function_id: "mem::export",
|
|
14635
|
-
payload
|
|
14971
|
+
payload
|
|
14636
14972
|
})
|
|
14637
14973
|
};
|
|
14638
14974
|
});
|
|
@@ -15118,9 +15454,35 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15118
15454
|
const authErr = checkAuth(req, secret);
|
|
15119
15455
|
if (authErr) return authErr;
|
|
15120
15456
|
const memories = await kv.list(KV.memories);
|
|
15457
|
+
const latest = req.query_params?.["latest"] === "true";
|
|
15458
|
+
const normalizedAgentId = typeof req.query_params?.["agentId"] === "string" ? req.query_params["agentId"].trim() : void 0;
|
|
15459
|
+
const wildcardAgent = normalizedAgentId === "*";
|
|
15460
|
+
const explicitAgentId = normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0;
|
|
15461
|
+
const includeOrphans = req.query_params?.["includeOrphans"] === "true";
|
|
15462
|
+
const filterAgentId = wildcardAgent ? void 0 : explicitAgentId ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
15463
|
+
let filtered = latest ? memories.filter((m) => m.isLatest) : memories;
|
|
15464
|
+
if (filterAgentId) filtered = filtered.filter((m) => m.agentId === filterAgentId || includeOrphans && m.agentId === void 0);
|
|
15465
|
+
if (req.query_params?.["count"] === "true") return {
|
|
15466
|
+
status_code: 200,
|
|
15467
|
+
body: {
|
|
15468
|
+
total: filtered.length,
|
|
15469
|
+
latestCount: filtered.filter((m) => m.isLatest).length
|
|
15470
|
+
}
|
|
15471
|
+
};
|
|
15472
|
+
const rawLimit = req.query_params?.["limit"];
|
|
15473
|
+
const rawOffset = req.query_params?.["offset"];
|
|
15474
|
+
const parsedLimit = typeof rawLimit === "string" ? Number(rawLimit) : NaN;
|
|
15475
|
+
const parsedOffset = typeof rawOffset === "string" ? Number(rawOffset) : NaN;
|
|
15476
|
+
const limit = Number.isInteger(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 5e3) : void 0;
|
|
15477
|
+
const offset = Number.isInteger(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0;
|
|
15121
15478
|
return {
|
|
15122
15479
|
status_code: 200,
|
|
15123
|
-
body: {
|
|
15480
|
+
body: {
|
|
15481
|
+
memories: limit !== void 0 ? filtered.slice(offset, offset + limit) : filtered,
|
|
15482
|
+
total: filtered.length,
|
|
15483
|
+
offset,
|
|
15484
|
+
limit: limit ?? null
|
|
15485
|
+
}
|
|
15124
15486
|
};
|
|
15125
15487
|
});
|
|
15126
15488
|
sdk.registerTrigger({
|
|
@@ -18733,201 +19095,6 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
18733
19095
|
});
|
|
18734
19096
|
}
|
|
18735
19097
|
|
|
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
19098
|
//#endregion
|
|
18932
19099
|
//#region src/eval/metrics-store.ts
|
|
18933
19100
|
var MetricsStore = class {
|
|
@@ -19065,6 +19232,21 @@ function initMetrics(getMeter) {
|
|
|
19065
19232
|
|
|
19066
19233
|
//#endregion
|
|
19067
19234
|
//#region src/index.ts
|
|
19235
|
+
function workerPidfilePath() {
|
|
19236
|
+
return join(homedir(), ".agentmemory", "worker.pid");
|
|
19237
|
+
}
|
|
19238
|
+
function writeWorkerPidfile() {
|
|
19239
|
+
try {
|
|
19240
|
+
const p = workerPidfilePath();
|
|
19241
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
19242
|
+
writeFileSync(p, `${process.pid}\n`, { encoding: "utf-8" });
|
|
19243
|
+
} catch {}
|
|
19244
|
+
}
|
|
19245
|
+
function clearWorkerPidfile() {
|
|
19246
|
+
try {
|
|
19247
|
+
unlinkSync(workerPidfilePath());
|
|
19248
|
+
} catch {}
|
|
19249
|
+
}
|
|
19068
19250
|
function hasGetMeter(sdk) {
|
|
19069
19251
|
return typeof sdk === "object" && sdk !== null && "getMeter" in sdk && typeof sdk.getMeter === "function";
|
|
19070
19252
|
}
|
|
@@ -19105,6 +19287,7 @@ async function main() {
|
|
|
19105
19287
|
framework: "iii-sdk"
|
|
19106
19288
|
}
|
|
19107
19289
|
});
|
|
19290
|
+
writeWorkerPidfile();
|
|
19108
19291
|
const kv = new StateKV(sdk);
|
|
19109
19292
|
const secret = getEnvVar("AGENTMEMORY_SECRET");
|
|
19110
19293
|
const metricsStore = new MetricsStore(kv);
|
|
@@ -19200,6 +19383,7 @@ async function main() {
|
|
|
19200
19383
|
registerMcpEndpoints(sdk, kv, secret);
|
|
19201
19384
|
const healthMonitor = registerHealthMonitor(sdk, kv);
|
|
19202
19385
|
const indexPersistence = new IndexPersistence(kv, bm25Index, vectorIndex);
|
|
19386
|
+
setIndexPersistence(indexPersistence);
|
|
19203
19387
|
const loaded = await indexPersistence.load().catch((err) => {
|
|
19204
19388
|
console.warn(`[agentmemory] Failed to load persisted index:`, err);
|
|
19205
19389
|
return null;
|
|
@@ -19317,6 +19501,7 @@ async function main() {
|
|
|
19317
19501
|
console.warn(`[agentmemory] Failed to save index on shutdown:`, err);
|
|
19318
19502
|
});
|
|
19319
19503
|
await sdk.shutdown();
|
|
19504
|
+
clearWorkerPidfile();
|
|
19320
19505
|
process.exit(0);
|
|
19321
19506
|
};
|
|
19322
19507
|
process.on("SIGINT", shutdown);
|
|
@@ -19329,4 +19514,4 @@ main().catch((err) => {
|
|
|
19329
19514
|
|
|
19330
19515
|
//#endregion
|
|
19331
19516
|
export { };
|
|
19332
|
-
//# sourceMappingURL=src-
|
|
19517
|
+
//# sourceMappingURL=src-gpTAJuBy.mjs.map
|