@nowledge/openclaw-nowledge-mem 0.6.6 → 0.6.8
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 +26 -3
- package/CLAUDE.md +10 -6
- package/README.md +35 -20
- package/openclaw.plugin.json +2 -3
- package/package.json +1 -1
- package/src/config.js +114 -110
- package/src/index.js +4 -0
- package/src/tools/status.js +145 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,15 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Nowledge Mem OpenClaw plugin will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.8] - 2026-02-27
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **Config priority flipped**: `~/.nowledge-mem/openclaw.json` now takes priority over OpenClaw plugin settings. Most users configure via OpenClaw UI (no file needed). Users who create the file get predictable overrides.
|
|
10
|
+
- **No auto-created config file**: The plugin no longer creates `~/.nowledge-mem/openclaw.json` on first startup. OpenClaw's settings UI works out of the box. Create the file manually when you need persistent or scripted config.
|
|
11
|
+
- **Status tool shows config sources**: `nowledge_mem_status` now shows where each setting comes from (`file`, `pluginConfig`, `env`, or `default`).
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **configSchema broke OpenClaw form UI**: `additionalProperties: true` (added in 0.6.7) caused OpenClaw's config form to show "Unsupported schema mode. Use Raw mode." Removed the field entirely - JSON Schema defaults to allowing additional properties when omitted, so typos still don't crash the plugin, and the form renders normally.
|
|
16
|
+
|
|
17
|
+
## [0.6.7] - 2026-02-27
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `nowledge_mem_status` tool: verify effective config, backend connectivity, and version. Shows mode (local/remote), whether API key is set, backend health, and all config values. 10 tools total.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **configSchema too strict**: `additionalProperties` changed from `false` to `true`. Previously, a typo in openclaw.json config (e.g. `sesionContext`) caused the plugin to silently fail to load. Now OpenClaw passes validation and the plugin's own parser gives a descriptive error listing valid keys.
|
|
26
|
+
- **apiUrl port typo**: configSchema description had port 14142 instead of 14242.
|
|
27
|
+
|
|
5
28
|
## [0.6.6] - 2026-02-27
|
|
6
29
|
|
|
7
30
|
### Changed - Config file + env var cascade
|
|
8
31
|
|
|
9
32
|
**Config file at `~/.nowledge-mem/openclaw.json`**
|
|
10
33
|
|
|
11
|
-
The plugin now reads configuration from a dedicated config file, independent of OpenClaw's `openclaw.json`. The file is auto-created on first startup with sensible defaults. If you had settings in OpenClaw's plugin config, those values are automatically
|
|
34
|
+
The plugin now reads configuration from a dedicated config file, independent of OpenClaw's `openclaw.json`. The file is auto-created on first startup with sensible defaults. If you had settings in OpenClaw's plugin config, those values are automatically seeded into the file on first run. No manual steps needed.
|
|
12
35
|
|
|
13
|
-
This
|
|
36
|
+
This gives you a single, easy-to-find place for all Nowledge Mem settings. The file persists across plugin reinstalls and OpenClaw upgrades.
|
|
14
37
|
|
|
15
38
|
**Config cascade** (4 layers, highest to lowest priority):
|
|
16
39
|
1. `api.pluginConfig` - from OpenClaw's plugin config (when supported)
|
|
@@ -34,7 +57,7 @@ This ensures your settings survive OpenClaw version upgrades that may strip cust
|
|
|
34
57
|
|
|
35
58
|
### Added - Thread tools + sourceThreadId linkage
|
|
36
59
|
|
|
37
|
-
**Two new tools for conversation retrieval.**
|
|
60
|
+
**Two new tools for conversation retrieval.**
|
|
38
61
|
|
|
39
62
|
- `nowledge_mem_thread_search` - search past conversations by keyword. Returns matched threads with message snippets and relevance scores. Use when the user asks about a past discussion or wants to find a specific conversation.
|
|
40
63
|
- `nowledge_mem_thread_fetch` - fetch full messages from a specific thread with pagination support. Use to progressively retrieve long conversations. Accepts `offset` and `limit` for paginated retrieval.
|
package/CLAUDE.md
CHANGED
|
@@ -28,7 +28,7 @@ Reflects Nowledge Mem's genuine v0.6 strengths:
|
|
|
28
28
|
src/
|
|
29
29
|
index.js - plugin registration (tools, hooks, commands, CLI)
|
|
30
30
|
client.js - CLI wrapper with API fallback; credential handling
|
|
31
|
-
config.js - config cascade:
|
|
31
|
+
config.js - config cascade: ~/.nowledge-mem/openclaw.json > pluginConfig > env vars > defaults
|
|
32
32
|
hooks/
|
|
33
33
|
behavioral.js - always-on behavioral guidance (~50 tokens/turn)
|
|
34
34
|
recall.js - before_prompt_build: inject Working Memory + recalled memories
|
|
@@ -44,10 +44,10 @@ src/
|
|
|
44
44
|
thread-search.js - search past conversations by keyword
|
|
45
45
|
thread-fetch.js - fetch full messages from a thread with pagination
|
|
46
46
|
openclaw.plugin.json - manifest + config schema (version, uiHints, configSchema)
|
|
47
|
-
~/.nowledge-mem/openclaw.json - user config file (
|
|
47
|
+
~/.nowledge-mem/openclaw.json - optional user config file (overrides OpenClaw settings when present)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
## Tool Surface (
|
|
50
|
+
## Tool Surface (10 tools)
|
|
51
51
|
|
|
52
52
|
### OpenClaw Memory Slot (required for system prompt activation)
|
|
53
53
|
- `memory_search` - multi-signal: BM25 + embedding + label + graph + decay. Returns `matchedVia` ("Text Match 100% + Semantic 69%"), `importance`, bi-temporal filters (`event_date_from/to`, `recorded_date_from/to`). Also returns `relatedThreads` (past conversation snippets matching the query) and `sourceThreadId` (link to source conversation). Mode: `"multi-signal"`.
|
|
@@ -64,6 +64,9 @@ openclaw.plugin.json - manifest + config schema (version, uiHints, configSchema)
|
|
|
64
64
|
- `nowledge_mem_thread_search` - search past conversations by keyword. Returns threads with matched message snippets, relevance scores, and message counts. Supports `source` filter.
|
|
65
65
|
- `nowledge_mem_thread_fetch` - fetch full messages from a specific thread. Supports pagination via `offset` + `limit` for progressive retrieval of long conversations.
|
|
66
66
|
|
|
67
|
+
### Diagnostics
|
|
68
|
+
- `nowledge_mem_status` - show effective config (mode, apiUrl, apiKey set, sessionContext, sessionDigest, etc.), backend connectivity, and version. No parameters.
|
|
69
|
+
|
|
67
70
|
## Hook Surface
|
|
68
71
|
|
|
69
72
|
- `before_prompt_build` (always-on) - behavioral guidance: brief note telling agent to save/search proactively. Adjusts when sessionContext is on to avoid redundant searches.
|
|
@@ -80,7 +83,7 @@ openclaw.plugin.json - manifest + config schema (version, uiHints, configSchema)
|
|
|
80
83
|
|
|
81
84
|
## Config Keys
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
Optional config file at `~/.nowledge-mem/openclaw.json`. Falls through to OpenClaw plugin settings when the file is absent.
|
|
84
87
|
|
|
85
88
|
| Key | Type | Default | Env Var | Description |
|
|
86
89
|
|-----|------|---------|---------|-------------|
|
|
@@ -91,7 +94,9 @@ Config file at `~/.nowledge-mem/openclaw.json` (auto-created on first run). Also
|
|
|
91
94
|
| `apiUrl` | string | `""` | `NMEM_API_URL` | Remote server URL. Empty = local (127.0.0.1:14242) |
|
|
92
95
|
| `apiKey` | string | `""` | `NMEM_API_KEY` | API key. Never logged. |
|
|
93
96
|
|
|
94
|
-
**Priority**:
|
|
97
|
+
**Priority**: config file > pluginConfig > env var > default.
|
|
98
|
+
|
|
99
|
+
`parseConfig()` returns `_sources` object tracking where each value came from (`"file"`, `"pluginConfig"`, `"env"`, `"default"`). Used by `nowledge_mem_status` tool.
|
|
95
100
|
|
|
96
101
|
Legacy aliases accepted silently in all sources for backward compat:
|
|
97
102
|
`autoRecall`→`sessionContext`, `autoCapture`→`sessionDigest`, `captureMinInterval`→`digestMinInterval`, `maxRecallResults`→`maxContextResults`.
|
|
@@ -99,7 +104,6 @@ Legacy aliases accepted silently in all sources for backward compat:
|
|
|
99
104
|
### Credential Handling Rules
|
|
100
105
|
- `apiKey` → ONLY via child process env (`NMEM_API_KEY`). Never CLI arg, never logged.
|
|
101
106
|
- `apiUrl` → passed as `--api-url` flag to CLI (not a secret).
|
|
102
|
-
- Config values win over environment variables.
|
|
103
107
|
- `_spawnEnv()` builds per-spawn env; `_apiUrlArgs()` adds `--api-url` when non-default.
|
|
104
108
|
|
|
105
109
|
## CLI Surface (nmem commands used by plugin)
|
package/README.md
CHANGED
|
@@ -32,13 +32,15 @@ Start Nowledge Mem desktop app or run `nmem serve`, then enable the plugin:
|
|
|
32
32
|
}
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
That's it. The agent gets
|
|
35
|
+
That's it. The agent gets 10 tools and calls them on demand. No extra tokens wasted.
|
|
36
36
|
|
|
37
37
|
### Remote mode
|
|
38
38
|
|
|
39
39
|
Connect to a Nowledge Mem server running elsewhere (a VPS, a home server, or a shared team instance). See [remote access guide](https://mem.nowledge.co/docs/remote-access) for server setup.
|
|
40
40
|
|
|
41
|
-
Enable the plugin the same way as local mode, then set
|
|
41
|
+
Enable the plugin the same way as local mode, then set `apiUrl` and `apiKey` in the OpenClaw plugin settings.
|
|
42
|
+
|
|
43
|
+
Or use a config file at `~/.nowledge-mem/openclaw.json`:
|
|
42
44
|
|
|
43
45
|
```json
|
|
44
46
|
{
|
|
@@ -62,7 +64,7 @@ flowchart TD
|
|
|
62
64
|
B --> C["Behavioral guidance(always, ~110 tokens)"]
|
|
63
65
|
B -.->|"sessionContext: true"| D["Working Memory +relevant memories (~1-2 KB)"]
|
|
64
66
|
|
|
65
|
-
C --> E["Agent processes message(
|
|
67
|
+
C --> E["Agent processes message(10 tools available)"]
|
|
66
68
|
D --> E
|
|
67
69
|
|
|
68
70
|
E --> F{"Needs past context?"}
|
|
@@ -91,6 +93,7 @@ The behavioral hook nudges the agent to **search before answering** and **save a
|
|
|
91
93
|
| Memory has `sourceThreadId` | `nowledge_mem_thread_fetch` | Fetch full source conversation. Pagination via `offset` + `limit`. |
|
|
92
94
|
| "Find our discussion about X" | `nowledge_mem_thread_search` | Search past conversations by keyword. Returns matched snippets. |
|
|
93
95
|
| "Forget X" | `nowledge_mem_forget` | Delete by ID or search query. |
|
|
96
|
+
| "Is my setup working?" | `nowledge_mem_status` | Show effective config, backend connectivity, and version. |
|
|
94
97
|
|
|
95
98
|
### Session Lifecycle (Automatic Capture)
|
|
96
99
|
|
|
@@ -153,21 +156,21 @@ flowchart LR
|
|
|
153
156
|
subgraph "default" ["Default (recommended)"]
|
|
154
157
|
direction TB
|
|
155
158
|
D1["Every turn: behavioral guidance (~50 tokens)"]
|
|
156
|
-
D2["Agent calls
|
|
159
|
+
D2["Agent calls 10 tools on demand"]
|
|
157
160
|
D3["Session end: thread capture + LLM distillation"]
|
|
158
161
|
end
|
|
159
162
|
|
|
160
163
|
subgraph context ["Session Context"]
|
|
161
164
|
direction TB
|
|
162
165
|
C1["Every turn: guidance + Working Memory+ recalled memories (~1-2 KB)"]
|
|
163
|
-
C2["Agent calls
|
|
166
|
+
C2["Agent calls 10 tools on demand"]
|
|
164
167
|
C3["Session end: thread capture + LLM distillation"]
|
|
165
168
|
end
|
|
166
169
|
|
|
167
170
|
subgraph minimal ["Minimal"]
|
|
168
171
|
direction TB
|
|
169
172
|
M1["Every turn: behavioral guidance (~50 tokens)"]
|
|
170
|
-
M2["Agent calls
|
|
173
|
+
M2["Agent calls 10 tools on demand"]
|
|
171
174
|
M3["No automatic capture"]
|
|
172
175
|
end
|
|
173
176
|
```
|
|
@@ -254,8 +257,8 @@ The plugin supports three modes. Choose based on how much you want to guarantee
|
|
|
254
257
|
|
|
255
258
|
| Mode | Config | Behavior | Tradeoff |
|
|
256
259
|
|------|--------|----------|----------|
|
|
257
|
-
| **Default** (recommended) | `sessionContext: false, sessionDigest: true` | Agent calls
|
|
258
|
-
| **Session context** | `sessionContext: true` | Working Memory + relevant memories injected at prompt time, plus all
|
|
260
|
+
| **Default** (recommended) | `sessionContext: false, sessionDigest: true` | Agent calls 10 tools on demand. Behavioral guidance every turn (~50 tokens). Conversations captured + distilled at session end. | Lowest cost. Agent decides when to search, usually smart, occasionally forgets. |
|
|
261
|
+
| **Session context** | `sessionContext: true` | Working Memory + relevant memories injected at prompt time, plus all 10 tools still available. | ~1-2 KB/turn. Guarantees context is always present. Best for short sessions or critical workflows. |
|
|
259
262
|
| **Minimal** | `sessionDigest: false` | Tool-only, no automatic capture. | Zero overhead beyond guidance. Use when you handle memory manually. |
|
|
260
263
|
|
|
261
264
|
**Which mode should you use?**
|
|
@@ -319,7 +322,22 @@ openclaw nowledge-mem status
|
|
|
319
322
|
|
|
320
323
|
## Configuration
|
|
321
324
|
|
|
322
|
-
|
|
325
|
+
No config needed to get started. The defaults work for local mode.
|
|
326
|
+
|
|
327
|
+
To change settings, use the OpenClaw plugin settings UI. Changes take effect on restart.
|
|
328
|
+
|
|
329
|
+
| Key | Type | Default | Description |
|
|
330
|
+
|-----|------|---------|-------------|
|
|
331
|
+
| `sessionContext` | boolean | `false` | Inject Working Memory + relevant memories at prompt time |
|
|
332
|
+
| `sessionDigest` | boolean | `true` | Thread capture + LLM distillation at session end |
|
|
333
|
+
| `digestMinInterval` | integer | `300` | Minimum seconds between session digests for the same thread (0-86400) |
|
|
334
|
+
| `maxContextResults` | integer | `5` | Max memories to inject at prompt time (1-20, only used when sessionContext is enabled) |
|
|
335
|
+
| `apiUrl` | string | `""` | Remote server URL. Empty = local (`http://127.0.0.1:14242`) |
|
|
336
|
+
| `apiKey` | string | `""` | API key for remote access. Injected as `NMEM_API_KEY` env var, never logged |
|
|
337
|
+
|
|
338
|
+
### Advanced: config file
|
|
339
|
+
|
|
340
|
+
For persistent or scripted config, create `~/.nowledge-mem/openclaw.json`:
|
|
323
341
|
|
|
324
342
|
```json
|
|
325
343
|
{
|
|
@@ -332,16 +350,7 @@ On first startup, the plugin creates a config file at `~/.nowledge-mem/openclaw.
|
|
|
332
350
|
}
|
|
333
351
|
```
|
|
334
352
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
| Key | Type | Default | Description |
|
|
338
|
-
|-----|------|---------|-------------|
|
|
339
|
-
| `sessionContext` | boolean | `false` | Inject Working Memory + relevant memories at prompt time |
|
|
340
|
-
| `sessionDigest` | boolean | `true` | Thread capture + LLM distillation at session end |
|
|
341
|
-
| `digestMinInterval` | integer | `300` | Minimum seconds between session digests for the same thread (0-86400) |
|
|
342
|
-
| `maxContextResults` | integer | `5` | Max memories to inject at prompt time (1-20, only used when sessionContext is enabled) |
|
|
343
|
-
| `apiUrl` | string | `""` | Remote server URL. Empty = local (`http://127.0.0.1:14242`) |
|
|
344
|
-
| `apiKey` | string | `""` | API key for remote access. Injected as `NMEM_API_KEY` env var, never logged |
|
|
353
|
+
The config file takes priority over OpenClaw settings. Only the keys you include are overridden; missing keys fall through to OpenClaw settings, then env vars, then defaults.
|
|
345
354
|
|
|
346
355
|
### Advanced: environment variables
|
|
347
356
|
|
|
@@ -356,7 +365,13 @@ NMEM_API_URL=https://...
|
|
|
356
365
|
NMEM_API_KEY=your-key
|
|
357
366
|
```
|
|
358
367
|
|
|
359
|
-
|
|
368
|
+
### Priority
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
config file > OpenClaw settings > env vars > defaults
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Use `nowledge_mem_status` (or `openclaw nowledge-mem status`) to see where each value comes from.
|
|
360
375
|
|
|
361
376
|
## What Makes This Different
|
|
362
377
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-nowledge-mem",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"kind": "memory",
|
|
5
5
|
"uiHints": {
|
|
6
6
|
"sessionContext": {
|
|
@@ -66,7 +66,6 @@
|
|
|
66
66
|
"default": "",
|
|
67
67
|
"description": "API key for remote access. Stored in plugin config, injected as NMEM_API_KEY env var. Never logged."
|
|
68
68
|
}
|
|
69
|
-
}
|
|
70
|
-
"additionalProperties": false
|
|
69
|
+
}
|
|
71
70
|
}
|
|
72
71
|
}
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
@@ -42,69 +42,14 @@ const ALIAS_KEYS = {
|
|
|
42
42
|
const CONFIG_DIR = join(homedir(), ".nowledge-mem");
|
|
43
43
|
const CONFIG_PATH = join(CONFIG_DIR, "openclaw.json");
|
|
44
44
|
|
|
45
|
-
const DEFAULT_CONFIG = {
|
|
46
|
-
sessionContext: false,
|
|
47
|
-
sessionDigest: true,
|
|
48
|
-
digestMinInterval: 300,
|
|
49
|
-
maxContextResults: 5,
|
|
50
|
-
apiUrl: "",
|
|
51
|
-
apiKey: "",
|
|
52
|
-
};
|
|
53
|
-
|
|
54
45
|
/**
|
|
55
|
-
* Read ~/.nowledge-mem/openclaw.json.
|
|
56
|
-
*
|
|
57
|
-
* (if any) — this migrates settings before OpenClaw strips them.
|
|
58
|
-
* Returns {} on any error — never crashes.
|
|
46
|
+
* Read ~/.nowledge-mem/openclaw.json if it exists.
|
|
47
|
+
* Returns {} when the file is missing or unreadable.
|
|
59
48
|
*/
|
|
60
|
-
function readConfigFile(logger
|
|
49
|
+
function readConfigFile(logger) {
|
|
61
50
|
try {
|
|
62
51
|
if (!existsSync(CONFIG_PATH)) {
|
|
63
|
-
|
|
64
|
-
// Users on older OpenClaw get their values written to the file
|
|
65
|
-
// BEFORE a future OpenClaw update can strip them.
|
|
66
|
-
const initial = { ...DEFAULT_CONFIG };
|
|
67
|
-
if (pluginCfg && typeof pluginCfg === "object") {
|
|
68
|
-
// Resolve aliases in pluginConfig for seeding
|
|
69
|
-
const resolved = { ...pluginCfg };
|
|
70
|
-
for (const [oldKey, newKey] of Object.entries(ALIAS_KEYS)) {
|
|
71
|
-
if (oldKey in resolved) {
|
|
72
|
-
if (!(newKey in resolved)) {
|
|
73
|
-
resolved[newKey] = resolved[oldKey];
|
|
74
|
-
}
|
|
75
|
-
delete resolved[oldKey];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (typeof resolved.sessionContext === "boolean")
|
|
80
|
-
initial.sessionContext = resolved.sessionContext;
|
|
81
|
-
if (typeof resolved.sessionDigest === "boolean")
|
|
82
|
-
initial.sessionDigest = resolved.sessionDigest;
|
|
83
|
-
if (
|
|
84
|
-
typeof resolved.digestMinInterval === "number" &&
|
|
85
|
-
Number.isFinite(resolved.digestMinInterval)
|
|
86
|
-
)
|
|
87
|
-
initial.digestMinInterval = resolved.digestMinInterval;
|
|
88
|
-
if (
|
|
89
|
-
typeof resolved.maxContextResults === "number" &&
|
|
90
|
-
Number.isFinite(resolved.maxContextResults)
|
|
91
|
-
)
|
|
92
|
-
initial.maxContextResults = resolved.maxContextResults;
|
|
93
|
-
if (typeof resolved.apiUrl === "string" && resolved.apiUrl.trim())
|
|
94
|
-
initial.apiUrl = resolved.apiUrl.trim();
|
|
95
|
-
// apiKey intentionally NOT written to disk — keep in env/pluginConfig only
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
99
|
-
writeFileSync(
|
|
100
|
-
CONFIG_PATH,
|
|
101
|
-
JSON.stringify(initial, null, 2) + "\n",
|
|
102
|
-
"utf-8",
|
|
103
|
-
);
|
|
104
|
-
logger?.info?.(
|
|
105
|
-
`nowledge-mem: created config at ${CONFIG_PATH} — edit to customize`,
|
|
106
|
-
);
|
|
107
|
-
return { ...initial };
|
|
52
|
+
return {};
|
|
108
53
|
}
|
|
109
54
|
const content = readFileSync(CONFIG_PATH, "utf-8").trim();
|
|
110
55
|
if (!content) return {};
|
|
@@ -148,7 +93,8 @@ function pickBool(obj, key) {
|
|
|
148
93
|
}
|
|
149
94
|
|
|
150
95
|
function pickNum(obj, key) {
|
|
151
|
-
if (typeof obj[key] === "number" && Number.isFinite(obj[key]))
|
|
96
|
+
if (typeof obj[key] === "number" && Number.isFinite(obj[key]))
|
|
97
|
+
return obj[key];
|
|
152
98
|
return undefined;
|
|
153
99
|
}
|
|
154
100
|
|
|
@@ -166,11 +112,27 @@ function resolveAliases(obj) {
|
|
|
166
112
|
return resolved;
|
|
167
113
|
}
|
|
168
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Return the first option whose value is not undefined.
|
|
117
|
+
* Each option is { value, source } where source is a label like
|
|
118
|
+
* "file", "pluginConfig", "env", or "default".
|
|
119
|
+
*/
|
|
120
|
+
function firstDefined(...options) {
|
|
121
|
+
for (const opt of options) {
|
|
122
|
+
if (opt.value !== undefined) return opt;
|
|
123
|
+
}
|
|
124
|
+
return options[options.length - 1];
|
|
125
|
+
}
|
|
126
|
+
|
|
169
127
|
// -------------------------------------------------------------------------
|
|
170
128
|
|
|
171
129
|
/**
|
|
172
130
|
* Parse plugin config with cascade:
|
|
173
|
-
*
|
|
131
|
+
* ~/.nowledge-mem/openclaw.json > pluginConfig > env vars > defaults
|
|
132
|
+
*
|
|
133
|
+
* The config file (if it exists) takes highest priority so users who
|
|
134
|
+
* create it get predictable behavior. Most users won't have one and
|
|
135
|
+
* will configure via OpenClaw's plugin settings UI (pluginConfig).
|
|
174
136
|
*
|
|
175
137
|
* Canonical keys: sessionContext, sessionDigest, digestMinInterval,
|
|
176
138
|
* maxContextResults, apiUrl, apiKey
|
|
@@ -184,21 +146,20 @@ function resolveAliases(obj) {
|
|
|
184
146
|
* Environment variables (all optional):
|
|
185
147
|
* NMEM_SESSION_CONTEXT — true/1/yes to enable (alias: NMEM_AUTO_RECALL)
|
|
186
148
|
* NMEM_SESSION_DIGEST — true/1/yes to enable (alias: NMEM_AUTO_CAPTURE)
|
|
187
|
-
* NMEM_DIGEST_MIN_INTERVAL — seconds (0
|
|
188
|
-
* NMEM_MAX_CONTEXT_RESULTS — integer (1
|
|
149
|
+
* NMEM_DIGEST_MIN_INTERVAL — seconds (0-86400)
|
|
150
|
+
* NMEM_MAX_CONTEXT_RESULTS — integer (1-20)
|
|
189
151
|
* NMEM_API_URL — remote server URL
|
|
190
152
|
* NMEM_API_KEY — API key (never logged)
|
|
191
153
|
*/
|
|
192
154
|
export function parseConfig(raw, logger) {
|
|
193
155
|
const pluginCfg = raw && typeof raw === "object" ? raw : {};
|
|
194
|
-
const fileCfg = readConfigFile(logger
|
|
156
|
+
const fileCfg = readConfigFile(logger);
|
|
195
157
|
|
|
196
158
|
// Resolve aliases in both sources
|
|
197
159
|
const resolvedPlugin = resolveAliases(pluginCfg);
|
|
198
160
|
const resolvedFile = resolveAliases(fileCfg);
|
|
199
161
|
|
|
200
162
|
// Strict: reject unknown keys in pluginConfig (typo catcher).
|
|
201
|
-
// File config is our own — validated by shape, no strict check needed.
|
|
202
163
|
const unknownKeys = Object.keys(resolvedPlugin).filter(
|
|
203
164
|
(k) => !ALLOWED_KEYS.has(k),
|
|
204
165
|
);
|
|
@@ -209,55 +170,97 @@ export function parseConfig(raw, logger) {
|
|
|
209
170
|
);
|
|
210
171
|
}
|
|
211
172
|
|
|
212
|
-
|
|
213
|
-
const sessionContext =
|
|
214
|
-
pickBool(resolvedPlugin, "sessionContext") ??
|
|
215
|
-
pickBool(resolvedFile, "sessionContext") ??
|
|
216
|
-
envBool("NMEM_SESSION_CONTEXT", "NMEM_AUTO_RECALL") ??
|
|
217
|
-
false;
|
|
173
|
+
const _sources = {};
|
|
218
174
|
|
|
219
|
-
// ---
|
|
220
|
-
const
|
|
221
|
-
pickBool(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
175
|
+
// --- sessionContext: file > pluginConfig > env > default ---
|
|
176
|
+
const sc = firstDefined(
|
|
177
|
+
{ value: pickBool(resolvedFile, "sessionContext"), source: "file" },
|
|
178
|
+
{
|
|
179
|
+
value: pickBool(resolvedPlugin, "sessionContext"),
|
|
180
|
+
source: "pluginConfig",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
value: envBool("NMEM_SESSION_CONTEXT", "NMEM_AUTO_RECALL"),
|
|
184
|
+
source: "env",
|
|
185
|
+
},
|
|
186
|
+
{ value: false, source: "default" },
|
|
187
|
+
);
|
|
188
|
+
const sessionContext = sc.value;
|
|
189
|
+
_sources.sessionContext = sc.source;
|
|
225
190
|
|
|
226
|
-
// ---
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
:
|
|
191
|
+
// --- sessionDigest: file > pluginConfig > env > default ---
|
|
192
|
+
const sd = firstDefined(
|
|
193
|
+
{ value: pickBool(resolvedFile, "sessionDigest"), source: "file" },
|
|
194
|
+
{
|
|
195
|
+
value: pickBool(resolvedPlugin, "sessionDigest"),
|
|
196
|
+
source: "pluginConfig",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
value: envBool("NMEM_SESSION_DIGEST", "NMEM_AUTO_CAPTURE"),
|
|
200
|
+
source: "env",
|
|
201
|
+
},
|
|
202
|
+
{ value: true, source: "default" },
|
|
203
|
+
);
|
|
204
|
+
const sessionDigest = sd.value;
|
|
205
|
+
_sources.sessionDigest = sd.source;
|
|
236
206
|
|
|
237
|
-
// ---
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
207
|
+
// --- digestMinInterval: file > pluginConfig > env > default ---
|
|
208
|
+
const dmiEnv =
|
|
209
|
+
envInt("NMEM_DIGEST_MIN_INTERVAL") ?? envInt("NMEM_CAPTURE_MIN_INTERVAL");
|
|
210
|
+
const dmi = firstDefined(
|
|
211
|
+
{ value: pickNum(resolvedFile, "digestMinInterval"), source: "file" },
|
|
212
|
+
{
|
|
213
|
+
value: pickNum(resolvedPlugin, "digestMinInterval"),
|
|
214
|
+
source: "pluginConfig",
|
|
215
|
+
},
|
|
216
|
+
{ value: dmiEnv, source: "env" },
|
|
217
|
+
{ value: 300, source: "default" },
|
|
218
|
+
);
|
|
219
|
+
const digestMinInterval = Math.min(86400, Math.max(0, Math.trunc(dmi.value)));
|
|
220
|
+
_sources.digestMinInterval = dmi.source;
|
|
247
221
|
|
|
248
|
-
// ---
|
|
249
|
-
const
|
|
250
|
-
(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
222
|
+
// --- maxContextResults: file > pluginConfig > env > default ---
|
|
223
|
+
const mcrEnv =
|
|
224
|
+
envInt("NMEM_MAX_CONTEXT_RESULTS") ?? envInt("NMEM_MAX_RECALL_RESULTS");
|
|
225
|
+
const mcr = firstDefined(
|
|
226
|
+
{ value: pickNum(resolvedFile, "maxContextResults"), source: "file" },
|
|
227
|
+
{
|
|
228
|
+
value: pickNum(resolvedPlugin, "maxContextResults"),
|
|
229
|
+
source: "pluginConfig",
|
|
230
|
+
},
|
|
231
|
+
{ value: mcrEnv, source: "env" },
|
|
232
|
+
{ value: 5, source: "default" },
|
|
233
|
+
);
|
|
234
|
+
const maxContextResults = Math.min(20, Math.max(1, Math.trunc(mcr.value)));
|
|
235
|
+
_sources.maxContextResults = mcr.source;
|
|
236
|
+
|
|
237
|
+
// --- apiUrl: file > pluginConfig > env > "" ---
|
|
238
|
+
const fileUrl =
|
|
239
|
+
typeof resolvedFile.apiUrl === "string" && resolvedFile.apiUrl.trim();
|
|
240
|
+
const pluginUrl =
|
|
241
|
+
typeof resolvedPlugin.apiUrl === "string" && resolvedPlugin.apiUrl.trim();
|
|
242
|
+
const au = firstDefined(
|
|
243
|
+
{ value: fileUrl || undefined, source: "file" },
|
|
244
|
+
{ value: pluginUrl || undefined, source: "pluginConfig" },
|
|
245
|
+
{ value: envStr("NMEM_API_URL"), source: "env" },
|
|
246
|
+
{ value: "", source: "default" },
|
|
247
|
+
);
|
|
248
|
+
const apiUrl = au.value;
|
|
249
|
+
_sources.apiUrl = au.source;
|
|
254
250
|
|
|
255
|
-
// --- apiKey:
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
251
|
+
// --- apiKey: file > pluginConfig > env > "" ---
|
|
252
|
+
const fileKey =
|
|
253
|
+
typeof resolvedFile.apiKey === "string" && resolvedFile.apiKey.trim();
|
|
254
|
+
const pluginKey =
|
|
255
|
+
typeof resolvedPlugin.apiKey === "string" && resolvedPlugin.apiKey.trim();
|
|
256
|
+
const ak = firstDefined(
|
|
257
|
+
{ value: fileKey || undefined, source: "file" },
|
|
258
|
+
{ value: pluginKey || undefined, source: "pluginConfig" },
|
|
259
|
+
{ value: envStr("NMEM_API_KEY"), source: "env" },
|
|
260
|
+
{ value: "", source: "default" },
|
|
261
|
+
);
|
|
262
|
+
const apiKey = ak.value;
|
|
263
|
+
_sources.apiKey = ak.source;
|
|
261
264
|
|
|
262
265
|
return {
|
|
263
266
|
sessionContext,
|
|
@@ -266,5 +269,6 @@ export function parseConfig(raw, logger) {
|
|
|
266
269
|
maxContextResults,
|
|
267
270
|
apiUrl,
|
|
268
271
|
apiKey,
|
|
272
|
+
_sources,
|
|
269
273
|
};
|
|
270
274
|
}
|
package/src/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { createForgetTool } from "./tools/forget.js";
|
|
|
18
18
|
import { createMemoryGetTool } from "./tools/memory-get.js";
|
|
19
19
|
import { createMemorySearchTool } from "./tools/memory-search.js";
|
|
20
20
|
import { createSaveTool } from "./tools/save.js";
|
|
21
|
+
import { createStatusTool } from "./tools/status.js";
|
|
21
22
|
import { createThreadFetchTool } from "./tools/thread-fetch.js";
|
|
22
23
|
import { createThreadSearchTool } from "./tools/thread-search.js";
|
|
23
24
|
import { createTimelineTool } from "./tools/timeline.js";
|
|
@@ -52,6 +53,9 @@ export default {
|
|
|
52
53
|
api.registerTool(createThreadSearchTool(client, logger));
|
|
53
54
|
api.registerTool(createThreadFetchTool(client, logger));
|
|
54
55
|
|
|
56
|
+
// Diagnostics
|
|
57
|
+
api.registerTool(createStatusTool(client, logger, cfg));
|
|
58
|
+
|
|
55
59
|
// Always-on: behavioral guidance so the agent proactively saves and searches.
|
|
56
60
|
// Fires every turn via before_prompt_build — ~50 tokens, negligible cost.
|
|
57
61
|
// When sessionContext is on, guidance adjusts to avoid redundant searches.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nowledge_mem_status - verify config, connectivity, and backend health.
|
|
3
|
+
*
|
|
4
|
+
* Lets the user (or agent) confirm that apiUrl, apiKey, and other
|
|
5
|
+
* settings are taking effect, and that the backend is reachable.
|
|
6
|
+
* Shows where each config value comes from (file, pluginConfig, env, default).
|
|
7
|
+
*/
|
|
8
|
+
import { isDefaultApiUrl } from "../config.js";
|
|
9
|
+
|
|
10
|
+
export function createStatusTool(client, _logger, cfg) {
|
|
11
|
+
return {
|
|
12
|
+
name: "nowledge_mem_status",
|
|
13
|
+
description:
|
|
14
|
+
"Check Nowledge Mem status: effective configuration, backend connectivity, and version. " +
|
|
15
|
+
"Call this when the user wants to verify their setup is working, " +
|
|
16
|
+
"especially after changing apiUrl or apiKey for remote mode.",
|
|
17
|
+
parameters: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {},
|
|
20
|
+
},
|
|
21
|
+
async execute() {
|
|
22
|
+
const lines = [];
|
|
23
|
+
const details = {};
|
|
24
|
+
const sources = cfg._sources || {};
|
|
25
|
+
|
|
26
|
+
// 1. Mode + connection target
|
|
27
|
+
const remote = !isDefaultApiUrl(cfg.apiUrl);
|
|
28
|
+
const mode = remote ? "remote" : "local";
|
|
29
|
+
details.mode = mode;
|
|
30
|
+
details.apiUrl = cfg.apiUrl || "http://127.0.0.1:14242";
|
|
31
|
+
details.apiKeySet = Boolean(cfg.apiKey);
|
|
32
|
+
|
|
33
|
+
lines.push(
|
|
34
|
+
`Mode: ${remote ? `Remote (${cfg.apiUrl})` : "Local (127.0.0.1:14242)"}`,
|
|
35
|
+
);
|
|
36
|
+
lines.push(`API key: ${cfg.apiKey ? "set" : "not set"}`);
|
|
37
|
+
|
|
38
|
+
// 2. CLI resolution
|
|
39
|
+
try {
|
|
40
|
+
const cmd = client.resolveCommand();
|
|
41
|
+
details.cli = cmd.join(" ");
|
|
42
|
+
lines.push(`CLI: ${cmd.join(" ")}`);
|
|
43
|
+
} catch {
|
|
44
|
+
details.cli = null;
|
|
45
|
+
lines.push("CLI: not found (install with: pip install nmem-cli)");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Backend health
|
|
49
|
+
let healthy = false;
|
|
50
|
+
try {
|
|
51
|
+
healthy = await client.checkHealth();
|
|
52
|
+
} catch {
|
|
53
|
+
// checkHealth already returns false on error
|
|
54
|
+
}
|
|
55
|
+
details.healthy = healthy;
|
|
56
|
+
|
|
57
|
+
if (healthy) {
|
|
58
|
+
lines.push("Backend: reachable");
|
|
59
|
+
// Fetch detailed health info
|
|
60
|
+
try {
|
|
61
|
+
const health = await client.apiJson(
|
|
62
|
+
"GET",
|
|
63
|
+
"/health",
|
|
64
|
+
undefined,
|
|
65
|
+
5000,
|
|
66
|
+
);
|
|
67
|
+
if (health.version) {
|
|
68
|
+
details.version = health.version;
|
|
69
|
+
lines.push(`Version: ${health.version}`);
|
|
70
|
+
}
|
|
71
|
+
if (health.database_connected !== undefined) {
|
|
72
|
+
details.databaseConnected = health.database_connected;
|
|
73
|
+
lines.push(
|
|
74
|
+
`Database: ${health.database_connected ? "connected" : "disconnected"}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
// Health endpoint not available on older backends
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
lines.push("Backend: not reachable");
|
|
82
|
+
if (remote) {
|
|
83
|
+
lines.push(
|
|
84
|
+
" Check that apiUrl is correct and the server is running.",
|
|
85
|
+
);
|
|
86
|
+
if (!cfg.apiKey) {
|
|
87
|
+
lines.push(" Remote mode usually requires an API key.");
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
lines.push(" Ensure the Nowledge Mem desktop app is running.");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 4. Effective config with sources
|
|
95
|
+
lines.push("");
|
|
96
|
+
lines.push("Config (source):");
|
|
97
|
+
lines.push(
|
|
98
|
+
` sessionContext: ${cfg.sessionContext} (${sources.sessionContext || "?"})`,
|
|
99
|
+
);
|
|
100
|
+
lines.push(
|
|
101
|
+
` sessionDigest: ${cfg.sessionDigest} (${sources.sessionDigest || "?"})`,
|
|
102
|
+
);
|
|
103
|
+
lines.push(
|
|
104
|
+
` digestMinInterval: ${cfg.digestMinInterval}s (${sources.digestMinInterval || "?"})`,
|
|
105
|
+
);
|
|
106
|
+
lines.push(
|
|
107
|
+
` maxContextResults: ${cfg.maxContextResults} (${sources.maxContextResults || "?"})`,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
details.config = {
|
|
111
|
+
sessionContext: {
|
|
112
|
+
value: cfg.sessionContext,
|
|
113
|
+
source: sources.sessionContext,
|
|
114
|
+
},
|
|
115
|
+
sessionDigest: {
|
|
116
|
+
value: cfg.sessionDigest,
|
|
117
|
+
source: sources.sessionDigest,
|
|
118
|
+
},
|
|
119
|
+
digestMinInterval: {
|
|
120
|
+
value: cfg.digestMinInterval,
|
|
121
|
+
source: sources.digestMinInterval,
|
|
122
|
+
},
|
|
123
|
+
maxContextResults: {
|
|
124
|
+
value: cfg.maxContextResults,
|
|
125
|
+
source: sources.maxContextResults,
|
|
126
|
+
},
|
|
127
|
+
apiUrl: { value: cfg.apiUrl || "(local)", source: sources.apiUrl },
|
|
128
|
+
apiKey: {
|
|
129
|
+
value: cfg.apiKey ? "(set)" : "(not set)",
|
|
130
|
+
source: sources.apiKey,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
lines.push("");
|
|
135
|
+
lines.push(
|
|
136
|
+
"Priority: config file > OpenClaw settings > env vars > defaults",
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
141
|
+
details,
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|