@nowledge/openclaw-nowledge-mem 0.6.5 → 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 +104 -53
- package/CLAUDE.md +72 -60
- package/README.md +92 -55
- package/openclaw.plugin.json +8 -9
- package/package.json +1 -1
- package/src/config.js +220 -37
- package/src/hooks/capture.js +1 -1
- package/src/index.js +5 -1
- package/src/tools/status.js +145 -0
package/src/config.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
1
5
|
export const API_DEFAULT_URL = "http://127.0.0.1:14242";
|
|
2
6
|
|
|
3
7
|
/**
|
|
@@ -17,6 +21,11 @@ const ALLOWED_KEYS = new Set([
|
|
|
17
21
|
"maxContextResults",
|
|
18
22
|
"apiUrl",
|
|
19
23
|
"apiKey",
|
|
24
|
+
// Legacy aliases — accepted but not advertised
|
|
25
|
+
"autoRecall",
|
|
26
|
+
"autoCapture",
|
|
27
|
+
"captureMinInterval",
|
|
28
|
+
"maxRecallResults",
|
|
20
29
|
]);
|
|
21
30
|
|
|
22
31
|
// Backward-compatible aliases (old names → new names).
|
|
@@ -28,11 +37,69 @@ const ALIAS_KEYS = {
|
|
|
28
37
|
maxRecallResults: "maxContextResults",
|
|
29
38
|
};
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
// --- config file ---------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
const CONFIG_DIR = join(homedir(), ".nowledge-mem");
|
|
43
|
+
const CONFIG_PATH = join(CONFIG_DIR, "openclaw.json");
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Read ~/.nowledge-mem/openclaw.json if it exists.
|
|
47
|
+
* Returns {} when the file is missing or unreadable.
|
|
48
|
+
*/
|
|
49
|
+
function readConfigFile(logger) {
|
|
50
|
+
try {
|
|
51
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
const content = readFileSync(CONFIG_PATH, "utf-8").trim();
|
|
55
|
+
if (!content) return {};
|
|
56
|
+
return JSON.parse(content);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
logger?.warn?.(
|
|
59
|
+
`nowledge-mem: could not read ${CONFIG_PATH}: ${err instanceof Error ? err.message : err}`,
|
|
60
|
+
);
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Exported for tests / CLI tooling. */
|
|
66
|
+
export { CONFIG_PATH };
|
|
67
|
+
|
|
68
|
+
// --- env var helpers -----------------------------------------------------
|
|
69
|
+
|
|
70
|
+
function envStr(name) {
|
|
71
|
+
const v = process.env[name];
|
|
72
|
+
return typeof v === "string" ? v.trim() : undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function envBool(name, fallbackName) {
|
|
76
|
+
const v = envStr(name) ?? envStr(fallbackName);
|
|
77
|
+
if (v === undefined) return undefined;
|
|
78
|
+
return v === "true" || v === "1" || v === "yes";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function envInt(name) {
|
|
82
|
+
const v = envStr(name);
|
|
83
|
+
if (v === undefined) return undefined;
|
|
84
|
+
const n = Number.parseInt(v, 10);
|
|
85
|
+
return Number.isFinite(n) ? n : undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// --- helpers to read a boolean/number from resolved sources --------------
|
|
89
|
+
|
|
90
|
+
function pickBool(obj, key) {
|
|
91
|
+
if (typeof obj[key] === "boolean") return obj[key];
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function pickNum(obj, key) {
|
|
96
|
+
if (typeof obj[key] === "number" && Number.isFinite(obj[key]))
|
|
97
|
+
return obj[key];
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
33
100
|
|
|
34
|
-
|
|
35
|
-
|
|
101
|
+
/** Resolve legacy aliases in an object: old names → new names. */
|
|
102
|
+
function resolveAliases(obj) {
|
|
36
103
|
const resolved = { ...obj };
|
|
37
104
|
for (const [oldKey, newKey] of Object.entries(ALIAS_KEYS)) {
|
|
38
105
|
if (oldKey in resolved) {
|
|
@@ -42,50 +109,166 @@ export function parseConfig(raw) {
|
|
|
42
109
|
delete resolved[oldKey];
|
|
43
110
|
}
|
|
44
111
|
}
|
|
112
|
+
return resolved;
|
|
113
|
+
}
|
|
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
|
+
|
|
127
|
+
// -------------------------------------------------------------------------
|
|
45
128
|
|
|
46
|
-
|
|
47
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Parse plugin config with cascade:
|
|
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).
|
|
136
|
+
*
|
|
137
|
+
* Canonical keys: sessionContext, sessionDigest, digestMinInterval,
|
|
138
|
+
* maxContextResults, apiUrl, apiKey
|
|
139
|
+
*
|
|
140
|
+
* Legacy aliases (accepted from all sources; never shown in docs):
|
|
141
|
+
* autoRecall → sessionContext
|
|
142
|
+
* autoCapture → sessionDigest
|
|
143
|
+
* captureMinInterval → digestMinInterval
|
|
144
|
+
* maxRecallResults → maxContextResults
|
|
145
|
+
*
|
|
146
|
+
* Environment variables (all optional):
|
|
147
|
+
* NMEM_SESSION_CONTEXT — true/1/yes to enable (alias: NMEM_AUTO_RECALL)
|
|
148
|
+
* NMEM_SESSION_DIGEST — true/1/yes to enable (alias: NMEM_AUTO_CAPTURE)
|
|
149
|
+
* NMEM_DIGEST_MIN_INTERVAL — seconds (0-86400)
|
|
150
|
+
* NMEM_MAX_CONTEXT_RESULTS — integer (1-20)
|
|
151
|
+
* NMEM_API_URL — remote server URL
|
|
152
|
+
* NMEM_API_KEY — API key (never logged)
|
|
153
|
+
*/
|
|
154
|
+
export function parseConfig(raw, logger) {
|
|
155
|
+
const pluginCfg = raw && typeof raw === "object" ? raw : {};
|
|
156
|
+
const fileCfg = readConfigFile(logger);
|
|
157
|
+
|
|
158
|
+
// Resolve aliases in both sources
|
|
159
|
+
const resolvedPlugin = resolveAliases(pluginCfg);
|
|
160
|
+
const resolvedFile = resolveAliases(fileCfg);
|
|
161
|
+
|
|
162
|
+
// Strict: reject unknown keys in pluginConfig (typo catcher).
|
|
163
|
+
const unknownKeys = Object.keys(resolvedPlugin).filter(
|
|
164
|
+
(k) => !ALLOWED_KEYS.has(k),
|
|
165
|
+
);
|
|
48
166
|
if (unknownKeys.length > 0) {
|
|
49
167
|
throw new Error(
|
|
50
168
|
`nowledge-mem: unknown config key${unknownKeys.length > 1 ? "s" : ""}: ${unknownKeys.join(", ")}. ` +
|
|
51
|
-
`Allowed keys: ${[...ALLOWED_KEYS].join(", ")}`,
|
|
169
|
+
`Allowed keys: ${[...ALLOWED_KEYS].filter((k) => !Object.keys(ALIAS_KEYS).includes(k)).join(", ")}`,
|
|
52
170
|
);
|
|
53
171
|
}
|
|
54
172
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
173
|
+
const _sources = {};
|
|
174
|
+
|
|
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;
|
|
190
|
+
|
|
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;
|
|
206
|
+
|
|
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;
|
|
221
|
+
|
|
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;
|
|
61
250
|
|
|
62
|
-
// apiKey:
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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;
|
|
68
264
|
|
|
69
265
|
return {
|
|
70
|
-
sessionContext
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
sessionDigest:
|
|
75
|
-
typeof resolved.sessionDigest === "boolean"
|
|
76
|
-
? resolved.sessionDigest
|
|
77
|
-
: true,
|
|
78
|
-
digestMinInterval:
|
|
79
|
-
typeof resolved.digestMinInterval === "number" &&
|
|
80
|
-
Number.isFinite(resolved.digestMinInterval)
|
|
81
|
-
? Math.min(86400, Math.max(0, Math.trunc(resolved.digestMinInterval)))
|
|
82
|
-
: 300,
|
|
83
|
-
maxContextResults:
|
|
84
|
-
typeof resolved.maxContextResults === "number" &&
|
|
85
|
-
Number.isFinite(resolved.maxContextResults)
|
|
86
|
-
? Math.min(20, Math.max(1, Math.trunc(resolved.maxContextResults)))
|
|
87
|
-
: 5,
|
|
266
|
+
sessionContext,
|
|
267
|
+
sessionDigest,
|
|
268
|
+
digestMinInterval,
|
|
269
|
+
maxContextResults,
|
|
88
270
|
apiUrl,
|
|
89
271
|
apiKey,
|
|
272
|
+
_sources,
|
|
90
273
|
};
|
|
91
274
|
}
|
package/src/hooks/capture.js
CHANGED
|
@@ -291,7 +291,7 @@ export function buildAgentEndCaptureHandler(client, cfg, logger) {
|
|
|
291
291
|
});
|
|
292
292
|
|
|
293
293
|
// 2. Triage + distill: language-agnostic LLM-based capture.
|
|
294
|
-
// Defensive guard
|
|
294
|
+
// Defensive guard - registration in index.js already gates on sessionDigest,
|
|
295
295
|
// but check here too so the handler is safe if called directly.
|
|
296
296
|
if (!cfg.sessionDigest) return;
|
|
297
297
|
|
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";
|
|
@@ -30,8 +31,8 @@ export default {
|
|
|
30
31
|
kind: "memory",
|
|
31
32
|
|
|
32
33
|
register(api) {
|
|
33
|
-
const cfg = parseConfig(api.pluginConfig);
|
|
34
34
|
const logger = api.logger;
|
|
35
|
+
const cfg = parseConfig(api.pluginConfig, logger);
|
|
35
36
|
const client = new NowledgeMemClient(logger, {
|
|
36
37
|
apiUrl: cfg.apiUrl,
|
|
37
38
|
apiKey: cfg.apiKey,
|
|
@@ -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
|
+
}
|