@gamaze/hicortex 0.4.3 → 0.4.5

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/dist/index.js CHANGED
@@ -42,7 +42,9 @@ var __importStar = (this && this.__importStar) || (function () {
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
43
  const node_path_1 = require("node:path");
44
44
  const db_js_1 = require("./db.js");
45
- const license_js_1 = require("./license.js");
45
+ const features_js_1 = require("./features.js");
46
+ const extensions_js_1 = require("./extensions.js");
47
+ const state_js_1 = require("./state.js");
46
48
  const llm_js_1 = require("./llm.js");
47
49
  const node_fs_1 = require("node:fs");
48
50
  const node_os_1 = require("node:os");
@@ -87,8 +89,10 @@ exports.default = {
87
89
  llm = new llm_js_1.LlmClient(llmConfig);
88
90
  log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model} ` +
89
91
  `(reflect: ${llmConfig.reflectModel})`);
90
- // License check (non-blocking)
91
- (0, license_js_1.validateLicense)(config.licenseKey, stateDir).catch((err) => log(`[hicortex] License validation failed: ${err}`));
92
+ // One-time migration of legacy state files (no-op if state.json exists)
93
+ (0, state_js_1.migrateLegacyState)(stateDir);
94
+ // License: init feature cache (sync after this returns)
95
+ await (0, features_js_1.initFeatures)(config.licenseKey, stateDir);
92
96
  // Schedule nightly consolidation
93
97
  const consolidateHour = config.consolidateHour ?? 2;
94
98
  cancelConsolidation = (0, consolidate_js_1.scheduleConsolidation)(db, llm, embedder_js_1.embed, consolidateHour);
@@ -123,9 +127,13 @@ exports.default = {
123
127
  const lessons = storage.getLessons(db, 7, ctx.project);
124
128
  if (lessons.length === 0)
125
129
  return {};
126
- const maxFeatures = (0, license_js_1.getFeatures)(stateDir);
127
- const maxLessons = maxFeatures.maxMemories === -1 ? 20 : 10;
128
- const formatted = lessons.slice(0, maxLessons).map((l) => {
130
+ const maxLessons = (0, features_js_1.lessonsLimit)();
131
+ const selected = await (0, extensions_js_1.getLessonSelector)().select(lessons, {
132
+ maxLessons,
133
+ project: ctx.project,
134
+ agentId: ctx.agentId,
135
+ });
136
+ const formatted = selected.map((l) => {
129
137
  // Extract just the lesson text from the structured content
130
138
  const match = l.content.match(/## Lesson: (.+)/);
131
139
  return match ? `- ${match[1]}` : `- ${l.content.slice(0, 200)}`;
@@ -136,9 +144,9 @@ exports.default = {
136
144
  "\n";
137
145
  // Daily friendly reminder when at memory cap
138
146
  const memCount = storage.countMemories(db);
139
- if (maxFeatures.maxMemories > 0 && memCount >= maxFeatures.maxMemories) {
147
+ if ((0, features_js_1.memoryCapReached)(memCount)) {
140
148
  context +=
141
- `\n---\nHicortex free tier: ${maxFeatures.maxMemories} memories stored. ` +
149
+ `\n---\nHicortex free tier: ${(0, features_js_1.maxMemoriesAllowed)()} memories stored. ` +
142
150
  `New memories can no longer be saved, and your agent has stopped learning and self-improving from new sessions. ` +
143
151
  `Existing memories, lessons, and search still work. ` +
144
152
  `Upgrade for unlimited usage: https://hicortex.gamaze.com/ ` +
@@ -169,10 +177,8 @@ exports.default = {
169
177
  if (entries.length === 0)
170
178
  return;
171
179
  // Check license cap
172
- const features = (0, license_js_1.getFeatures)(stateDir);
173
- if (features.maxMemories > 0 &&
174
- storage.countMemories(db) >= features.maxMemories) {
175
- console.warn(`[hicortex] Free tier limit reached (${features.maxMemories} memories). ` +
180
+ if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
181
+ console.warn(`[hicortex] Free tier limit reached (${(0, features_js_1.maxMemoriesAllowed)()} memories). ` +
176
182
  `Search and lessons still work, but new memories won't be saved. ` +
177
183
  `Upgrade for unlimited usage: https://hicortex.gamaze.com/`);
178
184
  return;
@@ -299,12 +305,11 @@ exports.default = {
299
305
  async execute(_callId, args, context) {
300
306
  if (!db)
301
307
  return { error: "Hicortex not initialized" };
302
- const features = (0, license_js_1.getFeatures)(stateDir);
303
- if (features.maxMemories > 0 && storage.countMemories(db) >= features.maxMemories) {
308
+ if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
304
309
  return {
305
310
  content: [{
306
311
  type: "text",
307
- text: `Free tier limit reached (${features.maxMemories} memories). ` +
312
+ text: `Free tier limit reached (${(0, features_js_1.maxMemoriesAllowed)()} memories). ` +
308
313
  `Your existing memories and lessons still work — search and recall are unaffected. ` +
309
314
  `New memories won't be saved until you upgrade.\n\n` +
310
315
  `Upgrade for unlimited usage: https://hicortex.gamaze.com/`
@@ -461,28 +466,7 @@ async function autoConfigureLlm(pluginConfig, log) {
461
466
  const msg = err instanceof Error ? err.message : String(err);
462
467
  log(`[hicortex] LLM test failed (${llmConfig.baseUrl}): ${msg}`);
463
468
  }
464
- // Step 4: If test failed and this is z.ai, try the alternate endpoint
465
- if (llmConfig.provider === "zai") {
466
- const altUrl = llmConfig.baseUrl.includes("/coding/")
467
- ? llmConfig.baseUrl.replace("/coding/", "/")
468
- : llmConfig.baseUrl.replace("/api/paas/", "/api/coding/paas/");
469
- log(`[hicortex] Trying alternate z.ai endpoint: ${altUrl}`);
470
- llmConfig.baseUrl = altUrl;
471
- const altClient = new llm_js_1.LlmClient(llmConfig);
472
- try {
473
- const response = await altClient.completeFast("Respond with just the word OK", 10);
474
- if (response && response.length > 0) {
475
- log(`[hicortex] LLM connection verified on alternate endpoint`);
476
- persistProviderConfig(llmConfig, log);
477
- return llmConfig;
478
- }
479
- }
480
- catch (err) {
481
- const msg = err instanceof Error ? err.message : String(err);
482
- log(`[hicortex] Alternate z.ai endpoint also failed: ${msg}`);
483
- }
484
- }
485
- // Step 5: Fall back — return the config anyway, log instructions
469
+ // Step 4: Fall back return the config anyway, log instructions
486
470
  log(`[hicortex] WARNING: Could not verify LLM connection. ` +
487
471
  `Distillation and consolidation may fail. ` +
488
472
  `To fix: add models.providers.${llmConfig.provider}.baseUrl to ~/.openclaw/openclaw.json ` +
@@ -529,26 +513,6 @@ function persistProviderConfig(llmConfig, log) {
529
513
  // Non-fatal — config works in memory even if we can't persist
530
514
  }
531
515
  }
532
- /**
533
- * Track when the memory cap was first hit. Returns days since cap was reached.
534
- * Stores timestamp in stateDir/cap-hit.txt on first detection.
535
- */
536
- function getDaysSinceCapHit(dir) {
537
- const capFile = (0, node_path_1.join)(dir, "cap-hit.txt");
538
- try {
539
- const ts = (0, node_fs_1.readFileSync)(capFile, "utf-8").trim();
540
- const hitDate = new Date(ts);
541
- return Math.floor((Date.now() - hitDate.getTime()) / (1000 * 60 * 60 * 24));
542
- }
543
- catch {
544
- // First time hitting cap — record it
545
- try {
546
- (0, node_fs_1.writeFileSync)(capFile, new Date().toISOString());
547
- }
548
- catch { /* non-fatal */ }
549
- return 0;
550
- }
551
- }
552
516
  const HICORTEX_TOOLS = [
553
517
  "hicortex_search",
554
518
  "hicortex_context",
package/dist/init.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Detection:
5
5
  * 1. Local HC server running (localhost:8787)
6
- * 2. Remote HC server (HICORTEX_SERVER_URL or bedrock:8787)
6
+ * 2. Remote HC server (HICORTEX_SERVER_URL any reachable host:port)
7
7
  * 3. OC plugin installed (~/.openclaw/openclaw.json)
8
8
  * 4. CC MCP already registered (~/.claude/settings.json)
9
9
  * 5. Existing DB (~/.hicortex/ or ~/.openclaw/data/)
package/dist/init.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Detection:
6
6
  * 1. Local HC server running (localhost:8787)
7
- * 2. Remote HC server (HICORTEX_SERVER_URL or bedrock:8787)
7
+ * 2. Remote HC server (HICORTEX_SERVER_URL any reachable host:port)
8
8
  * 3. OC plugin installed (~/.openclaw/openclaw.json)
9
9
  * 4. CC MCP already registered (~/.claude/settings.json)
10
10
  * 5. Existing DB (~/.hicortex/ or ~/.openclaw/data/)
@@ -185,10 +185,10 @@ When invoked with \`/learn <text>\`, store the learning in long-term memory via
185
185
 
186
186
  ## Example
187
187
 
188
- \`/learn z.ai API uses Bearer auth on all endpoints, not x-api-key\`
188
+ \`/learn always check provider docs before assuming an API uses the same auth scheme as OpenAI\`
189
189
 
190
190
  Becomes a call to hicortex_ingest with:
191
- - content: "LEARNING: z.ai API uses Bearer auth on all three endpoints (paas, coding, anthropic). Not x-api-key. (2026-03-26)"
191
+ - content: "LEARNING: always check provider docs before assuming an API uses the same auth scheme as OpenAI header names and token formats vary widely (Bearer vs x-api-key vs custom)."
192
192
  - memory_type: "lesson"
193
193
  `;
194
194
  const learnPath = (0, node_path_1.join)(CC_COMMANDS_DIR, "learn.md");
@@ -402,7 +402,7 @@ async function persistLlmConfig() {
402
402
  options.push({
403
403
  label: "Other provider (requires API key)",
404
404
  save: async () => {
405
- console.log("\n Providers: Anthropic, OpenAI, Google, z.ai, OpenRouter, or any OpenAI-compatible");
405
+ console.log("\n Providers: Anthropic, OpenAI, Google, OpenRouter, or any OpenAI-compatible endpoint");
406
406
  const baseUrl = await ask(" Provider base URL: ");
407
407
  if (!baseUrl) {
408
408
  console.log(" ⚠ Cancelled.");
package/dist/license.d.ts CHANGED
@@ -1,5 +1,15 @@
1
+ /**
2
+ * License API client.
3
+ *
4
+ * This module is a thin wrapper over the validation HTTP endpoint at
5
+ * https://hicortex.gamaze.com/api/validate. Persistence and feature gating
6
+ * live in features.ts + state.ts; this file does NOT touch disk.
7
+ *
8
+ * Offline grace: when the API is unreachable, we fall back to the cached
9
+ * tier in state.json (written by features.ts on the last successful
10
+ * validation). If the cached tier was validated within OFFLINE_GRACE_DAYS,
11
+ * we treat it as still valid.
12
+ */
1
13
  import type { LicenseInfo } from "./types.js";
2
- /** Validate a license key against the Hicortex API */
14
+ /** Validate a license key against the Hicortex API. */
3
15
  export declare function validateLicense(key: string | undefined, stateDir: string): Promise<LicenseInfo>;
4
- /** Get current features, using cache or free tier defaults */
5
- export declare function getFeatures(stateDir: string): LicenseInfo["features"];
package/dist/license.js CHANGED
@@ -1,13 +1,23 @@
1
1
  "use strict";
2
+ /**
3
+ * License API client.
4
+ *
5
+ * This module is a thin wrapper over the validation HTTP endpoint at
6
+ * https://hicortex.gamaze.com/api/validate. Persistence and feature gating
7
+ * live in features.ts + state.ts; this file does NOT touch disk.
8
+ *
9
+ * Offline grace: when the API is unreachable, we fall back to the cached
10
+ * tier in state.json (written by features.ts on the last successful
11
+ * validation). If the cached tier was validated within OFFLINE_GRACE_DAYS,
12
+ * we treat it as still valid.
13
+ */
2
14
  Object.defineProperty(exports, "__esModule", { value: true });
3
15
  exports.validateLicense = validateLicense;
4
- exports.getFeatures = getFeatures;
5
- const node_fs_1 = require("node:fs");
6
- const node_path_1 = require("node:path");
16
+ const state_js_1 = require("./state.js");
7
17
  const VALIDATE_URL = "https://hicortex.gamaze.com/api/validate";
8
18
  const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
9
19
  const OFFLINE_GRACE_DAYS = 7;
10
- // In-memory cache
20
+ // In-memory cache for the current process
11
21
  let cachedLicense = null;
12
22
  let cacheTimestamp = 0;
13
23
  const FREE_LICENSE = {
@@ -21,12 +31,12 @@ const FREE_LICENSE = {
21
31
  remoteIngest: true,
22
32
  },
23
33
  };
24
- /** Validate a license key against the Hicortex API */
34
+ /** Validate a license key against the Hicortex API. */
25
35
  async function validateLicense(key, stateDir) {
26
36
  // No key = free tier
27
37
  if (!key)
28
38
  return FREE_LICENSE;
29
- // Check in-memory cache
39
+ // Check in-memory cache (24h TTL)
30
40
  if (cachedLicense && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
31
41
  return cachedLicense;
32
42
  }
@@ -41,57 +51,36 @@ async function validateLicense(key, stateDir) {
41
51
  throw new Error(`HTTP ${resp.status}`);
42
52
  }
43
53
  const data = (await resp.json());
44
- // Cache result
45
54
  cachedLicense = data;
46
55
  cacheTimestamp = Date.now();
47
- // Persist last successful validation timestamp for offline grace
48
- if (data.valid) {
49
- persistValidationTimestamp(stateDir);
50
- }
51
56
  return data;
52
57
  }
53
58
  catch {
54
- // Network failure — check offline grace period
55
- return offlineFallback(key, stateDir);
56
- }
57
- }
58
- /** Get current features, using cache or free tier defaults */
59
- function getFeatures(stateDir) {
60
- if (cachedLicense)
61
- return cachedLicense.features;
62
- return FREE_LICENSE.features;
63
- }
64
- function persistValidationTimestamp(stateDir) {
65
- try {
66
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(stateDir, "license-validated.txt"), new Date().toISOString());
67
- }
68
- catch {
69
- // Non-critical
59
+ // Network failure — check offline grace period via state.tier
60
+ return offlineFallback(stateDir);
70
61
  }
71
62
  }
72
- function offlineFallback(key, stateDir) {
73
- const tsPath = (0, node_path_1.join)(stateDir, "license-validated.txt");
74
- if (!(0, node_fs_1.existsSync)(tsPath))
63
+ /**
64
+ * Offline fallback: if state.tier was validated within the grace period,
65
+ * return its cached features as if validation succeeded. Otherwise, free tier.
66
+ */
67
+ function offlineFallback(stateDir) {
68
+ const persisted = (0, state_js_1.loadState)(stateDir).tier;
69
+ if (!persisted)
75
70
  return FREE_LICENSE;
76
71
  try {
77
- const lastValidated = new Date((0, node_fs_1.readFileSync)(tsPath, "utf-8").trim());
72
+ const lastValidated = new Date(persisted.validatedAt);
78
73
  const daysSince = (Date.now() - lastValidated.getTime()) / (1000 * 60 * 60 * 24);
79
74
  if (daysSince <= OFFLINE_GRACE_DAYS) {
80
- // Within grace period — assume last known state was valid
81
- return cachedLicense ?? {
75
+ return {
82
76
  valid: true,
83
- tier: "pro",
84
- features: {
85
- reflection: true,
86
- vectorSearch: true,
87
- maxMemories: -1,
88
- crossAgent: true,
89
- },
77
+ tier: persisted.tier,
78
+ features: persisted.features,
90
79
  };
91
80
  }
92
81
  }
93
82
  catch {
94
- // Corrupted file
83
+ // Corrupted timestamp — fall through
95
84
  }
96
85
  return FREE_LICENSE;
97
86
  }
package/dist/llm.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Multi-provider LLM client for consolidation and distillation.
3
- * Ported from hicortex/consolidate/llm.py.
4
3
  *
5
4
  * Resolution for OC adapter (resolveLlmConfig):
6
5
  * 1. Plugin config (llmBaseUrl, llmApiKey, llmModel)
@@ -10,12 +9,14 @@
10
9
  *
11
10
  * Resolution for CC adapter (resolveLlmConfigForCC):
12
11
  * 1. Explicit env vars (HICORTEX_LLM_BASE_URL + HICORTEX_LLM_API_KEY + HICORTEX_LLM_MODEL)
13
- * 2. ANTHROPIC_API_KEY → Haiku (cheap, CC users always have this)
14
- * 3. OPENAI_API_KEY → gpt-4o-mini
15
- * 4. GOOGLE_API_KEY → gemini-2.0-flash
16
- * 5. Fallback: Ollama at http://localhost:11434
12
+ * 2. ANTHROPIC_API_KEY → Claude Haiku (cheap, CC users always have this)
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
17
17
  *
18
- * Supports: OpenAI, Anthropic, Google, OpenRouter, Ollama, z.ai, and 15+ more
18
+ * Supports any OpenAI-compatible endpoint plus first-class support for
19
+ * OpenAI, Anthropic, Google, Ollama, OpenRouter, and Claude CLI.
19
20
  */
20
21
  export interface LlmConfig {
21
22
  baseUrl: string;
@@ -116,7 +117,7 @@ export declare class LlmClient {
116
117
  */
117
118
  private completeOllama;
118
119
  /**
119
- * Anthropic Messages API (/v1/messages). Used for Anthropic and z.ai.
120
+ * Anthropic Messages API (/v1/messages).
120
121
  * Auth via x-api-key header.
121
122
  */
122
123
  private completeAnthropic;
package/dist/llm.js CHANGED
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Multi-provider LLM client for consolidation and distillation.
4
- * Ported from hicortex/consolidate/llm.py.
5
4
  *
6
5
  * Resolution for OC adapter (resolveLlmConfig):
7
6
  * 1. Plugin config (llmBaseUrl, llmApiKey, llmModel)
@@ -11,12 +10,14 @@
11
10
  *
12
11
  * Resolution for CC adapter (resolveLlmConfigForCC):
13
12
  * 1. Explicit env vars (HICORTEX_LLM_BASE_URL + HICORTEX_LLM_API_KEY + HICORTEX_LLM_MODEL)
14
- * 2. ANTHROPIC_API_KEY → Haiku (cheap, CC users always have this)
15
- * 3. OPENAI_API_KEY → gpt-4o-mini
16
- * 4. GOOGLE_API_KEY → gemini-2.0-flash
17
- * 5. Fallback: Ollama at http://localhost:11434
13
+ * 2. ANTHROPIC_API_KEY → Claude 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
18
18
  *
19
- * Supports: OpenAI, Anthropic, Google, OpenRouter, Ollama, z.ai, and 15+ more
19
+ * Supports any OpenAI-compatible endpoint plus first-class support for
20
+ * OpenAI, Anthropic, Google, Ollama, OpenRouter, and Claude CLI.
20
21
  */
21
22
  Object.defineProperty(exports, "__esModule", { value: true });
22
23
  exports.LlmClient = exports.RateLimitError = void 0;
@@ -76,7 +77,7 @@ function resolveLlmConfigForCC(overrides) {
76
77
  baseUrl: overrides.llmBaseUrl,
77
78
  apiKey: overrides.llmApiKey,
78
79
  model: overrides.llmModel ?? "claude-haiku-4-5-20251001",
79
- reflectModel: overrides.reflectModel ?? overrides.llmModel ?? "claude-sonnet-4-5-20250514",
80
+ reflectModel: overrides.reflectModel ?? overrides.llmModel ?? "claude-sonnet-4-6",
80
81
  provider,
81
82
  };
82
83
  }
@@ -90,7 +91,7 @@ function resolveLlmConfigForCC(overrides) {
90
91
  baseUrl: hcBaseUrl,
91
92
  apiKey: hcApiKey,
92
93
  model: hcModel ?? "claude-haiku-4-5-20251001",
93
- reflectModel: process.env.HICORTEX_REFLECT_MODEL ?? hcModel ?? "claude-sonnet-4-5-20250514",
94
+ reflectModel: process.env.HICORTEX_REFLECT_MODEL ?? hcModel ?? "claude-sonnet-4-6",
94
95
  provider,
95
96
  };
96
97
  }
@@ -101,7 +102,7 @@ function resolveLlmConfigForCC(overrides) {
101
102
  baseUrl: process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
102
103
  apiKey: anthropicKey,
103
104
  model: "claude-haiku-4-5-20251001",
104
- reflectModel: "claude-sonnet-4-5-20250514",
105
+ reflectModel: "claude-sonnet-4-6",
105
106
  provider: "anthropic",
106
107
  };
107
108
  }
@@ -111,8 +112,8 @@ function resolveLlmConfigForCC(overrides) {
111
112
  return {
112
113
  baseUrl,
113
114
  apiKey: openaiKey,
114
- model: "gpt-4o-mini",
115
- reflectModel: "gpt-4o-mini",
115
+ model: "gpt-5.4-nano",
116
+ reflectModel: "gpt-5.4-nano",
116
117
  provider: detectProvider(baseUrl),
117
118
  };
118
119
  }
@@ -121,8 +122,8 @@ function resolveLlmConfigForCC(overrides) {
121
122
  return {
122
123
  baseUrl: "https://generativelanguage.googleapis.com/v1beta",
123
124
  apiKey: googleKey,
124
- model: "gemini-2.0-flash",
125
- reflectModel: "gemini-2.0-flash",
125
+ model: "gemini-2.5-flash",
126
+ reflectModel: "gemini-2.5-flash",
126
127
  provider: "google",
127
128
  };
128
129
  }
@@ -150,8 +151,6 @@ function detectProvider(url) {
150
151
  return "openrouter";
151
152
  if (u.includes("googleapis") || u.includes("generativelanguage"))
152
153
  return "google";
153
- if (u.includes("z.ai") || u.includes("zai"))
154
- return "zai";
155
154
  return "openai";
156
155
  }
157
156
  function readOpenClawConfig() {
@@ -162,7 +161,7 @@ function readOpenClawConfig() {
162
161
  const primary = config?.agents?.defaults?.model?.primary;
163
162
  if (!primary)
164
163
  return null;
165
- // primary format is "provider/model" (e.g. "zai/glm-5-turbo", "openai/gpt-4o")
164
+ // primary format is "provider/model" (e.g. "openai/gpt-5", "anthropic/claude-sonnet-4-6")
166
165
  if (typeof primary === "string" && (primary.includes("/") || primary.includes(":"))) {
167
166
  const sep = primary.includes("/") ? "/" : ":";
168
167
  const [providerHint, ...rest] = primary.split(sep);
@@ -216,7 +215,7 @@ function readOcAuthKey(provider) {
216
215
  const raw = (0, node_fs_1.readFileSync)(authPath, "utf-8");
217
216
  const auth = JSON.parse(raw);
218
217
  const profiles = auth?.profiles ?? {};
219
- // Look for a profile matching the provider (e.g. "zai:default")
218
+ // Look for a profile matching the provider (e.g. "openai:default")
220
219
  for (const [profileId, profile] of Object.entries(profiles)) {
221
220
  const p = profile;
222
221
  if (p?.provider === provider ||
@@ -245,8 +244,8 @@ function resolveFromEnv() {
245
244
  return {
246
245
  baseUrl,
247
246
  apiKey: openaiKey,
248
- model: process.env.OPENAI_MODEL ?? "gpt-4o-mini",
249
- reflectModel: process.env.OPENAI_MODEL ?? "gpt-4o-mini",
247
+ model: process.env.OPENAI_MODEL ?? "gpt-5.4-nano",
248
+ reflectModel: process.env.OPENAI_MODEL ?? "gpt-5.4-nano",
250
249
  provider,
251
250
  };
252
251
  }
@@ -255,8 +254,8 @@ function resolveFromEnv() {
255
254
  return {
256
255
  baseUrl: process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
257
256
  apiKey: anthropicKey,
258
- model: "claude-sonnet-4-20250514",
259
- reflectModel: "claude-sonnet-4-20250514",
257
+ model: "claude-sonnet-4-6",
258
+ reflectModel: "claude-sonnet-4-6",
260
259
  provider: "anthropic",
261
260
  };
262
261
  }
@@ -265,8 +264,8 @@ function resolveFromEnv() {
265
264
  return {
266
265
  baseUrl: "https://generativelanguage.googleapis.com/v1beta",
267
266
  apiKey: googleKey,
268
- model: "gemini-2.0-flash",
269
- reflectModel: "gemini-2.0-flash",
267
+ model: "gemini-2.5-flash",
268
+ reflectModel: "gemini-2.5-flash",
270
269
  provider: "google",
271
270
  };
272
271
  }
@@ -280,33 +279,23 @@ function getEnvKeyForProvider(provider) {
280
279
  return process.env.ANTHROPIC_API_KEY;
281
280
  case "google":
282
281
  return process.env.GOOGLE_API_KEY;
283
- case "zai":
284
- return process.env.ZAI_API_KEY ?? process.env.LLM_API_KEY;
285
282
  default:
286
283
  return undefined;
287
284
  }
288
285
  }
289
- /** Default base URLs for all OC-supported providers (from OC gateway binary). */
286
+ /**
287
+ * Default base URLs for first-class supported providers.
288
+ *
289
+ * For any other provider, set llmBaseUrl explicitly in your config or use
290
+ * an OpenAI-compatible endpoint. The detectProvider() function will treat
291
+ * unknown URLs as openai-compatible by default.
292
+ */
290
293
  const PROVIDER_BASE_URLS = {
291
294
  openai: "https://api.openai.com/v1",
292
295
  anthropic: "https://api.anthropic.com",
293
296
  google: "https://generativelanguage.googleapis.com/v1beta",
294
297
  ollama: "http://localhost:11434",
295
298
  openrouter: "https://openrouter.ai/api",
296
- zai: "https://api.z.ai/api/anthropic",
297
- groq: "https://api.groq.com/openai/v1",
298
- deepseek: "https://api.deepseek.com",
299
- mistral: "https://api.mistral.ai/v1",
300
- together: "https://api.together.xyz/v1",
301
- perplexity: "https://api.perplexity.ai",
302
- nvidia: "https://integrate.api.nvidia.com/v1",
303
- xai: "https://api.x.ai/v1",
304
- venice: "https://api.venice.ai/api/v1",
305
- minimax: "https://api.minimaxi.com/v1",
306
- moonshot: "https://api.moonshot.ai/v1",
307
- kimi: "https://api.kimi.com/coding",
308
- chutes: "https://api.chutes.ai",
309
- kilo: "https://api.kilo.ai/api/gateway",
310
299
  };
311
300
  function getDefaultUrlForProvider(provider) {
312
301
  return PROVIDER_BASE_URLS[provider.toLowerCase()] ?? "https://api.openai.com/v1";
@@ -505,7 +494,7 @@ class LlmClient {
505
494
  if (this.config.provider === "ollama") {
506
495
  return this.completeOllama(model, prompt, maxTokens, timeoutMs);
507
496
  }
508
- if (this.config.provider === "anthropic" || this.config.provider === "zai") {
497
+ if (this.config.provider === "anthropic") {
509
498
  return this.completeAnthropic(model, prompt, maxTokens, timeoutMs);
510
499
  }
511
500
  return this.completeOpenAiCompat(model, prompt, maxTokens, timeoutMs);
@@ -586,7 +575,7 @@ class LlmClient {
586
575
  return result.trim();
587
576
  }
588
577
  /**
589
- * Anthropic Messages API (/v1/messages). Used for Anthropic and z.ai.
578
+ * Anthropic Messages API (/v1/messages).
590
579
  * Auth via x-api-key header.
591
580
  */
592
581
  async completeAnthropic(model, prompt, maxTokens, timeoutMs) {
@@ -622,8 +611,8 @@ class LlmClient {
622
611
  */
623
612
  async completeOpenAiCompat(model, prompt, maxTokens, timeoutMs) {
624
613
  const baseUrl = this.config.baseUrl.replace(/\/$/, "");
625
- // Some providers (z.ai) include version in base URL already
626
- const hasVersion = /\/v\d+\/?$/.test(baseUrl) || baseUrl.includes("/paas/v");
614
+ // Some providers include the API version in the base URL already
615
+ const hasVersion = /\/v\d+\/?$/.test(baseUrl);
627
616
  const url = hasVersion
628
617
  ? `${baseUrl}/chat/completions`
629
618
  : `${baseUrl}/v1/chat/completions`;
@@ -647,10 +636,6 @@ class LlmClient {
647
636
  this.handleRateLimit(resp);
648
637
  if (!resp.ok) {
649
638
  const text = await resp.text().catch(() => "");
650
- // z.ai: "Insufficient balance" likely means wrong endpoint (coding vs paas)
651
- if (text.includes("1113") || text.includes("Insufficient balance")) {
652
- console.log(`[hicortex] LLM billing error. Check that llmBaseUrl matches your plan. Current: ${baseUrl}`);
653
- }
654
639
  throw new Error(`LLM API error ${resp.status}: ${text}`);
655
640
  }
656
641
  const data = (await resp.json());
@@ -55,7 +55,8 @@ const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
55
55
  const zod_1 = require("zod");
56
56
  const db_js_1 = require("./db.js");
57
57
  const llm_js_1 = require("./llm.js");
58
- const license_js_1 = require("./license.js");
58
+ const features_js_1 = require("./features.js");
59
+ const state_js_1 = require("./state.js");
59
60
  const embedder_js_1 = require("./embedder.js");
60
61
  const storage = __importStar(require("./storage.js"));
61
62
  const retrieval = __importStar(require("./retrieval.js"));
@@ -121,12 +122,11 @@ function createMcpServer() {
121
122
  }, async ({ content, project, memory_type }) => {
122
123
  if (!db)
123
124
  return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
124
- const features = (0, license_js_1.getFeatures)(stateDir);
125
- if (features.maxMemories > 0 && storage.countMemories(db) >= features.maxMemories) {
125
+ if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
126
126
  return {
127
127
  content: [{
128
128
  type: "text",
129
- text: `Free tier limit reached (${features.maxMemories} memories). ` +
129
+ text: `Free tier limit reached (${(0, features_js_1.maxMemoriesAllowed)()} memories). ` +
130
130
  `Your existing memories and lessons still work — search and recall are unaffected. ` +
131
131
  `New memories won't be saved until you upgrade.\n\n` +
132
132
  `Upgrade for unlimited usage: https://hicortex.gamaze.com/`
@@ -296,11 +296,13 @@ async function startServer(options = {}) {
296
296
  ? `${llmConfig.reflectProvider}/${llmConfig.reflectModel}@${llmConfig.reflectBaseUrl}`
297
297
  : llmConfig.reflectModel;
298
298
  console.log(`[hicortex] LLM fast: ${llmConfig.provider}/${llmConfig.model}${distillInfo ? `, distill: ${distillInfo}` : ""}, reflect: ${reflectInfo}`);
299
- // License: read from options, config file, or env var
299
+ // One-time migration of legacy state files (no-op if state.json exists)
300
+ (0, state_js_1.migrateLegacyState)(stateDir);
301
+ // License: read from options, config file, or env var, init feature cache
300
302
  const licenseKey = options.licenseKey
301
303
  ?? savedConfig?.licenseKey
302
304
  ?? process.env.HICORTEX_LICENSE_KEY;
303
- (0, license_js_1.validateLicense)(licenseKey, stateDir).catch((err) => console.log(`[hicortex] License validation failed: ${err}`));
305
+ await (0, features_js_1.initFeatures)(licenseKey, stateDir);
304
306
  if (licenseKey) {
305
307
  console.log(`[hicortex] License key configured`);
306
308
  }
@@ -411,15 +413,12 @@ async function startServer(options = {}) {
411
413
  // Pro license blocks remote ingest (upgrade to Team for multi-client)
412
414
  const ip = req.ip ?? req.socket.remoteAddress ?? "";
413
415
  const isLocal = ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1";
414
- if (!isLocal) {
415
- const features = (0, license_js_1.getFeatures)(stateDir);
416
- if (features.remoteIngest === false) {
417
- res.status(403).json({
418
- error: "Pro license is single-machine. Upgrade to Team for multi-client remote ingestion.",
419
- upgrade: "https://hicortex.gamaze.com/",
420
- });
421
- return;
422
- }
416
+ if (!isLocal && !(0, features_js_1.remoteIngestAllowed)()) {
417
+ res.status(403).json({
418
+ error: "Pro license is single-machine. Upgrade to Team for multi-client remote ingestion.",
419
+ upgrade: "https://hicortex.gamaze.com/",
420
+ });
421
+ return;
423
422
  }
424
423
  const { content, source_agent, project, memory_type, privacy, source_session, session_date } = req.body ?? {};
425
424
  if (!content || typeof content !== "string") {
@@ -440,9 +439,8 @@ async function startServer(options = {}) {
440
439
  }
441
440
  }
442
441
  // License check
443
- const features = (0, license_js_1.getFeatures)(stateDir);
444
- if (features.maxMemories > 0 && storage.countMemories(db) >= features.maxMemories) {
445
- res.status(429).json({ error: "Memory limit reached", limit: features.maxMemories });
442
+ if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
443
+ res.status(429).json({ error: "Memory limit reached", limit: (0, features_js_1.maxMemoriesAllowed)() });
446
444
  return;
447
445
  }
448
446
  try {