@gamaze/hicortex 0.7.0 → 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.
Files changed (54) hide show
  1. package/README.md +57 -39
  2. package/dist/claude-md.d.ts +9 -21
  3. package/dist/claude-md.js +9 -241
  4. package/dist/cli.d.ts +3 -2
  5. package/dist/cli.js +29 -11
  6. package/dist/consolidate.js +76 -26
  7. package/dist/db.js +24 -0
  8. package/dist/embedder.d.ts +11 -0
  9. package/dist/embedder.js +27 -0
  10. package/dist/extensions.d.ts +41 -88
  11. package/dist/extensions.js +36 -61
  12. package/dist/features.d.ts +21 -25
  13. package/dist/features.js +47 -83
  14. package/dist/graph.d.ts +1 -1
  15. package/dist/graph.js +13 -7
  16. package/dist/hermes-transcript-reader.d.ts +27 -0
  17. package/dist/hermes-transcript-reader.js +134 -0
  18. package/dist/index.d.ts +16 -4
  19. package/dist/index.js +252 -344
  20. package/dist/init.d.ts +41 -1
  21. package/dist/init.js +545 -190
  22. package/dist/lesson-selection.d.ts +62 -0
  23. package/dist/lesson-selection.js +159 -0
  24. package/dist/lessons-context.d.ts +17 -0
  25. package/dist/lessons-context.js +96 -0
  26. package/dist/llm.d.ts +42 -29
  27. package/dist/llm.js +89 -270
  28. package/dist/mcp-server.d.ts +0 -1
  29. package/dist/mcp-server.js +407 -88
  30. package/dist/nightly.d.ts +9 -6
  31. package/dist/nightly.js +197 -357
  32. package/dist/oc-transcript-reader.d.ts +20 -0
  33. package/dist/oc-transcript-reader.js +61 -0
  34. package/dist/pi-transcript-reader.d.ts +1 -0
  35. package/dist/prompts.d.ts +5 -0
  36. package/dist/prompts.js +29 -0
  37. package/dist/status.js +22 -2
  38. package/dist/storage.d.ts +7 -1
  39. package/dist/storage.js +28 -7
  40. package/dist/transcript-reader.d.ts +19 -0
  41. package/dist/transcript-reader.js +17 -3
  42. package/dist/types.d.ts +16 -0
  43. package/dist/types.js +7 -0
  44. package/dist/uninstall.js +31 -1
  45. package/hermes-plugin/hicortex/README.md +77 -0
  46. package/hermes-plugin/hicortex/__init__.py +17 -0
  47. package/hermes-plugin/hicortex/client.py +162 -0
  48. package/hermes-plugin/hicortex/config.py +105 -0
  49. package/hermes-plugin/hicortex/plugin.yaml +12 -0
  50. package/hermes-plugin/hicortex/provider.py +432 -0
  51. package/openclaw.plugin.json +17 -44
  52. package/package.json +7 -5
  53. package/dist/pro-loader.d.ts +0 -33
  54. package/dist/pro-loader.js +0 -187
