@psiclawops/hypermem 0.8.5 → 0.9.0
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 -0
- package/INSTALL.md +132 -9
- package/README.md +119 -272
- package/bench/README.md +42 -0
- package/bench/data-access-bench.mjs +380 -0
- package/bin/hypermem-bench.mjs +2 -0
- package/bin/hypermem-doctor.mjs +412 -0
- package/bin/hypermem-model-audit.mjs +339 -0
- package/bin/hypermem-status.mjs +491 -70
- package/dist/adaptive-lifecycle.d.ts +81 -0
- package/dist/adaptive-lifecycle.d.ts.map +1 -0
- package/dist/adaptive-lifecycle.js +190 -0
- package/dist/budget-policy.d.ts +1 -1
- package/dist/budget-policy.d.ts.map +1 -1
- package/dist/budget-policy.js +10 -5
- package/dist/cache.d.ts +1 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +2 -0
- package/dist/composition-snapshot-integrity.d.ts +36 -0
- package/dist/composition-snapshot-integrity.d.ts.map +1 -0
- package/dist/composition-snapshot-integrity.js +131 -0
- package/dist/composition-snapshot-runtime.d.ts +59 -0
- package/dist/composition-snapshot-runtime.d.ts.map +1 -0
- package/dist/composition-snapshot-runtime.js +250 -0
- package/dist/composition-snapshot-store.d.ts +44 -0
- package/dist/composition-snapshot-store.d.ts.map +1 -0
- package/dist/composition-snapshot-store.js +117 -0
- package/dist/compositor.d.ts +125 -1
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +692 -44
- package/dist/doc-chunk-store.d.ts +19 -0
- package/dist/doc-chunk-store.d.ts.map +1 -1
- package/dist/doc-chunk-store.js +56 -6
- package/dist/hybrid-retrieval.d.ts +38 -0
- package/dist/hybrid-retrieval.d.ts.map +1 -1
- package/dist/hybrid-retrieval.js +86 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -2
- package/dist/knowledge-store.d.ts +4 -1
- package/dist/knowledge-store.d.ts.map +1 -1
- package/dist/knowledge-store.js +27 -4
- package/dist/library-schema.d.ts +12 -8
- package/dist/library-schema.d.ts.map +1 -1
- package/dist/library-schema.js +22 -8
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +7 -3
- package/dist/metrics-dashboard.d.ts +18 -1
- package/dist/metrics-dashboard.d.ts.map +1 -1
- package/dist/metrics-dashboard.js +52 -14
- package/dist/reranker.d.ts +1 -1
- package/dist/reranker.js +2 -2
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +28 -1
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +2 -0
- package/dist/topic-synthesizer.d.ts +20 -0
- package/dist/topic-synthesizer.d.ts.map +1 -1
- package/dist/topic-synthesizer.js +113 -3
- package/dist/trigger-registry.d.ts.map +1 -1
- package/dist/trigger-registry.js +10 -2
- package/dist/types.d.ts +271 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +7 -7
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +17 -7
- package/docs/DIAGNOSTICS.md +205 -0
- package/docs/INTEGRATION_VALIDATION.md +186 -0
- package/docs/MIGRATION.md +9 -6
- package/docs/MIGRATION_GUIDE.md +125 -101
- package/docs/ROADMAP.md +238 -20
- package/docs/TUNING.md +19 -5
- package/install.sh +152 -401
- package/memory-plugin/LICENSE +190 -0
- package/memory-plugin/README.md +20 -0
- package/memory-plugin/dist/index.js +50 -0
- package/memory-plugin/package.json +2 -2
- package/package.json +18 -4
- package/plugin/LICENSE +190 -0
- package/plugin/README.md +20 -0
- package/plugin/dist/index.d.ts +29 -0
- package/plugin/dist/index.d.ts.map +1 -1
- package/plugin/dist/index.js +288 -23
- package/plugin/dist/index.js.map +1 -1
- package/plugin/package.json +2 -2
- package/scripts/install-runtime.mjs +12 -1
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* hypermem-doctor — installed-system validator for HyperMem + OpenClaw.
|
|
4
|
+
*
|
|
5
|
+
* This tool is intentionally read-only. It reports required failures,
|
|
6
|
+
* recommended settings, and exact config commands operators can review.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { spawnSync } from 'node:child_process';
|
|
13
|
+
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
|
|
16
|
+
function usage() {
|
|
17
|
+
console.log(`
|
|
18
|
+
hypermem doctor — validate a HyperMem/OpenClaw installation
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
hypermem-doctor [options]
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--openclaw-config <path> OpenClaw config to inspect
|
|
25
|
+
default: ~/.openclaw/openclaw.json
|
|
26
|
+
--hypermem-config <path> HyperMem config to inspect
|
|
27
|
+
default: ~/.openclaw/hypermem/config.json
|
|
28
|
+
--data-dir <path> HyperMem data dir
|
|
29
|
+
default: config value or ~/.openclaw/hypermem
|
|
30
|
+
--json Output machine-readable JSON
|
|
31
|
+
--fix-plan Print exact read-only remediation commands
|
|
32
|
+
--strict Treat recommendation warnings as failures
|
|
33
|
+
--skip-runtime Do not call openclaw plugins list
|
|
34
|
+
-h, --help Show this help
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
hypermem-doctor
|
|
38
|
+
hypermem-doctor --fix-plan
|
|
39
|
+
hypermem-doctor --json --strict
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
44
|
+
usage();
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getArg(flag) {
|
|
49
|
+
const idx = args.indexOf(flag);
|
|
50
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const home = os.homedir();
|
|
54
|
+
const flags = {
|
|
55
|
+
json: args.includes('--json'),
|
|
56
|
+
fixPlan: args.includes('--fix-plan'),
|
|
57
|
+
strict: args.includes('--strict'),
|
|
58
|
+
skipRuntime: args.includes('--skip-runtime'),
|
|
59
|
+
openclawConfig: path.resolve(getArg('--openclaw-config') || process.env.OPENCLAW_CONFIG || path.join(home, '.openclaw', 'openclaw.json')),
|
|
60
|
+
hypermemConfig: path.resolve(getArg('--hypermem-config') || path.join(home, '.openclaw', 'hypermem', 'config.json')),
|
|
61
|
+
dataDir: getArg('--data-dir') ? path.resolve(getArg('--data-dir')) : null,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function readJson(filePath) {
|
|
65
|
+
if (!existsSync(filePath)) return { exists: false, value: null, error: null };
|
|
66
|
+
try {
|
|
67
|
+
return { exists: true, value: JSON.parse(readFileSync(filePath, 'utf8')), error: null };
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return { exists: true, value: null, error: err.message };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const openclawRead = readJson(flags.openclawConfig);
|
|
74
|
+
const hypermemRead = readJson(flags.hypermemConfig);
|
|
75
|
+
const openclaw = openclawRead.value ?? {};
|
|
76
|
+
const hypermem = hypermemRead.value ?? {};
|
|
77
|
+
|
|
78
|
+
const pluginConfig = openclaw?.plugins?.entries?.hypercompositor?.config
|
|
79
|
+
?? openclaw?.plugins?.entries?.hypermem?.config
|
|
80
|
+
?? {};
|
|
81
|
+
const dataDir = flags.dataDir
|
|
82
|
+
|| process.env.HYPERMEM_DATA_DIR
|
|
83
|
+
|| pluginConfig.dataDir
|
|
84
|
+
|| hypermem.dataDir
|
|
85
|
+
|| path.join(home, '.openclaw', 'hypermem');
|
|
86
|
+
|
|
87
|
+
const checks = [];
|
|
88
|
+
const recommendations = [];
|
|
89
|
+
const commands = [];
|
|
90
|
+
|
|
91
|
+
function add(kind, status, id, message, details = {}) {
|
|
92
|
+
const item = { kind, status, id, message, ...details };
|
|
93
|
+
checks.push(item);
|
|
94
|
+
if (details.command) commands.push(details.command);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function required(status, id, message, details = {}) {
|
|
98
|
+
add('required', status, id, message, details);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function recommended(status, id, message, details = {}) {
|
|
102
|
+
add('recommended', status, id, message, details);
|
|
103
|
+
if (status !== 'ok') recommendations.push({ id, message, ...details });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function get(obj, dotted) {
|
|
107
|
+
return dotted.split('.').reduce((acc, key) => (acc && typeof acc === 'object' ? acc[key] : undefined), obj);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isPlainObject(value) {
|
|
111
|
+
return value && typeof value === 'object' && !Array.isArray(value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function arrayIncludesLike(values, needle) {
|
|
115
|
+
if (!Array.isArray(values)) return false;
|
|
116
|
+
return values.some(value => String(value).includes(needle));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function setCommand(pathKey, value, strictJson = false) {
|
|
120
|
+
const rendered = typeof value === 'string' && !strictJson
|
|
121
|
+
? value
|
|
122
|
+
: JSON.stringify(value);
|
|
123
|
+
return `openclaw config set ${pathKey} ${shellQuote(rendered)}${strictJson ? ' --strict-json' : ''}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function shellQuote(value) {
|
|
127
|
+
if (/^[A-Za-z0-9_./:@=-]+$/.test(value)) return value;
|
|
128
|
+
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function statExists(filePath) {
|
|
132
|
+
try {
|
|
133
|
+
return existsSync(filePath) ? statSync(filePath) : null;
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function findMessageDb(dir) {
|
|
140
|
+
const agentsDir = path.join(dir, 'agents');
|
|
141
|
+
if (!existsSync(agentsDir)) return null;
|
|
142
|
+
try {
|
|
143
|
+
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
144
|
+
if (!entry.isDirectory()) continue;
|
|
145
|
+
const candidate = path.join(agentsDir, entry.name, 'messages.db');
|
|
146
|
+
if (existsSync(candidate)) return candidate;
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function checkConfigReadable() {
|
|
155
|
+
if (!openclawRead.exists) {
|
|
156
|
+
required('fail', 'openclaw-config-present', `OpenClaw config not found: ${flags.openclawConfig}`);
|
|
157
|
+
} else if (openclawRead.error) {
|
|
158
|
+
required('fail', 'openclaw-config-json', `OpenClaw config is not valid JSON: ${openclawRead.error}`);
|
|
159
|
+
} else {
|
|
160
|
+
required('ok', 'openclaw-config-json', `OpenClaw config readable: ${flags.openclawConfig}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (hypermemRead.exists && hypermemRead.error) {
|
|
164
|
+
required('fail', 'hypermem-config-json', `HyperMem config is not valid JSON: ${hypermemRead.error}`);
|
|
165
|
+
} else if (hypermemRead.exists) {
|
|
166
|
+
required('ok', 'hypermem-config-json', `HyperMem config readable: ${flags.hypermemConfig}`);
|
|
167
|
+
} else {
|
|
168
|
+
recommended('warn', 'hypermem-config-present', `No legacy HyperMem config found at ${flags.hypermemConfig}; ok if config lives in openclaw.json`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function checkPluginWiring() {
|
|
173
|
+
const loadPaths = get(openclaw, 'plugins.load.paths') ?? get(openclaw, 'plugins.paths') ?? [];
|
|
174
|
+
const contextEngine = get(openclaw, 'plugins.slots.contextEngine');
|
|
175
|
+
const memorySlot = get(openclaw, 'plugins.slots.memory');
|
|
176
|
+
const allow = get(openclaw, 'plugins.allow');
|
|
177
|
+
|
|
178
|
+
required(contextEngine === 'hypercompositor' ? 'ok' : 'fail',
|
|
179
|
+
'context-engine-slot',
|
|
180
|
+
contextEngine === 'hypercompositor'
|
|
181
|
+
? 'Context engine slot points to hypercompositor'
|
|
182
|
+
: `Context engine slot is ${JSON.stringify(contextEngine)}; expected hypercompositor`,
|
|
183
|
+
{ command: contextEngine === 'hypercompositor' ? undefined : setCommand('plugins.slots.contextEngine', 'hypercompositor') });
|
|
184
|
+
|
|
185
|
+
required(memorySlot === 'hypermem' ? 'ok' : 'fail',
|
|
186
|
+
'memory-slot',
|
|
187
|
+
memorySlot === 'hypermem'
|
|
188
|
+
? 'Memory slot points to hypermem'
|
|
189
|
+
: `Memory slot is ${JSON.stringify(memorySlot)}; expected hypermem`,
|
|
190
|
+
{ command: memorySlot === 'hypermem' ? undefined : setCommand('plugins.slots.memory', 'hypermem') });
|
|
191
|
+
|
|
192
|
+
required(arrayIncludesLike(loadPaths, 'hypermem/plugin') || arrayIncludesLike(loadPaths, 'hypercompositor') ? 'ok' : 'fail',
|
|
193
|
+
'hypercompositor-path',
|
|
194
|
+
'Plugin load paths include the hypercompositor package path');
|
|
195
|
+
required(arrayIncludesLike(loadPaths, 'hypermem/memory-plugin') || arrayIncludesLike(loadPaths, 'hypermem-memory') ? 'ok' : 'fail',
|
|
196
|
+
'hypermem-memory-path',
|
|
197
|
+
'Plugin load paths include the HyperMem memory package path');
|
|
198
|
+
|
|
199
|
+
if (Array.isArray(allow) && allow.length > 0) {
|
|
200
|
+
required(allow.includes('hypercompositor') && allow.includes('hypermem') ? 'ok' : 'fail',
|
|
201
|
+
'plugins-allow-merged',
|
|
202
|
+
allow.includes('hypercompositor') && allow.includes('hypermem')
|
|
203
|
+
? 'Plugin allowlist includes hypercompositor and hypermem'
|
|
204
|
+
: 'Plugin allowlist exists but does not include both hypercompositor and hypermem; merge them without deleting existing entries');
|
|
205
|
+
} else {
|
|
206
|
+
required('ok', 'plugins-allow-merged', 'Plugin allowlist is unset/empty, so HyperMem is not blocked by allowlist');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function checkRuntimePlugins() {
|
|
211
|
+
if (flags.skipRuntime) {
|
|
212
|
+
recommended('ok', 'runtime-plugin-list', 'Runtime plugin load check skipped');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const result = spawnSync('openclaw', ['plugins', 'list'], { encoding: 'utf8', timeout: 8000 });
|
|
216
|
+
if (result.error) {
|
|
217
|
+
recommended('warn', 'runtime-plugin-list', `Could not run openclaw plugins list: ${result.error.message}`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
221
|
+
const hasComposer = /hypercompositor/.test(output);
|
|
222
|
+
const hasMemory = /\bhypermem\b/.test(output);
|
|
223
|
+
recommended(hasComposer && hasMemory ? 'ok' : 'warn',
|
|
224
|
+
'runtime-plugin-list',
|
|
225
|
+
hasComposer && hasMemory
|
|
226
|
+
? 'Runtime plugin list mentions hypercompositor and hypermem'
|
|
227
|
+
: 'Runtime plugin list did not clearly show both hypercompositor and hypermem; restart gateway after config changes');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function checkOpenClawRecommendations() {
|
|
231
|
+
const expected = [
|
|
232
|
+
['agents.defaults.contextPruning.mode', 'off', 'required', false],
|
|
233
|
+
['agents.defaults.promptOverlays.gpt5.personality', 'off', 'recommended', false],
|
|
234
|
+
['agents.defaults.startupContext.dailyMemoryDays', 4, 'recommended', true],
|
|
235
|
+
['agents.defaults.startupContext.maxFileChars', 4000, 'recommended', true],
|
|
236
|
+
['agents.defaults.startupContext.maxTotalChars', 12000, 'recommended', true],
|
|
237
|
+
['agents.defaults.startupContext.maxFileBytes', 32768, 'recommended', true],
|
|
238
|
+
['agents.defaults.bootstrapMaxChars', 20000, 'recommended', true],
|
|
239
|
+
['agents.defaults.compaction.mode', 'safeguard', 'recommended', false],
|
|
240
|
+
['agents.defaults.compaction.reserveTokens', 16384, 'recommended', true],
|
|
241
|
+
['agents.defaults.compaction.keepRecentTokens', 6000, 'recommended', true],
|
|
242
|
+
['agents.defaults.compaction.reserveTokensFloor', 15000, 'recommended', true],
|
|
243
|
+
['agents.defaults.compaction.maxHistoryShare', 0.65, 'recommended', true],
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
for (const [pathKey, value, level, strictJson] of expected) {
|
|
247
|
+
const actual = get(openclaw, pathKey);
|
|
248
|
+
const ok = actual === value;
|
|
249
|
+
const message = ok
|
|
250
|
+
? `${pathKey} is recommended value ${JSON.stringify(value)}`
|
|
251
|
+
: `${pathKey} is ${JSON.stringify(actual)}; recommended ${JSON.stringify(value)}`;
|
|
252
|
+
const details = ok ? {} : { command: setCommand(pathKey, value, strictJson) };
|
|
253
|
+
if (level === 'required') required(ok ? 'ok' : 'fail', pathKey, message, details);
|
|
254
|
+
else recommended(ok ? 'ok' : 'warn', pathKey, message, details);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const injection = get(openclaw, 'agents.defaults.contextInjection');
|
|
258
|
+
if (injection == null || injection === 'always' || injection === 'continuation-skip') {
|
|
259
|
+
recommended('ok', 'agents.defaults.contextInjection', `Context injection mode is ${JSON.stringify(injection ?? 'default')}`);
|
|
260
|
+
} else {
|
|
261
|
+
recommended('warn', 'agents.defaults.contextInjection', `Unknown context injection mode ${JSON.stringify(injection)}; verify bootstrap file injection still matches OpenClaw defaults`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function checkDataDir() {
|
|
266
|
+
const dirStat = statExists(dataDir);
|
|
267
|
+
required(dirStat?.isDirectory() ? 'ok' : 'fail', 'data-dir', dirStat?.isDirectory() ? `HyperMem data dir exists: ${dataDir}` : `HyperMem data dir missing: ${dataDir}`);
|
|
268
|
+
|
|
269
|
+
for (const [id, file] of [
|
|
270
|
+
['library-db', path.join(dataDir, 'library.db')],
|
|
271
|
+
['vectors-db', path.join(dataDir, 'vectors.db')],
|
|
272
|
+
]) {
|
|
273
|
+
const st = statExists(file);
|
|
274
|
+
required(st?.isFile() ? 'ok' : 'fail', id, st?.isFile() ? `${path.basename(file)} exists` : `${path.basename(file)} missing in ${dataDir}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const messageDb = findMessageDb(dataDir);
|
|
278
|
+
recommended(messageDb ? 'ok' : 'warn', 'messages-db', messageDb ? `Found agent messages DB: ${messageDb}` : 'No agent messages.db found yet; ok before first real agent turn');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const MODEL_CONTEXT_WINDOWS = [
|
|
282
|
+
['claude-opus-4', 200000], ['claude-sonnet-4', 200000], ['claude', 200000],
|
|
283
|
+
['gpt-5', 128000], ['gpt-4o', 128000], ['gpt-4', 128000], ['o3', 128000], ['o4', 128000],
|
|
284
|
+
['gemini-3.1', 1000000], ['gemini-2.5', 1000000], ['gemini', 1000000],
|
|
285
|
+
['glm-5', 131072], ['glm-4', 131072], ['qwen3', 262144], ['qwen', 131072], ['deepseek', 131072],
|
|
286
|
+
];
|
|
287
|
+
const HIGH_RISK_PROVIDERS = ['openai/', 'openai-codex/', 'openrouter/', 'lmstudio/', 'vllm/', 'ollama/', 'litellm/', 'copilot-local/'];
|
|
288
|
+
|
|
289
|
+
function normalizeModel(value) {
|
|
290
|
+
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function addModel(out, value) {
|
|
294
|
+
if (typeof value === 'string') {
|
|
295
|
+
const normalized = normalizeModel(value);
|
|
296
|
+
if (normalized.includes('/')) out.add(normalized);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (Array.isArray(value)) {
|
|
300
|
+
for (const item of value) addModel(out, item);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (!isPlainObject(value)) return;
|
|
304
|
+
addModel(out, value.primary);
|
|
305
|
+
addModel(out, value.model);
|
|
306
|
+
addModel(out, value.id);
|
|
307
|
+
addModel(out, value.name);
|
|
308
|
+
if (Array.isArray(value.fallbacks)) for (const fb of value.fallbacks) addModel(out, fb);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function collectModels() {
|
|
312
|
+
const out = new Set();
|
|
313
|
+
addModel(out, get(openclaw, 'agents.defaults.model'));
|
|
314
|
+
addModel(out, get(openclaw, 'agents.defaults.fallbacks'));
|
|
315
|
+
addModel(out, get(openclaw, 'agents.defaults.heartbeat.model'));
|
|
316
|
+
addModel(out, get(openclaw, 'agents.defaults.subagents.model'));
|
|
317
|
+
if (Array.isArray(openclaw?.agents?.list)) {
|
|
318
|
+
for (const agent of openclaw.agents.list) {
|
|
319
|
+
addModel(out, agent?.model);
|
|
320
|
+
addModel(out, agent?.fallbacks);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return [...out].sort();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function contextOverrides() {
|
|
327
|
+
return pluginConfig?.compositor?.contextWindowOverrides
|
|
328
|
+
?? pluginConfig?.contextWindowOverrides
|
|
329
|
+
?? hypermem?.compositor?.contextWindowOverrides
|
|
330
|
+
?? hypermem?.contextWindowOverrides
|
|
331
|
+
?? {};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function checkModels() {
|
|
335
|
+
const models = collectModels();
|
|
336
|
+
const overrides = contextOverrides();
|
|
337
|
+
if (models.length === 0) {
|
|
338
|
+
recommended('warn', 'model-audit', 'No configured provider/model ids found to audit');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
for (const model of models) {
|
|
343
|
+
const detected = MODEL_CONTEXT_WINDOWS.find(([pattern]) => model.includes(pattern));
|
|
344
|
+
const override = overrides[model];
|
|
345
|
+
const hasCompleteOverride = Number.isInteger(override?.contextTokens) && Number.isInteger(override?.contextWindow);
|
|
346
|
+
const highRisk = HIGH_RISK_PROVIDERS.some(prefix => model.startsWith(prefix));
|
|
347
|
+
|
|
348
|
+
if (!detected && !hasCompleteOverride) {
|
|
349
|
+
recommended('warn', `model-window:${model}`, `${model} has no known context-window pattern and no complete contextWindowOverrides entry`, {
|
|
350
|
+
command: `# Add plugins.entries.hypercompositor.config.contextWindowOverrides[${JSON.stringify(model)}] with contextTokens and contextWindow after provider validation`,
|
|
351
|
+
});
|
|
352
|
+
} else if (highRisk && !hasCompleteOverride) {
|
|
353
|
+
recommended('warn', `model-window:${model}`, `${model} is on an OpenAI-compatible or local gateway path; add explicit contextWindowOverrides unless logs prove runtime tokenBudget is correct`, {
|
|
354
|
+
command: `# Verify logs show: budget source: runtime tokenBudget=... model=${model}`,
|
|
355
|
+
});
|
|
356
|
+
} else if (override && !hasCompleteOverride) {
|
|
357
|
+
recommended('warn', `model-window:${model}`, `${model} has an incomplete contextWindowOverrides entry; set both contextTokens and contextWindow`);
|
|
358
|
+
} else {
|
|
359
|
+
recommended('ok', `model-window:${model}`, `${model} has ${hasCompleteOverride ? 'explicit context-window override' : `known context-window pattern (${detected?.[1]} tokens)`}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
checkConfigReadable();
|
|
365
|
+
if (openclawRead.value) {
|
|
366
|
+
checkPluginWiring();
|
|
367
|
+
checkOpenClawRecommendations();
|
|
368
|
+
checkModels();
|
|
369
|
+
}
|
|
370
|
+
checkDataDir();
|
|
371
|
+
checkRuntimePlugins();
|
|
372
|
+
|
|
373
|
+
const failedRequired = checks.filter(c => c.kind === 'required' && c.status === 'fail');
|
|
374
|
+
const warnings = checks.filter(c => c.status === 'warn');
|
|
375
|
+
const summary = {
|
|
376
|
+
status: failedRequired.length > 0 ? 'fail' : (warnings.length > 0 ? 'warn' : 'ok'),
|
|
377
|
+
strictStatus: failedRequired.length > 0 || (flags.strict && warnings.length > 0) ? 'fail' : 'ok',
|
|
378
|
+
openclawConfig: flags.openclawConfig,
|
|
379
|
+
hypermemConfig: flags.hypermemConfig,
|
|
380
|
+
dataDir,
|
|
381
|
+
counts: {
|
|
382
|
+
ok: checks.filter(c => c.status === 'ok').length,
|
|
383
|
+
warn: warnings.length,
|
|
384
|
+
fail: failedRequired.length,
|
|
385
|
+
},
|
|
386
|
+
checks,
|
|
387
|
+
fixPlan: [...new Set(commands.filter(Boolean))],
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
if (flags.json) {
|
|
391
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
392
|
+
} else {
|
|
393
|
+
const icon = summary.status === 'ok' ? '✅' : summary.status === 'warn' ? '⚠️' : '❌';
|
|
394
|
+
console.log(`${icon} hypermem doctor: ${summary.status.toUpperCase()} (${summary.counts.ok} ok, ${summary.counts.warn} warn, ${summary.counts.fail} fail)`);
|
|
395
|
+
console.log(`OpenClaw config: ${summary.openclawConfig}`);
|
|
396
|
+
console.log(`HyperMem config: ${summary.hypermemConfig}`);
|
|
397
|
+
console.log(`Data dir: ${summary.dataDir}`);
|
|
398
|
+
console.log('');
|
|
399
|
+
|
|
400
|
+
for (const check of checks) {
|
|
401
|
+
const mark = check.status === 'ok' ? '✅' : check.status === 'warn' ? '⚠️' : '❌';
|
|
402
|
+
console.log(`${mark} [${check.kind}] ${check.id}: ${check.message}`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (flags.fixPlan && summary.fixPlan.length > 0) {
|
|
406
|
+
console.log('\nFix plan:');
|
|
407
|
+
for (const command of summary.fixPlan) console.log(` ${command}`);
|
|
408
|
+
console.log(' openclaw gateway restart');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
process.exit(summary.strictStatus === 'fail' ? 1 : 0);
|