@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.
@@ -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-BF0pgZmI.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-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 url = `${this.baseUrl}/v1/messages`;
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 fetch(this.baseUrl, {
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 fetch(`${API_BASE}?key=${this.apiKey}`, {
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 url = `${this.baseUrl}/v1/embeddings`;
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 fetch(API_URL$2, {
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 fetch(API_URL$1, {
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 fetch(API_URL, {
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([isSlotsEnabled() ? listPinnedSlots(kv).catch(() => []) : Promise.resolve([]), kv.get(KV.profiles, data.project).catch(() => null)]);
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: 107 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
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-3Oy_OOlF.mjs.map
18688
+ //# sourceMappingURL=src-C7vygXCj.mjs.map