package/dist/llm.js CHANGED
@@ -2,76 +2,40 @@
2
2
  /**
3
3
  * Multi-provider LLM client for consolidation and distillation.
4
4
  *
5
- * Resolution for OC adapter (resolveLlmConfig):
6
- * 1. Plugin config (llmBaseUrl, llmApiKey, llmModel)
7
- * 2. ~/.openclaw/openclaw.json agents.defaults.model.primary
8
- * 3. Environment vars: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY
9
- * 4. Fallback: Ollama at http://localhost:11434
5
+ * Resolution (resolveExplicitLlmConfig):
6
+ * 1. Explicit config-file overrides (llmBaseUrl + llmApiKey + llmModel)
7
+ * 2. Hicortex-specific env vars (HICORTEX_LLM_BASE_URL + HICORTEX_LLM_API_KEY + HICORTEX_LLM_MODEL)
8
+ * Returns null when nothing explicit is set — no silent defaults.
10
9
  *
11
- * Resolution for CC adapter (resolveLlmConfigForCC):
12
- * 1. Explicit env vars (HICORTEX_LLM_BASE_URL + HICORTEX_LLM_API_KEY + HICORTEX_LLM_MODEL)
13
- * 2. ANTHROPIC_API_KEYClaude Haiku (cheap, CC users always have this)
14
- * 3. OPENAI_API_KEY → gpt-5.4-nano
15
- * 4. GOOGLE_API_KEY → gemini-2.5-flash
16
- * 5. Claude CLI fallback (uses subscription, no API key needed)
17
- * 6. Fallback: Ollama at http://localhost:11434
10
+ * Explicit backends (handled by call sites before resolveExplicitLlmConfig):
11
+ * - llmBackend: "claude-cli" claudeCliConfig()
12
+ * - llmBackend: "ollama" explicit ollama LlmConfig
18
13
  *
19
14
  * Supports any OpenAI-compatible endpoint plus first-class support for
20
15
  * OpenAI, Anthropic, Google, Ollama, OpenRouter, and Claude CLI.
21
16
  */
22
17
  Object.defineProperty(exports, "__esModule", { value: true });
23
- exports.LlmClient = exports.RateLimitError = void 0;
24
- exports.resolveLlmConfig = resolveLlmConfig;
25
- exports.resolveLlmConfigForCC = resolveLlmConfigForCC;
18
+ exports.LlmClient = exports.RateLimitError = exports.resolveLlmConfigForCC = void 0;
19
+ exports.resolveExplicitLlmConfig = resolveExplicitLlmConfig;
26
20
  exports.findClaudeBinary = findClaudeBinary;
27
21
  exports.claudeCliConfig = claudeCliConfig;
28
22
  exports.probeOllama = probeOllama;
29
23
  exports.probeOllamaModel = probeOllamaModel;
30
- exports.preferOllamaForBatch = preferOllamaForBatch;
31
- const node_fs_1 = require("node:fs");
32
- const node_path_1 = require("node:path");
33
- const node_os_1 = require("node:os");
24
+ exports.resolveDistillFallback = resolveDistillFallback;
34
25
  /**
35
- * Resolve LLM configuration from plugin config, OpenClaw config, env vars, or Ollama fallback.
36
- */
37
- function resolveLlmConfig(pluginConfig) {
38
- // 1. Plugin config
39
- if (pluginConfig?.llmBaseUrl && pluginConfig?.llmApiKey) {
40
- const provider = detectProvider(pluginConfig.llmBaseUrl);
41
- return {
42
- baseUrl: pluginConfig.llmBaseUrl,
43
- apiKey: pluginConfig.llmApiKey,
44
- model: pluginConfig.llmModel ?? "qwen3.5:4b",
45
- reflectModel: pluginConfig.reflectModel ?? pluginConfig.llmModel ?? "qwen3.5:cloud",
46
- provider,
47
- };
48
- }
49
- // 2. OpenClaw config file
50
- const ocConfig = readOpenClawConfig();
51
- if (ocConfig) {
52
- return ocConfig;
53
- }
54
- // 3. Environment variables
55
- const envConfig = resolveFromEnv();
56
- if (envConfig) {
57
- return envConfig;
58
- }
59
- // 4. Fallback: Ollama
60
- return {
61
- baseUrl: "http://localhost:11434",
62
- apiKey: "",
63
- model: "qwen3.5:4b",
64
- reflectModel: "qwen3.5:cloud",
65
- provider: "ollama",
66
- };
67
- }
68
- /**
69
- * Resolve LLM configuration for Claude Code (no OC config file).
70
- * Uses env vars only — CC users always have ANTHROPIC_API_KEY.
71
- * Defaults to Haiku for distillation/scoring (~$0.50/mo).
26
+ * Resolve LLM configuration from explicit config-file overrides or
27
+ * Hicortex-specific env vars only. Returns null when nothing explicit is set.
28
+ *
29
+ * Call sites (mcp-server, nightly) handle the named backends (claude-cli,
30
+ * ollama) before reaching this function. Only call this for the "other
31
+ * provider" / API-key case where no named backend is in config.json.
32
+ *
33
+ * NO implicit fallbacks: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY
34
+ * alone in the environment do NOT configure an LLM — the user must have
35
+ * chosen a provider via `npx @gamaze/hicortex init`.
72
36
  */
