@bamdra/bamdra-openclaw-memory 0.3.13 → 0.3.14

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
@@ -36,22 +36,30 @@ var ContextAssembler = class {
36
36
  }
37
37
  assemble(input) {
38
38
  const sections = [];
39
+ const maxFactValueChars = this.config.contextAssembly?.maxFactValueChars ?? 280;
40
+ const recentMessageMaxChars = this.config.contextAssembly?.recentMessageMaxChars ?? 1200;
39
41
  if (input.topic) {
40
42
  sections.push({
41
43
  kind: "topic",
42
- content: `Topic: ${input.topic.title}
43
- Labels: ${joinList(input.topic.labels)}`
44
+ content: trimToLength(
45
+ `Topic: ${input.topic.title}
46
+ Labels: ${joinList(input.topic.labels)}`,
47
+ 240
48
+ )
44
49
  });
45
50
  if (this.config.contextAssembly?.includeTopicShortSummary !== false) {
46
51
  sections.push({
47
52
  kind: "summary",
48
- content: input.topic.summaryShort || "(no short summary yet)"
53
+ content: trimToLength(input.topic.summaryShort || "(no short summary yet)", 600)
49
54
  });
50
55
  }
51
56
  if (this.config.contextAssembly?.includeOpenLoops !== false && input.topic.openLoops.length > 0) {
52
57
  sections.push({
53
58
  kind: "open_loops",
54
- content: input.topic.openLoops.map((item) => `- ${item}`).join("\n")
59
+ content: trimToLength(
60
+ input.topic.openLoops.map((item) => `- ${trimToLength(item, 120)}`).join("\n"),
61
+ 600
62
+ )
55
63
  });
56
64
  }
57
65
  }
@@ -70,22 +78,30 @@ Labels: ${joinList(input.topic.labels)}`
70
78
  kind: "facts",
71
79
  content: facts.map((fact) => {
72
80
  const prefix = fact.sensitivity === "secret_ref" ? "[secret-ref]" : `[${fact.category}]`;
73
- return `${prefix} ${fact.key}: ${fact.value}`;
81
+ return `${prefix} ${trimToLength(fact.key, 80)}: ${trimToLength(fact.value, maxFactValueChars)}`;
74
82
  }).join("\n")
75
83
  });
76
84
  }
77
85
  if (input.recentMessages.length > 0) {
78
86
  sections.push({
79
87
  kind: "recent_messages",
80
- content: input.recentMessages.map(({ message }) => `${message.role}: ${message.text}`).join("\n")
88
+ content: trimToLength(
89
+ input.recentMessages.map(({ message }) => `${message.role}: ${trimToLength(normalizeWhitespace(message.text), 220)}`).join("\n"),
90
+ recentMessageMaxChars
91
+ )
81
92
  });
82
93
  }
94
+ const maxChars = this.config.contextAssembly?.maxChars ?? 4e3;
95
+ const text = trimToLength(
96
+ sections.filter((section) => section.content.trim().length > 0).map((section) => `[${section.kind}]
97
+ ${section.content}`).join("\n\n"),
98
+ maxChars
99
+ );
83
100
  return {
84
101
  sessionId: input.sessionId,
85
102
  topicId: input.topic?.id ?? null,
86
- text: sections.map((section) => `[${section.kind}]
87
- ${section.content}`).join("\n\n"),
88
- sections
103
+ text,
104
+ sections: sections.filter((section) => section.content.trim().length > 0)
89
105
  };
90
106
  }
91
107
  };
@@ -95,6 +111,22 @@ function joinList(values) {
95
111
  function limitFacts(values, limit) {
96
112
  return values.slice(0, Math.max(0, limit));
97
113
  }
114
+ function trimToLength(value, maxChars) {
115
+ if (maxChars <= 0) {
116
+ return "";
117
+ }
118
+ const normalized = normalizeWhitespace(value);
119
+ if (normalized.length <= maxChars) {
120
+ return normalized;
121
+ }
122
+ if (maxChars <= 3) {
123
+ return normalized.slice(0, maxChars);
124
+ }
125
+ return `${normalized.slice(0, maxChars - 3).trimEnd()}...`;
126
+ }
127
+ function normalizeWhitespace(value) {
128
+ return value.replace(/\s+/g, " ").trim();
129
+ }
98
130
 
99
131
  // ../../packages/fact-extractor/src/index.ts
100
132
  var FactExtractor = class {
@@ -1337,9 +1369,11 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1337
1369
  const sessionId = getSessionIdFromHookContext(hookContext);
1338
1370
  await backfillResolvedIdentity(store, sessionId);
1339
1371
  const text = getTextFromHookContext(event);
1372
+ const hasMultimodalPayload = containsNonTextualPayload(event) || containsNonTextualPayload(hookContext);
1340
1373
  logMemoryEvent("hook-assemble-received", {
1341
1374
  hasSessionId: Boolean(sessionId),
1342
- hasText: Boolean(text)
1375
+ hasText: Boolean(text),
1376
+ hasMultimodalPayload
1343
1377
  });
1344
1378
  if (!sessionId) {
1345
1379
  return;
@@ -1360,14 +1394,21 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1360
1394
  query: text,
1361
1395
  limit: 3
1362
1396
  }) : null;
1363
- const assembledWithRecall = knowledgeRecall != null ? mergeLocalKnowledgeRecall(assembled, knowledgeRecall) : assembled;
1397
+ const assembledWithRecall = knowledgeRecall != null ? mergeLocalKnowledgeRecall(
1398
+ assembled,
1399
+ knowledgeRecall,
1400
+ config.contextAssembly?.recallMaxChars ?? 900
1401
+ ) : assembled;
1364
1402
  logMemoryEvent("hook-assemble-complete", {
1365
1403
  sessionId,
1366
1404
  topicId: assembledWithRecall.topicId,
1367
1405
  sections: assembledWithRecall.sections.length,
1368
1406
  localKnowledgeHits: knowledgeRecall == null ? 0 : knowledgeRecall.vectors.length + knowledgeRecall.facts.length + knowledgeRecall.topics.length
1369
1407
  });
1370
- return buildBeforePromptBuildResult(assembledWithRecall);
1408
+ return buildBeforePromptBuildResult(
1409
+ assembledWithRecall,
1410
+ hasMultimodalPayload ? config.contextAssembly?.maxCharsWhenMultimodal ?? 1200 : config.contextAssembly?.maxChars ?? 4e3
1411
+ );
1371
1412
  });
1372
1413
  } else {
1373
1414
  logMemoryEvent("register-typed-hooks-skipped", { reason: "typed hook registrar unavailable" });
@@ -1933,7 +1974,12 @@ function normalizeMemoryConfig(inputConfig) {
1933
1974
  includeTopicShortSummary: inputConfig?.contextAssembly?.includeTopicShortSummary ?? true,
1934
1975
  includeOpenLoops: inputConfig?.contextAssembly?.includeOpenLoops ?? true,
1935
1976
  alwaysFactLimit: inputConfig?.contextAssembly?.alwaysFactLimit ?? 12,
1936
- topicFactLimit: inputConfig?.contextAssembly?.topicFactLimit ?? 16
1977
+ topicFactLimit: inputConfig?.contextAssembly?.topicFactLimit ?? 16,
1978
+ maxChars: inputConfig?.contextAssembly?.maxChars ?? 4e3,
1979
+ maxCharsWhenMultimodal: inputConfig?.contextAssembly?.maxCharsWhenMultimodal ?? 1200,
1980
+ recentMessageMaxChars: inputConfig?.contextAssembly?.recentMessageMaxChars ?? 1200,
1981
+ maxFactValueChars: inputConfig?.contextAssembly?.maxFactValueChars ?? 280,
1982
+ recallMaxChars: inputConfig?.contextAssembly?.recallMaxChars ?? 900
1937
1983
  }
1938
1984
  };
1939
1985
  }
@@ -2007,14 +2053,11 @@ function extractTextFromInput(input) {
2007
2053
  return normalizeHookText(lastUserMessage?.text ?? lastUserMessage?.content);
2008
2054
  }
2009
2055
  function normalizeHookText(value) {
2010
- if (typeof value !== "string") {
2011
- return null;
2012
- }
2013
- const normalized = value.trim();
2056
+ const normalized = extractTextFromStructuredValue(value).trim();
2014
2057
  return normalized ? normalized : null;
2015
2058
  }
2016
- function buildBeforePromptBuildResult(assembled) {
2017
- const text = assembled.text.trim();
2059
+ function buildBeforePromptBuildResult(assembled, maxChars) {
2060
+ const text = trimToLength2(assembled.text.trim(), maxChars);
2018
2061
  if (!text) {
2019
2062
  return void 0;
2020
2063
  }
@@ -2047,30 +2090,92 @@ function shouldPreferLocalKnowledgeFirst(text) {
2047
2090
  ];
2048
2091
  return signals.some((signal) => normalized.includes(signal));
2049
2092
  }
2050
- function mergeLocalKnowledgeRecall(assembled, search) {
2051
- const vectorLines = search.vectors.slice(0, 3).map((item) => `- [vector] ${item.title} (${item.sourcePath}): ${item.text.slice(0, 220)}`);
2052
- const factLines = search.facts.slice(0, 2).map((item) => `- [fact] ${item.fact.key}: ${item.fact.value}`);
2053
- const topicLines = search.topics.slice(0, 2).map((item) => `- [topic] ${item.topic.title}: ${item.topic.summaryShort}`);
2093
+ function mergeLocalKnowledgeRecall(assembled, search, maxChars) {
2094
+ const vectorLines = search.vectors.slice(0, 3).map((item) => `- [vector] ${trimToLength2(item.title, 80)} (${trimToLength2(item.sourcePath, 120)}): ${trimToLength2(item.text, 180)}`);
2095
+ const factLines = search.facts.slice(0, 2).map((item) => `- [fact] ${trimToLength2(item.fact.key, 80)}: ${trimToLength2(item.fact.value, 140)}`);
2096
+ const topicLines = search.topics.slice(0, 2).map((item) => `- [topic] ${trimToLength2(item.topic.title, 80)}: ${trimToLength2(item.topic.summaryShort, 140)}`);
2054
2097
  const lines = [...vectorLines, ...factLines, ...topicLines];
2055
2098
  if (lines.length === 0) {
2056
2099
  return assembled;
2057
2100
  }
2058
- const content = [
2101
+ const content = trimToLength2([
2059
2102
  "Prefer answering from local memory and knowledge before using web search.",
2060
2103
  ...lines
2061
- ].join("\n");
2104
+ ].join("\n"), maxChars);
2062
2105
  return {
2063
2106
  ...assembled,
2064
- text: `[facts]
2107
+ text: trimToLength2(`[facts]
2065
2108
  ${content}
2066
2109
 
2067
- ${assembled.text}`.trim(),
2110
+ ${assembled.text}`.trim(), assembled.text.length + maxChars + 32),
2068
2111
  sections: [
2069
2112
  { kind: "facts", content },
2070
2113
  ...assembled.sections
2071
2114
  ]
2072
2115
  };
2073
2116
  }
2117
+ function extractTextFromStructuredValue(value, depth = 0) {
2118
+ if (depth > 4 || value == null) {
2119
+ return "";
2120
+ }
2121
+ if (typeof value === "string") {
2122
+ return value;
2123
+ }
2124
+ if (Array.isArray(value)) {
2125
+ return value.map((item) => extractTextFromStructuredValue(item, depth + 1)).filter(Boolean).join("\n");
2126
+ }
2127
+ if (typeof value !== "object") {
2128
+ return "";
2129
+ }
2130
+ const candidate = value;
2131
+ const direct = [
2132
+ candidate.text,
2133
+ candidate.input_text,
2134
+ candidate.caption
2135
+ ].map((item) => extractTextFromStructuredValue(item, depth + 1)).filter(Boolean);
2136
+ if (direct.length > 0) {
2137
+ return direct.join("\n");
2138
+ }
2139
+ return [
2140
+ extractTextFromStructuredValue(candidate.content, depth + 1),
2141
+ extractTextFromStructuredValue(candidate.message, depth + 1),
2142
+ extractTextFromStructuredValue(candidate.input, depth + 1),
2143
+ extractTextFromStructuredValue(candidate.messages, depth + 1)
2144
+ ].filter(Boolean).join("\n");
2145
+ }
2146
+ function containsNonTextualPayload(value, depth = 0) {
2147
+ if (depth > 4 || value == null) {
2148
+ return false;
2149
+ }
2150
+ if (Array.isArray(value)) {
2151
+ return value.some((item) => containsNonTextualPayload(item, depth + 1));
2152
+ }
2153
+ if (typeof value !== "object") {
2154
+ return false;
2155
+ }
2156
+ const candidate = value;
2157
+ const typeValue = typeof candidate.type === "string" ? candidate.type.toLowerCase() : "";
2158
+ if (typeValue.includes("image") || typeValue.includes("audio") || typeValue.includes("video") || "image_url" in candidate || "input_image" in candidate) {
2159
+ return true;
2160
+ }
2161
+ if (typeof candidate.mimeType === "string" && candidate.mimeType.startsWith("image/")) {
2162
+ return true;
2163
+ }
2164
+ return Object.values(candidate).some((item) => containsNonTextualPayload(item, depth + 1));
2165
+ }
2166
+ function trimToLength2(value, maxChars) {
2167
+ if (maxChars <= 0) {
2168
+ return "";
2169
+ }
2170
+ const normalized = value.replace(/\s+/g, " ").trim();
2171
+ if (normalized.length <= maxChars) {
2172
+ return normalized;
2173
+ }
2174
+ if (maxChars <= 3) {
2175
+ return normalized.slice(0, maxChars);
2176
+ }
2177
+ return `${normalized.slice(0, maxChars - 3).trimEnd()}...`;
2178
+ }
2074
2179
 
2075
2180
  // src/index.ts
2076
2181
  var import_node_fs2 = require("node:fs");
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "name": "Bamdra OpenClaw Memory",
13
13
  "description": "Unified topic-aware memory plugin for OpenClaw",
14
- "version": "0.3.13",
14
+ "version": "0.3.14",
15
15
  "main": "./index.js",
16
16
  "configSchema": {
17
17
  "type": "object",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamdra/bamdra-openclaw-memory",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "Unified topic-aware memory plugin for OpenClaw with durable SQLite recall and bounded context growth.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.bamdra.com",
@@ -7,7 +7,7 @@
7
7
  "skills": ["./skills"],
8
8
  "name": "Bamdra OpenClaw Memory",
9
9
  "description": "Unified topic-aware memory plugin for OpenClaw",
10
- "version": "0.3.13",
10
+ "version": "0.3.14",
11
11
  "main": "./dist/index.js",
12
12
  "configSchema": {
13
13
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamdra/bamdra-openclaw-memory",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "Unified topic-aware memory plugin for OpenClaw with durable SQLite recall and bounded context growth.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.bamdra.com",