@xdarkicex/openclaw-memory-libravdb 1.6.29 → 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.
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
  };
@@ -34727,12 +34743,271 @@ var MEMORY_PROMPT_HEADER = [
34727
34743
  "in context via the context-engine assembler when available and relevant.",
34728
34744
  ""
34729
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
+ }
34730
34765
  function buildMemoryPromptSection(_getClient, _cfg) {
34731
34766
  return function memoryPromptSection({
34732
- availableTools: _availableTools,
34767
+ availableTools,
34733
34768
  citationsMode: _citationsMode
34734
34769
  }) {
34735
- 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
34736
35011
  };
34737
35012
  }
34738
35013
 
@@ -38061,7 +38336,7 @@ function enrichStartupError(error2, healthMessage) {
38061
38336
  // src/index.ts
38062
38337
  var MEMORY_ID = "libravdb-memory";
38063
38338
  var LIGHTWEIGHT_MODES = /* @__PURE__ */ new Set(["cli-metadata", "setup-only"]);
38064
- var RUNTIME_CLEANUP_SHUTDOWN_REASONS = /* @__PURE__ */ new Set(["delete", "restart"]);
38339
+ var RUNTIME_CLEANUP_SHUTDOWN_REASONS = /* @__PURE__ */ new Set(["delete"]);
38065
38340
  function shouldShutdownRuntimeForLifecycleCleanup(reason) {
38066
38341
  return RUNTIME_CLEANUP_SHUTDOWN_REASONS.has(reason);
38067
38342
  }
@@ -38095,6 +38370,12 @@ function register(api) {
38095
38370
  }
38096
38371
  const runtimeOrNull = isLightweight ? null : createPluginRuntime(cfg, logger);
38097
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
+ }
38098
38379
  if (isLightweight || isDiscovery) {
38099
38380
  if (!isLightweight) {
38100
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.29",
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.29",
3
+ "version": "1.6.30",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",