@gamaze/hicortex 0.3.12 → 0.3.14

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/llm.d.ts CHANGED
@@ -85,6 +85,7 @@ export declare class LlmClient {
85
85
  completeDistill(prompt: string, maxTokens?: number): Promise<string>;
86
86
  /**
87
87
  * Complete with overridden baseUrl/apiKey/provider (used for reflect tier with separate endpoint).
88
+ * Creates a temporary LlmClient to avoid mutating shared config under concurrent calls.
88
89
  */
89
90
  private completeWithOverride;
90
91
  private complete;
package/dist/llm.js CHANGED
@@ -199,34 +199,6 @@ function readOcProviderBaseUrl(config, provider) {
199
199
  return providerConfig.baseUrl;
200
200
  return undefined;
201
201
  }
202
- /**
203
- * Read provider base URL from per-agent models.json files.
204
- * Scans all agent dirs for a matching provider entry.
205
- */
206
- function readAgentModelsBaseUrl(provider) {
207
- try {
208
- const { readdirSync } = require("node:fs");
209
- const agentsDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "agents");
210
- const agents = readdirSync(agentsDir);
211
- for (const agentId of agents) {
212
- try {
213
- const modelsPath = (0, node_path_1.join)(agentsDir, agentId, "agent", "models.json");
214
- const raw = (0, node_fs_1.readFileSync)(modelsPath, "utf-8");
215
- const models = JSON.parse(raw);
216
- const providerConfig = models?.providers?.[provider];
217
- if (providerConfig?.baseUrl)
218
- return providerConfig.baseUrl;
219
- }
220
- catch {
221
- // Skip agents without models.json
222
- }
223
- }
224
- }
225
- catch {
226
- // No agents dir
227
- }
228
- return undefined;
229
- }
230
202
  /**
231
203
  * Read API key from OC's per-agent auth-profiles.json.
232
204
  * Scans all agent dirs for a matching provider profile.
@@ -337,9 +309,6 @@ const PROVIDER_BASE_URLS = {
337
309
  function getDefaultUrlForProvider(provider) {
338
310
  return PROVIDER_BASE_URLS[provider.toLowerCase()] ?? "https://api.openai.com/v1";
339
311
  }
340
- function getEnvKeyForZai() {
341
- return process.env.ZAI_API_KEY ?? process.env.LLM_API_KEY;
342
- }
343
312
  /**
344
313
  * Find the claude CLI binary. Returns the full path or null.
345
314
  */
@@ -438,24 +407,16 @@ class LlmClient {
438
407
  }
439
408
  /**
440
409
  * Complete with overridden baseUrl/apiKey/provider (used for reflect tier with separate endpoint).
410
+ * Creates a temporary LlmClient to avoid mutating shared config under concurrent calls.
441
411
  */
442
412
  async completeWithOverride(baseUrl, apiKey, provider, model, prompt, maxTokens, timeoutMs) {
443
- if (this.isRateLimited) {
444
- throw new RateLimitError(this.rateLimitedUntil - Date.now());
445
- }
446
- // Temporarily swap config for this call
447
- const saved = { baseUrl: this.config.baseUrl, apiKey: this.config.apiKey, provider: this.config.provider };
448
- this.config.baseUrl = baseUrl;
449
- this.config.apiKey = apiKey;
450
- this.config.provider = provider;
451
- try {
452
- return await this.complete(model, prompt, maxTokens, timeoutMs);
453
- }
454
- finally {
455
- this.config.baseUrl = saved.baseUrl;
456
- this.config.apiKey = saved.apiKey;
457
- this.config.provider = saved.provider;
458
- }
413
+ const tempClient = new LlmClient({
414
+ ...this.config,
415
+ baseUrl,
416
+ apiKey,
417
+ provider,
418
+ });
419
+ return tempClient.complete(model, prompt, maxTokens, timeoutMs);
459
420
  }
