@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.
Files changed (47) hide show
  1. package/README.md +150 -7
  2. package/dist/cli.d.mts +5 -1
  3. package/dist/cli.d.mts.map +1 -0
  4. package/dist/cli.mjs +103 -692
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/connect-BQQXpyDS.mjs +763 -0
  7. package/dist/connect-BQQXpyDS.mjs.map +1 -0
  8. package/dist/hooks/post-tool-use.mjs +1 -1
  9. package/dist/hooks/post-tool-use.mjs.map +1 -1
  10. package/dist/hooks/stop.mjs +8 -0
  11. package/dist/hooks/stop.mjs.map +1 -1
  12. package/dist/{image-refs-R3tin9MR.mjs → image-refs-CJS5B9Gq.mjs} +2 -2
  13. package/dist/{image-refs-R3tin9MR.mjs.map → image-refs-CJS5B9Gq.mjs.map} +1 -1
  14. package/dist/{image-store-DyrKZKqZ.mjs → image-store-CdE0amb1.mjs} +1 -1
  15. package/dist/index.mjs +450 -242
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/logger-xlVlvCWX.mjs +43 -0
  18. package/dist/logger-xlVlvCWX.mjs.map +1 -0
  19. package/dist/schema-BkALl7Z_.mjs +74 -0
  20. package/dist/schema-BkALl7Z_.mjs.map +1 -0
  21. package/dist/{src-D5arboxc.mjs → src-gpTAJuBy.mjs} +428 -243
  22. package/dist/src-gpTAJuBy.mjs.map +1 -0
  23. package/dist/{standalone-C7BgzzIN.mjs → standalone-C4i7ktpn.mjs} +18 -6
  24. package/dist/standalone-C4i7ktpn.mjs.map +1 -0
  25. package/dist/standalone.d.mts.map +1 -1
  26. package/dist/standalone.mjs +15 -4
  27. package/dist/standalone.mjs.map +1 -1
  28. package/dist/{tools-registry-CRTWUFw9.mjs → tools-registry-B7Y6nJsr.mjs} +36 -11
  29. package/dist/tools-registry-B7Y6nJsr.mjs.map +1 -0
  30. package/dist/version-DvQMNbEH.mjs +6 -0
  31. package/dist/version-DvQMNbEH.mjs.map +1 -0
  32. package/dist/viewer/index.html +77 -9
  33. package/package.json +6 -4
  34. package/plugin/.claude-plugin/plugin.json +1 -1
  35. package/plugin/.codex-plugin/plugin.json +1 -1
  36. package/plugin/.mcp.json +3 -2
  37. package/plugin/opencode/agentmemory-capture.ts +34 -9
  38. package/plugin/scripts/diagnostics.d.mts +17 -0
  39. package/plugin/scripts/diagnostics.d.mts.map +1 -0
  40. package/plugin/scripts/diagnostics.mjs.map +1 -0
  41. package/plugin/scripts/post-tool-use.mjs +1 -1
  42. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  43. package/plugin/scripts/stop.mjs +8 -0
  44. package/plugin/scripts/stop.mjs.map +1 -1
  45. package/dist/src-D5arboxc.mjs.map +0 -1
  46. package/dist/standalone-C7BgzzIN.mjs.map +0 -1
  47. package/dist/tools-registry-CRTWUFw9.mjs.map +0 -1
@@ -1,9 +1,11 @@
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
- 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 loadSnapshotConfig, a as getConsolidationDecayDays, c as isAutoCompressEnabled, d as isDropStaleIndexEnabled, f as isGraphExtractionEnabled, g as loadFallbackConfig, h as loadEmbeddingConfig, i as detectLlmProviderKind, l as isConsolidationEnabled, m as loadConfig, n as getVisibleTools, o as getEnvVar, p as loadClaudeBridgeConfig, r as detectEmbeddingProvider, t as getAllTools, u as isContextInjectionEnabled, v as loadTeamConfig } from "./tools-registry-CRTWUFw9.mjs";
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 `${baseUrl}/v1/chat/completions`;
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 `${baseUrl}/v1/embeddings`;
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-DyrKZKqZ.mjs");
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-R3tin9MR.mjs");
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-DyrKZKqZ.mjs");
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 = await kv.get(KV.sessions, payload.sessionId);
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-R3tin9MR.mjs");
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) await recordAudit(kv, "forget", "mem::forget", [...deletedMemoryIds, ...deletedObservationIds], {
5288
- sessionId: data.sessionId,
5289
- deleted,
5290
- memoriesDeleted: deletedMemoryIds.length,
5291
- observationsDeleted: deletedObservationIds.length,
5292
- sessionDeleted: deletedSession,
5293
- reason: "user-initiated forget"
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-R3tin9MR.mjs");
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
- recordAccessBatch(kv, expanded.map((e) => e.observation.id));
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: expanded.length,
5911
+ returned: scoped.length,
5912
+ filteredOutOfScope: expanded.length - scoped.length,
5835
5913
  truncated
5836
5914
  });
5837
5915
  return {
5838
5916
  mode: "expanded",
5839
- results: expanded,
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 [hybridResults, lessons] = await Promise.all([searchFn(data.query, limit), includeLessons ? recallLessons(sdk, data.query, lessonLimit, data.project) : Promise.resolve([])]);
5852
- const compact = hybridResults.map((r) => ({
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-R3tin9MR.mjs");
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="([^"]+)"[^>]*>([\s\S]*?)<\/entity>/g;
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-R3tin9MR.mjs");
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) await recordAudit(kv, "delete", "mem::retention-evict", evictedIds, {
12794
- threshold,
12795
- evicted,
12796
- evictedEpisodic,
12797
- evictedSemantic,
12798
- reason: "retention score below threshold"
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: await kv.list(KV.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: await kv.list(KV.observations(sessionId)) }
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: { memories: req.query_params?.["latest"] === "true" ? memories.filter((m) => m.isLatest) : memories }
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-D5arboxc.mjs.map
19517
+ //# sourceMappingURL=src-gpTAJuBy.mjs.map