@nowledge/openclaw-nowledge-mem 0.8.18 → 0.8.21

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
@@ -4,6 +4,28 @@ All notable changes to the Nowledge Mem OpenClaw plugin will be documented in th
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## [0.8.21] - 2026-04-29
8
+
9
+ ### Fixed
10
+
11
+ - ClawHub installs no longer fail archive verification because `.clawhubignore` is now excluded from the published package manifest as well as the archive.
12
+
13
+ ## [0.8.20] - 2026-04-27
14
+
15
+ ### Fixed
16
+
17
+ - ClawHub packages now exclude local `node_modules/`, preventing unpublished development artifacts from entering the security scan.
18
+ - Remote HTTP requests no longer place API keys in URL query strings. The plugin keeps credentials in `Authorization` and `X-NMEM-API-Key` headers only.
19
+
20
+ ## [0.8.19] - 2026-04-21
21
+
22
+ ### Fixed
23
+
24
+ - `corpusSupplement` fallback now preserves the user's configured intent when host registration is unavailable. The plugin keeps its own recall path active instead of silently mutating config state.
25
+ - `nowledge_mem_status` now reports `corpusSupplement` as configured vs runtime-active, including fallback reason details when OpenClaw does not expose supplement registration in the current runtime.
26
+ - Context Engine recall now follows **runtime supplement activation** instead of static config. If `corpusSupplement` is configured but host registration fails, prompt-time recall no longer gets skipped.
27
+ - `nowledge_mem_status` no longer claims prompt-time fallback recall when `sessionContext=false`; status text now reflects the real runtime path.
28
+
7
29
  ## [0.8.18] - 2026-04-20
8
30
 
9
31
  ### Fixed
package/README.md CHANGED
@@ -428,6 +428,14 @@ No config is required for a normal npm install. The defaults work for local mode
428
428
 
429
429
  To change settings, use the OpenClaw plugin settings UI. Changes take effect on restart.
430
430
 
431
+ ## Customize without editing the plugin
432
+
433
+ OpenClaw does not currently have a separate user-owned instruction file in this integration contract.
434
+
435
+ - Use the OpenClaw plugin settings UI for supported behavior changes such as `sessionContext`, digest controls, remote credentials, and space selection.
436
+ - If you need additional behavioral guidance, customize OpenClaw's own prompt or agent configuration surface.
437
+ - Do not patch the installed plugin source under the OpenClaw extensions directory.
438
+
431
439
  | Key | Type | Default | Description |
432
440
  |-----|------|---------|-------------|
433
441
  | `sessionContext` | boolean | `false` | Inject Working Memory + relevant memories at prompt time |
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-nowledge-mem",
3
3
  "name": "Nowledge Mem",
4
4
  "description": "Cross-tool knowledge graph memory for OpenClaw with Working Memory, graph search, and session distillation.",
5
- "version": "0.8.18",
5
+ "version": "0.8.21",
6
6
  "kind": ["memory", "context-engine"],
