@gamaze/hicortex 0.16.7 → 0.16.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/README.md +7 -28
- package/dist/classify-domains.d.ts +12 -13
- package/dist/classify-domains.js +12 -40
- package/dist/consolidate.d.ts +5 -5
- package/dist/consolidate.js +3 -4
- package/dist/distiller.d.ts +12 -4
- package/dist/distiller.js +20 -15
- package/dist/domain-classify.d.ts +7 -7
- package/dist/domain-classify.js +7 -7
- package/dist/init.d.ts +9 -10
- package/dist/init.js +11 -14
- package/dist/llm.d.ts +35 -147
- package/dist/llm.js +63 -379
- package/dist/mcp-server.js +12 -52
- package/dist/nightly-status.js +1 -2
- package/dist/nightly.js +9 -48
- package/dist/types.d.ts +23 -57
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -76,8 +76,8 @@ const init_js_1 = require("./init.js");
|
|
|
76
76
|
// ---------------------------------------------------------------------------
|
|
77
77
|
let db = null;
|
|
78
78
|
let llm = null;
|
|
79
|
-
// llmConfig is module-level so the /distill handler can
|
|
80
|
-
//
|
|
79
|
+
// llmConfig is module-level so the /distill handler can read numCtx without
|
|
80
|
+
// re-resolving on every request. null when no LLM is configured.
|
|
81
81
|
let llmConfig = null;
|
|
82
82
|
// One-time-per-process deprecation warning for the `?privacy=` query param
|
|
83
83
|
// (0.16.x: the column is vestigial, never filtered). Old clients/plugins still
|
|
@@ -94,9 +94,6 @@ function warnDeprecatedPrivacyParamIfPresent(query, route) {
|
|
|
94
94
|
`privacy is no longer filtered server-side (the column is vestigial). ` +
|
|
95
95
|
`Use a separate Hicortex server for isolation. (This warning fires once per process.)`);
|
|
96
96
|
}
|
|
97
|
-
// distillFallbackMode controls whether a failed remote distill endpoint causes an
|
|
98
|
-
// immediate abort ("strict", default) or a fallback to the base model ("local").
|
|
99
|
-
let distillFallbackMode = "strict";
|
|
100
97
|
let stateDir = "";
|
|
101
98
|
// Resolved contextClients list (spec §2) — the harness names allowed to inject
|
|
102
99
|
// the standing context layer. Echoed by GET /context so each hook self-gates.
|
|
@@ -374,7 +371,8 @@ async function startServer(options = {}) {
|
|
|
374
371
|
// Named backends (claude-cli, ollama) → immediate config; everything else
|
|
375
372
|
// goes through resolveExplicitLlmConfig which requires a user-chosen provider.
|
|
376
373
|
// If nothing is configured: start recall-only with an unmissable warning.
|
|
377
|
-
|
|
374
|
+
// One model serves all phases (#231) — no per-tier overlay here.
|
|
375
|
+
const savedConfig = readConfigFile(stateDir);
|
|
378
376
|
// 0.16.2 activation gap: self-heal the agentId provenance field for
|
|
379
377
|
// pre-0.16.2 server installs on first boot after upgrade. The server's own
|
|
380
378
|
// nightly captures its sessions to localhost:8787/distill and needs this id;
|
|
@@ -402,7 +400,6 @@ async function startServer(options = {}) {
|
|
|
402
400
|
baseUrl: savedConfig.llmBaseUrl ?? "http://localhost:11434",
|
|
403
401
|
apiKey: "",
|
|
404
402
|
model: savedConfig.llmModel ?? "qwen3.5:4b",
|
|
405
|
-
reflectModel: savedConfig.reflectModel ?? savedConfig.llmModel ?? "qwen3.5:4b",
|
|
406
403
|
provider: "ollama",
|
|
407
404
|
};
|
|
408
405
|
}
|
|
@@ -411,41 +408,15 @@ async function startServer(options = {}) {
|
|
|
411
408
|
llmBaseUrl: savedConfig?.llmBaseUrl,
|
|
412
409
|
llmApiKey: savedConfig?.llmApiKey,
|
|
413
410
|
llmModel: savedConfig?.llmModel,
|
|
414
|
-
reflectModel: savedConfig?.reflectModel,
|
|
415
411
|
});
|
|
416
412
|
}
|
|
417
413
|
if (llmConfig) {
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
if (savedConfig?.distillBaseUrl) {
|
|
423
|
-
llmConfig.distillBaseUrl = savedConfig.distillBaseUrl;
|
|
424
|
-
llmConfig.distillApiKey = savedConfig.distillApiKey ?? llmConfig.apiKey;
|
|
425
|
-
llmConfig.distillProvider = savedConfig.distillProvider ?? llmConfig.provider;
|
|
426
|
-
}
|
|
427
|
-
// Heavy-phase tuning (#220): maxTokens + enableThinking, validated + copied
|
|
428
|
-
// via the shared overlay (also applied in resolveSavedLlmConfig for the
|
|
429
|
-
// nightly's reflect + classify). Wrong-typed values warn + drop.
|
|
414
|
+
// Tuning (#220: maxTokens + enableThinking + numCtx + flush), validated +
|
|
415
|
+
// copied via the shared overlay (also applied in resolveSavedLlmConfig for
|
|
416
|
+
// the nightly). Wrong-typed values warn + drop.
|
|
430
417
|
(0, llm_js_1.applyTierTuningOverlay)(llmConfig, savedConfig);
|
|
431
|
-
// Apply separate reflect endpoint if configured (e.g. remote Ollama with larger model)
|
|
432
|
-
if (savedConfig?.reflectBaseUrl) {
|
|
433
|
-
llmConfig.reflectBaseUrl = savedConfig.reflectBaseUrl;
|
|
434
|
-
llmConfig.reflectApiKey = savedConfig.reflectApiKey ?? llmConfig.apiKey;
|
|
435
|
-
llmConfig.reflectProvider = savedConfig.reflectProvider ?? llmConfig.provider;
|
|
436
|
-
}
|
|
437
|
-
// distillFallback: "strict" (default) aborts on remote failure so the session
|
|
438
|
-
// is retried next run. "local" restores 0.9.0 fallback-to-base-model behavior.
|
|
439
|
-
const df = savedConfig?.distillFallback;
|
|
440
|
-
distillFallbackMode = df === "local" ? "local" : "strict";
|
|
441
418
|
llm = new llm_js_1.LlmClient(llmConfig);
|
|
442
|
-
|
|
443
|
-
? `${llmConfig.distillProvider}/${llmConfig.distillModel}@${llmConfig.distillBaseUrl}`
|
|
444
|
-
: llmConfig.distillModel ? llmConfig.distillModel : "";
|
|
445
|
-
const reflectInfo = llmConfig.reflectBaseUrl
|
|
446
|
-
? `${llmConfig.reflectProvider}/${llmConfig.reflectModel}@${llmConfig.reflectBaseUrl}`
|
|
447
|
-
: llmConfig.reflectModel;
|
|
448
|
-
console.log(`[hicortex] LLM fast: ${llmConfig.provider}/${llmConfig.model}${distillInfo ? `, distill: ${distillInfo}` : ""}, reflect: ${reflectInfo}`);
|
|
419
|
+
console.log(`[hicortex] LLM (one model, all phases): ${llmConfig.provider}/${llmConfig.model}`);
|
|
449
420
|
}
|
|
450
421
|
else {
|
|
451
422
|
llm = null;
|
|
@@ -884,23 +855,12 @@ async function startServer(options = {}) {
|
|
|
884
855
|
return;
|
|
885
856
|
}
|
|
886
857
|
}
|
|
887
|
-
// Pre-flight the distill endpoint. In strict mode (default) a failed remote
|
|
888
|
-
// probe returns "abort" immediately without mutating llmConfig — the nightly
|
|
889
|
-
// watermark stays put so the session is re-shipped next run. In "local" mode
|
|
890
|
-
// the config is mutated to fall back to the base model.
|
|
891
|
-
const cfg = llmConfig;
|
|
892
|
-
const distillFallbackStatus = await (0, llm_js_1.resolveDistillFallback)(cfg, distillFallbackMode);
|
|
893
|
-
if (distillFallbackStatus === "abort") {
|
|
894
|
-
res.status(503).json({ error: "Distill endpoint unavailable — session will be retried next run" });
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
858
|
// Cache detectChunkSize per endpoint so we probe at most once per server boot.
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
const
|
|
901
|
-
const cacheKey = `${effectiveProvider}/${effectiveModel}@${effectiveBaseUrl}`;
|
|
859
|
+
// numCtx is passed so chunk size derives from the request's ACTUAL context
|
|
860
|
+
// window (#231, #228) — the chunker and the request agree by construction.
|
|
861
|
+
const cacheKey = `${llmConfig.provider}/${llmConfig.model}@${llmConfig.baseUrl}`;
|
|
902
862
|
if (!chunkSizeCache.has(cacheKey)) {
|
|
903
|
-
chunkSizeCache.set(cacheKey, await (0, distiller_js_1.detectChunkSize)(
|
|
863
|
+
chunkSizeCache.set(cacheKey, await (0, distiller_js_1.detectChunkSize)(llmConfig.provider, llmConfig.model, llmConfig.baseUrl, llmConfig.numCtx));
|
|
904
864
|
}
|
|
905
865
|
const chunkSize = chunkSizeCache.get(cacheKey);
|
|
906
866
|
const date = typeof session_date === "string" && session_date ? session_date : new Date().toISOString().slice(0, 10);
|
package/dist/nightly-status.js
CHANGED
|
@@ -18,7 +18,6 @@ const node_os_1 = require("node:os");
|
|
|
18
18
|
const node_child_process_1 = require("node:child_process");
|
|
19
19
|
const db_js_1 = require("./db.js");
|
|
20
20
|
const state_js_1 = require("./state.js");
|
|
21
|
-
const llm_js_1 = require("./llm.js");
|
|
22
21
|
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
23
22
|
const CONFIG_PATH = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
|
|
24
23
|
async function showNightlyStatus() {
|
|
@@ -39,7 +38,7 @@ async function showNightlyStatus() {
|
|
|
39
38
|
try {
|
|
40
39
|
// No `?? {}` coercion: a null/invalid parse must fall through to the catch
|
|
41
40
|
// below (as it did pre-0.13.1) rather than print a fabricated-healthy status.
|
|
42
|
-
const config =
|
|
41
|
+
const config = JSON.parse((0, node_fs_1.readFileSync)(CONFIG_PATH, "utf-8"));
|
|
43
42
|
const backend = config.llmBackend ?? "auto-detect";
|
|
44
43
|
const model = config.llmModel ?? "default";
|
|
45
44
|
const mode = config.mode === "client" ? "client → " + (config.serverUrl ?? "?") : "server (local)";
|
package/dist/nightly.js
CHANGED
|
@@ -371,57 +371,18 @@ async function runNightly(options = {}) {
|
|
|
371
371
|
console.error("[hicortex] consolidation skipped: no LLM configured — run npx @gamaze/hicortex init");
|
|
372
372
|
}
|
|
373
373
|
else {
|
|
374
|
-
//
|
|
375
|
-
// If
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const health = await (0, llm_js_1.probeOllamaModel)(llmConfig.reflectBaseUrl, reflectModel);
|
|
382
|
-
if (!health.ok) {
|
|
383
|
-
const reason = health.reason === "unreachable"
|
|
384
|
-
? `reflect endpoint unreachable (${llmConfig.reflectBaseUrl})`
|
|
385
|
-
: `reflect model not loaded (${reflectModel} missing on ${llmConfig.reflectBaseUrl})`;
|
|
386
|
-
console.warn(`[hicortex] ${reason} — skipping reflection, scoring + linking will still run`);
|
|
387
|
-
skipReflection = true;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
// Content-based domain classification (config-owned `domains`) uses
|
|
391
|
-
// the classify tier (classifyModel/classifyBaseUrl) when configured,
|
|
392
|
-
// else the reflect tier. Pre-flight the endpoint classification will
|
|
393
|
-
// ACTUALLY use (resolveClassifyProbeTarget is the shared source of
|
|
394
|
-
// truth with `hicortex classify-domains`). If it is down, content
|
|
395
|
-
// classification is NOT ready this run (strict — skip, don't fall
|
|
396
|
-
// back). When no `domains` list is configured, this is inert and the
|
|
397
|
-
// legacy project-grouping path runs.
|
|
374
|
+
// One model serves all phases (#231) — there is no separate endpoint to
|
|
375
|
+
// pre-flight. If the model doesn't answer, `complete()` already retries at
|
|
376
|
+
// 30s/60s/120s (~3.5 min); anything still failing after that is an outage,
|
|
377
|
+
// not a blip. A failed phase costs latency, not data: capture cursors hold
|
|
378
|
+
// on failure (dup-over-loss), and consolidation has resumable cursors
|
|
379
|
+
// (domainCursor, supersessionCursor). The nightly runs 2-4×/day, so the
|
|
380
|
+
// wait is hours — no polling, no new config. (Issue #231.)
|
|
398
381
|
const cfgDomains = (0, domain_classify_js_1.parseConfigDomains)(savedConfig);
|
|
399
|
-
let contentDomainsReady = true;
|
|
400
|
-
if (cfgDomains) {
|
|
401
|
-
const classifyTarget = (0, llm_js_1.resolveClassifyProbeTarget)(llmConfig);
|
|
402
|
-
if (classifyTarget?.tier === "reflect") {
|
|
403
|
-
// Classification rides the reflect endpoint — reuse the probe above.
|
|
404
|
-
contentDomainsReady = !skipReflection;
|
|
405
|
-
}
|
|
406
|
-
else if (classifyTarget) {
|
|
407
|
-
const health = await (0, llm_js_1.probeOllamaModel)(classifyTarget.baseUrl, classifyTarget.model);
|
|
408
|
-
if (!health.ok) {
|
|
409
|
-
const reason = health.reason === "unreachable"
|
|
410
|
-
? `classify endpoint unreachable (${classifyTarget.baseUrl})`
|
|
411
|
-
: `classify model not loaded (${classifyTarget.model} missing on ${classifyTarget.baseUrl})`;
|
|
412
|
-
console.warn(`[hicortex] ${reason}`);
|
|
413
|
-
contentDomainsReady = false;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
// classifyTarget === null → base endpoint or API provider, no probe.
|
|
417
|
-
if (!contentDomainsReady) {
|
|
418
|
-
console.warn("[hicortex] content-domain classification skipped — classification endpoint offline (strict)");
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
382
|
console.log(`[hicortex] Running consolidation...`);
|
|
422
|
-
const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun,
|
|
383
|
+
const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun, false, undefined, {
|
|
423
384
|
domains: cfgDomains,
|
|
424
|
-
contentDomainsReady,
|
|
385
|
+
contentDomainsReady: true,
|
|
425
386
|
weakPrimaryFloor: (0, nofit_js_1.resolveWeakPrimaryFloor)(savedConfig),
|
|
426
387
|
}, {
|
|
427
388
|
minSimilarity: savedConfig?.supersessionMinSimilarity,
|
package/dist/types.d.ts
CHANGED
|
@@ -154,16 +154,6 @@ export interface ConsolidationReport {
|
|
|
154
154
|
calls_by_stage: Record<string, number>;
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
|
-
/**
|
|
158
|
-
* A single per-stage override inside the nested `models` server-config block.
|
|
159
|
-
* Consumed by `applyModelsBlock` (llm.ts), which re-exports this type.
|
|
160
|
-
*/
|
|
161
|
-
export interface ModelTierOverride {
|
|
162
|
-
model?: string;
|
|
163
|
-
baseUrl?: string;
|
|
164
|
-
apiKey?: string;
|
|
165
|
-
provider?: string;
|
|
166
|
-
}
|
|
167
157
|
/** Plugin configuration from openclaw.plugin.json configSchema. */
|
|
168
158
|
export interface HicortexConfig {
|
|
169
159
|
licenseKey?: string;
|
|
@@ -186,33 +176,6 @@ export interface HicortexConfig {
|
|
|
186
176
|
llmApiKey?: string;
|
|
187
177
|
/** @deprecated Use the Hicortex server for distillation and consolidation. */
|
|
188
178
|
llmModel?: string;
|
|
189
|
-
/** @deprecated Use the Hicortex server for distillation and consolidation. */
|
|
190
|
-
reflectModel?: string;
|
|
191
|
-
/**
|
|
192
|
-
* Optional dedicated model for memory tag classification (server-side
|
|
193
|
-
* nightly + `hicortex classify-domains`). When unset, classification uses
|
|
194
|
-
* the reflect tier exactly as before.
|
|
195
|
-
*/
|
|
196
|
-
classifyModel?: string;
|
|
197
|
-
/**
|
|
198
|
-
* Optional dedicated endpoint for classification. When only classifyModel
|
|
199
|
-
* is set, it runs on the reflect endpoint (else the base endpoint).
|
|
200
|
-
*/
|
|
201
|
-
classifyBaseUrl?: string;
|
|
202
|
-
/** Optional API key for the classify endpoint (defaults to the base apiKey). */
|
|
203
|
-
classifyApiKey?: string;
|
|
204
|
-
/** Optional provider for the classify endpoint (defaults to the base provider). */
|
|
205
|
-
classifyProvider?: string;
|
|
206
|
-
/**
|
|
207
|
-
* Server config (NOT an OC-plugin key): nested per-stage model overrides.
|
|
208
|
-
* `{ score|distill|reflect|classify: { model?, baseUrl?, apiKey?, provider? } }`.
|
|
209
|
-
* Normalized onto the flat `llm*` / `distill*` / `reflect*` / `classify*`
|
|
210
|
-
* keys at read time (see applyModelsBlock in llm.ts); nested wins, and the
|
|
211
|
-
* flat keys remain supported at lower precedence. Happy path is a single model via
|
|
212
|
-
* `llmModel`; use this block only for per-stage routing. `score.provider` is
|
|
213
|
-
* ignored (base provider comes from llmBackend).
|
|
214
|
-
*/
|
|
215
|
-
models?: Record<string, ModelTierOverride>;
|
|
216
179
|
/** @deprecated Consolidation is owned by the server nightly. */
|
|
217
180
|
consolidateHour?: number;
|
|
218
181
|
/** @deprecated The OC plugin no longer opens its own database. */
|
|
@@ -283,34 +246,37 @@ export interface HicortexConfig {
|
|
|
283
246
|
*/
|
|
284
247
|
preflightRetryGapMs?: number;
|
|
285
248
|
/**
|
|
286
|
-
* Max output tokens for the LLM phases — distillation,
|
|
287
|
-
* and scoring
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
* at the model's natural end (finish_reason stop), so a higher cap costs no latency
|
|
249
|
+
* Max output tokens for the ONE LLM model used by all phases — distillation,
|
|
250
|
+
* reflection, classification, and scoring. Default 8192. An explicit value
|
|
251
|
+
* overrides the default. A ceiling, not a target: generation stops at the
|
|
252
|
+
* model's natural end (finish_reason stop), so a higher cap costs no latency
|
|
291
253
|
* when it finishes early. Read in llm.ts; see #220.
|
|
292
254
|
*/
|
|
293
255
|
maxTokens?: number;
|
|
294
256
|
/**
|
|
295
257
|
* Toggle the model's internal reasoning ("thinking") stream on the openai-compat
|
|
296
|
-
* path — applies to
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
258
|
+
* path — applies to ALL phases (distill / reflect / classify / scoring) since one
|
|
259
|
+
* model serves all of them. Default false. A thinking model with thinking ON can
|
|
260
|
+
* burn the entire token budget on an unclosed <think> block and emit nothing
|
|
261
|
+
* (probed 2026-08-04). When set (true or false), completeOpenAiCompat sends
|
|
262
|
+
* chat_template_kwargs:{enable_thinking}. LOCAL-ENDPOINT ONLY: this is meaningful
|
|
263
|
+
* only for a chat-template-aware server (ollama, mlx-lm). If the one model is a
|
|
264
|
+
* cloud OpenAI-compatible endpoint (OpenAI / OpenRouter / Groq / z.ai), LEAVE THIS
|
|
265
|
+
* UNSET — the non-standard chat_template_kwargs field rides every call and can 400
|
|
266
|
+
* the whole pipeline (provider cannot distinguish MLX-gateway-as-openai from real
|
|
267
|
+
* cloud openai, so the gate must be operator-set, not detected). No effect on the
|
|
268
|
+
* anthropic or claude-cli paths. See #220, #231.
|
|
304
269
|
*/
|
|
305
270
|
enableThinking?: boolean;
|
|
306
271
|
/**
|
|
307
|
-
* Context window for the
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
272
|
+
* Context window for ollama (the one model, all phases). Default 8192 — the point
|
|
273
|
+
* where context stops being the binding constraint for a sub-8B model on ollama
|
|
274
|
+
* (above it the SMALL_MODEL_MAX_CHUNK_CHARS speed cap binds instead, so extra
|
|
275
|
+
* context buys nothing). Also drives `detectChunkSize`'s chunk sizing
|
|
276
|
+
* (chunkChars ≤ numCtx × 0.6 × 4 chars), so numCtx is the single dial and the
|
|
277
|
+
* chunker/request agreement is enforced by construction (#228). For an ≥8B model
|
|
278
|
+
* on ollama the speed cap is 60,000 chars, needing numCtx ≈ 25000 to reach — raise
|
|
279
|
+
* it if running 8B+ locally. No effect for non-ollama providers.
|
|
314
280
|
*/
|
|
315
281
|
numCtx?: number;
|
|
316
282
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.8",
|
|
4
4
|
"description": "Self-learning memory for AI agents — experience captured automatically, distilled into lessons overnight, shared across your whole fleet. Works with Hermes, OpenClaw, Claude Code, and Pi.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|