@agentmemory/agentmemory 0.9.16 → 0.9.18
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/.env.example +6 -0
- package/AGENTS.md +5 -5
- package/README.md +41 -6
- package/dist/.env.example +6 -0
- package/dist/cli.mjs +12 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +324 -17
- package/dist/index.mjs.map +1 -1
- package/dist/{src-3Oy_OOlF.mjs → src-C7vygXCj.mjs} +315 -15
- package/dist/src-C7vygXCj.mjs.map +1 -0
- package/dist/{standalone-BQOaGF4z.mjs → standalone-kg2TedgD.mjs} +18 -9
- package/dist/standalone-kg2TedgD.mjs.map +1 -0
- package/dist/standalone.mjs +17 -8
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-BF0pgZmI.mjs → tools-registry-BFKFKmYh.mjs} +11 -4
- package/dist/tools-registry-BFKFKmYh.mjs.map +1 -0
- package/dist/viewer/favicon.svg +1 -0
- package/dist/viewer/index.html +190 -60
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/src-3Oy_OOlF.mjs.map +0 -1
- package/dist/standalone-BQOaGF4z.mjs.map +0 -1
- package/dist/tools-registry-BF0pgZmI.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as STREAM, c as jaccardSimilarity, i as KV, n as bootLog, o as fingerprintId, r as logger, s as generateId, t as VERSION } from "./cli.mjs";
|
|
2
2
|
import { a as isManagedImagePath, getImageRefCount, i as getMaxBytes, n as IMAGES_DIR, r as deleteImage, t as withKeyedLock } from "./image-refs-R3tin9MR.mjs";
|
|
3
|
-
import { _ as loadTeamConfig, a as getConsolidationDecayDays, c as isAutoCompressEnabled, d as isGraphExtractionEnabled, f as loadClaudeBridgeConfig, g as loadSnapshotConfig, h as loadFallbackConfig, i as detectLlmProviderKind, l as isConsolidationEnabled, m as loadEmbeddingConfig, n as getVisibleTools, o as getEnvVar, p as loadConfig, r as detectEmbeddingProvider, t as getAllTools, u as isContextInjectionEnabled } from "./tools-registry-
|
|
3
|
+
import { _ as loadTeamConfig, a as getConsolidationDecayDays, c as isAutoCompressEnabled, d as isGraphExtractionEnabled, f as loadClaudeBridgeConfig, g as loadSnapshotConfig, h as loadFallbackConfig, i as detectLlmProviderKind, l as isConsolidationEnabled, m as loadEmbeddingConfig, n as getVisibleTools, o as getEnvVar, p as loadConfig, r as detectEmbeddingProvider, t as getAllTools, u as isContextInjectionEnabled } from "./tools-registry-BFKFKmYh.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { execFile } from "node:child_process";
|
|
6
6
|
import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -104,6 +104,20 @@ var AnthropicProvider = class {
|
|
|
104
104
|
}
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/providers/_fetch.ts
|
|
109
|
+
function fetchWithTimeout(url, init, timeoutMs) {
|
|
110
|
+
const parsed = timeoutMs ?? Number.parseInt(getEnvVar("AGENTMEMORY_LLM_TIMEOUT_MS") ?? "60000", 10);
|
|
111
|
+
const ms = Number.isFinite(parsed) && parsed > 0 ? parsed : 6e4;
|
|
112
|
+
const ctl = new AbortController();
|
|
113
|
+
const signal = init.signal ? AbortSignal.any([init.signal, ctl.signal]) : ctl.signal;
|
|
114
|
+
const t = setTimeout(() => ctl.abort(), ms);
|
|
115
|
+
return fetch(url, {
|
|
116
|
+
...init,
|
|
117
|
+
signal
|
|
118
|
+
}).finally(() => clearTimeout(t));
|
|
119
|
+
}
|
|
120
|
+
|
|
107
121
|
//#endregion
|
|
108
122
|
//#region src/providers/minimax.ts
|
|
109
123
|
/**
|
|
@@ -139,8 +153,7 @@ var MinimaxProvider = class {
|
|
|
139
153
|
return this.call(systemPrompt, userPrompt);
|
|
140
154
|
}
|
|
141
155
|
async call(systemPrompt, userPrompt) {
|
|
142
|
-
const
|
|
143
|
-
const response = await fetch(url, {
|
|
156
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/v1/messages`, {
|
|
144
157
|
method: "POST",
|
|
145
158
|
headers: {
|
|
146
159
|
"Content-Type": "application/json",
|
|
@@ -184,6 +197,146 @@ var NoopProvider = class {
|
|
|
184
197
|
}
|
|
185
198
|
};
|
|
186
199
|
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/providers/openai.ts
|
|
202
|
+
const DEFAULT_BASE_URL$1 = "https://api.openai.com";
|
|
203
|
+
const DEFAULT_TIMEOUT_MS = 6e4;
|
|
204
|
+
const DEFAULT_AZURE_API_VERSION = "2024-08-01-preview";
|
|
205
|
+
/**
|
|
206
|
+
* OpenAI-compatible LLM provider.
|
|
207
|
+
*
|
|
208
|
+
* Uses raw fetch (no SDK) to support any OpenAI-compatible endpoint:
|
|
209
|
+
* - OpenAI official
|
|
210
|
+
* - Azure OpenAI (auto-detected from .openai.azure.com host)
|
|
211
|
+
* - DeepSeek
|
|
212
|
+
* - 硅基流动 (SiliconFlow)
|
|
213
|
+
* - vLLM / LM Studio / Ollama (with OpenAI compatibility layer)
|
|
214
|
+
* - Any other proxy implementing /v1/chat/completions
|
|
215
|
+
*
|
|
216
|
+
* Required env vars:
|
|
217
|
+
* OPENAI_API_KEY — API key
|
|
218
|
+
*
|
|
219
|
+
* Optional:
|
|
220
|
+
* OPENAI_BASE_URL — base URL without path (default: https://api.openai.com).
|
|
221
|
+
* Azure: https://<resource>.openai.azure.com/openai/deployments/<deployment>
|
|
222
|
+
* OPENAI_MODEL — model name (default: gpt-4o-mini)
|
|
223
|
+
* OPENAI_API_VERSION — Azure api-version query param (default: 2024-08-01-preview)
|
|
224
|
+
* OPENAI_TIMEOUT_MS — outbound fetch timeout in ms (OpenAI-scoped alias,
|
|
225
|
+
* takes precedence over AGENTMEMORY_LLM_TIMEOUT_MS
|
|
226
|
+
* for back-compat with the v0.9.17 shipping name).
|
|
227
|
+
* AGENTMEMORY_LLM_TIMEOUT_MS — outbound fetch timeout in ms shared across all
|
|
228
|
+
* raw-fetch LLM + embedding providers. Used when
|
|
229
|
+
* OPENAI_TIMEOUT_MS is not set. Default: 60000.
|
|
230
|
+
* MAX_TOKENS — max output tokens (default: from config or 4096)
|
|
231
|
+
* OPENAI_REASONING_EFFORT — "low" | "medium" | "high" | "none"
|
|
232
|
+
* Passthrough for reasoning models (e.g. Ollama Cloud
|
|
233
|
+
* thinking models). Set to "none" to ensure
|
|
234
|
+
* message.content is populated instead of only
|
|
235
|
+
* message.reasoning.
|
|
236
|
+
*/
|
|
237
|
+
var OpenAIProvider = class {
|
|
238
|
+
name = "openai";
|
|
239
|
+
apiKey;
|
|
240
|
+
model;
|
|
241
|
+
maxTokens;
|
|
242
|
+
baseUrl;
|
|
243
|
+
reasoningEffort;
|
|
244
|
+
timeoutMs;
|
|
245
|
+
isAzure;
|
|
246
|
+
azureApiVersion;
|
|
247
|
+
constructor(apiKey, model, maxTokens, baseURL) {
|
|
248
|
+
this.apiKey = apiKey;
|
|
249
|
+
this.model = model;
|
|
250
|
+
this.maxTokens = maxTokens;
|
|
251
|
+
this.baseUrl = (baseURL || getEnvVar("OPENAI_BASE_URL") || DEFAULT_BASE_URL$1).replace(/\/+$/, "");
|
|
252
|
+
this.reasoningEffort = getEnvVar("OPENAI_REASONING_EFFORT") || void 0;
|
|
253
|
+
this.timeoutMs = resolveTimeout();
|
|
254
|
+
this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION;
|
|
255
|
+
this.isAzure = detectAzure(this.baseUrl);
|
|
256
|
+
}
|
|
257
|
+
async compress(systemPrompt, userPrompt) {
|
|
258
|
+
return this.call(systemPrompt, userPrompt);
|
|
259
|
+
}
|
|
260
|
+
async summarize(systemPrompt, userPrompt) {
|
|
261
|
+
return this.call(systemPrompt, userPrompt);
|
|
262
|
+
}
|
|
263
|
+
buildUrl() {
|
|
264
|
+
if (this.isAzure) {
|
|
265
|
+
const sep = this.baseUrl.includes("?") ? "&" : "?";
|
|
266
|
+
return `${this.baseUrl}/chat/completions${sep}api-version=${encodeURIComponent(this.azureApiVersion)}`;
|
|
267
|
+
}
|
|
268
|
+
return `${this.baseUrl}/v1/chat/completions`;
|
|
269
|
+
}
|
|
270
|
+
buildHeaders() {
|
|
271
|
+
if (this.isAzure) return {
|
|
272
|
+
"Content-Type": "application/json",
|
|
273
|
+
"api-key": this.apiKey
|
|
274
|
+
};
|
|
275
|
+
return {
|
|
276
|
+
"Content-Type": "application/json",
|
|
277
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
async call(systemPrompt, userPrompt) {
|
|
281
|
+
const url = this.buildUrl();
|
|
282
|
+
const body = {
|
|
283
|
+
model: this.model,
|
|
284
|
+
max_tokens: this.maxTokens,
|
|
285
|
+
messages: [{
|
|
286
|
+
role: "system",
|
|
287
|
+
content: systemPrompt
|
|
288
|
+
}, {
|
|
289
|
+
role: "user",
|
|
290
|
+
content: userPrompt
|
|
291
|
+
}]
|
|
292
|
+
};
|
|
293
|
+
if (this.reasoningEffort) body.reasoning_effort = this.reasoningEffort;
|
|
294
|
+
let response;
|
|
295
|
+
try {
|
|
296
|
+
response = await fetchWithTimeout(url, {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: this.buildHeaders(),
|
|
299
|
+
body: JSON.stringify(body)
|
|
300
|
+
}, this.timeoutMs);
|
|
301
|
+
} catch (err) {
|
|
302
|
+
if (err instanceof Error && err.name === "AbortError") throw new Error(`OpenAI API request timed out after ${this.timeoutMs}ms — set OPENAI_TIMEOUT_MS (or AGENTMEMORY_LLM_TIMEOUT_MS) to raise the bound or check the provider status.`);
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
305
|
+
if (!response.ok) {
|
|
306
|
+
const text = await response.text();
|
|
307
|
+
throw new Error(`OpenAI API error (${response.status}): ${text}`);
|
|
308
|
+
}
|
|
309
|
+
const data = await response.json();
|
|
310
|
+
const message = data.choices?.[0]?.message;
|
|
311
|
+
const content = message?.content;
|
|
312
|
+
if (content) return content;
|
|
313
|
+
const reasoning = message?.reasoning;
|
|
314
|
+
if (reasoning) return reasoning;
|
|
315
|
+
throw new Error(`OpenAI returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
function resolveTimeout() {
|
|
319
|
+
const openai = parsePositiveInt(getEnvVar("OPENAI_TIMEOUT_MS"));
|
|
320
|
+
if (openai !== void 0) return openai;
|
|
321
|
+
const globalMs = parsePositiveInt(getEnvVar("AGENTMEMORY_LLM_TIMEOUT_MS"));
|
|
322
|
+
if (globalMs !== void 0) return globalMs;
|
|
323
|
+
return DEFAULT_TIMEOUT_MS;
|
|
324
|
+
}
|
|
325
|
+
function parsePositiveInt(raw) {
|
|
326
|
+
if (!raw) return void 0;
|
|
327
|
+
const trimmed = raw.trim();
|
|
328
|
+
if (!/^\d+$/.test(trimmed)) return void 0;
|
|
329
|
+
const n = Number(trimmed);
|
|
330
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
331
|
+
}
|
|
332
|
+
function detectAzure(baseUrl) {
|
|
333
|
+
try {
|
|
334
|
+
return new URL(baseUrl).hostname.endsWith(".openai.azure.com");
|
|
335
|
+
} catch {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
187
340
|
//#endregion
|
|
188
341
|
//#region src/providers/openrouter.ts
|
|
189
342
|
var OpenRouterProvider = class {
|
|
@@ -206,7 +359,7 @@ var OpenRouterProvider = class {
|
|
|
206
359
|
return this.call(systemPrompt, userPrompt);
|
|
207
360
|
}
|
|
208
361
|
async call(systemPrompt, userPrompt) {
|
|
209
|
-
const response = await
|
|
362
|
+
const response = await fetchWithTimeout(this.baseUrl, {
|
|
210
363
|
method: "POST",
|
|
211
364
|
headers: {
|
|
212
365
|
"Content-Type": "application/json",
|
|
@@ -375,7 +528,7 @@ var GeminiEmbeddingProvider = class {
|
|
|
375
528
|
const results = [];
|
|
376
529
|
for (let i = 0; i < texts.length; i += BATCH_LIMIT) {
|
|
377
530
|
const chunk = texts.slice(i, i + BATCH_LIMIT);
|
|
378
|
-
const response = await
|
|
531
|
+
const response = await fetchWithTimeout(`${API_BASE}?key=${this.apiKey}`, {
|
|
379
532
|
method: "POST",
|
|
380
533
|
headers: { "Content-Type": "application/json" },
|
|
381
534
|
body: JSON.stringify({ requests: chunk.map((t) => ({
|
|
@@ -464,8 +617,7 @@ var OpenAIEmbeddingProvider = class {
|
|
|
464
617
|
return result;
|
|
465
618
|
}
|
|
466
619
|
async embedBatch(texts) {
|
|
467
|
-
const
|
|
468
|
-
const response = await fetch(url, {
|
|
620
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/v1/embeddings`, {
|
|
469
621
|
method: "POST",
|
|
470
622
|
headers: {
|
|
471
623
|
Authorization: `Bearer ${this.apiKey}`,
|
|
@@ -500,7 +652,7 @@ var VoyageEmbeddingProvider = class {
|
|
|
500
652
|
return result;
|
|
501
653
|
}
|
|
502
654
|
async embedBatch(texts) {
|
|
503
|
-
const response = await
|
|
655
|
+
const response = await fetchWithTimeout(API_URL$2, {
|
|
504
656
|
method: "POST",
|
|
505
657
|
headers: {
|
|
506
658
|
Authorization: `Bearer ${this.apiKey}`,
|
|
@@ -536,7 +688,7 @@ var CohereEmbeddingProvider = class {
|
|
|
536
688
|
return result;
|
|
537
689
|
}
|
|
538
690
|
async embedBatch(texts) {
|
|
539
|
-
const response = await
|
|
691
|
+
const response = await fetchWithTimeout(API_URL$1, {
|
|
540
692
|
method: "POST",
|
|
541
693
|
headers: {
|
|
542
694
|
Authorization: `Bearer ${this.apiKey}`,
|
|
@@ -574,7 +726,7 @@ var OpenRouterEmbeddingProvider = class {
|
|
|
574
726
|
return result;
|
|
575
727
|
}
|
|
576
728
|
async embedBatch(texts) {
|
|
577
|
-
const response = await
|
|
729
|
+
const response = await fetchWithTimeout(API_URL, {
|
|
578
730
|
method: "POST",
|
|
579
731
|
headers: {
|
|
580
732
|
Authorization: `Bearer ${this.apiKey}`,
|
|
@@ -769,6 +921,11 @@ function createBaseProvider(config) {
|
|
|
769
921
|
return new OpenRouterProvider(geminiKey, config.model, config.maxTokens, "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions");
|
|
770
922
|
}
|
|
771
923
|
case "openrouter": return new OpenRouterProvider(requireEnvVar("OPENROUTER_API_KEY"), config.model, config.maxTokens, "https://openrouter.ai/api/v1/chat/completions");
|
|
924
|
+
case "openai": {
|
|
925
|
+
const openaiKey = getEnvVar("OPENAI_API_KEY");
|
|
926
|
+
if (!openaiKey) throw new Error("OPENAI_API_KEY is required for the openai provider");
|
|
927
|
+
return new OpenAIProvider(openaiKey, config.model, config.maxTokens, config.baseURL);
|
|
928
|
+
}
|
|
772
929
|
case "noop": return new NoopProvider();
|
|
773
930
|
default: return new AgentSDKProvider();
|
|
774
931
|
}
|
|
@@ -3959,7 +4116,11 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
3959
4116
|
sdk.registerFunction("mem::context", async (data) => {
|
|
3960
4117
|
const budget = data.budget || tokenBudget;
|
|
3961
4118
|
const blocks = [];
|
|
3962
|
-
const [pinnedSlots, profile] = await Promise.all([
|
|
4119
|
+
const [pinnedSlots, profile, lessons] = await Promise.all([
|
|
4120
|
+
isSlotsEnabled() ? listPinnedSlots(kv).catch(() => []) : Promise.resolve([]),
|
|
4121
|
+
kv.get(KV.profiles, data.project).catch(() => null),
|
|
4122
|
+
kv.list(KV.lessons).catch(() => [])
|
|
4123
|
+
]);
|
|
3963
4124
|
const slotContent = renderPinnedContext(pinnedSlots);
|
|
3964
4125
|
if (slotContent) blocks.push({
|
|
3965
4126
|
type: "memory",
|
|
@@ -3983,6 +4144,24 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
3983
4144
|
});
|
|
3984
4145
|
}
|
|
3985
4146
|
}
|
|
4147
|
+
const relevantLessons = lessons.filter((l) => !l.deleted && (!l.project || l.project === data.project)).sort((a, b) => {
|
|
4148
|
+
const scoreA = (a.project === data.project ? 1.5 : 1) * a.confidence;
|
|
4149
|
+
return (b.project === data.project ? 1.5 : 1) * b.confidence - scoreA;
|
|
4150
|
+
}).slice(0, 10);
|
|
4151
|
+
if (relevantLessons.length > 0) {
|
|
4152
|
+
const lessonsContent = `## Lessons Learned\n${relevantLessons.map((l) => `- (${l.confidence.toFixed(2)}) ${l.content}${l.context ? ` — ${l.context}` : ""}`).join("\n")}`;
|
|
4153
|
+
const mostRecent = relevantLessons.reduce((acc, l) => {
|
|
4154
|
+
const t = new Date(l.lastReinforcedAt || l.updatedAt).getTime();
|
|
4155
|
+
return t > acc ? t : acc;
|
|
4156
|
+
}, 0);
|
|
4157
|
+
blocks.push({
|
|
4158
|
+
type: "memory",
|
|
4159
|
+
content: lessonsContent,
|
|
4160
|
+
tokens: estimateTokens$1(lessonsContent),
|
|
4161
|
+
recency: mostRecent,
|
|
4162
|
+
sourceIds: relevantLessons.map((l) => l.id)
|
|
4163
|
+
});
|
|
4164
|
+
}
|
|
3986
4165
|
const sessions = (await kv.list(KV.sessions)).filter((s) => s.project === data.project && s.id !== data.sessionId).sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).slice(0, 10);
|
|
3987
4166
|
const summariesPerSession = await Promise.all(sessions.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
|
|
3988
4167
|
const sessionsNeedingObs = [];
|
|
@@ -4805,6 +4984,46 @@ const DEFAULTS$1 = {
|
|
|
4805
4984
|
lowImportanceThreshold: 3,
|
|
4806
4985
|
maxObservationsPerProject: 1e4
|
|
4807
4986
|
};
|
|
4987
|
+
function isValidRecoveryResult(result) {
|
|
4988
|
+
if (!result || typeof result !== "object") return false;
|
|
4989
|
+
if (!("success" in result)) return true;
|
|
4990
|
+
return result.success !== false;
|
|
4991
|
+
}
|
|
4992
|
+
function isCompressedObservation(observation) {
|
|
4993
|
+
return "title" in observation && typeof observation.title === "string" && observation.title.length > 0;
|
|
4994
|
+
}
|
|
4995
|
+
async function recoverStaleSession(sdk, sessionId) {
|
|
4996
|
+
try {
|
|
4997
|
+
const result = await sdk.trigger({
|
|
4998
|
+
function_id: "event::session::stopped",
|
|
4999
|
+
payload: { sessionId }
|
|
5000
|
+
});
|
|
5001
|
+
if (!isValidRecoveryResult(result)) {
|
|
5002
|
+
logger.warn("Stale session recovery failed", {
|
|
5003
|
+
sessionId,
|
|
5004
|
+
result
|
|
5005
|
+
});
|
|
5006
|
+
return false;
|
|
5007
|
+
}
|
|
5008
|
+
return true;
|
|
5009
|
+
} catch (err) {
|
|
5010
|
+
logger.warn("Stale session recovery failed", {
|
|
5011
|
+
sessionId,
|
|
5012
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5013
|
+
});
|
|
5014
|
+
return false;
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5017
|
+
async function runRecoveredSessionConsolidation(sdk) {
|
|
5018
|
+
try {
|
|
5019
|
+
await sdk.trigger({
|
|
5020
|
+
function_id: "mem::consolidate-pipeline",
|
|
5021
|
+
payload: { tier: "all" }
|
|
5022
|
+
});
|
|
5023
|
+
} catch (err) {
|
|
5024
|
+
logger.warn("Recovered session consolidation failed", { error: err instanceof Error ? err.message : String(err) });
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
4808
5027
|
function registerEvictFunction(sdk, kv) {
|
|
4809
5028
|
sdk.registerFunction("mem::evict", async (data) => {
|
|
4810
5029
|
const dryRun = data?.dryRun ?? false;
|
|
@@ -4823,6 +5042,7 @@ function registerEvictFunction(sdk, kv) {
|
|
|
4823
5042
|
nonLatestMemories: 0,
|
|
4824
5043
|
dryRun
|
|
4825
5044
|
};
|
|
5045
|
+
let recoveredStaleSessions = 0;
|
|
4826
5046
|
const sessions = await kv.list(KV.sessions).catch(() => []);
|
|
4827
5047
|
const summaries = await kv.list(KV.summaries).catch(() => []);
|
|
4828
5048
|
const summaryIds = new Set(summaries.map((s) => s.sessionId));
|
|
@@ -4830,6 +5050,23 @@ function registerEvictFunction(sdk, kv) {
|
|
|
4830
5050
|
if (!session.startedAt) continue;
|
|
4831
5051
|
if (now - new Date(session.startedAt).getTime() > cfg.staleSessionDays * MS_PER_DAY$1 && !summaryIds.has(session.id)) if (dryRun) stats.staleSessions++;
|
|
4832
5052
|
else {
|
|
5053
|
+
const observations = await kv.list(KV.observations(session.id)).catch((err) => {
|
|
5054
|
+
logger.warn("Stale session observation scan failed", {
|
|
5055
|
+
sessionId: session.id,
|
|
5056
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5057
|
+
});
|
|
5058
|
+
return null;
|
|
5059
|
+
});
|
|
5060
|
+
if (!observations) continue;
|
|
5061
|
+
let recovered = false;
|
|
5062
|
+
if (observations.some(isCompressedObservation)) {
|
|
5063
|
+
recovered = await recoverStaleSession(sdk, session.id);
|
|
5064
|
+
if (!recovered) continue;
|
|
5065
|
+
recoveredStaleSessions++;
|
|
5066
|
+
} else if (observations.length > 0) {
|
|
5067
|
+
logger.warn("Stale session has no compressed observations", { sessionId: session.id });
|
|
5068
|
+
continue;
|
|
5069
|
+
}
|
|
4833
5070
|
try {
|
|
4834
5071
|
await kv.delete(KV.sessions, session.id);
|
|
4835
5072
|
stats.staleSessions++;
|
|
@@ -4843,11 +5080,12 @@ function registerEvictFunction(sdk, kv) {
|
|
|
4843
5080
|
}
|
|
4844
5081
|
await recordAudit(kv, "delete", "mem::evict", [session.id], {
|
|
4845
5082
|
resource: "session",
|
|
4846
|
-
reason: "stale_session_without_summary",
|
|
5083
|
+
reason: recovered ? "stale_session_recovered_then_evicted" : "stale_session_without_summary",
|
|
4847
5084
|
dryRun
|
|
4848
5085
|
});
|
|
4849
5086
|
}
|
|
4850
5087
|
}
|
|
5088
|
+
if (!dryRun && recoveredStaleSessions > 0) await runRecoveredSessionConsolidation(sdk);
|
|
4851
5089
|
const projectObs = /* @__PURE__ */ new Map();
|
|
4852
5090
|
for (const session of sessions) {
|
|
4853
5091
|
const compressed = (await kv.list(KV.observations(session.id)).catch(() => [])).filter((o) => o.title);
|
|
@@ -5656,7 +5894,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
5656
5894
|
"0.9.13",
|
|
5657
5895
|
"0.9.14",
|
|
5658
5896
|
"0.9.15",
|
|
5659
|
-
"0.9.16"
|
|
5897
|
+
"0.9.16",
|
|
5898
|
+
"0.9.17",
|
|
5899
|
+
"0.9.18"
|
|
5660
5900
|
]).has(importData.version)) return {
|
|
5661
5901
|
success: false,
|
|
5662
5902
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -17850,7 +18090,38 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
17850
18090
|
|
|
17851
18091
|
//#endregion
|
|
17852
18092
|
//#region src/viewer/server.ts
|
|
18093
|
+
function loadViewerFavicon() {
|
|
18094
|
+
const base = dirname(fileURLToPath(import.meta.url));
|
|
18095
|
+
const candidates = [
|
|
18096
|
+
join(base, "..", "src", "viewer", "favicon.svg"),
|
|
18097
|
+
join(base, "..", "viewer", "favicon.svg"),
|
|
18098
|
+
join(base, "viewer", "favicon.svg")
|
|
18099
|
+
];
|
|
18100
|
+
for (const path of candidates) try {
|
|
18101
|
+
return readFileSync(path);
|
|
18102
|
+
} catch {}
|
|
18103
|
+
return null;
|
|
18104
|
+
}
|
|
17853
18105
|
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());
|
|
18106
|
+
const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
|
|
18107
|
+
function buildAllowedHosts(origins, listenPort) {
|
|
18108
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
18109
|
+
for (const o of origins) try {
|
|
18110
|
+
const parsed = new URL(o);
|
|
18111
|
+
if (parsed.host) hosts.add(parsed.host.toLowerCase());
|
|
18112
|
+
} catch {}
|
|
18113
|
+
hosts.add(`localhost:${listenPort}`);
|
|
18114
|
+
hosts.add(`127.0.0.1:${listenPort}`);
|
|
18115
|
+
hosts.add(`[::1]:${listenPort}`);
|
|
18116
|
+
for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h);
|
|
18117
|
+
return hosts;
|
|
18118
|
+
}
|
|
18119
|
+
function isHostAllowed(headerHost, allowed) {
|
|
18120
|
+
if (typeof headerHost !== "string") return false;
|
|
18121
|
+
const lower = headerHost.toLowerCase().trim();
|
|
18122
|
+
if (!lower) return false;
|
|
18123
|
+
return allowed.has(lower);
|
|
18124
|
+
}
|
|
17854
18125
|
function corsHeaders(req) {
|
|
17855
18126
|
const origin = req.headers.origin || "";
|
|
17856
18127
|
const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
|
|
@@ -17894,7 +18165,17 @@ const MAX_VIEWER_PORT_RETRIES = 10;
|
|
|
17894
18165
|
function startViewerServer(port, _kv, _sdk, secret, restPort) {
|
|
17895
18166
|
const resolvedRestPort = restPort ?? port - 2;
|
|
17896
18167
|
const requestedPort = port;
|
|
18168
|
+
let allowedHosts = null;
|
|
17897
18169
|
const server = createServer(async (req, res) => {
|
|
18170
|
+
if (!allowedHosts) {
|
|
18171
|
+
const addr = server.address();
|
|
18172
|
+
allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port);
|
|
18173
|
+
}
|
|
18174
|
+
if (!isHostAllowed(req.headers.host, allowedHosts)) {
|
|
18175
|
+
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
18176
|
+
res.end("forbidden host");
|
|
18177
|
+
return;
|
|
18178
|
+
}
|
|
17898
18179
|
const raw = req.url || "/";
|
|
17899
18180
|
const qIdx = raw.indexOf("?");
|
|
17900
18181
|
const pathname = qIdx >= 0 ? raw.slice(0, qIdx) : raw;
|
|
@@ -17923,6 +18204,20 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
|
|
|
17923
18204
|
res.end("viewer not found");
|
|
17924
18205
|
return;
|
|
17925
18206
|
}
|
|
18207
|
+
if (method === "GET" && pathname === "/favicon.svg") {
|
|
18208
|
+
const favicon = loadViewerFavicon();
|
|
18209
|
+
if (favicon) {
|
|
18210
|
+
res.writeHead(200, {
|
|
18211
|
+
"Content-Type": "image/svg+xml",
|
|
18212
|
+
"Cache-Control": "public, max-age=3600"
|
|
18213
|
+
});
|
|
18214
|
+
res.end(favicon);
|
|
18215
|
+
return;
|
|
18216
|
+
}
|
|
18217
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
18218
|
+
res.end("favicon not found");
|
|
18219
|
+
return;
|
|
18220
|
+
}
|
|
17926
18221
|
try {
|
|
17927
18222
|
await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret);
|
|
17928
18223
|
} catch (err) {
|
|
@@ -18158,6 +18453,11 @@ async function main() {
|
|
|
18158
18453
|
serviceName: OTEL_CONFIG.serviceName,
|
|
18159
18454
|
serviceVersion: OTEL_CONFIG.serviceVersion,
|
|
18160
18455
|
metricsExportIntervalMs: OTEL_CONFIG.metricsExportIntervalMs
|
|
18456
|
+
},
|
|
18457
|
+
telemetry: {
|
|
18458
|
+
project_name: "agentmemory",
|
|
18459
|
+
language: "node",
|
|
18460
|
+
framework: "iii-sdk"
|
|
18161
18461
|
}
|
|
18162
18462
|
});
|
|
18163
18463
|
const kv = new StateKV(sdk);
|
|
@@ -18317,7 +18617,7 @@ async function main() {
|
|
|
18317
18617
|
console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
|
|
18318
18618
|
}
|
|
18319
18619
|
bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
18320
|
-
bootLog(`REST API:
|
|
18620
|
+
bootLog(`REST API: 121 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
|
|
18321
18621
|
bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
|
|
18322
18622
|
const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
|
|
18323
18623
|
const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
|
|
@@ -18385,4 +18685,4 @@ main().catch((err) => {
|
|
|
18385
18685
|
|
|
18386
18686
|
//#endregion
|
|
18387
18687
|
export { };
|
|
18388
|
-
//# sourceMappingURL=src-
|
|
18688
|
+
//# sourceMappingURL=src-C7vygXCj.mjs.map
|