@nowledge/openclaw-nowledge-mem 0.6.13 → 0.6.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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to the Nowledge Mem OpenClaw plugin will be documented in this file.
4
4
 
5
+ ## [0.6.14] - 2026-03-17
6
+
7
+ ### Added
8
+
9
+ - **Configurable thread message truncation.** New `maxThreadMessageChars` setting (200-20000, default 800) controls how many characters are preserved per captured thread message. Higher values keep more context in long conversations. Configurable via dashboard, config file, or `NMEM_MAX_THREAD_MESSAGE_CHARS` env var. Contributed by @blessonism.
10
+
5
11
  ## [0.6.13] - 2026-03-17
6
12
 
7
13
  ### Fixed
package/CLAUDE.md CHANGED
@@ -101,6 +101,7 @@ Optional config file at `~/.nowledge-mem/openclaw.json`. Falls through to OpenCl
101
101
  | `digestMinInterval` | integer 0-86400 | `300` | `NMEM_DIGEST_MIN_INTERVAL` | Minimum seconds between session digests |
102
102
  | `maxContextResults` | integer 1-20 | `5` | `NMEM_MAX_CONTEXT_RESULTS` | How many memories to inject at prompt time |
103
103
  | `recallMinScore` | integer 0-100 | `0` | `NMEM_RECALL_MIN_SCORE` | Min relevance score (%) to include in auto-recall |
104
+ | `maxThreadMessageChars` | integer 200-20000 | `800` | `NMEM_MAX_THREAD_MESSAGE_CHARS` | Max chars per captured thread message before truncation |
104
105
  | `apiUrl` | string | `""` | `NMEM_API_URL` | Remote server URL. Empty = local (127.0.0.1:14242) |
105
106
  | `apiKey` | string | `""` | `NMEM_API_KEY` | API key. Never logged. |
106
107
 
@@ -156,6 +157,16 @@ nmem wm patch --heading "## Notes" --append "New note from agent"
156
157
  NMEM_API_URL=https://your-server NMEM_API_KEY=key nmem status
157
158
  ```
158
159
 
160
+ ## Version Bump Checklist
161
+
162
+ All three files must match on every release:
163
+
164
+ 1. **`package.json`** — `"version"` (npm registry reads this for `npm publish`)
165
+ 2. **`openclaw.plugin.json`** — `"version"` (OpenClaw runtime reads this at load time)
166
+ 3. **`CHANGELOG.md`** — new `## [x.y.z] - YYYY-MM-DD` section header
167
+
168
+ After bumping, commit inside the `community/` submodule, then stage the updated submodule ref in the parent repo.
169
+
159
170
  ## Known Gaps / Accepted Limitations
160
171
 
161
172
  1. **Feed API `date_from`/`date_to`** - supported. Backend filters events by YYYY-MM-DD range. CLI: `nmem f --from/--to`. Plugin: `nowledge_mem_timeline` accepts `date_from`/`date_to`.
package/README.md CHANGED
@@ -368,6 +368,7 @@ To change settings, use the OpenClaw plugin settings UI. Changes take effect on
368
368
  | `digestMinInterval` | integer | `300` | Minimum seconds between session digests for the same thread (0-86400) |
369
369
  | `maxContextResults` | integer | `5` | Max memories to inject at prompt time (1-20, only used when sessionContext is enabled) |
370
370
  | `recallMinScore` | integer | `0` | Min relevance score (0-100%) to include in auto-recall. 0 = include all |
371
+ | `maxThreadMessageChars` | integer | `800` | Maximum characters preserved per captured OpenClaw thread message before truncation |
371
372
  | `apiUrl` | string | `""` | Remote server URL. Empty = local (`http://127.0.0.1:14242`) |
372
373
  | `apiKey` | string | `""` | API key for remote access. Injected as `NMEM_API_KEY` env var, never logged |
373
374
 
@@ -382,6 +383,7 @@ For persistent or scripted config, create `~/.nowledge-mem/openclaw.json`:
382
383
  "digestMinInterval": 300,
383
384
  "maxContextResults": 5,
384
385
  "recallMinScore": 0,
386
+ "maxThreadMessageChars": 800,
385
387
  "apiUrl": "",
386
388
  "apiKey": ""
387
389
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "openclaw-nowledge-mem",
3
- "version": "0.6.13",
3
+ "version": "0.6.14",
4
4
  "kind": "memory",
5
5
  "skills": ["skills/memory-guide"],
6
6
  "uiHints": {
@@ -24,6 +24,10 @@
24
24
  "label": "Min recall score (%)",
25
25
  "help": "Only inject memories with relevance score above this threshold (0-100). Set to 0 to include all results."
26
26
  },
