@nowledge/openclaw-nowledge-mem 0.6.13 → 0.6.15
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 +16 -0
- package/CLAUDE.md +20 -6
- package/README.md +16 -11
- package/openclaw.plugin.json +12 -1
- package/package.json +1 -1
- package/src/client.js +1 -1
- package/src/config.js +75 -9
- package/src/hooks/capture.js +17 -7
- package/src/tools/status.js +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Nowledge Mem OpenClaw plugin will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.15] - 2026-03-18
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **Shared config for remote credentials.** The plugin now reads `apiUrl` and `apiKey` from `~/.nowledge-mem/config.json` — the same file used by nmem CLI, Bub, Claude Code, and other integrations. One config file connects all your tools. The legacy `~/.nowledge-mem/openclaw.json` is still honored at highest priority for backward compatibility, but is no longer the recommended path. New cascade for credentials: `openclaw.json` (legacy) > OpenClaw dashboard > `config.json` (shared) > env vars > defaults.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Trailing slash in API URL no longer causes 404.** URLs like `https://mem.example.com/` (with trailing slash) produced double-slash paths in API fallback requests. The URL is now normalized at construction time.
|
|
14
|
+
|
|
15
|
+
## [0.6.14] - 2026-03-17
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **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.
|
|
20
|
+
|
|
5
21
|
## [0.6.13] - 2026-03-17
|
|
6
22
|
|
|
7
23
|
### Fixed
|
package/CLAUDE.md
CHANGED
|
@@ -9,7 +9,7 @@ Continuation guide for `community/nowledge-mem-openclaw-plugin`.
|
|
|
9
9
|
- Memory backend: `nmem` CLI (fallback: `uvx --from nmem-cli nmem`)
|
|
10
10
|
- OpenClaw minimum: `2026.3.7` (`appendSystemContext` / system-context guidance required)
|
|
11
11
|
- Architecture: **CLI-first via OpenClaw runtime** - all CLI execution goes through `api.runtime.system.runCommandWithTimeout`, not direct `child_process`
|
|
12
|
-
- Remote mode:
|
|
12
|
+
- Remote mode: `~/.nowledge-mem/config.json` (shared) or OpenClaw dashboard. Legacy `openclaw.json` still honored.
|
|
13
13
|
|
|
14
14
|
## Design Philosophy
|
|
15
15
|
|
|
@@ -30,7 +30,7 @@ src/
|
|
|
30
30
|
index.js - plugin registration (tools, hooks, commands, CLI)
|
|
31
31
|
client.js - CLI wrapper with API fallback; async runtime command execution; credential handling
|
|
32
32
|
spawn-env.js - env-only credential injection for the nmem runner
|
|
33
|
-
config.js - config cascade:
|
|
33
|
+
config.js - config cascade: openclaw.json (legacy) > pluginConfig > config.json (credentials) > env > defaults
|
|
34
34
|
hooks/
|
|
35
35
|
behavioral.js - always-on behavioral guidance (~50 tokens/turn)
|
|
36
36
|
recall.js - before_prompt_build: inject Working Memory + recalled memories
|
|
@@ -49,7 +49,8 @@ skills/
|
|
|
49
49
|
memory-guide/
|
|
50
50
|
SKILL.md - agent behavioral skill: when/how to search, save, explore memory (auto-discovered by OpenClaw)
|
|
51
51
|
openclaw.plugin.json - manifest + config schema (version, uiHints, configSchema, skills)
|
|
52
|
-
~/.nowledge-mem/openclaw.json -
|
|
52
|
+
~/.nowledge-mem/openclaw.json - legacy config file (still honored, deprecated in docs)
|
|
53
|
+
~/.nowledge-mem/config.json - shared credentials (apiUrl/apiKey) read by all Nowledge Mem tools
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
## Tool Surface (10 tools)
|
|
@@ -92,7 +93,8 @@ openclaw.plugin.json - manifest + config schema (version, uiHints, configSchema,
|
|
|
92
93
|
|
|
93
94
|
## Config Keys
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
Plugin settings: OpenClaw dashboard (pluginConfig). Legacy `~/.nowledge-mem/openclaw.json` still honored.
|
|
97
|
+
Credentials (apiUrl/apiKey): also reads `~/.nowledge-mem/config.json` (shared with all Nowledge Mem tools).
|
|
96
98
|
|
|
97
99
|
| Key | Type | Default | Env Var | Description |
|
|
98
100
|
|-----|------|---------|---------|-------------|
|
|
@@ -101,12 +103,14 @@ Optional config file at `~/.nowledge-mem/openclaw.json`. Falls through to OpenCl
|
|
|
101
103
|
| `digestMinInterval` | integer 0-86400 | `300` | `NMEM_DIGEST_MIN_INTERVAL` | Minimum seconds between session digests |
|
|
102
104
|
| `maxContextResults` | integer 1-20 | `5` | `NMEM_MAX_CONTEXT_RESULTS` | How many memories to inject at prompt time |
|
|
103
105
|
| `recallMinScore` | integer 0-100 | `0` | `NMEM_RECALL_MIN_SCORE` | Min relevance score (%) to include in auto-recall |
|
|
106
|
+
| `maxThreadMessageChars` | integer 200-20000 | `800` | `NMEM_MAX_THREAD_MESSAGE_CHARS` | Max chars per captured thread message before truncation |
|
|
104
107
|
| `apiUrl` | string | `""` | `NMEM_API_URL` | Remote server URL. Empty = local (127.0.0.1:14242) |
|
|
105
108
|
| `apiKey` | string | `""` | `NMEM_API_KEY` | API key. Never logged. |
|
|
106
109
|
|
|
107
|
-
**Priority
|
|
110
|
+
**Priority** (plugin-specific keys): `openclaw.json` (legacy) > pluginConfig > env var > default.
|
|
111
|
+
**Priority** (apiUrl/apiKey): `openclaw.json` (legacy) > pluginConfig > `config.json` (shared) > env var > default.
|
|
108
112
|
|
|
109
|
-
`parseConfig()` returns `_sources` object tracking where each value came from (`"file"`, `"pluginConfig"`, `"env"`, `"default"`). Used by `nowledge_mem_status` tool.
|
|
113
|
+
`parseConfig()` returns `_sources` object tracking where each value came from (`"file"`, `"pluginConfig"`, `"sharedConfig"`, `"env"`, `"default"`). Used by `nowledge_mem_status` tool.
|
|
110
114
|
|
|
111
115
|
Legacy aliases accepted silently in all sources for backward compat:
|
|
112
116
|
`autoRecall`→`sessionContext`, `autoCapture`→`sessionDigest`, `captureMinInterval`→`digestMinInterval`, `maxRecallResults`→`maxContextResults`.
|
|
@@ -156,6 +160,16 @@ nmem wm patch --heading "## Notes" --append "New note from agent"
|
|
|
156
160
|
NMEM_API_URL=https://your-server NMEM_API_KEY=key nmem status
|
|
157
161
|
```
|
|
158
162
|
|
|
163
|
+
## Version Bump Checklist
|
|
164
|
+
|
|
165
|
+
All three files must match on every release:
|
|
166
|
+
|
|
167
|
+
1. **`package.json`** — `"version"` (npm registry reads this for `npm publish`)
|
|
168
|
+
2. **`openclaw.plugin.json`** — `"version"` (OpenClaw runtime reads this at load time)
|
|
169
|
+
3. **`CHANGELOG.md`** — new `## [x.y.z] - YYYY-MM-DD` section header
|
|
170
|
+
|
|
171
|
+
After bumping, commit inside the `community/` submodule, then stage the updated submodule ref in the parent repo.
|
|
172
|
+
|
|
159
173
|
## Known Gaps / Accepted Limitations
|
|
160
174
|
|
|
161
175
|
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,26 +368,22 @@ 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
|
|
|
374
|
-
###
|
|
375
|
+
### Remote access
|
|
375
376
|
|
|
376
|
-
|
|
377
|
+
Create `~/.nowledge-mem/config.json` with your credentials. This file is shared by all Nowledge Mem integrations (nmem CLI, Bub, Claude Code, etc.) so one file connects everything:
|
|
377
378
|
|
|
378
379
|
```json
|
|
379
380
|
{
|
|
380
|
-
"
|
|
381
|
-
"
|
|
382
|
-
"digestMinInterval": 300,
|
|
383
|
-
"maxContextResults": 5,
|
|
384
|
-
"recallMinScore": 0,
|
|
385
|
-
"apiUrl": "",
|
|
386
|
-
"apiKey": ""
|
|
381
|
+
"apiUrl": "https://<your-url>",
|
|
382
|
+
"apiKey": "nmem_..."
|
|
387
383
|
}
|
|
388
384
|
```
|
|
389
385
|
|
|
390
|
-
|
|
386
|
+
See [Access Mem Anywhere](https://mem.nowledge.co/docs/remote-access).
|
|
391
387
|
|
|
392
388
|
### Advanced: environment variables
|
|
393
389
|
|
|
@@ -399,16 +395,25 @@ NMEM_SESSION_DIGEST=true
|
|
|
399
395
|
NMEM_DIGEST_MIN_INTERVAL=300
|
|
400
396
|
NMEM_MAX_CONTEXT_RESULTS=5
|
|
401
397
|
NMEM_RECALL_MIN_SCORE=0
|
|
398
|
+
NMEM_MAX_THREAD_MESSAGE_CHARS=800
|
|
402
399
|
NMEM_API_URL=https://...
|
|
403
400
|
NMEM_API_KEY=your-key
|
|
404
401
|
```
|
|
405
402
|
|
|
406
403
|
### Priority
|
|
407
404
|
|
|
405
|
+
Plugin-specific settings:
|
|
408
406
|
```
|
|
409
|
-
|
|
407
|
+
openclaw.json (legacy) > OpenClaw dashboard > env vars > defaults
|
|
410
408
|
```
|
|
411
409
|
|
|
410
|
+
Credentials (apiUrl, apiKey):
|
|
411
|
+
```
|
|
412
|
+
openclaw.json (legacy) > OpenClaw dashboard > config.json (shared) > env vars > defaults
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
`~/.nowledge-mem/openclaw.json` is still honored for backward compatibility but is no longer the recommended path. New users should configure plugin-specific settings via the OpenClaw dashboard and shared credentials via `~/.nowledge-mem/config.json`.
|
|
416
|
+
|
|
412
417
|
Use `nowledge_mem_status` (or `openclaw nowledge-mem status`) to see where each value comes from.
|
|
413
418
|
|
|
414
419
|
### Trust warning after install
|
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.15",
|
|
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
package/src/client.js
CHANGED
|
@@ -83,7 +83,7 @@ export class NowledgeMemClient {
|
|
|
83
83
|
this.nmemCmdPromise = null;
|
|
84
84
|
// Resolved once from config + env (config wins over env, both win over default)
|
|
85
85
|
this._apiUrl =
|
|
86
|
-
(credentials.apiUrl || "").trim() || "http://127.0.0.1:14242";
|
|
86
|
+
((credentials.apiUrl || "").trim() || "http://127.0.0.1:14242").replace(/\/+$/, "");
|
|
87
87
|
this._apiKey = (credentials.apiKey || "").trim();
|
|
88
88
|
}
|
|
89
89
|
|
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
|
|
@@ -38,13 +39,20 @@ const ALIAS_KEYS = {
|
|
|
38
39
|
maxRecallResults: "maxContextResults",
|
|
39
40
|
};
|
|
40
41
|
|
|
41
|
-
// --- config
|
|
42
|
+
// --- config files --------------------------------------------------------
|
|
42
43
|
|
|
43
44
|
const CONFIG_DIR = join(homedir(), ".nowledge-mem");
|
|
45
|
+
|
|
46
|
+
// Legacy dedicated config (deprecated — still honored for backward compat)
|
|
44
47
|
const CONFIG_PATH = join(CONFIG_DIR, "openclaw.json");
|
|
45
48
|
|
|
49
|
+
// Shared config read by all Nowledge Mem integrations (nmem CLI, Bub, Claude Code, etc.)
|
|
50
|
+
const SHARED_CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
51
|
+
|
|
46
52
|
/**
|
|
47
53
|
* Read ~/.nowledge-mem/openclaw.json if it exists.
|
|
54
|
+
* Deprecated: new users should use the OpenClaw dashboard for plugin settings
|
|
55
|
+
* and ~/.nowledge-mem/config.json for shared credentials (apiUrl, apiKey).
|
|
48
56
|
* Returns {} when the file is missing or unreadable.
|
|
49
57
|
*/
|
|
50
58
|
function readConfigFile(logger) {
|
|
@@ -63,8 +71,37 @@ function readConfigFile(logger) {
|
|
|
63
71
|
}
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Read apiUrl and apiKey from the shared ~/.nowledge-mem/config.json.
|
|
76
|
+
* Only credentials are extracted — plugin-specific keys are ignored.
|
|
77
|
+
* Returns { apiUrl?, apiKey? } or {} when the file is missing or unreadable.
|
|
78
|
+
*/
|
|
79
|
+
function readSharedConfig(logger) {
|
|
80
|
+
try {
|
|
81
|
+
if (!existsSync(SHARED_CONFIG_PATH)) {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
const content = readFileSync(SHARED_CONFIG_PATH, "utf-8").trim();
|
|
85
|
+
if (!content) return {};
|
|
86
|
+
const parsed = JSON.parse(content);
|
|
87
|
+
const result = {};
|
|
88
|
+
if (typeof parsed.apiUrl === "string" && parsed.apiUrl.trim()) {
|
|
89
|
+
result.apiUrl = parsed.apiUrl.trim();
|
|
90
|
+
}
|
|
91
|
+
if (typeof parsed.apiKey === "string" && parsed.apiKey.trim()) {
|
|
92
|
+
result.apiKey = parsed.apiKey.trim();
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
} catch (err) {
|
|
96
|
+
logger?.warn?.(
|
|
97
|
+
`nowledge-mem: could not read ${SHARED_CONFIG_PATH}: ${err instanceof Error ? err.message : err}`,
|
|
98
|
+
);
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
66
103
|
/** Exported for tests / CLI tooling. */
|
|
67
|
-
export { CONFIG_PATH };
|
|
104
|
+
export { CONFIG_PATH, SHARED_CONFIG_PATH };
|
|
68
105
|
|
|
69
106
|
// --- env var helpers -----------------------------------------------------
|
|
70
107
|
|
|
@@ -129,14 +166,21 @@ function firstDefined(...options) {
|
|
|
129
166
|
|
|
130
167
|
/**
|
|
131
168
|
* Parse plugin config with cascade:
|
|
132
|
-
* ~/.nowledge-mem/openclaw.json > pluginConfig > env vars > defaults
|
|
133
169
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
170
|
+
* Plugin-specific keys (sessionContext, sessionDigest, etc.):
|
|
171
|
+
* openclaw.json (legacy) > pluginConfig (dashboard) > env vars > defaults
|
|
172
|
+
*
|
|
173
|
+
* Credentials (apiUrl, apiKey):
|
|
174
|
+
* openclaw.json (legacy) > pluginConfig (dashboard) > config.json (shared) > env vars > defaults
|
|
175
|
+
*
|
|
176
|
+
* Most users configure via the OpenClaw dashboard (pluginConfig) and
|
|
177
|
+
* ~/.nowledge-mem/config.json for shared credentials across all tools.
|
|
178
|
+
*
|
|
179
|
+
* ~/.nowledge-mem/openclaw.json is still honored for backward compat
|
|
180
|
+
* but is no longer the recommended path.
|
|
137
181
|
*
|
|
138
182
|
* Canonical keys: sessionContext, sessionDigest, digestMinInterval,
|
|
139
|
-
* maxContextResults, recallMinScore, apiUrl, apiKey
|
|
183
|
+
* maxContextResults, recallMinScore, maxThreadMessageChars, apiUrl, apiKey
|
|
140
184
|
*
|
|
141
185
|
* Legacy aliases (accepted from all sources; never shown in docs):
|
|
142
186
|
* autoRecall → sessionContext
|
|
@@ -150,12 +194,14 @@ function firstDefined(...options) {
|
|
|
150
194
|
* NMEM_DIGEST_MIN_INTERVAL — seconds (0-86400)
|
|
151
195
|
* NMEM_MAX_CONTEXT_RESULTS — integer (1-20)
|
|
152
196
|
* NMEM_RECALL_MIN_SCORE — integer (0-100)
|
|
197
|
+
* NMEM_MAX_THREAD_MESSAGE_CHARS — integer (200-20000)
|
|
153
198
|
* NMEM_API_URL — remote server URL
|
|
154
199
|
* NMEM_API_KEY — API key (never logged)
|
|
155
200
|
*/
|
|
156
201
|
export function parseConfig(raw, logger) {
|
|
157
202
|
const pluginCfg = raw && typeof raw === "object" ? raw : {};
|
|
158
203
|
const fileCfg = readConfigFile(logger);
|
|
204
|
+
const sharedCfg = readSharedConfig(logger);
|
|
159
205
|
|
|
160
206
|
// Resolve aliases in both sources
|
|
161
207
|
const resolvedPlugin = resolveAliases(pluginCfg);
|
|
@@ -250,7 +296,24 @@ export function parseConfig(raw, logger) {
|
|
|
250
296
|
const recallMinScore = Math.min(100, Math.max(0, Math.trunc(rms.value)));
|
|
251
297
|
_sources.recallMinScore = rms.source;
|
|
252
298
|
|
|
253
|
-
// ---
|
|
299
|
+
// --- maxThreadMessageChars: file > pluginConfig > env > default ---
|
|
300
|
+
const mtmcEnv = envInt("NMEM_MAX_THREAD_MESSAGE_CHARS");
|
|
301
|
+
const mtmc = firstDefined(
|
|
302
|
+
{ value: pickNum(resolvedFile, "maxThreadMessageChars"), source: "file" },
|
|
303
|
+
{
|
|
304
|
+
value: pickNum(resolvedPlugin, "maxThreadMessageChars"),
|
|
305
|
+
source: "pluginConfig",
|
|
306
|
+
},
|
|
307
|
+
{ value: mtmcEnv, source: "env" },
|
|
308
|
+
{ value: 800, source: "default" },
|
|
309
|
+
);
|
|
310
|
+
const maxThreadMessageChars = Math.min(
|
|
311
|
+
20000,
|
|
312
|
+
Math.max(200, Math.trunc(mtmc.value)),
|
|
313
|
+
);
|
|
314
|
+
_sources.maxThreadMessageChars = mtmc.source;
|
|
315
|
+
|
|
316
|
+
// --- apiUrl: file (legacy) > pluginConfig > sharedConfig > env > "" ---
|
|
254
317
|
const fileUrl =
|
|
255
318
|
typeof resolvedFile.apiUrl === "string" && resolvedFile.apiUrl.trim();
|
|
256
319
|
const pluginUrl =
|
|
@@ -258,13 +321,14 @@ export function parseConfig(raw, logger) {
|
|
|
258
321
|
const au = firstDefined(
|
|
259
322
|
{ value: fileUrl || undefined, source: "file" },
|
|
260
323
|
{ value: pluginUrl || undefined, source: "pluginConfig" },
|
|
324
|
+
{ value: sharedCfg.apiUrl || undefined, source: "sharedConfig" },
|
|
261
325
|
{ value: envStr("NMEM_API_URL"), source: "env" },
|
|
262
326
|
{ value: "", source: "default" },
|
|
263
327
|
);
|
|
264
328
|
const apiUrl = au.value;
|
|
265
329
|
_sources.apiUrl = au.source;
|
|
266
330
|
|
|
267
|
-
// --- apiKey: file > pluginConfig > env > "" ---
|
|
331
|
+
// --- apiKey: file (legacy) > pluginConfig > sharedConfig > env > "" ---
|
|
268
332
|
const fileKey =
|
|
269
333
|
typeof resolvedFile.apiKey === "string" && resolvedFile.apiKey.trim();
|
|
270
334
|
const pluginKey =
|
|
@@ -272,6 +336,7 @@ export function parseConfig(raw, logger) {
|
|
|
272
336
|
const ak = firstDefined(
|
|
273
337
|
{ value: fileKey || undefined, source: "file" },
|
|
274
338
|
{ value: pluginKey || undefined, source: "pluginConfig" },
|
|
339
|
+
{ value: sharedCfg.apiKey || undefined, source: "sharedConfig" },
|
|
275
340
|
{ value: envStr("NMEM_API_KEY"), source: "env" },
|
|
276
341
|
{ value: "", source: "default" },
|
|
277
342
|
);
|
|
@@ -284,6 +349,7 @@ export function parseConfig(raw, logger) {
|
|
|
284
349
|
digestMinInterval,
|
|
285
350
|
maxContextResults,
|
|
286
351
|
recallMinScore,
|
|
352
|
+
maxThreadMessageChars,
|
|
287
353
|
apiUrl,
|
|
288
354
|
apiKey,
|
|
289
355
|
_sources,
|
package/src/hooks/capture.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
|
|
4
|
-
const
|
|
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 =
|
|
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
|
|
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({
|
|
382
|
+
await appendOrCreateThread({
|
|
383
|
+
client,
|
|
384
|
+
logger,
|
|
385
|
+
event,
|
|
386
|
+
ctx,
|
|
387
|
+
reason,
|
|
388
|
+
maxMessageChars: _cfg?.maxThreadMessageChars,
|
|
389
|
+
});
|
|
380
390
|
};
|
|
381
391
|
}
|
package/src/tools/status.js
CHANGED
|
@@ -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)",
|