460
421
  async complete(model, prompt, maxTokens, timeoutMs) {
461
422
  if (this.isRateLimited) {
@@ -530,7 +491,8 @@ class LlmClient {
530
491
  */
531
492
  async completeAnthropic(model, prompt, maxTokens, timeoutMs) {
532
493
  const baseUrl = this.config.baseUrl.replace(/\/$/, "");
533
- const url = `${baseUrl}/v1/messages`;
494
+ const hasVersion = /\/v\d+\/?$/.test(baseUrl);
495
+ const url = hasVersion ? `${baseUrl}/messages` : `${baseUrl}/v1/messages`;
534
496
  const resp = await fetch(url, {
535
497
  method: "POST",
536
498
  headers: {
@@ -178,7 +178,7 @@ async function startServer(options = {}) {
178
178
  const dbPath = (0, db_js_1.resolveDbPath)(options.dbPath);
179
179
  console.log(`[hicortex] Initializing database at ${dbPath}`);
180
180
  db = (0, db_js_1.initDb)(dbPath);
181
- stateDir = dbPath.replace(/\/hicortex\.db$/, "");
181
+ stateDir = require("node:path").dirname(dbPath);
182
182
  // LLM config: check config.json first, then env vars, then claude CLI
183
183
  const savedConfig = readConfigFile(stateDir);
184
184
  let llmConfig;
@@ -189,9 +189,24 @@ async function startServer(options = {}) {
189
189
  }
190
190
  else {
191
191
  console.warn("[hicortex] claude-cli configured but claude binary not found, falling back");
192
- llmConfig = (0, llm_js_1.resolveLlmConfigForCC)();
192
+ llmConfig = (0, llm_js_1.resolveLlmConfigForCC)({
193
+ llmBaseUrl: savedConfig?.llmBaseUrl,
194
+ llmApiKey: savedConfig?.llmApiKey,
195
+ llmModel: savedConfig?.llmModel,
196
+ reflectModel: savedConfig?.reflectModel,
197
+ });
193
198
  }
194
199
  }
200
+ else if (savedConfig?.llmBackend === "ollama") {
201
+ // Ollama: no API key needed, default to localhost:11434
202
+ llmConfig = {
203
+ baseUrl: savedConfig.llmBaseUrl ?? "http://localhost:11434",
204
+ apiKey: "",
205
+ model: savedConfig.llmModel ?? "qwen3.5:4b",
206
+ reflectModel: savedConfig.reflectModel ?? savedConfig.llmModel ?? "qwen3.5:4b",
207
+ provider: "ollama",
208
+ };
209
+ }
195
210
  else {
196
211
  llmConfig = (0, llm_js_1.resolveLlmConfigForCC)({
197
212
  llmBaseUrl: savedConfig?.llmBaseUrl,
@@ -242,25 +257,7 @@ async function startServer(options = {}) {
242
257
  // Express app
243
258
  const app = (0, express_1.default)();
244
259
  app.use(express_1.default.json());
245
- // Optional bearer token auth (skip for /health and localhost when no token)
246
- if (authToken) {
247
- console.log(`[hicortex] Bearer token auth enabled`);
248
- app.use((req, res, next) => {
249
- // Always allow health endpoint
250
- if (req.path === "/health")
251
- return next();
252
- // Allow localhost without auth
253
- const ip = req.ip ?? req.socket.remoteAddress ?? "";
254
- if (ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1")
255
- return next();
256
- // Check bearer token
257
- const auth = req.headers.authorization;
258
- if (auth === `Bearer ${authToken}`)
259
- return next();
260
- res.status(401).json({ error: "Unauthorized" });
261
- });
262
- }
263
- // CORS: allow Claude Desktop (https://claude.ai) and other browser-based MCP clients
260
+ // CORS: must be before auth so preflight OPTIONS requests get proper headers
264
261
  app.use((req, res, next) => {
265
262
  const origin = req.headers.origin;
266
263
  if (origin) {
@@ -268,6 +265,7 @@ async function startServer(options = {}) {
268
265
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
269
266
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization");
270
267
  res.setHeader("Access-Control-Allow-Credentials", "true");
268
+ res.setHeader("Vary", "Origin");
271
269
  }
272
270
  if (req.method === "OPTIONS") {
273
271
  res.status(204).end();
@@ -275,6 +273,21 @@ async function startServer(options = {}) {
275
273
  }
276
274
  next();
277
275
  });
276
+ // Optional bearer token auth (skip for /health, OPTIONS, and localhost)
277
+ if (authToken) {
278
+ console.log(`[hicortex] Bearer token auth enabled`);
279
+ app.use((req, res, next) => {
280
+ if (req.path === "/health")
281
+ return next();
282
+ const ip = req.ip ?? req.socket.remoteAddress ?? "";
283
+ if (ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1")
284
+ return next();
285
+ const auth = req.headers.authorization;
286
+ if (auth === `Bearer ${authToken}`)
287
+ return next();
288
+ res.status(401).json({ error: "Unauthorized" });
289
+ });
290
+ }
278
291
  // SSE transport management — each connection gets its own McpServer instance
279
292
  const transports = new Map();
280
293
  // Health endpoint
@@ -297,7 +310,14 @@ async function startServer(options = {}) {
297
310
  transport.onclose = () => {
298
311
  transports.delete(transport.sessionId);
299
312
  };
300
- await mcpServer.connect(transport);
313
+ try {
314
+ await mcpServer.connect(transport);
315
+ }
316
+ catch (err) {
317
+ transports.delete(transport.sessionId);
318
+ if (!res.headersSent)
319
+ res.status(500).json({ error: "MCP connect failed" });
320
+ }
301
321
  });
302
322
  // Message endpoint — client POSTs MCP messages here
303
323
  app.post("/messages", async (req, res) => {
@@ -307,8 +327,14 @@ async function startServer(options = {}) {
307
327
  return;
308
328
  }
309
329
  const transport = transports.get(sessionId);
310
- // Pass parsed body since express.json() already consumed the stream
311
- await transport.handlePostMessage(req, res, req.body);
330
+ try {
331
+ // Pass parsed body since express.json() already consumed the stream
332
+ await transport.handlePostMessage(req, res, req.body);
333
+ }
334
+ catch (err) {
335
+ if (!res.headersSent)
336
+ res.status(500).json({ error: "Message handling failed" });
337
+ }
312
338
  });
313
339
  // Start listening
314
340
  const server = app.listen(port, host, () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamaze/hicortex",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "Human-like memory for self-improving AI agents. Automatic capturing, nightly reflection, and cross-agent learning. Works with Claude Code and OpenClaw.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {