@xdarkicex/openclaw-memory-libravdb 1.6.28 → 1.6.30

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.
@@ -54,15 +54,22 @@ function normalizeCompactResult(response, options = {}) {
54
54
  ? response.summaryText
55
55
  : undefined,
56
56
  };
57
+ // When the engine owns compaction but refuses to compact while the session
58
+ // exceeds the threshold, this is not a successful skip — it's a failure.
59
+ // Signal ok:false so OpenClaw falls back to normal transcript compaction
60
+ // instead of accepting a bloated session.
61
+ const threshold = options.threshold;
62
+ const overBudget = threshold != null && tokensBefore >= threshold;
63
+ const engineRefused = !didCompact && overBudget;
57
64
  return {
58
- ok: true,
65
+ ok: !engineRefused,
59
66
  compacted: didCompact,
60
- ...(didCompact ? {} : { reason: "not_compacted" }),
67
+ ...(didCompact ? {} : { reason: engineRefused ? "overbudget_not_compacted" : "not_compacted" }),
61
68
  result: {
62
69
  tokensBefore,
63
70
  ...(details.summaryMethod ? { summary: details.summaryMethod } : {}),
64
71
  ...(details.summaryText ? { summaryText: details.summaryText } : {}),
65
- details,
72
+ details: { ...details, ...(threshold != null ? { threshold } : {}) },
66
73
  },
67
74
  };
68
75
  }
@@ -1084,8 +1091,10 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1084
1091
  const request = buildCompactSessionRequest(args);
1085
1092
  try {
1086
1093
  const client = await runtime.getClient();
1094
+ const threshold = getDynamicCompactThreshold(args.tokenBudget);
1087
1095
  return normalizeCompactResult(await client.compactSession(request), {
1088
1096
  tokensBefore: args.currentTokenCount,
1097
+ ...(threshold != null ? { threshold } : {}),
1089
1098
  });
1090
1099
  }
1091
1100
  catch (error) {
package/dist/index.js CHANGED
@@ -25830,6 +25830,7 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
25830
25830
  query: queryOrParams,
25831
25831
  limit: opts.limit ?? opts.k ?? opts.maxResults ?? opts.topK,
25832
25832
  minScore: opts.minScore,
25833
+ corpus: opts.corpus,
25833
25834
  sessionId: opts.sessionId,
25834
25835
  sessionKey: opts.sessionKey,
25835
25836
  userId: opts.userId,
@@ -25841,6 +25842,7 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
25841
25842
  return legacyCall ? { results: [], error: "Missing query text for LibraVDB memory search" } : [];
25842
25843
  }
25843
25844
  const dreamQuery = detectDreamQuerySignal(queryText);
25845
+ const searchCorpus = normalizeSearchCorpus(params.corpus);
25844
25846
  const sessionId = firstString(params.sessionId, params.context?.sessionId);
25845
25847
  const explicitUserId = firstString(params.userId, params.context?.userId);
25846
25848
  const resolvedUserId = explicitUserId ?? getResolvedUserId(firstString(params.sessionKey, params.context?.sessionKey));
@@ -25853,17 +25855,19 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
25853
25855
  const k = normalizePositiveInteger(params.k, params.limit, params.maxResults, params.topK, cfg.topK, 8);
25854
25856
  const minScore = normalizeNumber(params.minScore);
25855
25857
  const client = await getClient();
25856
- const result = dreamQuery.active && cfg.crossSessionRecall !== false ? await client.searchText({
25858
+ const result = dreamQuery.active && cfg.crossSessionRecall !== false && searchCorpus !== "sessions" ? await client.searchText({
25857
25859
  collection: resolveDreamCollection(userId),
25858
25860
  text: queryText,
25859
25861
  k
25860
- }) : await searchResolvedCollections(client, cfg, userId, sessionId, queryText, k);
25862
+ }) : await searchResolvedCollections(client, cfg, userId, sessionId, queryText, k, searchCorpus);
25861
25863
  const filteredResults = minScore === void 0 ? result.results : result.results.filter((item) => item.score >= minScore);
25862
25864
  const legacyResults = filteredResults.map((item) => {
25863
25865
  const meta = parseMetadataJson(item);
25866
+ const text = resolveSearchResultText(item, meta);
25864
25867
  return {
25865
25868
  ...item,
25866
- content: item.text || (typeof meta.text === "string" ? meta.text : "")
25869
+ text,
25870
+ content: text
25867
25871
  };
25868
25872
  });
25869
25873
  if (legacyCall) {
@@ -25872,10 +25876,10 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
25872
25876
  const memoryResults = filteredResults.map((item) => {
25873
25877
  const meta = parseMetadataJson(item);
25874
25878
  const collection = typeof meta.collection === "string" ? meta.collection : "memory";
25875
- const effectiveText = item.text || (typeof meta.text === "string" ? meta.text : "") || "";
25876
25879
  const relPath = encodeSearchResultPath(collection, item.id);
25877
- returnedSearchPaths.set(relPath, effectiveText);
25878
- return toMemorySearchResult(item);
25880
+ const text = resolveSearchResultText(item, meta);
25881
+ returnedSearchPaths.set(relPath, text);
25882
+ return toMemorySearchResult(item, meta, text);
25879
25883
  });
25880
25884
  return memoryResults;
25881
25885
  },
@@ -25916,8 +25920,8 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
25916
25920
  }
25917
25921
  };
25918
25922
  }
25919
- async function searchResolvedCollections(client, cfg, userId, sessionId, queryText, k) {
25920
- const collections = resolveSearchCollections(cfg, userId, sessionId);
25923
+ async function searchResolvedCollections(client, cfg, userId, sessionId, queryText, k, corpus) {
25924
+ const collections = resolveSearchCollections(cfg, userId, sessionId, corpus);
25921
25925
  if (collections.length === 0) {
25922
25926
  return { results: [] };
25923
25927
  }
@@ -25932,16 +25936,21 @@ async function searchResolvedCollections(client, cfg, userId, sessionId, queryTe
25932
25936
  excludeByCollection: {}
25933
25937
  });
25934
25938
  }
25935
- function resolveSearchCollections(cfg, userId, sessionId) {
25939
+ function resolveSearchCollections(cfg, userId, sessionId, corpus) {
25940
+ if (corpus === "sessions") {
25941
+ return sessionId ? [resolveSessionSearchCollection(cfg, sessionId)] : [];
25942
+ }
25943
+ const durableCollections = [resolveUserCollection(userId), "global"];
25944
+ if (corpus === "memory") {
25945
+ return durableCollections;
25946
+ }
25936
25947
  if (cfg.crossSessionRecall === false) {
25937
25948
  return sessionId ? [resolveSessionSearchCollection(cfg, sessionId)] : [];
25938
25949
  }
25939
- const collections = [resolveUserCollection(userId), "global"];
25940
25950
  if (!sessionId) {
25941
- return collections;
25951
+ return durableCollections;
25942
25952
  }
25943
- collections.unshift(resolveSessionSearchCollection(cfg, sessionId));
25944
- return collections;
25953
+ return [resolveSessionSearchCollection(cfg, sessionId), ...durableCollections];
25945
25954
  }
25946
25955
  function resolveSessionSearchCollection(cfg, sessionId) {
25947
25956
  if (cfg.useSessionSummarySearchExperiment) {
@@ -25964,6 +25973,9 @@ function firstString(...values) {
25964
25973
  }
25965
25974
  return void 0;
25966
25975
  }
25976
+ function normalizeSearchCorpus(value) {
25977
+ return value === "memory" || value === "sessions" ? value : "all";
25978
+ }
25967
25979
  function parseMetadataJson(item) {
25968
25980
  if (item.metadataJson && item.metadataJson.length > 0) {
25969
25981
  try {
@@ -25973,16 +25985,20 @@ function parseMetadataJson(item) {
25973
25985
  }
25974
25986
  return {};
25975
25987
  }
25976
- function toMemorySearchResult(item) {
25977
- const meta = parseMetadataJson(item);
25988
+ function resolveSearchResultText(item, meta = parseMetadataJson(item)) {
25989
+ if (typeof item.text === "string" && item.text.length > 0) {
25990
+ return item.text;
25991
+ }
25992
+ return typeof meta.text === "string" ? meta.text : item.text;
25993
+ }
25994
+ function toMemorySearchResult(item, meta = parseMetadataJson(item), text = resolveSearchResultText(item, meta)) {
25978
25995
  const collection = typeof meta.collection === "string" ? meta.collection : "memory";
25979
- const effectiveText = item.text || (typeof meta.text === "string" ? meta.text : "") || "";
25980
25996
  return {
25981
25997
  path: encodeSearchResultPath(collection, item.id),
25982
25998
  startLine: 1,
25983
- endLine: Math.max(1, effectiveText.split("\n").length),
25999
+ endLine: Math.max(1, text.split("\n").length),
25984
26000
  score: item.score,
25985
- snippet: effectiveText,
26001
+ snippet: text,
25986
26002
  source: collection.startsWith("session:") || collection.startsWith("session_") ? "sessions" : "memory",
25987
26003
  citation: `${collection}:${item.id}`
25988
26004
  };
@@ -26723,15 +26739,18 @@ function normalizeCompactResult(response, options = {}) {
26723
26739
  meanConfidence: typeof response?.meanConfidence === "number" ? response.meanConfidence : void 0,
26724
26740
  summaryText: typeof response?.summaryText === "string" && response.summaryText.length > 0 ? response.summaryText : void 0
26725
26741
  };
26742
+ const threshold = options.threshold;
26743
+ const overBudget = threshold != null && tokensBefore >= threshold;
26744
+ const engineRefused = !didCompact && overBudget;
26726
26745
  return {
26727
- ok: true,
26746
+ ok: !engineRefused,
26728
26747
  compacted: didCompact,
26729
- ...didCompact ? {} : { reason: "not_compacted" },
26748
+ ...didCompact ? {} : { reason: engineRefused ? "overbudget_not_compacted" : "not_compacted" },
26730
26749
  result: {
26731
26750
  tokensBefore,
26732
26751
  ...details.summaryMethod ? { summary: details.summaryMethod } : {},
26733
26752
  ...details.summaryText ? { summaryText: details.summaryText } : {},
26734
- details
26753
+ details: { ...details, ...threshold != null ? { threshold } : {} }
26735
26754
  }
26736
26755
  };
26737
26756
  }
@@ -27583,8 +27602,10 @@ function buildContextEngineFactory(runtime, cfg, logger = console) {
27583
27602
  const request3 = buildCompactSessionRequest(args);
27584
27603
  try {
27585
27604
  const client = await runtime.getClient();
27605
+ const threshold = getDynamicCompactThreshold(args.tokenBudget);
27586
27606
  return normalizeCompactResult(await client.compactSession(request3), {
27587
- tokensBefore: args.currentTokenCount
27607
+ tokensBefore: args.currentTokenCount,
27608
+ ...threshold != null ? { threshold } : {}
27588
27609
  });
27589
27610
  } catch (error2) {
27590
27611
  return {
@@ -34722,12 +34743,271 @@ var MEMORY_PROMPT_HEADER = [
34722
34743
  "in context via the context-engine assembler when available and relevant.",
34723
34744
  ""
34724
34745
  ];
34746
+ function buildToolGuidance(availableTools) {
34747
+ if (!availableTools?.has("memory_search")) {
34748
+ return [];
34749
+ }
34750
+ const lines = [
34751
+ "For explicit memory lookup requests, call `memory_search` first.",
34752
+ "Use it for prior turns, remembered facts, earliest interactions, and channel history.",
34753
+ "Do not answer memory lookup requests from prior transcript claims or earlier `memory_search` results; perform a fresh `memory_search` for the current request.",
34754
+ "For earliest or oldest memory questions, request enough results, compare timestamps in the returned snippets, and use `memory_get` if the snippet is not enough."
34755
+ ];
34756
+ if (availableTools.has("memory_get")) {
34757
+ lines.push("After a `memory_search` hit, call `memory_get` when exact wording or more context is needed.");
34758
+ }
34759
+ lines.push(
34760
+ "Do not treat a missing `MEMORY.md` file as missing memory; LibraVDB memory is vector-backed and retrieved through the memory tools.",
34761
+ ""
34762
+ );
34763
+ return lines;
34764
+ }
34725
34765
  function buildMemoryPromptSection(_getClient, _cfg) {
34726
34766
  return function memoryPromptSection({
34727
- availableTools: _availableTools,
34767
+ availableTools,
34728
34768
  citationsMode: _citationsMode
34729
34769
  }) {
34730
- return [...MEMORY_PROMPT_HEADER];
34770
+ return [...MEMORY_PROMPT_HEADER, ...buildToolGuidance(availableTools)];
34771
+ };
34772
+ }
34773
+
34774
+ // src/memory-tools.ts
34775
+ var MEMORY_SEARCH_SCHEMA = {
34776
+ type: "object",
34777
+ additionalProperties: false,
34778
+ properties: {
34779
+ query: {
34780
+ type: "string",
34781
+ description: "Semantic recall query for prior work, preferences, decisions, dates, people, todos, or session context."
34782
+ },
34783
+ maxResults: {
34784
+ type: "number",
34785
+ minimum: 1,
34786
+ maximum: 50,
34787
+ description: "Maximum number of memory hits to return."
34788
+ },
34789
+ minScore: {
34790
+ type: "number",
34791
+ minimum: 0,
34792
+ maximum: 1,
34793
+ description: "Minimum similarity score for returned hits."
34794
+ },
34795
+ corpus: {
34796
+ type: "string",
34797
+ enum: ["memory", "wiki", "all", "sessions"],
34798
+ description: "Corpus filter. LibraVDB serves memory/session hits; wiki is unsupported unless another plugin owns wiki tools."
34799
+ }
34800
+ },
34801
+ required: ["query"]
34802
+ };
34803
+ var MEMORY_GET_SCHEMA = {
34804
+ type: "object",
34805
+ additionalProperties: false,
34806
+ properties: {
34807
+ path: {
34808
+ type: "string",
34809
+ description: "A path returned by memory_search."
34810
+ },
34811
+ from: {
34812
+ type: "number",
34813
+ minimum: 1,
34814
+ description: "1-based starting line."
34815
+ },
34816
+ lines: {
34817
+ type: "number",
34818
+ minimum: 1,
34819
+ description: "Maximum number of lines to read."
34820
+ },
34821
+ corpus: {
34822
+ type: "string",
34823
+ enum: ["memory", "wiki", "all"],
34824
+ description: "Corpus filter. LibraVDB reads paths returned by memory_search."
34825
+ }
34826
+ },
34827
+ required: ["path"]
34828
+ };
34829
+ function createLibraVdbMemoryTools(getClient, cfg, logger = console) {
34830
+ const bridge = buildMemoryRuntimeBridge(getClient, cfg);
34831
+ const managers = /* @__PURE__ */ new Map();
34832
+ async function getManager(ctx, purpose) {
34833
+ const key = managerCacheKey(ctx);
34834
+ let manager = managers.get(key);
34835
+ if (!manager) {
34836
+ manager = bridge.getMemorySearchManager({
34837
+ agentId: normalizeOptionalString(ctx.agentId),
34838
+ purpose
34839
+ }).then((result) => result.manager).catch((error2) => {
34840
+ managers.delete(key);
34841
+ throw error2;
34842
+ });
34843
+ managers.set(key, manager);
34844
+ }
34845
+ return await manager;
34846
+ }
34847
+ return {
34848
+ createSearchTool(ctx = {}) {
34849
+ return {
34850
+ name: "memory_search",
34851
+ label: "Memory Search",
34852
+ description: "Mandatory fresh LibraVDB recall step: semantically search durable memory and session recall before answering questions about prior work, decisions, dates, people, preferences, todos, or history. Do not reuse prior transcript claims or older memory_search results for a new memory lookup. For earliest/oldest questions, request enough results and compare timestamps in snippets. If response has disabled=true, memory retrieval is unavailable and should be surfaced to the user.",
34853
+ parameters: MEMORY_SEARCH_SCHEMA,
34854
+ execute: async (_toolCallId, rawParams) => {
34855
+ const params = asToolParamsRecord(rawParams);
34856
+ const query = readRequiredStringParam(params, "query");
34857
+ const corpus = readMemoryCorpus(params.corpus);
34858
+ const maxResults = readNumberParam(params, "maxResults", { integer: true });
34859
+ const minScore = readNumberParam(params, "minScore");
34860
+ if (corpus === "wiki") {
34861
+ return jsonToolResult({
34862
+ results: [],
34863
+ disabled: true,
34864
+ error: "LibraVDB memory_search does not provide the wiki corpus; use corpus=memory, corpus=sessions, or corpus=all."
34865
+ });
34866
+ }
34867
+ try {
34868
+ const manager = await getManager(ctx, "tool-search");
34869
+ const rawResults = await manager.search({
34870
+ query,
34871
+ corpus,
34872
+ ...maxResults !== void 0 ? { maxResults } : {},
34873
+ ...minScore !== void 0 ? { minScore } : {},
34874
+ ...buildSearchContext(ctx)
34875
+ });
34876
+ const results = filterResultsByCorpus(rawResults, corpus);
34877
+ const status = manager.status();
34878
+ return jsonToolResult({
34879
+ results,
34880
+ provider: status.provider,
34881
+ model: status.model,
34882
+ backend: status.backend
34883
+ });
34884
+ } catch (error2) {
34885
+ logger.warn?.(`LibraVDB memory_search failed: ${formatError(error2)}`);
34886
+ return jsonToolResult({
34887
+ results: [],
34888
+ disabled: true,
34889
+ error: formatError(error2)
34890
+ });
34891
+ }
34892
+ }
34893
+ };
34894
+ },
34895
+ createGetTool(ctx = {}) {
34896
+ return {
34897
+ name: "memory_get",
34898
+ label: "Memory Get",
34899
+ description: "Read a bounded exact excerpt from a LibraVDB memory path returned by memory_search. Use this after memory_search when a hit needs exact wording or more context.",
34900
+ parameters: MEMORY_GET_SCHEMA,
34901
+ execute: async (_toolCallId, rawParams) => {
34902
+ const params = asToolParamsRecord(rawParams);
34903
+ const relPath = readRequiredStringParam(params, "path");
34904
+ const corpus = readMemoryGetCorpus(params.corpus);
34905
+ const from = readNumberParam(params, "from", { integer: true });
34906
+ const lines = readNumberParam(params, "lines", { integer: true });
34907
+ if (corpus === "wiki") {
34908
+ return jsonToolResult({
34909
+ path: relPath,
34910
+ text: "",
34911
+ disabled: true,
34912
+ error: "LibraVDB memory_get does not provide the wiki corpus; use paths returned by LibraVDB memory_search."
34913
+ });
34914
+ }
34915
+ try {
34916
+ const manager = await getManager(ctx, "tool-get");
34917
+ const result = await manager.readFile({
34918
+ relPath,
34919
+ ...from !== void 0 ? { from } : {},
34920
+ ...lines !== void 0 ? { lines } : {}
34921
+ });
34922
+ return jsonToolResult(result);
34923
+ } catch (error2) {
34924
+ logger.warn?.(`LibraVDB memory_get failed: ${formatError(error2)}`);
34925
+ return jsonToolResult({
34926
+ path: relPath,
34927
+ text: "",
34928
+ disabled: true,
34929
+ error: formatError(error2)
34930
+ });
34931
+ }
34932
+ }
34933
+ };
34934
+ }
34935
+ };
34936
+ }
34937
+ function buildSearchContext(ctx) {
34938
+ const agentId = normalizeOptionalString(ctx.agentId);
34939
+ const sessionId = normalizeOptionalString(ctx.sessionId);
34940
+ const sessionKey = normalizeOptionalString(ctx.sessionKey);
34941
+ return {
34942
+ ...agentId ? { agentId } : {},
34943
+ ...sessionId ? { sessionId } : {},
34944
+ ...sessionKey ? { sessionKey } : {},
34945
+ context: {
34946
+ ...agentId ? { agentId } : {},
34947
+ ...sessionId ? { sessionId } : {},
34948
+ ...sessionKey ? { sessionKey } : {}
34949
+ }
34950
+ };
34951
+ }
34952
+ function filterResultsByCorpus(results, corpus) {
34953
+ if (corpus === "sessions") {
34954
+ return results.filter((result) => result.source === "sessions");
34955
+ }
34956
+ if (corpus === "memory") {
34957
+ return results.filter((result) => result.source === "memory");
34958
+ }
34959
+ return results;
34960
+ }
34961
+ function managerCacheKey(ctx) {
34962
+ return [
34963
+ normalizeOptionalString(ctx.agentId) ?? "",
34964
+ normalizeOptionalString(ctx.sessionId) ?? "",
34965
+ normalizeOptionalString(ctx.sessionKey) ?? ""
34966
+ ].join("\0");
34967
+ }
34968
+ function asToolParamsRecord(value) {
34969
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
34970
+ return {};
34971
+ }
34972
+ return value;
34973
+ }
34974
+ function readRequiredStringParam(params, key) {
34975
+ const value = normalizeOptionalString(params[key]);
34976
+ if (!value) {
34977
+ throw new Error(`memory tool requires ${key}`);
34978
+ }
34979
+ return value;
34980
+ }
34981
+ function readNumberParam(params, key, options = {}) {
34982
+ const value = params[key];
34983
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim().length > 0 ? Number(value) : void 0;
34984
+ if (parsed === void 0 || !Number.isFinite(parsed)) {
34985
+ return void 0;
34986
+ }
34987
+ return options.integer ? Math.max(1, Math.floor(parsed)) : parsed;
34988
+ }
34989
+ function readMemoryCorpus(value) {
34990
+ return value === "memory" || value === "wiki" || value === "all" || value === "sessions" ? value : "all";
34991
+ }
34992
+ function readMemoryGetCorpus(value) {
34993
+ return value === "memory" || value === "wiki" || value === "all" ? value : "memory";
34994
+ }
34995
+ function normalizeOptionalString(value) {
34996
+ if (typeof value !== "string") {
34997
+ return void 0;
34998
+ }
34999
+ const trimmed = value.trim();
35000
+ return trimmed.length > 0 ? trimmed : void 0;
35001
+ }
35002
+ function jsonToolResult(details) {
35003
+ return {
35004
+ content: [
35005
+ {
35006
+ type: "text",
35007
+ text: JSON.stringify(details, null, 2)
35008
+ }
35009
+ ],
35010
+ details
34731
35011
  };
34732
35012
  }
34733
35013
 
@@ -38056,7 +38336,7 @@ function enrichStartupError(error2, healthMessage) {
38056
38336
  // src/index.ts
38057
38337
  var MEMORY_ID = "libravdb-memory";
38058
38338
  var LIGHTWEIGHT_MODES = /* @__PURE__ */ new Set(["cli-metadata", "setup-only"]);
38059
- var RUNTIME_CLEANUP_SHUTDOWN_REASONS = /* @__PURE__ */ new Set(["delete", "restart"]);
38339
+ var RUNTIME_CLEANUP_SHUTDOWN_REASONS = /* @__PURE__ */ new Set(["delete"]);
38060
38340
  function shouldShutdownRuntimeForLifecycleCleanup(reason) {
38061
38341
  return RUNTIME_CLEANUP_SHUTDOWN_REASONS.has(reason);
38062
38342
  }
@@ -38090,6 +38370,12 @@ function register(api) {
38090
38370
  }
38091
38371
  const runtimeOrNull = isLightweight ? null : createPluginRuntime(cfg, logger);
38092
38372
  registerMemoryCli(api, runtimeOrNull, cfg, logger);
38373
+ const ownsMemorySlot = memSlot === MEMORY_ID;
38374
+ if (runtimeOrNull && ownsMemorySlot) {
38375
+ const memoryTools = createLibraVdbMemoryTools(runtimeOrNull.getClient, cfg, logger);
38376
+ api.registerTool?.((ctx) => memoryTools.createSearchTool(ctx), { names: ["memory_search"] });
38377
+ api.registerTool?.((ctx) => memoryTools.createGetTool(ctx), { names: ["memory_get"] });
38378
+ }
38093
38379
  if (isLightweight || isDiscovery) {
38094
38380
  if (!isLightweight) {
38095
38381
  logger.info?.(
@@ -4,10 +4,26 @@ const MEMORY_PROMPT_HEADER = [
4
4
  "in context via the context-engine assembler when available and relevant.",
5
5
  "",
6
6
  ];
7
+ function buildToolGuidance(availableTools) {
8
+ if (!availableTools?.has("memory_search")) {
9
+ return [];
10
+ }
11
+ const lines = [
12
+ "For explicit memory lookup requests, call `memory_search` first.",
13
+ "Use it for prior turns, remembered facts, earliest interactions, and channel history.",
14
+ "Do not answer memory lookup requests from prior transcript claims or earlier `memory_search` results; perform a fresh `memory_search` for the current request.",
15
+ "For earliest or oldest memory questions, request enough results, compare timestamps in the returned snippets, and use `memory_get` if the snippet is not enough.",
16
+ ];
17
+ if (availableTools.has("memory_get")) {
18
+ lines.push("After a `memory_search` hit, call `memory_get` when exact wording or more context is needed.");
19
+ }
20
+ lines.push("Do not treat a missing `MEMORY.md` file as missing memory; LibraVDB memory is vector-backed and retrieved through the memory tools.", "");
21
+ return lines;
22
+ }
7
23
  export function buildMemoryPromptSection(_getClient, _cfg) {
8
- return function memoryPromptSection({ availableTools: _availableTools, citationsMode: _citationsMode, }) {
24
+ return function memoryPromptSection({ availableTools, citationsMode: _citationsMode, }) {
9
25
  // OpenClaw builds the memory prompt section synchronously for embedded runs.
10
26
  // Actual retrieval and ranking happen in the context engine during assemble().
11
- return [...MEMORY_PROMPT_HEADER];
27
+ return [...MEMORY_PROMPT_HEADER, ...buildToolGuidance(availableTools)];
12
28
  };
13
29
  }
@@ -10,6 +10,7 @@ type MemorySearchParams = {
10
10
  maxResults?: number;
11
11
  minScore?: number;
12
12
  topK?: number;
13
+ corpus?: "all" | "memory" | "sessions";
13
14
  userId?: string;
14
15
  agentId?: string;
15
16
  sessionId?: string;
@@ -49,10 +50,10 @@ export declare function buildMemoryRuntimeBridge(getClient: ClientGetter, cfg: P
49
50
  error: string;
50
51
  } | {
51
52
  results: {
53
+ text: string;
52
54
  content: string;
53
55
  id: string;
54
56
  score: number;
55
- text: string;
56
57
  metadataJson: Uint8Array<ArrayBuffer>;
57
58
  version: bigint;
58
59
  }[];
@@ -39,6 +39,7 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
39
39
  query: queryOrParams,
40
40
  limit: opts.limit ?? opts.k ?? opts.maxResults ?? opts.topK,
41
41
  minScore: opts.minScore,
42
+ corpus: opts.corpus,
42
43
  sessionId: opts.sessionId,
43
44
  sessionKey: opts.sessionKey,
44
45
  userId: opts.userId,
@@ -51,6 +52,7 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
51
52
  return legacyCall ? { results: [], error: "Missing query text for LibraVDB memory search" } : [];
52
53
  }
53
54
  const dreamQuery = detectDreamQuerySignal(queryText);
55
+ const searchCorpus = normalizeSearchCorpus(params.corpus);
54
56
  const sessionId = firstString(params.sessionId, params.context?.sessionId);
55
57
  const explicitUserId = firstString(params.userId, params.context?.userId);
56
58
  const resolvedUserId = explicitUserId ??
@@ -64,21 +66,23 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
64
66
  const k = normalizePositiveInteger(params.k, params.limit, params.maxResults, params.topK, cfg.topK, 8);
65
67
  const minScore = normalizeNumber(params.minScore);
66
68
  const client = await getClient();
67
- const result = dreamQuery.active && cfg.crossSessionRecall !== false
69
+ const result = dreamQuery.active && cfg.crossSessionRecall !== false && searchCorpus !== "sessions"
68
70
  ? await client.searchText({
69
71
  collection: resolveDreamCollection(userId),
70
72
  text: queryText,
71
73
  k,
72
74
  })
73
- : await searchResolvedCollections(client, cfg, userId, sessionId, queryText, k);
75
+ : await searchResolvedCollections(client, cfg, userId, sessionId, queryText, k, searchCorpus);
74
76
  const filteredResults = minScore === undefined
75
77
  ? result.results
76
78
  : result.results.filter((item) => item.score >= minScore);
77
79
  const legacyResults = filteredResults.map((item) => {
78
80
  const meta = parseMetadataJson(item);
81
+ const text = resolveSearchResultText(item, meta);
79
82
  return {
80
83
  ...item,
81
- content: item.text || (typeof meta.text === "string" ? meta.text : ""),
84
+ text,
85
+ content: text,
82
86
  };
83
87
  });
84
88
  if (legacyCall) {
@@ -87,10 +91,10 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
87
91
  const memoryResults = filteredResults.map((item) => {
88
92
  const meta = parseMetadataJson(item);
89
93
  const collection = typeof meta.collection === "string" ? meta.collection : "memory";
90
- const effectiveText = item.text || (typeof meta.text === "string" ? meta.text : "") || "";
91
94
  const relPath = encodeSearchResultPath(collection, item.id);
92
- returnedSearchPaths.set(relPath, effectiveText);
93
- return toMemorySearchResult(item);
95
+ const text = resolveSearchResultText(item, meta);
96
+ returnedSearchPaths.set(relPath, text);
97
+ return toMemorySearchResult(item, meta, text);
94
98
  });
95
99
  return memoryResults;
96
100
  },
@@ -134,8 +138,8 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
134
138
  },
135
139
  };
136
140
  }
137
- async function searchResolvedCollections(client, cfg, userId, sessionId, queryText, k) {
138
- const collections = resolveSearchCollections(cfg, userId, sessionId);
141
+ async function searchResolvedCollections(client, cfg, userId, sessionId, queryText, k, corpus) {
142
+ const collections = resolveSearchCollections(cfg, userId, sessionId, corpus);
139
143
  if (collections.length === 0) {
140
144
  return { results: [] };
141
145
  }
@@ -152,16 +156,21 @@ async function searchResolvedCollections(client, cfg, userId, sessionId, queryTe
152
156
  excludeByCollection: {},
153
157
  });
154
158
  }
155
- function resolveSearchCollections(cfg, userId, sessionId) {
159
+ function resolveSearchCollections(cfg, userId, sessionId, corpus) {
160
+ if (corpus === "sessions") {
161
+ return sessionId ? [resolveSessionSearchCollection(cfg, sessionId)] : [];
162
+ }
163
+ const durableCollections = [resolveUserCollection(userId), "global"];
164
+ if (corpus === "memory") {
165
+ return durableCollections;
166
+ }
156
167
  if (cfg.crossSessionRecall === false) {
157
168
  return sessionId ? [resolveSessionSearchCollection(cfg, sessionId)] : [];
158
169
  }
159
- const collections = [resolveUserCollection(userId), "global"];
160
170
  if (!sessionId) {
161
- return collections;
171
+ return durableCollections;
162
172
  }
163
- collections.unshift(resolveSessionSearchCollection(cfg, sessionId));
164
- return collections;
173
+ return [resolveSessionSearchCollection(cfg, sessionId), ...durableCollections];
165
174
  }
166
175
  function resolveSessionSearchCollection(cfg, sessionId) {
167
176
  if (cfg.useSessionSummarySearchExperiment) {
@@ -184,6 +193,9 @@ function firstString(...values) {
184
193
  }
185
194
  return undefined;
186
195
  }
196
+ function normalizeSearchCorpus(value) {
197
+ return value === "memory" || value === "sessions" ? value : "all";
198
+ }
187
199
  function parseMetadataJson(item) {
188
200
  if (item.metadataJson && item.metadataJson.length > 0) {
189
201
  try {
@@ -195,16 +207,20 @@ function parseMetadataJson(item) {
195
207
  }
196
208
  return {};
197
209
  }
198
- function toMemorySearchResult(item) {
199
- const meta = parseMetadataJson(item);
210
+ function resolveSearchResultText(item, meta = parseMetadataJson(item)) {
211
+ if (typeof item.text === "string" && item.text.length > 0) {
212
+ return item.text;
213
+ }
214
+ return typeof meta.text === "string" ? meta.text : item.text;
215
+ }
216
+ function toMemorySearchResult(item, meta = parseMetadataJson(item), text = resolveSearchResultText(item, meta)) {
200
217
  const collection = typeof meta.collection === "string" ? meta.collection : "memory";
201
- const effectiveText = item.text || (typeof meta.text === "string" ? meta.text : "") || "";
202
218
  return {
203
219
  path: encodeSearchResultPath(collection, item.id),
204
220
  startLine: 1,
205
- endLine: Math.max(1, effectiveText.split("\n").length),
221
+ endLine: Math.max(1, text.split("\n").length),
206
222
  score: item.score,
207
- snippet: effectiveText,
223
+ snippet: text,
208
224
  source: collection.startsWith("session:") || collection.startsWith("session_") ? "sessions" : "memory",
209
225
  citation: `${collection}:${item.id}`,
210
226
  };
@@ -0,0 +1,27 @@
1
+ import type { ClientGetter } from "./plugin-runtime.js";
2
+ import type { LoggerLike, PluginConfig } from "./types.js";
3
+ type MemoryToolContext = {
4
+ agentId?: string;
5
+ sessionId?: string;
6
+ sessionKey?: string;
7
+ };
8
+ type ToolContent = {
9
+ type: "text";
10
+ text: string;
11
+ };
12
+ type ToolResult<TDetails> = {
13
+ content: ToolContent[];
14
+ details: TDetails;
15
+ };
16
+ type AgentTool = {
17
+ name: string;
18
+ label: string;
19
+ description: string;
20
+ parameters: unknown;
21
+ execute(toolCallId: string, params: unknown): Promise<ToolResult<unknown>>;
22
+ };
23
+ export declare function createLibraVdbMemoryTools(getClient: ClientGetter, cfg: PluginConfig, logger?: LoggerLike): {
24
+ createSearchTool(ctx?: MemoryToolContext): AgentTool;
25
+ createGetTool(ctx?: MemoryToolContext): AgentTool;
26
+ };
27
+ export {};
@@ -0,0 +1,251 @@
1
+ import { formatError } from "./format-error.js";
2
+ import { buildMemoryRuntimeBridge } from "./memory-runtime.js";
3
+ const MEMORY_SEARCH_SCHEMA = {
4
+ type: "object",
5
+ additionalProperties: false,
6
+ properties: {
7
+ query: {
8
+ type: "string",
9
+ description: "Semantic recall query for prior work, preferences, decisions, dates, people, todos, or session context.",
10
+ },
11
+ maxResults: {
12
+ type: "number",
13
+ minimum: 1,
14
+ maximum: 50,
15
+ description: "Maximum number of memory hits to return.",
16
+ },
17
+ minScore: {
18
+ type: "number",
19
+ minimum: 0,
20
+ maximum: 1,
21
+ description: "Minimum similarity score for returned hits.",
22
+ },
23
+ corpus: {
24
+ type: "string",
25
+ enum: ["memory", "wiki", "all", "sessions"],
26
+ description: "Corpus filter. LibraVDB serves memory/session hits; wiki is unsupported unless another plugin owns wiki tools.",
27
+ },
28
+ },
29
+ required: ["query"],
30
+ };
31
+ const MEMORY_GET_SCHEMA = {
32
+ type: "object",
33
+ additionalProperties: false,
34
+ properties: {
35
+ path: {
36
+ type: "string",
37
+ description: "A path returned by memory_search.",
38
+ },
39
+ from: {
40
+ type: "number",
41
+ minimum: 1,
42
+ description: "1-based starting line.",
43
+ },
44
+ lines: {
45
+ type: "number",
46
+ minimum: 1,
47
+ description: "Maximum number of lines to read.",
48
+ },
49
+ corpus: {
50
+ type: "string",
51
+ enum: ["memory", "wiki", "all"],
52
+ description: "Corpus filter. LibraVDB reads paths returned by memory_search.",
53
+ },
54
+ },
55
+ required: ["path"],
56
+ };
57
+ export function createLibraVdbMemoryTools(getClient, cfg, logger = console) {
58
+ const bridge = buildMemoryRuntimeBridge(getClient, cfg);
59
+ const managers = new Map();
60
+ async function getManager(ctx, purpose) {
61
+ const key = managerCacheKey(ctx);
62
+ let manager = managers.get(key);
63
+ if (!manager) {
64
+ manager = bridge
65
+ .getMemorySearchManager({
66
+ agentId: normalizeOptionalString(ctx.agentId),
67
+ purpose,
68
+ })
69
+ .then((result) => result.manager)
70
+ .catch((error) => {
71
+ managers.delete(key);
72
+ throw error;
73
+ });
74
+ managers.set(key, manager);
75
+ }
76
+ return await manager;
77
+ }
78
+ return {
79
+ createSearchTool(ctx = {}) {
80
+ return {
81
+ name: "memory_search",
82
+ label: "Memory Search",
83
+ description: "Mandatory fresh LibraVDB recall step: semantically search durable memory and session recall before answering questions about prior work, decisions, dates, people, preferences, todos, or history. Do not reuse prior transcript claims or older memory_search results for a new memory lookup. For earliest/oldest questions, request enough results and compare timestamps in snippets. If response has disabled=true, memory retrieval is unavailable and should be surfaced to the user.",
84
+ parameters: MEMORY_SEARCH_SCHEMA,
85
+ execute: async (_toolCallId, rawParams) => {
86
+ const params = asToolParamsRecord(rawParams);
87
+ const query = readRequiredStringParam(params, "query");
88
+ const corpus = readMemoryCorpus(params.corpus);
89
+ const maxResults = readNumberParam(params, "maxResults", { integer: true });
90
+ const minScore = readNumberParam(params, "minScore");
91
+ if (corpus === "wiki") {
92
+ return jsonToolResult({
93
+ results: [],
94
+ disabled: true,
95
+ error: "LibraVDB memory_search does not provide the wiki corpus; use corpus=memory, corpus=sessions, or corpus=all.",
96
+ });
97
+ }
98
+ try {
99
+ const manager = await getManager(ctx, "tool-search");
100
+ const rawResults = await manager.search({
101
+ query,
102
+ corpus,
103
+ ...(maxResults !== undefined ? { maxResults } : {}),
104
+ ...(minScore !== undefined ? { minScore } : {}),
105
+ ...buildSearchContext(ctx),
106
+ });
107
+ const results = filterResultsByCorpus(rawResults, corpus);
108
+ const status = manager.status();
109
+ return jsonToolResult({
110
+ results,
111
+ provider: status.provider,
112
+ model: status.model,
113
+ backend: status.backend,
114
+ });
115
+ }
116
+ catch (error) {
117
+ logger.warn?.(`LibraVDB memory_search failed: ${formatError(error)}`);
118
+ return jsonToolResult({
119
+ results: [],
120
+ disabled: true,
121
+ error: formatError(error),
122
+ });
123
+ }
124
+ },
125
+ };
126
+ },
127
+ createGetTool(ctx = {}) {
128
+ return {
129
+ name: "memory_get",
130
+ label: "Memory Get",
131
+ description: "Read a bounded exact excerpt from a LibraVDB memory path returned by memory_search. Use this after memory_search when a hit needs exact wording or more context.",
132
+ parameters: MEMORY_GET_SCHEMA,
133
+ execute: async (_toolCallId, rawParams) => {
134
+ const params = asToolParamsRecord(rawParams);
135
+ const relPath = readRequiredStringParam(params, "path");
136
+ const corpus = readMemoryGetCorpus(params.corpus);
137
+ const from = readNumberParam(params, "from", { integer: true });
138
+ const lines = readNumberParam(params, "lines", { integer: true });
139
+ if (corpus === "wiki") {
140
+ return jsonToolResult({
141
+ path: relPath,
142
+ text: "",
143
+ disabled: true,
144
+ error: "LibraVDB memory_get does not provide the wiki corpus; use paths returned by LibraVDB memory_search.",
145
+ });
146
+ }
147
+ try {
148
+ const manager = await getManager(ctx, "tool-get");
149
+ const result = await manager.readFile({
150
+ relPath,
151
+ ...(from !== undefined ? { from } : {}),
152
+ ...(lines !== undefined ? { lines } : {}),
153
+ });
154
+ return jsonToolResult(result);
155
+ }
156
+ catch (error) {
157
+ logger.warn?.(`LibraVDB memory_get failed: ${formatError(error)}`);
158
+ return jsonToolResult({
159
+ path: relPath,
160
+ text: "",
161
+ disabled: true,
162
+ error: formatError(error),
163
+ });
164
+ }
165
+ },
166
+ };
167
+ },
168
+ };
169
+ }
170
+ function buildSearchContext(ctx) {
171
+ const agentId = normalizeOptionalString(ctx.agentId);
172
+ const sessionId = normalizeOptionalString(ctx.sessionId);
173
+ const sessionKey = normalizeOptionalString(ctx.sessionKey);
174
+ return {
175
+ ...(agentId ? { agentId } : {}),
176
+ ...(sessionId ? { sessionId } : {}),
177
+ ...(sessionKey ? { sessionKey } : {}),
178
+ context: {
179
+ ...(agentId ? { agentId } : {}),
180
+ ...(sessionId ? { sessionId } : {}),
181
+ ...(sessionKey ? { sessionKey } : {}),
182
+ },
183
+ };
184
+ }
185
+ function filterResultsByCorpus(results, corpus) {
186
+ if (corpus === "sessions") {
187
+ return results.filter((result) => result.source === "sessions");
188
+ }
189
+ if (corpus === "memory") {
190
+ return results.filter((result) => result.source === "memory");
191
+ }
192
+ return results;
193
+ }
194
+ function managerCacheKey(ctx) {
195
+ return [
196
+ normalizeOptionalString(ctx.agentId) ?? "",
197
+ normalizeOptionalString(ctx.sessionId) ?? "",
198
+ normalizeOptionalString(ctx.sessionKey) ?? "",
199
+ ].join("\0");
200
+ }
201
+ function asToolParamsRecord(value) {
202
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
203
+ return {};
204
+ }
205
+ return value;
206
+ }
207
+ function readRequiredStringParam(params, key) {
208
+ const value = normalizeOptionalString(params[key]);
209
+ if (!value) {
210
+ throw new Error(`memory tool requires ${key}`);
211
+ }
212
+ return value;
213
+ }
214
+ function readNumberParam(params, key, options = {}) {
215
+ const value = params[key];
216
+ const parsed = typeof value === "number"
217
+ ? value
218
+ : typeof value === "string" && value.trim().length > 0
219
+ ? Number(value)
220
+ : undefined;
221
+ if (parsed === undefined || !Number.isFinite(parsed)) {
222
+ return undefined;
223
+ }
224
+ return options.integer ? Math.max(1, Math.floor(parsed)) : parsed;
225
+ }
226
+ function readMemoryCorpus(value) {
227
+ return value === "memory" || value === "wiki" || value === "all" || value === "sessions"
228
+ ? value
229
+ : "all";
230
+ }
231
+ function readMemoryGetCorpus(value) {
232
+ return value === "memory" || value === "wiki" || value === "all" ? value : "memory";
233
+ }
234
+ function normalizeOptionalString(value) {
235
+ if (typeof value !== "string") {
236
+ return undefined;
237
+ }
238
+ const trimmed = value.trim();
239
+ return trimmed.length > 0 ? trimmed : undefined;
240
+ }
241
+ function jsonToolResult(details) {
242
+ return {
243
+ content: [
244
+ {
245
+ type: "text",
246
+ text: JSON.stringify(details, null, 2),
247
+ },
248
+ ],
249
+ details,
250
+ };
251
+ }
@@ -2,14 +2,18 @@
2
2
  "id": "libravdb-memory",
3
3
  "name": "LibraVDB Memory",
4
4
  "description": "Persistent vector memory with three-tier hybrid scoring",
5
- "version": "1.6.28",
5
+ "version": "1.6.30",
6
6
  "kind": [
7
7
  "memory",
8
8
  "context-engine"
9
9
  ],
10
10
  "contracts": {
11
11
  "memory": true,
12
- "contextEngine": true
12
+ "contextEngine": true,
13
+ "tools": [
14
+ "memory_search",
15
+ "memory_get"
16
+ ]
13
17
  },
14
18
  "activation": {
15
19
  "onCommands": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdarkicex/openclaw-memory-libravdb",
3
- "version": "1.6.28",
3
+ "version": "1.6.30",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",