27
+ "maxThreadMessageChars": {
28
+ "label": "Max thread message chars",
29
+ "help": "Maximum characters preserved per captured OpenClaw thread message (200-20000). Higher values keep more of long messages in Nowledge thread history."
30
+ },
27
31
  "apiUrl": {
28
32
  "label": "Server URL (remote mode)",
29
33
  "help": "Leave empty for local mode (default: http://127.0.0.1:14242). Set to your remote server URL for cross-device or team access. See: https://docs.nowledge.co/docs/remote-access"
@@ -68,6 +72,13 @@
68
72
  "maximum": 100,
69
73
  "description": "Minimum relevance score (0-100%) to include a memory in auto-recall. Set to 0 to include all results."
70
74
  },
75
+ "maxThreadMessageChars": {
76
+ "type": "integer",
77
+ "default": 800,
78
+ "minimum": 200,
79
+ "maximum": 20000,
80
+ "description": "Maximum characters preserved per captured OpenClaw thread message before truncation."
81
+ },
71
82
  "apiUrl": {
72
83
  "type": "string",
73
84
  "default": "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowledge/openclaw-nowledge-mem",
3
- "version": "0.6.13",
3
+ "version": "0.6.14",
4
4
  "type": "module",
5
5
  "description": "Nowledge Mem memory plugin for OpenClaw, local-first personal knowledge base",
6
6
  "author": {
package/src/config.js CHANGED
@@ -20,6 +20,7 @@ const ALLOWED_KEYS = new Set([
20
20
  "digestMinInterval",
21
21
  "maxContextResults",
22
22
  "recallMinScore",
23
+ "maxThreadMessageChars",
23
24
  "apiUrl",
24
25
  "apiKey",
25
26
  // Legacy aliases — accepted but not advertised
@@ -136,7 +137,7 @@ function firstDefined(...options) {
136
137
  * will configure via OpenClaw's plugin settings UI (pluginConfig).
137
138
  *
138
139
  * Canonical keys: sessionContext, sessionDigest, digestMinInterval,
139
- * maxContextResults, recallMinScore, apiUrl, apiKey
140
+ * maxContextResults, recallMinScore, maxThreadMessageChars, apiUrl, apiKey
140
141
  *
141
142
  * Legacy aliases (accepted from all sources; never shown in docs):
142
143
  * autoRecall → sessionContext
@@ -150,6 +151,7 @@ function firstDefined(...options) {
150
151
  * NMEM_DIGEST_MIN_INTERVAL — seconds (0-86400)
151
152
  * NMEM_MAX_CONTEXT_RESULTS — integer (1-20)
152
153
  * NMEM_RECALL_MIN_SCORE — integer (0-100)
154
+ * NMEM_MAX_THREAD_MESSAGE_CHARS — integer (200-20000)
153
155
  * NMEM_API_URL — remote server URL
154
156
  * NMEM_API_KEY — API key (never logged)
155
157
  */
@@ -250,6 +252,23 @@ export function parseConfig(raw, logger) {
250
252
  const recallMinScore = Math.min(100, Math.max(0, Math.trunc(rms.value)));
251
253
  _sources.recallMinScore = rms.source;
252
254
 
255
+ // --- maxThreadMessageChars: file > pluginConfig > env > default ---
256
+ const mtmcEnv = envInt("NMEM_MAX_THREAD_MESSAGE_CHARS");
257
+ const mtmc = firstDefined(
258
+ { value: pickNum(resolvedFile, "maxThreadMessageChars"), source: "file" },
259
+ {
260
+ value: pickNum(resolvedPlugin, "maxThreadMessageChars"),
261
+ source: "pluginConfig",
262
+ },
263
+ { value: mtmcEnv, source: "env" },
264
+ { value: 800, source: "default" },
265
+ );
266
+ const maxThreadMessageChars = Math.min(
267
+ 20000,
268
+ Math.max(200, Math.trunc(mtmc.value)),
269
+ );
270
+ _sources.maxThreadMessageChars = mtmc.source;
271
+
253
272
  // --- apiUrl: file > pluginConfig > env > "" ---
254
273
  const fileUrl =
255
274
  typeof resolvedFile.apiUrl === "string" && resolvedFile.apiUrl.trim();
@@ -284,6 +303,7 @@ export function parseConfig(raw, logger) {
284
303
  digestMinInterval,
285
304
  maxContextResults,
286
305
  recallMinScore,
306
+ maxThreadMessageChars,
287
307
  apiUrl,
288
308
  apiKey,
289
309
  _sources,
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
 
4
- const MAX_MESSAGE_CHARS = 800;
4
+ const DEFAULT_MAX_MESSAGE_CHARS = 800;
5
5
  const MAX_DISTILL_MESSAGE_CHARS = 2000;
6
6
  const MAX_CONVERSATION_CHARS = 30_000;
7
7
  const MIN_MESSAGES_FOR_DISTILL = 4;
@@ -23,7 +23,7 @@ function _setLastCapture(threadId, now) {
23
23
  }
24
24
  }
25
25
 
26
- function truncate(text, max = MAX_MESSAGE_CHARS) {
26
+ function truncate(text, max = DEFAULT_MAX_MESSAGE_CHARS) {
27
27
  const str = String(text || "").trim();
28
28
  if (!str) return "";
29
29
  return str.length > max ? `${str.slice(0, max)}…` : str;
@@ -48,7 +48,7 @@ function extractText(content) {
48
48
  return parts.join("\n").trim();
49
49
  }
50
50
 
51
- function normalizeRoleMessage(raw) {
51
+ function normalizeRoleMessage(raw, maxMessageChars = DEFAULT_MAX_MESSAGE_CHARS) {
52
52
  if (!raw || typeof raw !== "object") return null;
53
53
  const msg =
54
54
  raw.message && typeof raw.message === "object" ? raw.message : raw;
@@ -80,7 +80,7 @@ function normalizeRoleMessage(raw) {
80
80
 
81
81
  return {
82
82
  role,
83
- content: truncate(text),
83
+ content: truncate(text, maxMessageChars),
84
84
  fullContent: text,
85
85
  timestamp,
86
86
  externalHint,
@@ -170,7 +170,7 @@ async function resolveHookMessages(event) {
170
170
  return loadMessagesFromSessionFile(sessionFile);
171
171
  }
172
172
 
173
- async function appendOrCreateThread({ client, logger, event, ctx, reason }) {
173
+ async function appendOrCreateThread({ client, logger, event, ctx, reason, maxMessageChars = DEFAULT_MAX_MESSAGE_CHARS }) {
174
174
  const rawMessages = await resolveHookMessages(event);
175
175
  if (!Array.isArray(rawMessages) || rawMessages.length === 0) return;
176
176
 
@@ -178,7 +178,9 @@ async function appendOrCreateThread({ client, logger, event, ctx, reason }) {
178
178
  const sessionKey = String(ctx?.sessionKey || ctx?.sessionId || "session");
179
179
  const sessionId = String(ctx?.sessionId || "").trim();
180
180
  const title = buildThreadTitle(ctx, reason);
181
- const normalized = rawMessages.map(normalizeRoleMessage).filter(Boolean);
181
+ const normalized = rawMessages
182
+ .map((message) => normalizeRoleMessage(message, maxMessageChars))
183
+ .filter(Boolean);
182
184
  if (normalized.length === 0) return;
183
185
 
184
186
  const messages = normalized.map((message, index) => ({
@@ -291,6 +293,7 @@ export function buildAgentEndCaptureHandler(client, cfg, logger) {
291
293
  event,
292
294
  ctx,
293
295
  reason: "agent_end",
296
+ maxMessageChars: cfg.maxThreadMessageChars,
294
297
  });
295
298
 
296
299
  // 2. Triage + distill: language-agnostic LLM-based capture.
@@ -376,6 +379,13 @@ export function buildAgentEndCaptureHandler(client, cfg, logger) {
376
379
  export function buildBeforeResetCaptureHandler(client, _cfg, logger) {
377
380
  return async (event, ctx) => {
378
381
  const reason = typeof event?.reason === "string" ? event.reason : undefined;
379
- await appendOrCreateThread({ client, logger, event, ctx, reason });
382
+ await appendOrCreateThread({
383
+ client,
384
+ logger,
385
+ event,
386
+ ctx,
387
+ reason,
388
+ maxMessageChars: _cfg?.maxThreadMessageChars,
389
+ });
380
390
  };
381
391
  }
@@ -109,6 +109,9 @@ export function createStatusTool(client, _logger, cfg) {
109
109
  lines.push(
110
110
  ` recallMinScore: ${cfg.recallMinScore}% (${sources.recallMinScore || "?"})`,
111
111
  );
112
+ lines.push(
113
+ ` maxThreadMessageChars: ${cfg.maxThreadMessageChars} (${sources.maxThreadMessageChars || "?"})`,
114
+ );
112
115
 
113
116
  details.config = {
114
117
  sessionContext: {
@@ -131,6 +134,10 @@ export function createStatusTool(client, _logger, cfg) {
131
134
  value: cfg.recallMinScore,
132
135
  source: sources.recallMinScore,
133
136
  },
137
+ maxThreadMessageChars: {
138
+ value: cfg.maxThreadMessageChars,
139
+ source: sources.maxThreadMessageChars,
140
+ },
134
141
  apiUrl: { value: cfg.apiUrl || "(local)", source: sources.apiUrl },
135
142
  apiKey: {
136
143
  value: cfg.apiKey ? "(set)" : "(not set)",