7
7
  "skills": [
8
8
  "skills/memory-guide"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nowledge/openclaw-nowledge-mem",
3
3
  "displayName": "Nowledge Mem for OpenClaw",
4
- "version": "0.8.18",
4
+ "version": "0.8.21",
5
5
  "type": "module",
6
6
  "description": "Nowledge Mem memory plugin for OpenClaw, local-first personal knowledge base",
7
7
  "author": {
package/src/client.js CHANGED
@@ -137,16 +137,15 @@ export class NowledgeMemClient {
137
137
  const timer = setTimeout(() => controller.abort(), timeout);
138
138
  const scopedPath = this._withSpaceQuery(path);
139
139
  const scopedBody = this._withSpaceBody(body);
140
- const url = `${this.getApiBaseUrl()}${scopedPath}`;
141
140
  try {
142
- const response = await fetch(url, {
141
+ const requestBody =
142
+ scopedBody === undefined ? undefined : JSON.stringify(scopedBody);
143
+ const response = await fetch(`${this.getApiBaseUrl()}${scopedPath}`, {
143
144
  method,
144
145
  headers: this.getApiHeaders(),
145
- body:
146
- scopedBody === undefined ? undefined : JSON.stringify(scopedBody),
146
+ body: requestBody,
147
147
  signal: controller.signal,
148
148
  });
149
-
150
149
  const text = await response.text();
151
150
  let data = {};
152
151
  try {
@@ -154,7 +153,6 @@ export class NowledgeMemClient {
154
153
  } catch {
155
154
  data = { raw: text };
156
155
  }
157
-
158
156
  if (!response.ok) {
159
157
  const detail =
160
158
  typeof data?.detail === "string"
@@ -166,9 +166,15 @@ function getSessionState(key) {
166
166
  * @param {import('./client.js').NowledgeMemClient} client
167
167
  * @param {object} cfg Parsed plugin config
168
168
  * @param {object} logger OpenClaw logger
169
+ * @param {object} runtime Runtime state helpers supplied by register()
169
170
  * @returns {() => object} Factory that creates the engine instance
170
171
  */
171
- export function createNowledgeMemContextEngineFactory(client, cfg, logger) {
172
+ export function createNowledgeMemContextEngineFactory(
173
+ client,
174
+ cfg,
175
+ logger,
176
+ runtime = {},
177
+ ) {
172
178
  return () => {
173
179
  ceState.active = true;
174
180
  logger.info("nowledge-mem: context engine activated");
@@ -242,8 +248,12 @@ export function createNowledgeMemContextEngineFactory(client, cfg, logger) {
242
248
  }
243
249
 
244
250
  // 3. Recalled memories (when sessionContext enabled)
245
- // Skipped when corpus supplement handles search-based recall via memory-core.
246
- if (cfg.sessionContext && !cfg.corpusSupplement) {
251
+ // Skip only when corpus supplement is actually active at runtime.
252
+ const corpusSupplementActive =
253
+ typeof runtime.isCorpusSupplementActive === "function"
254
+ ? runtime.isCorpusSupplementActive() === true
255
+ : cfg.corpusSupplement === true;
256
+ if (cfg.sessionContext && !corpusSupplementActive) {
247
257
  const query = buildAssembleSearchQuery(prompt, messages);
248
258
  if (query) {
249
259
  try {
@@ -177,13 +177,16 @@ export function buildRecalledKnowledgeBlock(
177
177
  * When the context engine is active, this hook is a no-op —
178
178
  * assemble() handles recall via systemPromptAddition.
179
179
  */
180
- export function buildRecallHandler(client, cfg, logger) {
180
+ export function buildRecallHandler(client, cfg, logger, runtime = {}) {
181
181
  const minScore = (cfg.recallMinScore ?? 0) / 100; // config is 0-100, API is 0-1
182
182
 
183
183
  // When corpus supplement is active, memory-core's recall pipeline searches
184
184
  // Nowledge Mem via the supplement. We still inject Working Memory (only we
185
185
  // know about WM), but skip our own search-based recall to avoid duplicates.
186
- const skipSearchRecall = cfg.corpusSupplement;
186
+ const skipSearchRecall =
187
+ typeof runtime.skipSearchRecall === "boolean"
188
+ ? runtime.skipSearchRecall
189
+ : cfg.corpusSupplement;
187
190
 
188
191
  return async (event) => {
189
192
  if (ceState.active) return;
package/src/index.js CHANGED
@@ -80,6 +80,8 @@ export default {
80
80
  const pluginsAllow = api.config?.plugins?.allow;
81
81
  let contextEngineRegistered = false;
82
82
  let contextEngineRegistrationError = null;
83
+ let corpusSupplementActive = false;
84
+ let corpusSupplementRegistrationError = null;
83
85
 
84
86
  // --- Context Engine registration ---
85
87
  // When the user sets `plugins.slots.contextEngine: "nowledge-mem"` —
@@ -94,6 +96,9 @@ export default {
94
96
  client,
95
97
  cfg,
96
98
  logger,
99
+ {
100
+ isCorpusSupplementActive: () => corpusSupplementActive,
101
+ },
97
102
  );
98
103
  for (const contextEngineId of NOWLEDGE_MEM_CONTEXT_ENGINE_IDS) {
99
104
  api.registerContextEngine(contextEngineId, contextEngineFactory);
@@ -107,17 +112,6 @@ export default {
107
112
  );
108
113
  }
109
114
 
110
- api.registerTool(
111
- createStatusTool(client, logger, cfg, {
112
- memorySlot,
113
- contextEngineSlot,
114
- contextEngineIds: NOWLEDGE_MEM_CONTEXT_ENGINE_IDS,
115
- pluginsAllow,
116
- contextEngineRegistered,
117
- contextEngineRegistrationError,
118
- }),
119
- );
120
-
121
115
  // --- Corpus Supplement (when memory-core is the memory slot) ---
122
116
  // Makes Nowledge Mem's knowledge graph searchable through memory-core's
123
117
  // recall pipeline and dreaming promotion. Memories participate in
@@ -127,18 +121,32 @@ export default {
127
121
  api.registerMemoryCorpusSupplement(
128
122
  createNowledgeMemCorpusSupplement(client, cfg, logger),
129
123
  );
124
+ corpusSupplementActive = true;
130
125
  logger.info(
131
126
  "nowledge-mem: registered as MemoryCorpusSupplement for memory-core recall",
132
127
  );
133
128
  } catch (err) {
134
129
  // OpenClaw < corpus supplement support — fall back to normal recall
135
- cfg.corpusSupplement = false;
130
+ corpusSupplementRegistrationError = String(err);
136
131
  logger.info?.(
137
132
  `nowledge-mem: corpus supplement unavailable, falling back to normal recall (${err})`,
138
133
  );
139
134
  }
140
135
  }
141
136
 
137
+ api.registerTool(
138
+ createStatusTool(client, logger, cfg, {
139
+ memorySlot,
140
+ contextEngineSlot,
141
+ contextEngineIds: NOWLEDGE_MEM_CONTEXT_ENGINE_IDS,
142
+ pluginsAllow,
143
+ contextEngineRegistered,
144
+ contextEngineRegistrationError,
145
+ corpusSupplementActive,
146
+ corpusSupplementRegistrationError,
147
+ }),
148
+ );
149
+
142
150
  // --- Hooks ---
143
151
  // Behavioral + recall hooks defer to the active CE to avoid duplicate
144
152
  // prompt injection. Capture hooks remain enabled as a backstop because
@@ -151,7 +159,12 @@ export default {
151
159
 
152
160
  // Session context: inject Working Memory + recalled memories at prompt time.
153
161
  if (cfg.sessionContext) {
154
- api.on("before_prompt_build", buildRecallHandler(client, cfg, logger));
162
+ api.on(
163
+ "before_prompt_build",
164
+ buildRecallHandler(client, cfg, logger, {
165
+ skipSearchRecall: corpusSupplementActive,
166
+ }),
167
+ );
155
168
  }
156
169
 
157
170
  // Session digest: capture threads + LLM distillation at lifecycle events.
@@ -182,7 +195,7 @@ export default {
182
195
 
183
196
  const remoteMode = !isDefaultApiUrl(cfg.apiUrl);
184
197
  logger.info(
185
- `nowledge-mem: initialized (memorySlotSelected=${memorySlotSelected}, context=${cfg.sessionContext}, digest=${cfg.sessionDigest}, corpus=${cfg.corpusSupplement}, mode=${remoteMode ? `remote → ${cfg.apiUrl}` : "local"}, contextEngineIds=${NOWLEDGE_MEM_CONTEXT_ENGINE_ID}|${api.id})`,
198
+ `nowledge-mem: initialized (memorySlotSelected=${memorySlotSelected}, context=${cfg.sessionContext}, digest=${cfg.sessionDigest}, corpusConfigured=${cfg.corpusSupplement}, corpusActive=${corpusSupplementActive}, mode=${remoteMode ? `remote → ${cfg.apiUrl}` : "local"}, contextEngineIds=${NOWLEDGE_MEM_CONTEXT_ENGINE_ID}|${api.id})`,
186
199
  );
187
200
  },
188
201
  };
@@ -67,35 +67,62 @@ export function createStatusTool(client, _logger, cfg, runtimeInfo = {}) {
67
67
  // 0b. Memory slot check — show current mode and options
68
68
  const memorySlot = runtimeInfo.memorySlot;
69
69
  const contextEngineSlot = runtimeInfo.contextEngineSlot;
70
- const contextEngineRegistered = runtimeInfo.contextEngineRegistered === true;
71
- const contextEngineRegistrationError =
72
- typeof runtimeInfo.contextEngineRegistrationError === "string" &&
73
- runtimeInfo.contextEngineRegistrationError.trim()
74
- ? runtimeInfo.contextEngineRegistrationError.trim()
75
- : null;
76
- details.memorySlot = memorySlot ?? "(unknown)";
77
- const ceSlot =
78
- typeof contextEngineSlot === "string" && contextEngineSlot.trim()
79
- ? contextEngineSlot.trim()
80
- : "legacy";
81
- details.contextEngineSlot = ceSlot;
82
- details.contextEngineRegistered = contextEngineRegistered;
70
+ const contextEngineRegistered =
71
+ runtimeInfo.contextEngineRegistered === true;
72
+ const contextEngineRegistrationError =
73
+ typeof runtimeInfo.contextEngineRegistrationError === "string" &&
74
+ runtimeInfo.contextEngineRegistrationError.trim()
75
+ ? runtimeInfo.contextEngineRegistrationError.trim()
76
+ : null;
77
+ const corpusSupplementConfigured = cfg.corpusSupplement === true;
78
+ const corpusSupplementActive =
79
+ runtimeInfo.corpusSupplementActive === true;
80
+ const corpusSupplementRegistrationError =
81
+ typeof runtimeInfo.corpusSupplementRegistrationError === "string" &&
82
+ runtimeInfo.corpusSupplementRegistrationError.trim()
83
+ ? runtimeInfo.corpusSupplementRegistrationError.trim()
84
+ : null;
85
+ details.memorySlot = memorySlot ?? "(unknown)";
86
+ details.corpusSupplementConfigured = corpusSupplementConfigured;
87
+ details.corpusSupplementActive = corpusSupplementActive;
88
+ if (corpusSupplementRegistrationError) {
89
+ details.corpusSupplementRegistrationError =
90
+ corpusSupplementRegistrationError;
91
+ }
92
+ const ceSlot =
93
+ typeof contextEngineSlot === "string" && contextEngineSlot.trim()
94
+ ? contextEngineSlot.trim()
95
+ : "legacy";
96
+ details.contextEngineSlot = ceSlot;
97
+ details.contextEngineRegistered = contextEngineRegistered;
83
98
  if (memorySlot && memorySlot !== "openclaw-nowledge-mem") {
84
- const corpusOn = cfg.corpusSupplement === true;
85
- if (corpusOn) {
86
- lines.push(
87
- `Memory slot: "${memorySlot}" + corpus supplement active`,
88
- );
99
+ if (corpusSupplementConfigured && corpusSupplementActive) {
100
+ lines.push(`Memory slot: "${memorySlot}" + corpus supplement active`);
89
101
  lines.push(
90
102
  " Nowledge Mem feeds into memory-core's recall and dreaming pipeline. All tools available.",
91
103
  );
92
- } else {
93
- lines.push(
94
- `ℹ Memory slot: "${memorySlot}"`,
95
- );
104
+ } else if (corpusSupplementConfigured && !corpusSupplementActive) {
105
+ lines.push(`ℹ Memory slot: "${memorySlot}"`);
96
106
  lines.push(
97
- " Two options:",
107
+ " corpusSupplement is configured, but host registration is unavailable in this runtime.",
98
108
  );
109
+ if (cfg.sessionContext) {
110
+ lines.push(
111
+ " Fallback active: Nowledge Mem still injects Working Memory and uses its own recall path.",
112
+ );
113
+ } else {
114
+ lines.push(
115
+ " Fallback: Nowledge Mem tools remain available. Enable sessionContext for prompt-time Working Memory and recall.",
116
+ );
117
+ }
118
+ if (corpusSupplementRegistrationError) {
119
+ lines.push(
120
+ ` Supplement registration detail: ${corpusSupplementRegistrationError}`,
121
+ );
122
+ }
123
+ } else {
124
+ lines.push(`ℹ Memory slot: "${memorySlot}"`);
125
+ lines.push(" Two options:");
99
126
  lines.push(
100
127
  ' - Full mode: set plugins.slots.memory to "openclaw-nowledge-mem" for the complete tool surface',
101
128
  );
@@ -113,7 +140,9 @@ export function createStatusTool(client, _logger, cfg, runtimeInfo = {}) {
113
140
  isNowledgeMemContextEngineSlot(ceSlot) && contextEngineRegistered;
114
141
  const captureMode = !cfg.sessionDigest
115
142
  ? "disabled"
116
- : ceActive ? "context-engine+hooks" : "hooks";
143
+ : ceActive
144
+ ? "context-engine+hooks"
145
+ : "hooks";
117
146
  details.captureMode = captureMode;
118
147
  if (ceActive) {
119
148
  if (ceSlot === NOWLEDGE_MEM_CONTEXT_ENGINE_COMPAT_ALIAS) {
@@ -124,7 +153,9 @@ export function createStatusTool(client, _logger, cfg, runtimeInfo = {}) {
124
153
  ` OpenClaw selected the plugin id for the context-engine slot. ${NOWLEDGE_MEM_CONTEXT_ENGINE_ID} remains the canonical manual setting.`,
125
154
  );
126
155
  } else {
127
- lines.push(`Context Engine slot: ${NOWLEDGE_MEM_CONTEXT_ENGINE_ID} (active)`);
156
+ lines.push(
157
+ `Context Engine slot: ${NOWLEDGE_MEM_CONTEXT_ENGINE_ID} (active)`,
158
+ );
128
159
  }
129
160
  lines.push(
130
161
  " Thread capture runs through Context Engine afterTurn with hook fallback (agent_end, after_compaction, before_reset).",
@@ -146,7 +177,9 @@ export function createStatusTool(client, _logger, cfg, runtimeInfo = {}) {
146
177
  " The slot points to Nowledge Mem, but Context Engine registration is not active in this runtime. Thread capture is using hooks only.",
147
178
  );
148
179
  if (contextEngineRegistrationError) {
149
- lines.push(` CE registration detail: ${contextEngineRegistrationError}`);
180
+ lines.push(
181
+ ` CE registration detail: ${contextEngineRegistrationError}`,
182
+ );
150
183
  }
151
184
  } else {
152
185
  lines.push(
@@ -268,6 +301,14 @@ export function createStatusTool(client, _logger, cfg, runtimeInfo = {}) {
268
301
  lines.push(
269
302
  ` corpusSupplement: ${cfg.corpusSupplement} (${sources.corpusSupplement || "?"})`,
270
303
  );
304
+ if (corpusSupplementConfigured) {
305
+ const fallbackText = cfg.sessionContext
306
+ ? "configured but unavailable (fallback to plugin recall)"
307
+ : "configured but unavailable (sessionContext disabled)";
308
+ lines.push(
309
+ ` corpusSupplement runtime: ${corpusSupplementActive ? "active" : fallbackText}`,
310
+ );
311
+ }
271
312
 
272
313
  details.config = {
273
314
  sessionContext: {
@@ -297,6 +338,7 @@ export function createStatusTool(client, _logger, cfg, runtimeInfo = {}) {
297
338
  corpusSupplement: {
298
339
  value: cfg.corpusSupplement,
299
340
  source: sources.corpusSupplement,
341
+ active: corpusSupplementActive,
300
342
  },
301
343
  apiUrl: { value: cfg.apiUrl || "(local)", source: sources.apiUrl },
302
344
  apiKey: {