73
- function resolveLlmConfigForCC(overrides) {
74
- // 1. Explicit overrides (from config file or CLI args)
37
+ function resolveExplicitLlmConfig(overrides) {
38
+ // 1. Explicit config-file overrides (both baseUrl and apiKey required)
75
39
  if (overrides?.llmBaseUrl && overrides?.llmApiKey) {
76
40
  const provider = detectProvider(overrides.llmBaseUrl);
77
41
  return {
@@ -82,7 +46,7 @@ function resolveLlmConfigForCC(overrides) {
82
46
  provider,
83
47
  };
84
48
  }
85
- // 2. Hicortex-specific env vars
49
+ // 2. Hicortex-specific env vars (both base URL and API key required)
86
50
  const hcBaseUrl = process.env.HICORTEX_LLM_BASE_URL;
87
51
  const hcApiKey = process.env.HICORTEX_LLM_API_KEY;
88
52
  const hcModel = process.env.HICORTEX_LLM_MODEL;
@@ -96,52 +60,14 @@ function resolveLlmConfigForCC(overrides) {
96
60
  provider,
97
61
  };
98
62
  }
99
- // 3. Standard API key env vars (CC users almost always have ANTHROPIC_API_KEY)
100
- const anthropicKey = process.env.ANTHROPIC_API_KEY;
101
- if (anthropicKey) {
102
- return {
103
- baseUrl: process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
104
- apiKey: anthropicKey,
105
- model: "claude-haiku-4-5-20251001",
106
- reflectModel: "claude-sonnet-4-6",
107
- provider: "anthropic",
108
- };
109
- }
110
- const openaiKey = process.env.OPENAI_API_KEY;
111
- if (openaiKey) {
112
- const baseUrl = process.env.OPENAI_BASE_URL ?? "https://api.openai.com";
113
- return {
114
- baseUrl,
115
- apiKey: openaiKey,
116
- model: "gpt-5.4-nano",
117
- reflectModel: "gpt-5.4-nano",
118
- provider: detectProvider(baseUrl),
119
- };
120
- }
121
- const googleKey = process.env.GOOGLE_API_KEY;
122
- if (googleKey) {
123
- return {
124
- baseUrl: "https://generativelanguage.googleapis.com/v1beta",
125
- apiKey: googleKey,
126
- model: "gemini-2.5-flash",
127
- reflectModel: "gemini-2.5-flash",
128
- provider: "google",
129
- };
130
- }
131
- // 4. Claude CLI fallback (subscription users)
132
- const claudePath = findClaudeBinary();
133
- if (claudePath) {
134
- return claudeCliConfig(claudePath);
135
- }
136
- // 5. Ollama fallback (truly last resort)
137
- return {
138
- baseUrl: "http://localhost:11434",
139
- apiKey: "",
140
- model: "qwen3.5:4b",
141
- reflectModel: "qwen3.5:4b",
142
- provider: "ollama",
143
- };
63
+ // Nothing explicit caller decides what to do (recall-only mode, loud warning, etc.)
64
+ return null;
144
65
  }
66
+ /**
67
+ * @deprecated Use resolveExplicitLlmConfig. This alias exists only to ease
68
+ * the transition for any lingering call sites — remove after 0.10.0 ships.
69
+ */
70
+ exports.resolveLlmConfigForCC = resolveExplicitLlmConfig;
145
71
  function detectProvider(url) {
146
72
  const u = url.toLowerCase();
147
73
  if (u.includes("ollama") || u.includes(":11434"))
@@ -154,153 +80,6 @@ function detectProvider(url) {
154
80
  return "google";
155
81
  return "openai";
156
82
  }
157
- function readOpenClawConfig() {
158
- try {
159
- const configPath = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "openclaw.json");
160
- const raw = (0, node_fs_1.readFileSync)(configPath, "utf-8");
161
- const config = JSON.parse(raw);
162
- const primary = config?.agents?.defaults?.model?.primary;
163
- if (!primary)
164
- return null;
165
- // primary format is "provider/model" (e.g. "openai/gpt-5", "anthropic/claude-sonnet-4-6")
166
- if (typeof primary === "string" && (primary.includes("/") || primary.includes(":"))) {
167
- const sep = primary.includes("/") ? "/" : ":";
168
- const [providerHint, ...rest] = primary.split(sep);
169
- const model = rest.join(sep);
170
- // Accept any provider name — if it's in our URL map, we know it
171
- const hint = providerHint.toLowerCase();
172
- const provider = (hint in PROVIDER_BASE_URLS ? hint : "openai");
173
- // Resolve base URL: OC config → per-agent models.json → built-in defaults
174
- const baseUrl = readOcProviderBaseUrl(config, providerHint) ??
175
- getDefaultUrlForProvider(provider);
176
- // Resolve API key: OC auth-profiles.json → env vars
177
- const apiKey = readOcAuthKey(providerHint) ??
178
- getEnvKeyForProvider(provider);
179
- if (!apiKey && provider !== "ollama")
180
- return null;
181
- return {
182
- baseUrl,
183
- apiKey: apiKey ?? "",
184
- model,
185
- reflectModel: model,
186
- provider,
187
- };
188
- }
189
- }
190
- catch {
191
- // Config file doesn't exist or is invalid
192
- }
193
- return null;
194
- }
195
- /**
196
- * Read provider base URL from openclaw.json → models.providers.<name>.baseUrl
197
- */
198
- function readOcProviderBaseUrl(config, provider) {
199
- const providerConfig = config?.models?.providers?.[provider];
200
- if (providerConfig?.baseUrl)
201
- return providerConfig.baseUrl;
202
- return undefined;
203
- }
204
- /**
205
- * Read API key from OC's per-agent auth-profiles.json.
206
- * Scans all agent dirs for a matching provider profile.
207
- */
208
- function readOcAuthKey(provider) {
209
- try {
210
- const { readdirSync } = require("node:fs");
211
- const agentsDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "agents");
212
- const agents = readdirSync(agentsDir);
213
- for (const agentId of agents) {
214
- try {
215
- const authPath = (0, node_path_1.join)(agentsDir, agentId, "agent", "auth-profiles.json");
216
- const raw = (0, node_fs_1.readFileSync)(authPath, "utf-8");
217
- const auth = JSON.parse(raw);
218
- const profiles = auth?.profiles ?? {};
219
- // Look for a profile matching the provider (e.g. "openai:default")
220
- for (const [profileId, profile] of Object.entries(profiles)) {
221
- const p = profile;
222
- if (p?.provider === provider ||
223
- profileId.startsWith(`${provider}:`)) {
224
- if (p?.key)
225
- return p.key;
226
- }
227
- }
228
- }
229
- catch {
230
- // Skip agents without auth
231
- }
232
- }
233
- }
234
- catch {
235
- // No agents dir
236
- }
237
- return undefined;
238
- }
239
- function resolveFromEnv() {
240
- const openaiKey = process.env.OPENAI_API_KEY;
241
- const openaiBaseUrl = process.env.OPENAI_BASE_URL;
242
- if (openaiKey) {
243
- const baseUrl = openaiBaseUrl ?? "https://api.openai.com";
244
- const provider = detectProvider(baseUrl);
245
- return {
246
- baseUrl,
247
- apiKey: openaiKey,
248
- model: process.env.OPENAI_MODEL ?? "gpt-5.4-nano",
249
- reflectModel: process.env.OPENAI_MODEL ?? "gpt-5.4-nano",
250
- provider,
251
- };
252
- }
253
- const anthropicKey = process.env.ANTHROPIC_API_KEY;
254
- if (anthropicKey) {
255
- return {
256
- baseUrl: process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
257
- apiKey: anthropicKey,
258
- model: "claude-sonnet-4-6",
259
- reflectModel: "claude-sonnet-4-6",
260
- provider: "anthropic",
261
- };
262
- }
263
- const googleKey = process.env.GOOGLE_API_KEY;
264
- if (googleKey) {
265
- return {
266
- baseUrl: "https://generativelanguage.googleapis.com/v1beta",
267
- apiKey: googleKey,
268
- model: "gemini-2.5-flash",
269
- reflectModel: "gemini-2.5-flash",
270
- provider: "google",
271
- };
272
- }
273
- return null;
274
- }
275
- function getEnvKeyForProvider(provider) {
276
- switch (provider) {
277
- case "openai":
278
- return process.env.OPENAI_API_KEY;
279
- case "anthropic":
280
- return process.env.ANTHROPIC_API_KEY;
281
- case "google":
282
- return process.env.GOOGLE_API_KEY;
283
- default:
284
- return undefined;
285
- }
286
- }
287
- /**
288
- * Default base URLs for first-class supported providers.
289
- *
290
- * For any other provider, set llmBaseUrl explicitly in your config or use
291
- * an OpenAI-compatible endpoint. The detectProvider() function will treat
292
- * unknown URLs as openai-compatible by default.
293
- */
294
- const PROVIDER_BASE_URLS = {
295
- openai: "https://api.openai.com/v1",
296
- anthropic: "https://api.anthropic.com",
297
- google: "https://generativelanguage.googleapis.com/v1beta",
298
- ollama: "http://localhost:11434",
299
- openrouter: "https://openrouter.ai/api",
300
- };
301
- function getDefaultUrlForProvider(provider) {
302
- return PROVIDER_BASE_URLS[provider.toLowerCase()] ?? "https://api.openai.com/v1";
303
- }
304
83
  /**
305
84
  * Find the claude CLI binary. Returns the full path or null.
306
85
  */
