@nowledge/openclaw-nowledge-mem 0.7.1 → 0.7.3
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 +12 -1
- package/CLAUDE.md +3 -1
- package/README.md +50 -0
- package/SKILL.md +13 -0
- package/openclaw.plugin.json +20 -1
- package/package.json +1 -1
- package/src/config.js +41 -0
- package/src/context-engine.js +22 -2
- package/src/hooks/capture.js +96 -3
- package/src/index.js +8 -2
- package/src/tools/status.js +55 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Nowledge Mem OpenClaw plugin will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [0.7.3] - 2026-03-30
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Status tool now checks plugin trust and memory slot.** `nowledge_mem_status` reads `plugins.allow` from the OpenClaw config: warns when the allowlist exists but the plugin is missing (loading will be blocked); shows a tip when no allowlist is set (plugin loads via `entries.enabled`, but trust is implicit). Also warns when the memory slot points to `memory-core` instead of `openclaw-nowledge-mem` — a common misconfiguration after upgrading to OpenClaw 3.22+. Both checks include the exact fix command.
|
|
10
|
+
- **Clarified that no `tools.*` config is needed.** All plugin tools register automatically when the plugin loads. Listing `nowledge_mem_*` names in `tools.allow` is a misconfiguration — OpenClaw silently strips allowlists that contain only plugin entries. The only config users need is `plugins.allow: ["openclaw-nowledge-mem"]`. Updated troubleshooting in README, SKILL.md, and website docs (EN + ZH).
|
|
11
|
+
|
|
12
|
+
## [0.7.2] - 2026-03-30
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Status tool now reports memory slot configuration.** `nowledge_mem_status` checks whether the OpenClaw memory slot points to `openclaw-nowledge-mem`. If another plugin (e.g. the built-in `memory-core`) holds the slot, the tool shows a warning with the fix command. This catches a common misconfiguration after upgrading to OpenClaw 3.22+, where the implicit slot default changed to `memory-core`.
|
|
6
17
|
|
|
7
18
|
### Fixed
|
|
8
19
|
|
package/CLAUDE.md
CHANGED
|
@@ -118,7 +118,7 @@ When `contextEngine` points elsewhere (or is absent), hooks handle everything. N
|
|
|
118
118
|
- `nowledge_mem_thread_fetch` - fetch messages from a specific thread. Start with a small page, then use `offset` + `limit` for progressive retrieval only when more context is needed.
|
|
119
119
|
|
|
120
120
|
### Diagnostics
|
|
121
|
-
- `nowledge_mem_status` - show effective config (mode, apiUrl, apiKey set, sessionContext, sessionDigest, etc.), backend connectivity, and version. No parameters.
|
|
121
|
+
- `nowledge_mem_status` - show plugin trust status, memory slot status, effective config (mode, apiUrl, apiKey set, sessionContext, sessionDigest, etc.), backend connectivity, and version. Checks `plugins.allow`: warns if set but plugin not included (loading blocked); shows tip if not set (plugin loads via `entries.enabled` but trust is implicit). Warns if the memory slot points to `memory-core` instead of `openclaw-nowledge-mem` (common after OpenClaw 3.22+ upgrade). No parameters.
|
|
122
122
|
|
|
123
123
|
## Skill Surface
|
|
124
124
|
|
|
@@ -151,6 +151,8 @@ Credentials (apiUrl/apiKey): also reads `~/.nowledge-mem/config.json` (shared wi
|
|
|
151
151
|
| `maxContextResults` | integer 1-20 | `5` | `NMEM_MAX_CONTEXT_RESULTS` | How many memories to inject at prompt time |
|
|
152
152
|
| `recallMinScore` | integer 0-100 | `0` | `NMEM_RECALL_MIN_SCORE` | Min relevance score (%) to include in auto-recall |
|
|
153
153
|
| `maxThreadMessageChars` | integer 200-20000 | `800` | `NMEM_MAX_THREAD_MESSAGE_CHARS` | Max chars per captured thread message before truncation |
|
|
154
|
+
| `captureExclude` | string[] | `[]` | — | Session key glob patterns to skip during auto-capture. `*` matches within a colon-segment. Example: `["agent:*:cron:*"]` |
|
|
155
|
+
| `captureSkipMarker` | string | `"#nmem-skip"` | — | In-band marker: any message containing this text skips capture for the session. Not sticky across compaction |
|
|
154
156
|
| `apiUrl` | string | `""` | `NMEM_API_URL` | Remote server URL. Empty = local (127.0.0.1:14242) |
|
|
155
157
|
| `apiKey` | string | `""` | `NMEM_API_KEY` | API key. Never logged. |
|
|
156
158
|
|
package/README.md
CHANGED
|
@@ -433,6 +433,56 @@ If OpenClaw logs a warning like `plugins.allow is empty; discovered non-bundled
|
|
|
433
433
|
|
|
434
434
|
If you also use linked or workspace copies, review `plugins.load.paths` before relying on an allowlist. OpenClaw allowlists plugin ids, not install provenance.
|
|
435
435
|
|
|
436
|
+
## Troubleshooting
|
|
437
|
+
|
|
438
|
+
**Only `memory_search` and `memory_get` show up — saves go to local markdown files**
|
|
439
|
+
|
|
440
|
+
The memory slot is probably still set to the built-in `memory-core`. OpenClaw 3.22+ defaults to `memory-core` when no explicit slot is configured. Reinstall to reset the slot automatically:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
openclaw plugins install @nowledge/openclaw-nowledge-mem
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Or set the slot manually in `~/.openclaw/openclaw.json`:
|
|
447
|
+
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"plugins": {
|
|
451
|
+
"slots": { "memory": "openclaw-nowledge-mem" }
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Restart OpenClaw after either change.
|
|
457
|
+
|
|
458
|
+
**Plugin tools not available**
|
|
459
|
+
|
|
460
|
+
All 10 plugin tools register automatically when the plugin loads. No tool-level config is needed — just make sure the plugin itself is trusted:
|
|
461
|
+
|
|
462
|
+
```json
|
|
463
|
+
{
|
|
464
|
+
"plugins": {
|
|
465
|
+
"allow": ["openclaw-nowledge-mem"]
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
If `openclaw status` shows a CRITICAL warning about `plugins.allow`, this is the fix. Run `nowledge_mem_status` inside a conversation to check both plugin trust and memory slot status.
|
|
471
|
+
|
|
472
|
+
Do not list `nowledge_mem_*` tool names in `tools.allow` — OpenClaw silently strips allowlists that contain only plugin entries, so the config looks active but does nothing.
|
|
473
|
+
|
|
474
|
+
**Search timeouts with many concurrent agents**
|
|
475
|
+
|
|
476
|
+
When running many agents in parallel, all searches share a single database connection. Upgrade to Nowledge Mem v0.6.12+ (backend) so scoring writes no longer block search responses.
|
|
477
|
+
|
|
478
|
+
**`nmem --json status` fails in local mode**
|
|
479
|
+
|
|
480
|
+
The Nowledge Mem backend is not running. Start it from the desktop app, or run the backend manually.
|
|
481
|
+
|
|
482
|
+
**Remote mode not connecting**
|
|
483
|
+
|
|
484
|
+
Check `~/.nowledge-mem/config.json` credentials. Run `nmem --json --api-url "$URL" status` to verify the server is reachable.
|
|
485
|
+
|
|
436
486
|
## What Makes This Different
|
|
437
487
|
|
|
438
488
|
- **Local-first**: no API key, no cloud account. Your knowledge stays on your machine.
|
package/SKILL.md
CHANGED
|
@@ -186,6 +186,18 @@ First check the plugin status:
|
|
|
186
186
|
openclaw nowledge-mem status
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
+
If only `memory_search` and `memory_get` tools are available (other Nowledge Mem tools missing), the memory slot may still point to the built-in `memory-core`. Verify the slot in `~/.openclaw/openclaw.json`:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"plugins": {
|
|
194
|
+
"slots": { "memory": "openclaw-nowledge-mem" }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
If missing, reinstall (`openclaw plugins install @nowledge/openclaw-nowledge-mem`) which sets the slot automatically. This is common after upgrading OpenClaw to 3.22+ where the default memory slot changed.
|
|
200
|
+
|
|
189
201
|
Then verify in chat.
|
|
190
202
|
|
|
191
203
|
For a clean synthetic check:
|
|
@@ -237,6 +249,7 @@ What to expect:
|
|
|
237
249
|
| `plugins.allow is empty` warning | Add `openclaw-nowledge-mem` to `plugins.allow` if the user wants explicit trust |
|
|
238
250
|
| Remote config seems ignored | Check whether `~/.nowledge-mem/openclaw.json` is overriding plugin settings |
|
|
239
251
|
| Local mode unexpectedly talks to a remote server | Check for stale `NMEM_API_URL` / `NMEM_API_KEY` in the environment or an overriding `~/.nowledge-mem/openclaw.json` |
|
|
252
|
+
| Plugin tools missing | Ensure the plugin is in `plugins.allow: ["openclaw-nowledge-mem"]`. Do **not** put `nowledge_mem_*` tool names in `tools.allow` — OpenClaw silently strips plugin-only allowlists. No `tools.*` config is needed; plugin tools load automatically when the plugin is allowed. |
|
|
240
253
|
|
|
241
254
|
## Notes for Agents
|
|
242
255
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-nowledge-mem",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"kind": "memory",
|
|
5
5
|
"skills": ["skills/memory-guide"],
|
|
6
6
|
"uiHints": {
|
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
"label": "Max thread message chars",
|
|
29
29
|
"help": "Maximum characters preserved per captured OpenClaw thread message (200-20000). Higher values keep more of long messages in Nowledge thread history."
|
|
30
30
|
},
|
|
31
|
+
"captureExclude": {
|
|
32
|
+
"label": "Exclude session patterns",
|
|
33
|
+
"help": "Session key glob patterns to skip during auto-capture. Use * to match within a colon-segment. Example: agent:*:cron:*"
|
|
34
|
+
},
|
|
35
|
+
"captureSkipMarker": {
|
|
36
|
+
"label": "Skip marker",
|
|
37
|
+
"help": "Text marker in messages that prevents capture for the session. Default: #nmem-skip. Note: if compaction drops the marked message, capture resumes — use captureExclude patterns for persistent filtering."
|
|
38
|
+
},
|
|
31
39
|
"apiUrl": {
|
|
32
40
|
"label": "Server URL (remote mode)",
|
|
33
41
|
"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"
|
|
@@ -79,6 +87,17 @@
|
|
|
79
87
|
"maximum": 20000,
|
|
80
88
|
"description": "Maximum characters preserved per captured OpenClaw thread message before truncation."
|
|
81
89
|
},
|
|
90
|
+
"captureExclude": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"items": { "type": "string" },
|
|
93
|
+
"default": [],
|
|
94
|
+
"description": "Session key glob patterns to exclude from auto-capture. Glob * matches within a colon-delimited segment. Example: agent:*:cron:* excludes all cron job sessions."
|
|
95
|
+
},
|
|
96
|
+
"captureSkipMarker": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"default": "#nmem-skip",
|
|
99
|
+
"description": "Text marker in messages that prevents capture for the entire session. When any message contains this marker, the session is skipped. Note: compaction may drop the marked message — use captureExclude for persistent filtering."
|
|
100
|
+
},
|
|
82
101
|
"apiUrl": {
|
|
83
102
|
"type": "string",
|
|
84
103
|
"default": "",
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -21,6 +21,8 @@ const ALLOWED_KEYS = new Set([
|
|
|
21
21
|
"maxContextResults",
|
|
22
22
|
"recallMinScore",
|
|
23
23
|
"maxThreadMessageChars",
|
|
24
|
+
"captureExclude",
|
|
25
|
+
"captureSkipMarker",
|
|
24
26
|
"apiUrl",
|
|
25
27
|
"apiKey",
|
|
26
28
|
// Legacy aliases — accepted but not advertised
|
|
@@ -343,6 +345,43 @@ export function parseConfig(raw, logger) {
|
|
|
343
345
|
const apiKey = ak.value;
|
|
344
346
|
_sources.apiKey = ak.source;
|
|
345
347
|
|
|
348
|
+
// --- captureExclude: file > pluginConfig > default ---
|
|
349
|
+
const captureExclude = (() => {
|
|
350
|
+
const fromFile = Array.isArray(resolvedFile.captureExclude)
|
|
351
|
+
? resolvedFile.captureExclude
|
|
352
|
+
: null;
|
|
353
|
+
const fromPlugin = Array.isArray(resolvedPlugin.captureExclude)
|
|
354
|
+
? resolvedPlugin.captureExclude
|
|
355
|
+
: null;
|
|
356
|
+
_sources.captureExclude = fromFile
|
|
357
|
+
? "file"
|
|
358
|
+
: fromPlugin
|
|
359
|
+
? "pluginConfig"
|
|
360
|
+
: "default";
|
|
361
|
+
const entries = fromFile ?? fromPlugin ?? [];
|
|
362
|
+
return entries
|
|
363
|
+
.filter((v) => typeof v === "string" && v.trim())
|
|
364
|
+
.map((v) => v.trim());
|
|
365
|
+
})();
|
|
366
|
+
|
|
367
|
+
// --- captureSkipMarker: file > pluginConfig > default ---
|
|
368
|
+
const captureSkipMarker = (() => {
|
|
369
|
+
const fromFile =
|
|
370
|
+
typeof resolvedFile.captureSkipMarker === "string"
|
|
371
|
+
? resolvedFile.captureSkipMarker.trim()
|
|
372
|
+
: undefined;
|
|
373
|
+
const fromPlugin =
|
|
374
|
+
typeof resolvedPlugin.captureSkipMarker === "string"
|
|
375
|
+
? resolvedPlugin.captureSkipMarker.trim()
|
|
376
|
+
: undefined;
|
|
377
|
+
_sources.captureSkipMarker = fromFile
|
|
378
|
+
? "file"
|
|
379
|
+
: fromPlugin
|
|
380
|
+
? "pluginConfig"
|
|
381
|
+
: "default";
|
|
382
|
+
return fromFile || fromPlugin || "#nmem-skip";
|
|
383
|
+
})();
|
|
384
|
+
|
|
346
385
|
return {
|
|
347
386
|
sessionContext,
|
|
348
387
|
sessionDigest,
|
|
@@ -350,6 +389,8 @@ export function parseConfig(raw, logger) {
|
|
|
350
389
|
maxContextResults,
|
|
351
390
|
recallMinScore,
|
|
352
391
|
maxThreadMessageChars,
|
|
392
|
+
captureExclude,
|
|
393
|
+
captureSkipMarker,
|
|
353
394
|
apiUrl,
|
|
354
395
|
apiKey,
|
|
355
396
|
_sources,
|
package/src/context-engine.js
CHANGED
|
@@ -22,7 +22,12 @@
|
|
|
22
22
|
|
|
23
23
|
import { ceState } from "./ce-state.js";
|
|
24
24
|
import { BASE_GUIDANCE, SESSION_CONTEXT_GUIDANCE } from "./hooks/behavioral.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
appendOrCreateThread,
|
|
27
|
+
hasSkipMarker,
|
|
28
|
+
matchesExcludePattern,
|
|
29
|
+
triageAndDistill,
|
|
30
|
+
} from "./hooks/capture.js";
|
|
26
31
|
import {
|
|
27
32
|
MAX_QUERY_LENGTH,
|
|
28
33
|
SHORT_QUERY_THRESHOLD,
|
|
@@ -342,8 +347,23 @@ export function createNowledgeMemContextEngineFactory(client, cfg, logger) {
|
|
|
342
347
|
}) {
|
|
343
348
|
if (isHeartbeat) return;
|
|
344
349
|
|
|
350
|
+
// Normalize sessionKey consistently with hook handlers
|
|
351
|
+
const normalizedKey = String(sessionKey || sessionId || "");
|
|
352
|
+
|
|
353
|
+
// Capture exclusion: pattern-based and marker-based filters
|
|
354
|
+
if (matchesExcludePattern(normalizedKey, cfg.captureExclude)) {
|
|
355
|
+
logger.debug?.(`ce: skipped excluded session ${normalizedKey}`);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (hasSkipMarker(messages, cfg.captureSkipMarker)) {
|
|
359
|
+
logger.debug?.(
|
|
360
|
+
`ce: skipped session with skip marker ${normalizedKey}`,
|
|
361
|
+
);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
345
365
|
const event = { messages, sessionFile };
|
|
346
|
-
const ctx = { sessionId, sessionKey };
|
|
366
|
+
const ctx = { sessionId, sessionKey: normalizedKey };
|
|
347
367
|
|
|
348
368
|
// 1. Always capture thread (idempotent, deduped)
|
|
349
369
|
const captureResult = await appendOrCreateThread({
|
package/src/hooks/capture.js
CHANGED
|
@@ -40,6 +40,63 @@ function _setSyncedMessageCount(threadId, count) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Capture exclusion filters
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Compile a glob pattern (where `*` matches within a colon-delimited segment)
|
|
49
|
+
* into a RegExp. Results are cached since patterns are immutable after parse.
|
|
50
|
+
*/
|
|
51
|
+
const _compiledPatterns = new Map();
|
|
52
|
+
function _compileGlob(pattern) {
|
|
53
|
+
const key = String(pattern).toLowerCase();
|
|
54
|
+
let re = _compiledPatterns.get(key);
|
|
55
|
+
if (!re) {
|
|
56
|
+
re = new RegExp(
|
|
57
|
+
"^" +
|
|
58
|
+
key
|
|
59
|
+
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
|
60
|
+
.replace(/\*/g, "[^:]*") +
|
|
61
|
+
"$",
|
|
62
|
+
);
|
|
63
|
+
_compiledPatterns.set(key, re);
|
|
64
|
+
}
|
|
65
|
+
return re;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Test whether a session key matches any exclusion glob pattern.
|
|
70
|
+
* Glob `*` matches within a colon-delimited segment (not across colons).
|
|
71
|
+
* Example: "agent:*:cron:*" matches "agent:main:cron:abc123"
|
|
72
|
+
*
|
|
73
|
+
* Exported for reuse by Context Engine and other capture paths.
|
|
74
|
+
*/
|
|
75
|
+
export function matchesExcludePattern(sessionKey, patterns) {
|
|
76
|
+
if (!Array.isArray(patterns) || patterns.length === 0) return false;
|
|
77
|
+
const key = String(sessionKey || "").toLowerCase();
|
|
78
|
+
return patterns.some((pattern) => _compileGlob(pattern).test(key));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if any message contains the skip marker text.
|
|
83
|
+
* Scans both raw message content and nested message objects.
|
|
84
|
+
*
|
|
85
|
+
* Exported for reuse by Context Engine and other capture paths.
|
|
86
|
+
*/
|
|
87
|
+
export function hasSkipMarker(messages, marker) {
|
|
88
|
+
if (!marker || typeof marker !== "string" || !Array.isArray(messages)) return false;
|
|
89
|
+
const markerLc = marker.toLowerCase();
|
|
90
|
+
return messages.some((msg) => {
|
|
91
|
+
const text = extractText(msg?.content ?? msg?.message?.content);
|
|
92
|
+
return text.toLowerCase().includes(markerLc);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Message normalization utilities
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
43
100
|
export function truncate(text, max = DEFAULT_MAX_MESSAGE_CHARS) {
|
|
44
101
|
const str = String(text || "").trim();
|
|
45
102
|
if (!str) return "";
|
|
@@ -197,8 +254,9 @@ export async function appendOrCreateThread({
|
|
|
197
254
|
ctx,
|
|
198
255
|
reason,
|
|
199
256
|
maxMessageChars = DEFAULT_MAX_MESSAGE_CHARS,
|
|
257
|
+
resolvedMessages,
|
|
200
258
|
}) {
|
|
201
|
-
const rawMessages = await resolveHookMessages(event);
|
|
259
|
+
const rawMessages = resolvedMessages ?? (await resolveHookMessages(event));
|
|
202
260
|
if (!Array.isArray(rawMessages) || rawMessages.length === 0) return;
|
|
203
261
|
|
|
204
262
|
const threadId = buildStableThreadId(event, ctx);
|
|
@@ -413,6 +471,22 @@ export function buildAgentEndCaptureHandler(client, cfg, logger) {
|
|
|
413
471
|
if (!event?.success) return;
|
|
414
472
|
if (ctx?.trigger === "heartbeat") return;
|
|
415
473
|
|
|
474
|
+
// Layer 1: pattern-based exclusion (e.g. cron jobs, subagent sessions)
|
|
475
|
+
const sessionKey = String(ctx?.sessionKey || ctx?.sessionId || "");
|
|
476
|
+
if (matchesExcludePattern(sessionKey, cfg.captureExclude)) {
|
|
477
|
+
logger.debug?.(`capture: skipped excluded session ${sessionKey}`);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Layer 2: marker-based exclusion (user typed #nmem-skip in conversation)
|
|
482
|
+
const resolvedMessages = await resolveHookMessages(event);
|
|
483
|
+
if (hasSkipMarker(resolvedMessages, cfg.captureSkipMarker)) {
|
|
484
|
+
logger.debug?.(
|
|
485
|
+
`capture: skipped session with skip marker ${sessionKey}`,
|
|
486
|
+
);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
416
490
|
const captureResult = await appendOrCreateThread({
|
|
417
491
|
client,
|
|
418
492
|
logger,
|
|
@@ -420,6 +494,7 @@ export function buildAgentEndCaptureHandler(client, cfg, logger) {
|
|
|
420
494
|
ctx,
|
|
421
495
|
reason: "agent_end",
|
|
422
496
|
maxMessageChars: cfg.maxThreadMessageChars,
|
|
497
|
+
resolvedMessages,
|
|
423
498
|
});
|
|
424
499
|
|
|
425
500
|
await triageAndDistill({ client, cfg, logger, captureResult, ctx });
|
|
@@ -435,10 +510,27 @@ export function buildAgentEndCaptureHandler(client, cfg, logger) {
|
|
|
435
510
|
*
|
|
436
511
|
* Heartbeat sessions are skipped (same rationale as agent_end).
|
|
437
512
|
*/
|
|
438
|
-
export function buildBeforeResetCaptureHandler(client,
|
|
513
|
+
export function buildBeforeResetCaptureHandler(client, cfg, logger) {
|
|
439
514
|
return async (event, ctx) => {
|
|
440
515
|
if (ceState.active) return;
|
|
441
516
|
if (ctx?.trigger === "heartbeat") return;
|
|
517
|
+
|
|
518
|
+
// Layer 1: pattern-based exclusion
|
|
519
|
+
const sessionKey = String(ctx?.sessionKey || ctx?.sessionId || "");
|
|
520
|
+
if (matchesExcludePattern(sessionKey, cfg.captureExclude)) {
|
|
521
|
+
logger.debug?.(`capture: skipped excluded session ${sessionKey}`);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Layer 2: marker-based exclusion
|
|
526
|
+
const resolvedMessages = await resolveHookMessages(event);
|
|
527
|
+
if (hasSkipMarker(resolvedMessages, cfg.captureSkipMarker)) {
|
|
528
|
+
logger.debug?.(
|
|
529
|
+
`capture: skipped session with skip marker ${sessionKey}`,
|
|
530
|
+
);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
442
534
|
const reason = typeof event?.reason === "string" ? event.reason : undefined;
|
|
443
535
|
await appendOrCreateThread({
|
|
444
536
|
client,
|
|
@@ -446,7 +538,8 @@ export function buildBeforeResetCaptureHandler(client, _cfg, logger) {
|
|
|
446
538
|
event,
|
|
447
539
|
ctx,
|
|
448
540
|
reason,
|
|
449
|
-
maxMessageChars:
|
|
541
|
+
maxMessageChars: cfg.maxThreadMessageChars,
|
|
542
|
+
resolvedMessages,
|
|
450
543
|
});
|
|
451
544
|
};
|
|
452
545
|
}
|
package/src/index.js
CHANGED
|
@@ -54,8 +54,14 @@ export default {
|
|
|
54
54
|
api.registerTool(createThreadSearchTool(client, logger));
|
|
55
55
|
api.registerTool(createThreadFetchTool(client, logger));
|
|
56
56
|
|
|
57
|
-
// Diagnostics
|
|
58
|
-
|
|
57
|
+
// Diagnostics — pass runtime config so the status tool can detect:
|
|
58
|
+
// - plugins.allow missing or not including this plugin
|
|
59
|
+
// - memory slot pointing elsewhere (e.g. memory-core)
|
|
60
|
+
const memorySlot = api.config?.plugins?.slots?.memory;
|
|
61
|
+
const pluginsAllow = api.config?.plugins?.allow;
|
|
62
|
+
api.registerTool(
|
|
63
|
+
createStatusTool(client, logger, cfg, { memorySlot, pluginsAllow }),
|
|
64
|
+
);
|
|
59
65
|
|
|
60
66
|
// --- Context Engine registration ---
|
|
61
67
|
// When the user sets `plugins.slots.contextEngine: "nowledge-mem"`,
|
package/src/tools/status.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { isDefaultApiUrl } from "../config.js";
|
|
9
9
|
|
|
10
|
-
export function createStatusTool(client, _logger, cfg) {
|
|
10
|
+
export function createStatusTool(client, _logger, cfg, runtimeInfo = {}) {
|
|
11
11
|
return {
|
|
12
12
|
name: "nowledge_mem_status",
|
|
13
13
|
description:
|
|
@@ -23,6 +23,60 @@ export function createStatusTool(client, _logger, cfg) {
|
|
|
23
23
|
const details = {};
|
|
24
24
|
const sources = cfg._sources || {};
|
|
25
25
|
|
|
26
|
+
// 0a. Plugin trust check — detect missing plugins.allow
|
|
27
|
+
const pluginsAllow = runtimeInfo.pluginsAllow;
|
|
28
|
+
const pluginId = "openclaw-nowledge-mem";
|
|
29
|
+
details.pluginsAllow = !Array.isArray(pluginsAllow)
|
|
30
|
+
? "(not set)"
|
|
31
|
+
: pluginsAllow.includes(pluginId)
|
|
32
|
+
? "listed"
|
|
33
|
+
: "set but plugin not included";
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
Array.isArray(pluginsAllow) &&
|
|
37
|
+
pluginsAllow.length > 0 &&
|
|
38
|
+
!pluginsAllow.includes(pluginId)
|
|
39
|
+
) {
|
|
40
|
+
lines.push(
|
|
41
|
+
`⚠ plugins.allow is set but does not include "${pluginId}".`,
|
|
42
|
+
);
|
|
43
|
+
lines.push(
|
|
44
|
+
" The plugin will be BLOCKED from loading unless it holds the memory slot or is explicitly enabled.",
|
|
45
|
+
);
|
|
46
|
+
lines.push(
|
|
47
|
+
` Fix: add "${pluginId}" to plugins.allow in your OpenClaw config.`,
|
|
48
|
+
);
|
|
49
|
+
lines.push("");
|
|
50
|
+
} else if (!Array.isArray(pluginsAllow) || pluginsAllow.length === 0) {
|
|
51
|
+
lines.push(
|
|
52
|
+
"Plugin trust: plugins.allow not set (plugin loads via entries.enabled)",
|
|
53
|
+
);
|
|
54
|
+
lines.push(
|
|
55
|
+
` Tip: set plugins.allow to ["${pluginId}"] to pin trust explicitly.`,
|
|
56
|
+
);
|
|
57
|
+
lines.push("");
|
|
58
|
+
} else {
|
|
59
|
+
lines.push(`Plugin trust: ${pluginId} (allowlisted)`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 0b. Memory slot check — detect misconfiguration early
|
|
63
|
+
const memorySlot = runtimeInfo.memorySlot;
|
|
64
|
+
details.memorySlot = memorySlot ?? "(unknown)";
|
|
65
|
+
if (memorySlot && memorySlot !== "openclaw-nowledge-mem") {
|
|
66
|
+
lines.push(
|
|
67
|
+
`⚠ Memory slot: "${memorySlot}" — Nowledge Mem is NOT the active memory plugin.`,
|
|
68
|
+
);
|
|
69
|
+
lines.push(
|
|
70
|
+
" Only memory_search and memory_get will work. Other tools (save, connections, timeline, etc.) are inactive.",
|
|
71
|
+
);
|
|
72
|
+
lines.push(
|
|
73
|
+
' Fix: run "openclaw plugins install @nowledge/openclaw-nowledge-mem" or set plugins.slots.memory to "openclaw-nowledge-mem" in your config.',
|
|
74
|
+
);
|
|
75
|
+
lines.push("");
|
|
76
|
+
} else if (memorySlot === "openclaw-nowledge-mem") {
|
|
77
|
+
lines.push("Memory slot: openclaw-nowledge-mem (active)");
|
|
78
|
+
}
|
|
79
|
+
|
|
26
80
|
// 1. Mode + connection target
|
|
27
81
|
const remote = !isDefaultApiUrl(cfg.apiUrl);
|
|
28
82
|
const mode = remote ? "remote" : "local";
|