@gamaze/hicortex 0.7.1 → 0.10.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/README.md +57 -39
- package/dist/claude-md.d.ts +9 -21
- package/dist/claude-md.js +9 -241
- package/dist/cli.d.ts +3 -2
- package/dist/cli.js +29 -11
- package/dist/consolidate.js +0 -7
- package/dist/db.js +24 -0
- package/dist/embedder.d.ts +11 -0
- package/dist/embedder.js +27 -0
- package/dist/extensions.d.ts +41 -88
- package/dist/extensions.js +36 -61
- package/dist/features.d.ts +21 -25
- package/dist/features.js +47 -83
- package/dist/hermes-transcript-reader.d.ts +27 -0
- package/dist/hermes-transcript-reader.js +134 -0
- package/dist/index.d.ts +16 -4
- package/dist/index.js +252 -344
- package/dist/init.d.ts +41 -1
- package/dist/init.js +545 -190
- package/dist/lesson-selection.d.ts +62 -0
- package/dist/lesson-selection.js +159 -0
- package/dist/lessons-context.d.ts +17 -0
- package/dist/lessons-context.js +96 -0
- package/dist/llm.d.ts +42 -29
- package/dist/llm.js +89 -270
- package/dist/mcp-server.d.ts +0 -1
- package/dist/mcp-server.js +404 -86
- package/dist/nightly.d.ts +9 -6
- package/dist/nightly.js +197 -357
- package/dist/oc-transcript-reader.d.ts +20 -0
- package/dist/oc-transcript-reader.js +61 -0
- package/dist/pi-transcript-reader.d.ts +1 -0
- package/dist/status.js +22 -2
- package/dist/storage.d.ts +7 -1
- package/dist/storage.js +28 -7
- package/dist/transcript-reader.d.ts +19 -0
- package/dist/transcript-reader.js +17 -3
- package/dist/types.d.ts +10 -0
- package/dist/uninstall.js +31 -1
- package/hermes-plugin/hicortex/README.md +77 -0
- package/hermes-plugin/hicortex/__init__.py +17 -0
- package/hermes-plugin/hicortex/client.py +162 -0
- package/hermes-plugin/hicortex/config.py +105 -0
- package/hermes-plugin/hicortex/plugin.yaml +12 -0
- package/hermes-plugin/hicortex/provider.py +432 -0
- package/openclaw.plugin.json +17 -44
- package/package.json +7 -5
- package/dist/pro-loader.d.ts +0 -33
- package/dist/pro-loader.js +0 -187
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain-aware lesson selection engine — the DEFAULT selector for all installs.
|
|
3
|
+
*
|
|
4
|
+
* History: originally shipped as a Pro feature (src/pro/selection.ts), deleted
|
|
5
|
+
* with the Pro loader in #122, restored into core in #123. Free personal
|
|
6
|
+
* self-host = the full product; there is no gated variant of this selector.
|
|
7
|
+
*
|
|
8
|
+
* Behaviour: score each lesson by four axes, pick the top N, dedup
|
|
9
|
+
* semantically-adjacent entries by normalized-prefix matching.
|
|
10
|
+
*
|
|
11
|
+
* Scoring formula:
|
|
12
|
+
* score = w_project * project_match
|
|
13
|
+
* + w_recency * recency_score
|
|
14
|
+
* + w_strength * base_strength
|
|
15
|
+
* + w_access * access_affinity
|
|
16
|
+
*
|
|
17
|
+
* where:
|
|
18
|
+
* project_match:
|
|
19
|
+
* 1.0 if lesson.project === ctx.project
|
|
20
|
+
* 0.5 if lesson.project is in the same knowledge domain (via ctx.moduleIndex)
|
|
21
|
+
* 0.3 if lesson.project === "global"
|
|
22
|
+
* 0.0 otherwise
|
|
23
|
+
*
|
|
24
|
+
* recency_score:
|
|
25
|
+
* exp(-age_days / half_life_days) in [0, 1]
|
|
26
|
+
* half_life_days = 30 — lessons fade to half weight after 30 days
|
|
27
|
+
*
|
|
28
|
+
* base_strength:
|
|
29
|
+
* lesson.base_strength from the DB (already in [0, 1])
|
|
30
|
+
* represents the importance score from the nightly reflection stage
|
|
31
|
+
*
|
|
32
|
+
* access_affinity:
|
|
33
|
+
* min(access_count / 5, 1.0) — capped at 5 accesses
|
|
34
|
+
* lessons that have been retrieved before get a small boost
|
|
35
|
+
*
|
|
36
|
+
* Default weights:
|
|
37
|
+
* w_project = 0.40 (in-project relevance matters most)
|
|
38
|
+
* w_recency = 0.25 (newer lessons preferred)
|
|
39
|
+
* w_strength = 0.25 (important lessons preferred)
|
|
40
|
+
* w_access = 0.10 (proven-useful lessons preferred)
|
|
41
|
+
*
|
|
42
|
+
* After scoring, lessons are sorted high-to-low, then deduplicated via
|
|
43
|
+
* prefix matching: a lesson is considered a duplicate of an already-selected
|
|
44
|
+
* lesson if its normalized content is a prefix of the other (or vice versa),
|
|
45
|
+
* with at least 30 characters in common. This catches lessons that were
|
|
46
|
+
* rewritten slightly across nightly runs — e.g. "Always validate input" vs
|
|
47
|
+
* "Always validate input before processing any data". The 30-char floor
|
|
48
|
+
* prevents false positives on genuinely short, distinct lessons.
|
|
49
|
+
*
|
|
50
|
+
* When all lessons score identically (no project/date/strength metadata),
|
|
51
|
+
* the sort is stable, so input order is preserved — equivalent to the old
|
|
52
|
+
* slice(0, N) behaviour for metadata-free candidate pools.
|
|
53
|
+
*
|
|
54
|
+
* Generic over T so the same function handles:
|
|
55
|
+
* - Memory[] from local DB queries (server mode)
|
|
56
|
+
* - {content, created_at, base_strength, access_count} HTTP shape (client mode)
|
|
57
|
+
*/
|
|
58
|
+
import type { LessonSelector } from "./extensions.js";
|
|
59
|
+
/**
|
|
60
|
+
* The domain-aware lesson selector. Registered as the default in extensions.ts.
|
|
61
|
+
*/
|
|
62
|
+
export declare const domainAwareLessonSelector: LessonSelector;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Domain-aware lesson selection engine — the DEFAULT selector for all installs.
|
|
4
|
+
*
|
|
5
|
+
* History: originally shipped as a Pro feature (src/pro/selection.ts), deleted
|
|
6
|
+
* with the Pro loader in #122, restored into core in #123. Free personal
|
|
7
|
+
* self-host = the full product; there is no gated variant of this selector.
|
|
8
|
+
*
|
|
9
|
+
* Behaviour: score each lesson by four axes, pick the top N, dedup
|
|
10
|
+
* semantically-adjacent entries by normalized-prefix matching.
|
|
11
|
+
*
|
|
12
|
+
* Scoring formula:
|
|
13
|
+
* score = w_project * project_match
|
|
14
|
+
* + w_recency * recency_score
|
|
15
|
+
* + w_strength * base_strength
|
|
16
|
+
* + w_access * access_affinity
|
|
17
|
+
*
|
|
18
|
+
* where:
|
|
19
|
+
* project_match:
|
|
20
|
+
* 1.0 if lesson.project === ctx.project
|
|
21
|
+
* 0.5 if lesson.project is in the same knowledge domain (via ctx.moduleIndex)
|
|
22
|
+
* 0.3 if lesson.project === "global"
|
|
23
|
+
* 0.0 otherwise
|
|
24
|
+
*
|
|
25
|
+
* recency_score:
|
|
26
|
+
* exp(-age_days / half_life_days) in [0, 1]
|
|
27
|
+
* half_life_days = 30 — lessons fade to half weight after 30 days
|
|
28
|
+
*
|
|
29
|
+
* base_strength:
|
|
30
|
+
* lesson.base_strength from the DB (already in [0, 1])
|
|
31
|
+
* represents the importance score from the nightly reflection stage
|
|
32
|
+
*
|
|
33
|
+
* access_affinity:
|
|
34
|
+
* min(access_count / 5, 1.0) — capped at 5 accesses
|
|
35
|
+
* lessons that have been retrieved before get a small boost
|
|
36
|
+
*
|
|
37
|
+
* Default weights:
|
|
38
|
+
* w_project = 0.40 (in-project relevance matters most)
|
|
39
|
+
* w_recency = 0.25 (newer lessons preferred)
|
|
40
|
+
* w_strength = 0.25 (important lessons preferred)
|
|
41
|
+
* w_access = 0.10 (proven-useful lessons preferred)
|
|
42
|
+
*
|
|
43
|
+
* After scoring, lessons are sorted high-to-low, then deduplicated via
|
|
44
|
+
* prefix matching: a lesson is considered a duplicate of an already-selected
|
|
45
|
+
* lesson if its normalized content is a prefix of the other (or vice versa),
|
|
46
|
+
* with at least 30 characters in common. This catches lessons that were
|
|
47
|
+
* rewritten slightly across nightly runs — e.g. "Always validate input" vs
|
|
48
|
+
* "Always validate input before processing any data". The 30-char floor
|
|
49
|
+
* prevents false positives on genuinely short, distinct lessons.
|
|
50
|
+
*
|
|
51
|
+
* When all lessons score identically (no project/date/strength metadata),
|
|
52
|
+
* the sort is stable, so input order is preserved — equivalent to the old
|
|
53
|
+
* slice(0, N) behaviour for metadata-free candidate pools.
|
|
54
|
+
*
|
|
55
|
+
* Generic over T so the same function handles:
|
|
56
|
+
* - Memory[] from local DB queries (server mode)
|
|
57
|
+
* - {content, created_at, base_strength, access_count} HTTP shape (client mode)
|
|
58
|
+
*/
|
|
59
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
|
+
exports.domainAwareLessonSelector = void 0;
|
|
61
|
+
const W_PROJECT = 0.40;
|
|
62
|
+
const W_RECENCY = 0.25;
|
|
63
|
+
const W_STRENGTH = 0.25;
|
|
64
|
+
const W_ACCESS = 0.10;
|
|
65
|
+
const RECENCY_HALF_LIFE_DAYS = 30;
|
|
66
|
+
function parseDate(ts) {
|
|
67
|
+
if (!ts)
|
|
68
|
+
return null;
|
|
69
|
+
const d = new Date(ts);
|
|
70
|
+
return isNaN(d.getTime()) ? null : d;
|
|
71
|
+
}
|
|
72
|
+
function projectMatch(lesson, targetProject, moduleIndex) {
|
|
73
|
+
if (!lesson.project)
|
|
74
|
+
return 0.0;
|
|
75
|
+
if (targetProject && lesson.project === targetProject)
|
|
76
|
+
return 1.0;
|
|
77
|
+
if (lesson.project === "global")
|
|
78
|
+
return 0.3;
|
|
79
|
+
// Domain-aware: same domain = 0.5
|
|
80
|
+
if (targetProject && moduleIndex) {
|
|
81
|
+
const targetDomain = moduleIndex.domains.find((d) => d.projects.includes(targetProject));
|
|
82
|
+
if (targetDomain && targetDomain.projects.includes(lesson.project)) {
|
|
83
|
+
return 0.5;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return 0.0;
|
|
87
|
+
}
|
|
88
|
+
function recencyScore(lesson, now) {
|
|
89
|
+
const created = parseDate(lesson.created_at);
|
|
90
|
+
if (!created)
|
|
91
|
+
return 0.5; // unknown date → neutral score
|
|
92
|
+
const ageMs = now.getTime() - created.getTime();
|
|
93
|
+
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
94
|
+
if (ageDays < 0)
|
|
95
|
+
return 1.0; // future-dated, treat as very recent
|
|
96
|
+
return Math.exp(-ageDays / RECENCY_HALF_LIFE_DAYS);
|
|
97
|
+
}
|
|
98
|
+
function accessAffinity(lesson) {
|
|
99
|
+
const count = lesson.access_count ?? 0;
|
|
100
|
+
return Math.min(count / 5, 1.0);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Normalize a lesson's content to its canonical form for prefix comparison.
|
|
104
|
+
* Lowercase, collapse whitespace, trim. Full content is preserved so prefix
|
|
105
|
+
* matching below can handle lessons of different lengths.
|
|
106
|
+
*/
|
|
107
|
+
function dedupKey(lesson) {
|
|
108
|
+
return lesson.content.toLowerCase().replace(/\s+/g, " ").trim();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Minimum shared-prefix length for two lessons to be considered duplicates.
|
|
112
|
+
* Shorter values cause false positives on genuinely distinct short lessons;
|
|
113
|
+
* longer values miss legitimate duplicates that happen to differ in the
|
|
114
|
+
* first few dozen characters.
|
|
115
|
+
*/
|
|
116
|
+
const DEDUP_MIN_SHARED_PREFIX = 30;
|
|
117
|
+
function isPrefixDuplicate(candidate, existing) {
|
|
118
|
+
const shorter = candidate.length < existing.length ? candidate : existing;
|
|
119
|
+
const longer = candidate.length < existing.length ? existing : candidate;
|
|
120
|
+
return shorter.length >= DEDUP_MIN_SHARED_PREFIX && longer.startsWith(shorter);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The domain-aware lesson selector. Registered as the default in extensions.ts.
|
|
124
|
+
*/
|
|
125
|
+
exports.domainAwareLessonSelector = {
|
|
126
|
+
select(lessons, ctx) {
|
|
127
|
+
if (lessons.length === 0)
|
|
128
|
+
return [];
|
|
129
|
+
const now = new Date();
|
|
130
|
+
// Score every lesson
|
|
131
|
+
const scored = lessons.map((lesson) => {
|
|
132
|
+
const pMatch = projectMatch(lesson, ctx.project, ctx.moduleIndex);
|
|
133
|
+
const rScore = recencyScore(lesson, now);
|
|
134
|
+
const sScore = lesson.base_strength ?? 0.5;
|
|
135
|
+
const aScore = accessAffinity(lesson);
|
|
136
|
+
const score = W_PROJECT * pMatch +
|
|
137
|
+
W_RECENCY * rScore +
|
|
138
|
+
W_STRENGTH * sScore +
|
|
139
|
+
W_ACCESS * aScore;
|
|
140
|
+
return { lesson, score };
|
|
141
|
+
});
|
|
142
|
+
// Sort high → low (Array.prototype.sort is stable — equal scores keep input order)
|
|
143
|
+
scored.sort((a, b) => b.score - a.score);
|
|
144
|
+
// Select top N with prefix-based dedup
|
|
145
|
+
const selected = [];
|
|
146
|
+
const selectedKeys = [];
|
|
147
|
+
for (const { lesson } of scored) {
|
|
148
|
+
if (selected.length >= ctx.maxLessons)
|
|
149
|
+
break;
|
|
150
|
+
const key = dedupKey(lesson);
|
|
151
|
+
const isDup = selectedKeys.some((existing) => isPrefixDuplicate(key, existing));
|
|
152
|
+
if (isDup)
|
|
153
|
+
continue;
|
|
154
|
+
selectedKeys.push(key);
|
|
155
|
+
selected.push(lesson);
|
|
156
|
+
}
|
|
157
|
+
return selected;
|
|
158
|
+
},
|
|
159
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lessons-context — query-time lessons fetch for the CC SessionStart hook.
|
|
3
|
+
*
|
|
4
|
+
* Replaces file-based injection (injectLessons / injectLessonsFromServer).
|
|
5
|
+
* Reads ~/.hicortex/config.json to find the server URL, GETs /lessons,
|
|
6
|
+
* and prints a compact Markdown block to stdout so CC picks it up as
|
|
7
|
+
* session context.
|
|
8
|
+
*
|
|
9
|
+
* Fail-soft by design: ANY failure (missing config, network error, non-2xx,
|
|
10
|
+
* parse error) results in silent exit-0. A broken hook must never block a
|
|
11
|
+
* CC session.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Fetch lessons from the configured server and return a formatted Markdown
|
|
15
|
+
* block, or null on any failure (caller should print nothing and exit 0).
|
|
16
|
+
*/
|
|
17
|
+
export declare function fetchLessonsContext(): Promise<string | null>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* lessons-context — query-time lessons fetch for the CC SessionStart hook.
|
|
4
|
+
*
|
|
5
|
+
* Replaces file-based injection (injectLessons / injectLessonsFromServer).
|
|
6
|
+
* Reads ~/.hicortex/config.json to find the server URL, GETs /lessons,
|
|
7
|
+
* and prints a compact Markdown block to stdout so CC picks it up as
|
|
8
|
+
* session context.
|
|
9
|
+
*
|
|
10
|
+
* Fail-soft by design: ANY failure (missing config, network error, non-2xx,
|
|
11
|
+
* parse error) results in silent exit-0. A broken hook must never block a
|
|
12
|
+
* CC session.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.fetchLessonsContext = fetchLessonsContext;
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const node_path_1 = require("node:path");
|
|
18
|
+
const node_os_1 = require("node:os");
|
|
19
|
+
const features_js_1 = require("./features.js");
|
|
20
|
+
const extensions_js_1 = require("./extensions.js");
|
|
21
|
+
const state_js_1 = require("./state.js");
|
|
22
|
+
const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
23
|
+
const DEFAULT_PORT = 8787;
|
|
24
|
+
/**
|
|
25
|
+
* Fetch lessons from the configured server and return a formatted Markdown
|
|
26
|
+
* block, or null on any failure (caller should print nothing and exit 0).
|
|
27
|
+
*/
|
|
28
|
+
async function fetchLessonsContext() {
|
|
29
|
+
let config = {};
|
|
30
|
+
try {
|
|
31
|
+
config = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(HICORTEX_HOME, "config.json"), "utf-8"));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// No config file — server not set up yet. Fail soft.
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
// Determine server URL: client mode uses serverUrl; server mode uses localhost.
|
|
38
|
+
const serverUrl = config.mode === "client" && typeof config.serverUrl === "string"
|
|
39
|
+
? config.serverUrl.replace(/\/+$/, "")
|
|
40
|
+
: `http://127.0.0.1:${config.port ?? DEFAULT_PORT}`;
|
|
41
|
+
const authToken = config.authToken;
|
|
42
|
+
let data;
|
|
43
|
+
try {
|
|
44
|
+
const resp = await fetch(`${serverUrl}/lessons`, {
|
|
45
|
+
headers: authToken ? { "Authorization": `Bearer ${authToken}` } : {},
|
|
46
|
+
signal: AbortSignal.timeout(3000),
|
|
47
|
+
});
|
|
48
|
+
if (!resp.ok)
|
|
49
|
+
return null;
|
|
50
|
+
data = await resp.json();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const maxLessons = (0, features_js_1.lessonsLimit)();
|
|
56
|
+
const moduleIndex = data.moduleIndex ?? (0, state_js_1.loadState)(HICORTEX_HOME).moduleIndex;
|
|
57
|
+
// The SessionStart hook runs in the session's working directory, whose last
|
|
58
|
+
// path component matches the capture-side CC project name convention
|
|
59
|
+
// (transcript-reader's decodeProjectDirName also takes the last component).
|
|
60
|
+
// This enables in-project + same-domain lesson boosting on the CC path.
|
|
61
|
+
const project = (0, node_path_1.basename)(process.cwd()) || null;
|
|
62
|
+
const selected = await (0, extensions_js_1.getLessonSelector)().select(data.lessons, { maxLessons, moduleIndex, project });
|
|
63
|
+
const lessonLines = selected.map((l) => {
|
|
64
|
+
const titleMatch = l.content.match(/## Lesson: (.+)/);
|
|
65
|
+
const typeMatch = l.content.match(/\*\*Type:\*\* (\w+)/);
|
|
66
|
+
const severityMatch = l.content.match(/\*\*Severity:\*\* (\w+)/);
|
|
67
|
+
const title = titleMatch ? titleMatch[1] : l.content.slice(0, 150);
|
|
68
|
+
const meta = [severityMatch?.[1], typeMatch?.[1]].filter(Boolean).join(", ");
|
|
69
|
+
return `- ${title}${meta ? ` (${meta})` : ""}`;
|
|
70
|
+
});
|
|
71
|
+
const parts = ["## Hicortex Memory", ""];
|
|
72
|
+
parts.push("You have access to shared long-term memory across all agents and sessions.");
|
|
73
|
+
parts.push("BEFORE making decisions, search memory: `hicortex_search` for prior decisions on the same topic.");
|
|
74
|
+
parts.push("Use `hicortex_context` at session start for recent project state.");
|
|
75
|
+
if (lessonLines.length > 0) {
|
|
76
|
+
parts.push("", "### Lessons (updated nightly)");
|
|
77
|
+
parts.push(...lessonLines);
|
|
78
|
+
}
|
|
79
|
+
// Memory index
|
|
80
|
+
const { index } = data;
|
|
81
|
+
if (moduleIndex && moduleIndex.domains.length > 0) {
|
|
82
|
+
parts.push("", "### Memory Index");
|
|
83
|
+
for (const domain of moduleIndex.domains) {
|
|
84
|
+
const kwStr = domain.keywords.length > 0 ? `: ${domain.keywords.join(", ")}` : "";
|
|
85
|
+
parts.push(`${domain.name} (${domain.memoryCount} memories, ${domain.lessonCount} lessons)${kwStr}`);
|
|
86
|
+
parts.push(` ${domain.projects.join(" | ")}`);
|
|
87
|
+
}
|
|
88
|
+
parts.push(`${index.total} memories, ${index.lessonCount} lessons, ${index.sourceCount} agents. Search with \`hicortex_search\`.`);
|
|
89
|
+
}
|
|
90
|
+
else if (index.projects.length > 0) {
|
|
91
|
+
parts.push("", "### Memory Index");
|
|
92
|
+
parts.push(index.projects.map(p => `${p.name}: ${p.count}`).join(" | "));
|
|
93
|
+
parts.push(`${index.total} memories, ${index.lessonCount} lessons, ${index.sourceCount} agents. Search with \`hicortex_search\`.`);
|
|
94
|
+
}
|
|
95
|
+
return parts.join("\n");
|
|
96
|
+
}
|
package/dist/llm.d.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Multi-provider LLM client for consolidation and distillation.
|
|
3
3
|
*
|
|
4
|
-
* Resolution
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
7
|
-
*
|
|
8
|
-
* 4. Fallback: Ollama at http://localhost:11434
|
|
4
|
+
* Resolution (resolveExplicitLlmConfig):
|
|
5
|
+
* 1. Explicit config-file overrides (llmBaseUrl + llmApiKey + llmModel)
|
|
6
|
+
* 2. Hicortex-specific env vars (HICORTEX_LLM_BASE_URL + HICORTEX_LLM_API_KEY + HICORTEX_LLM_MODEL)
|
|
7
|
+
* Returns null when nothing explicit is set — no silent defaults.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* 3. OPENAI_API_KEY → gpt-5.4-nano
|
|
14
|
-
* 4. GOOGLE_API_KEY → gemini-2.5-flash
|
|
15
|
-
* 5. Claude CLI fallback (uses subscription, no API key needed)
|
|
16
|
-
* 6. Fallback: Ollama at http://localhost:11434
|
|
9
|
+
* Explicit backends (handled by call sites before resolveExplicitLlmConfig):
|
|
10
|
+
* - llmBackend: "claude-cli" → claudeCliConfig()
|
|
11
|
+
* - llmBackend: "ollama" → explicit ollama LlmConfig
|
|
17
12
|
*
|
|
18
13
|
* Supports any OpenAI-compatible endpoint plus first-class support for
|
|
19
14
|
* OpenAI, Anthropic, Google, Ollama, OpenRouter, and Claude CLI.
|
|
@@ -36,25 +31,28 @@ export interface LlmConfig {
|
|
|
36
31
|
reflectProvider?: string;
|
|
37
32
|
}
|
|
38
33
|
/**
|
|
39
|
-
* Resolve LLM configuration from
|
|
34
|
+
* Resolve LLM configuration from explicit config-file overrides or
|
|
35
|
+
* Hicortex-specific env vars only. Returns null when nothing explicit is set.
|
|
36
|
+
*
|
|
37
|
+
* Call sites (mcp-server, nightly) handle the named backends (claude-cli,
|
|
38
|
+
* ollama) before reaching this function. Only call this for the "other
|
|
39
|
+
* provider" / API-key case where no named backend is in config.json.
|
|
40
|
+
*
|
|
41
|
+
* NO implicit fallbacks: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY
|
|
42
|
+
* alone in the environment do NOT configure an LLM — the user must have
|
|
43
|
+
* chosen a provider via `npx @gamaze/hicortex init`.
|
|
40
44
|
*/
|
|
41
|
-
export declare function
|
|
45
|
+
export declare function resolveExplicitLlmConfig(overrides?: {
|
|
42
46
|
llmBaseUrl?: string;
|
|
43
47
|
llmApiKey?: string;
|
|
44
48
|
llmModel?: string;
|
|
45
49
|
reflectModel?: string;
|
|
46
|
-
}): LlmConfig;
|
|
50
|
+
}): LlmConfig | null;
|
|
47
51
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* Defaults to Haiku for distillation/scoring (~$0.50/mo).
|
|
52
|
+
* @deprecated Use resolveExplicitLlmConfig. This alias exists only to ease
|
|
53
|
+
* the transition for any lingering call sites — remove after 0.10.0 ships.
|
|
51
54
|
*/
|
|
52
|
-
export declare
|
|
53
|
-
llmBaseUrl?: string;
|
|
54
|
-
llmApiKey?: string;
|
|
55
|
-
llmModel?: string;
|
|
56
|
-
reflectModel?: string;
|
|
57
|
-
}): LlmConfig;
|
|
55
|
+
export declare const resolveLlmConfigForCC: typeof resolveExplicitLlmConfig;
|
|
58
56
|
/**
|
|
59
57
|
* Find the claude CLI binary. Returns the full path or null.
|
|
60
58
|
*/
|
|
@@ -78,7 +76,7 @@ export declare function probeOllama(baseUrl?: string): Promise<string | null>;
|
|
|
78
76
|
* - `ok: false, reason: "unreachable"` — network failure or non-2xx.
|
|
79
77
|
* - `ok: false, reason: "model_missing"` — endpoint is up but the
|
|
80
78
|
* model isn't listed (the exact case that caused data loss when
|
|
81
|
-
*
|
|
79
|
+
* a remote Ollama box didn't have the distill model loaded).
|
|
82
80
|
*
|
|
83
81
|
* Matches on exact name OR name prefix ("qwen3.5:35b" matches "qwen3.5:35b-a3b").
|
|
84
82
|
*/
|
|
@@ -89,11 +87,26 @@ export declare function probeOllamaModel(baseUrl: string, modelName: string): Pr
|
|
|
89
87
|
reason: "unreachable" | "model_missing";
|
|
90
88
|
}>;
|
|
91
89
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
90
|
+
* Resolve the distillation endpoint before a /distill request.
|
|
91
|
+
*
|
|
92
|
+
* @param config LlmConfig (mutated in "local" mode when fallback is used)
|
|
93
|
+
* @param mode
|
|
94
|
+
* "strict" (default) — when a separate distillBaseUrl is configured and its
|
|
95
|
+
* Ollama probe fails, return "abort" immediately WITHOUT mutating config.
|
|
96
|
+
* The session is not distilled now; the nightly watermark is not advanced,
|
|
97
|
+
* so the session is re-shipped on the next run (harness stores retain raw
|
|
98
|
+
* for 30–90 days — the retry IS the queue). Prefer this to producing
|
|
99
|
+
* low-quality memories from a weak fallback model.
|
|
100
|
+
* "local" — legacy 0.9.0 behaviour: fall back to the base endpoint (local
|
|
101
|
+
* Ollama or API provider) when the remote is down. Mutates config IN PLACE
|
|
102
|
+
* to repoint distill* at the fallback.
|
|
103
|
+
*
|
|
104
|
+
* Returns:
|
|
105
|
+
* "ok" — remote distill endpoint healthy, or no separate endpoint set
|
|
106
|
+
* "fellback" — ("local" mode only) remote down; distill redirected to base
|
|
107
|
+
* "abort" — remote down and fallback not allowed (strict) or both down (local)
|
|
95
108
|
*/
|
|
96
|
-
export declare function
|
|
109
|
+
export declare function resolveDistillFallback(config: LlmConfig, mode?: "strict" | "local"): Promise<"ok" | "fellback" | "abort">;
|
|
97
110
|
export declare class RateLimitError extends Error {
|
|
98
111
|
retryAfterMs: number;
|
|
99
112
|
constructor(retryAfterMs: number);
|