@@ -377,7 +156,7 @@ async function probeOllama(baseUrl = "http://localhost:11434") {
377
156
  * - `ok: false, reason: "unreachable"` — network failure or non-2xx.
378
157
  * - `ok: false, reason: "model_missing"` — endpoint is up but the
379
158
  * model isn't listed (the exact case that caused data loss when
380
- * mhac-pro's Ollama didn't have the distill model loaded).
159
+ * a remote Ollama box didn't have the distill model loaded).
381
160
  *
382
161
  * Matches on exact name OR name prefix ("qwen3.5:35b" matches "qwen3.5:35b-a3b").
383
162
  */
@@ -398,25 +177,65 @@ async function probeOllamaModel(baseUrl, modelName) {
398
177
  }
399
178
  }
400
179
  /**
401
- * For batch operations (nightly pipeline), prefer Ollama when available.
402
- * Claude CLI has strict rate limits that kill batch distillation.
403
- * Falls back to the provided config if Ollama is unreachable.
180
+ * Resolve the distillation endpoint before a /distill request.
181
+ *
182
+ * @param config LlmConfig (mutated in "local" mode when fallback is used)
183
+ * @param mode
184
+ * "strict" (default) — when a separate distillBaseUrl is configured and its
185
+ * Ollama probe fails, return "abort" immediately WITHOUT mutating config.
186
+ * The session is not distilled now; the nightly watermark is not advanced,
187
+ * so the session is re-shipped on the next run (harness stores retain raw
188
+ * for 30–90 days — the retry IS the queue). Prefer this to producing
189
+ * low-quality memories from a weak fallback model.
190
+ * "local" — legacy 0.9.0 behaviour: fall back to the base endpoint (local
191
+ * Ollama or API provider) when the remote is down. Mutates config IN PLACE
192
+ * to repoint distill* at the fallback.
193
+ *
194
+ * Returns:
195
+ * "ok" — remote distill endpoint healthy, or no separate endpoint set
196
+ * "fellback" — ("local" mode only) remote down; distill redirected to base
197
+ * "abort" — remote down and fallback not allowed (strict) or both down (local)
404
198
  */
405
- async function preferOllamaForBatch(resolved, ollamaBaseUrl = "http://localhost:11434") {
406
- // Only override claude-cli (which has rate limits)
407
- if (resolved.provider !== "claude-cli")
408
- return resolved;
409
- const model = await probeOllama(ollamaBaseUrl);
410
- if (!model)
411
- return resolved;
412
- return {
413
- ...resolved,
414
- baseUrl: ollamaBaseUrl,
415
- apiKey: "",
416
- model,
417
- reflectModel: resolved.reflectModel ?? model,
418
- provider: "ollama",
419
- };
199
+ async function resolveDistillFallback(config, mode = "strict") {
200
+ const distillProvider = config.distillProvider ?? config.provider;
201
+ // Only a remote Ollama distill endpoint can go unreachable mid-run; API
202
+ // providers are cloud-reachable and need no fallback.
203
+ if (!config.distillBaseUrl || distillProvider !== "ollama")
204
+ return "ok";
205
+ const distillModel = config.distillModel ?? config.model;
206
+ const remote = await probeOllamaModel(config.distillBaseUrl, distillModel);
207
+ if (remote.ok)
208
+ return "ok";
209
+ const reason = remote.reason === "unreachable"
210
+ ? `remote distill endpoint unreachable (${config.distillBaseUrl})`
211
+ : `remote distill model not loaded (${distillModel} on ${config.distillBaseUrl})`;
212
+ if (mode === "strict") {
213
+ // Do not mutate config. Log once and let the caller return 503 so the
214
+ // nightly watermark stays put and the session is retried next run.
215
+ console.error(`[hicortex] ABORT: ${reason} — session will be retried next run`);
216
+ return "abort";
217
+ }
218
+ // "local" mode: fall back to the base endpoint.
219
+ // If the base is Ollama, verify it is actually up before committing;
220
+ // if the base is an API provider, it is cloud-reachable.
221
+ if (config.provider === "ollama") {
222
+ const local = await probeOllamaModel(config.baseUrl, config.model);
223
+ if (!local.ok) {
224
+ console.error(`[hicortex] ABORT: ${reason}, and local fallback (${config.model} on ${config.baseUrl}) also unavailable — retry next run`);
225
+ return "abort";
226
+ }
227
+ config.distillBaseUrl = config.baseUrl;
228
+ }
229
+ else {
230
+ // Base is an API provider — route distill through it (no separate baseUrl).
231
+ config.distillBaseUrl = undefined;
232
+ }
233
+ config.distillModel = config.model;
234
+ config.distillProvider = config.provider;
235
+ config.distillApiKey = config.apiKey;
236
+ console.warn(`[hicortex] ${reason} — falling back to base endpoint for distillation ` +
237
+ `(${config.provider}/${config.model}). Lower quality, but capture continues.`);
238
+ return "fellback";
420
239
  }
421
240
  // ---------------------------------------------------------------------------
422
241
  // LLM Client class
@@ -13,7 +13,6 @@
13
13
  export declare function startServer(options?: {
14
14
  port?: number;
15
15
  host?: string;
16
- consolidateHour?: number;
17
16
  dbPath?: string;
18
17
  licenseKey?: string;
19
18
  }): Promise<void>;