@gamaze/hicortex 0.2.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/dist/index.js ADDED
@@ -0,0 +1,557 @@
1
+ "use strict";
2
+ /**
3
+ * Hicortex OpenClaw Plugin — Long-term Memory That Learns.
4
+ *
5
+ * Pure in-process plugin: no sidecar, no HTTP. Uses better-sqlite3 + sqlite-vec
6
+ * for storage, @huggingface/transformers for embeddings, and multi-provider LLM
7
+ * for distillation and consolidation.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ const node_path_1 = require("node:path");
44
+ const node_fs_1 = require("node:fs");
45
+ const db_js_1 = require("./db.js");
46
+ const license_js_1 = require("./license.js");
47
+ const llm_js_1 = require("./llm.js");
48
+ const node_fs_2 = require("node:fs");
49
+ const node_os_1 = require("node:os");
50
+ const embedder_js_1 = require("./embedder.js");
51
+ const storage = __importStar(require("./storage.js"));
52
+ const retrieval = __importStar(require("./retrieval.js"));
53
+ const distiller_js_1 = require("./distiller.js");
54
+ const consolidate_js_1 = require("./consolidate.js");
55
+ // ---------------------------------------------------------------------------
56
+ // Module state — initialized in registerService.start()
57
+ // ---------------------------------------------------------------------------
58
+ let db = null;
59
+ let llm = null;
60
+ let cancelConsolidation = null;
61
+ let stateDir = "";
62
+ // ---------------------------------------------------------------------------
63
+ // Plugin export
64
+ // ---------------------------------------------------------------------------
65
+ exports.default = {
66
+ id: "hicortex",
67
+ name: "Hicortex \u2014 Long-term Memory That Learns",
68
+ kind: "lifecycle",
69
+ register(api) {
70
+ // -----------------------------------------------------------------------
71
+ // Background service: init DB, embedder, LLM, consolidation timer
72
+ // -----------------------------------------------------------------------
73
+ api.registerService({
74
+ id: "hicortex-service",
75
+ async start(ctx) {
76
+ const config = (ctx.config ?? {});
77
+ stateDir = ctx.stateDir ?? (0, node_path_1.join)(process.env.HOME ?? "~", ".hicortex");
78
+ const log = ctx.logger
79
+ ? (msg) => ctx.logger.info(msg)
80
+ : console.log;
81
+ // Ensure data directory exists
82
+ const dataDir = (0, node_path_1.join)(stateDir, "data");
83
+ (0, node_fs_1.mkdirSync)(dataDir, { recursive: true });
84
+ // Initialize database
85
+ const dbPath = config.dbPath ?? (0, node_path_1.join)(dataDir, "hicortex.db");
86
+ log(`[hicortex] Initializing database at ${dbPath}`);
87
+ db = (0, db_js_1.initDb)(dbPath);
88
+ // Auto-configure LLM: resolve → test → persist
89
+ const llmConfig = await autoConfigureLlm(config, log);
90
+ llm = new llm_js_1.LlmClient(llmConfig);
91
+ log(`[hicortex] LLM: ${llmConfig.provider}/${llmConfig.model} ` +
92
+ `(reflect: ${llmConfig.reflectModel})`);
93
+ // License check (non-blocking)
94
+ (0, license_js_1.validateLicense)(config.licenseKey, stateDir).catch((err) => log(`[hicortex] License validation failed: ${err}`));
95
+ // Schedule nightly consolidation
96
+ const consolidateHour = config.consolidateHour ?? 2;
97
+ cancelConsolidation = (0, consolidate_js_1.scheduleConsolidation)(db, llm, embedder_js_1.embed, consolidateHour);
98
+ // Seed the bootstrap lesson on first run
99
+ await injectSeedLesson(db, log);
100
+ // Auto-add tools to tools.allow if using a restrictive profile
101
+ ensureToolsAllowed(log);
102
+ // Log stats
103
+ const stats = (0, db_js_1.getStats)(db, dbPath);
104
+ log(`[hicortex] Ready: ${stats.memories} memories, ${stats.links} links, ` +
105
+ `${Math.round(stats.db_size_bytes / 1024)} KB`);
106
+ },
107
+ async stop() {
108
+ if (cancelConsolidation) {
109
+ cancelConsolidation();
110
+ cancelConsolidation = null;
111
+ }
112
+ if (db) {
113
+ db.close();
114
+ db = null;
115
+ }
116
+ llm = null;
117
+ },
118
+ });
119
+ // -----------------------------------------------------------------------
120
+ // Hook: before_agent_start — inject lessons into agent context
121
+ // -----------------------------------------------------------------------
122
+ api.on("before_agent_start", async (event, ctx) => {
123
+ if (!db)
124
+ return {};
125
+ try {
126
+ const lessons = storage.getLessons(db, 7, ctx.project);
127
+ if (lessons.length === 0)
128
+ return {};
129
+ const maxFeatures = (0, license_js_1.getFeatures)(stateDir);
130
+ const maxLessons = maxFeatures.maxMemories === -1 ? 20 : 10;
131
+ const formatted = lessons.slice(0, maxLessons).map((l) => {
132
+ // Extract just the lesson text from the structured content
133
+ const match = l.content.match(/## Lesson: (.+)/);
134
+ return match ? `- ${match[1]}` : `- ${l.content.slice(0, 200)}`;
135
+ });
136
+ let context = `\n\n## Hicortex Lessons (auto-injected from long-term memory)\n` +
137
+ `These are actionable lessons learned from past sessions:\n\n` +
138
+ formatted.join("\n") +
139
+ "\n";
140
+ // Daily friendly reminder when at memory cap
141
+ const memCount = storage.countMemories(db);
142
+ if (maxFeatures.maxMemories > 0 && memCount >= maxFeatures.maxMemories) {
143
+ context +=
144
+ `\n---\nHicortex free tier: ${maxFeatures.maxMemories} memories stored. ` +
145
+ `New memories can no longer be saved, and your agent has stopped learning and self-improving from new sessions. ` +
146
+ `Existing memories, lessons, and search still work. ` +
147
+ `Upgrade for unlimited usage: https://hicortex.gamaze.com/ ` +
148
+ `— after purchase, tell me your key and I'll activate it for you.\n`;
149
+ }
150
+ return { appendSystemContext: context };
151
+ }
152
+ catch {
153
+ return {};
154
+ }
155
+ });
156
+ // -----------------------------------------------------------------------
157
+ // Hook: agent_end — distill conversation into memories
158
+ // -----------------------------------------------------------------------
159
+ api.on("agent_end", async (event, ctx) => {
160
+ if (!db || !llm)
161
+ return;
162
+ if (!event.success || event.messages.length < 4)
163
+ return;
164
+ try {
165
+ const transcript = (0, distiller_js_1.extractConversationText)(event.messages);
166
+ if (transcript.length < 200)
167
+ return;
168
+ const date = new Date().toISOString().slice(0, 10);
169
+ const projectName = ctx.project ?? "unknown";
170
+ const sourceAgent = `openclaw/${ctx.agentId ?? "unknown"}`;
171
+ const entries = await (0, distiller_js_1.distillSession)(llm, transcript, projectName, date);
172
+ if (entries.length === 0)
173
+ return;
174
+ // Check license cap
175
+ const features = (0, license_js_1.getFeatures)(stateDir);
176
+ if (features.maxMemories > 0 &&
177
+ storage.countMemories(db) >= features.maxMemories) {
178
+ console.warn(`[hicortex] Free tier limit reached (${features.maxMemories} memories). ` +
179
+ `Search and lessons still work, but new memories won't be saved. ` +
180
+ `Upgrade for unlimited usage: https://hicortex.gamaze.com/`);
181
+ return;
182
+ }
183
+ // Embed and ingest each entry
184
+ for (const entry of entries) {
185
+ try {
186
+ const embedding = await (0, embedder_js_1.embed)(entry);
187
+ storage.insertMemory(db, entry, embedding, {
188
+ sourceAgent,
189
+ sourceSession: ctx.sessionKey,
190
+ project: projectName,
191
+ privacy: "WORK",
192
+ memoryType: "episode",
193
+ });
194
+ }
195
+ catch (err) {
196
+ const msg = err instanceof Error ? err.message : String(err);
197
+ console.error(`[hicortex] Failed to ingest entry: ${msg}`);
198
+ }
199
+ }
200
+ }
201
+ catch {
202
+ // Non-fatal — session capture failing shouldn't break the agent
203
+ }
204
+ });
205
+ // -----------------------------------------------------------------------
206
+ // Hook: session_end — check if consolidation is overdue
207
+ // -----------------------------------------------------------------------
208
+ api.on("session_end", async (event, _ctx) => {
209
+ if (!db || !llm || event.messageCount < 4)
210
+ return;
211
+ // Opportunistic consolidation if no nightly run in 48h
212
+ try {
213
+ const { readFileSync: readFs } = await import("node:fs");
214
+ const { join: joinPath } = await import("node:path");
215
+ const { homedir: homeDir } = await import("node:os");
216
+ const lastPath = joinPath(homeDir(), ".claude", "memory", "last-consolidated.txt");
217
+ const lastTs = readFs(lastPath, "utf-8").trim();
218
+ const lastDate = new Date(lastTs);
219
+ const hoursSince = (Date.now() - lastDate.getTime()) / (1000 * 60 * 60);
220
+ if (hoursSince > 48) {
221
+ console.log("[hicortex] Consolidation overdue — triggering now");
222
+ (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed).catch((err) => {
223
+ console.error("[hicortex] Opportunistic consolidation failed:", err);
224
+ });
225
+ }
226
+ }
227
+ catch {
228
+ // No timestamp file — first run, consolidation will happen on schedule
229
+ }
230
+ });
231
+ // -----------------------------------------------------------------------
232
+ // Tools — registered as factory functions per OC plugin API
233
+ // -----------------------------------------------------------------------
234
+ api.registerTool((_ctx) => ({
235
+ name: "hicortex_search",
236
+ description: "Search long-term memory using semantic similarity. Returns the most relevant memories from past sessions.",
237
+ parameters: {
238
+ type: "object",
239
+ properties: {
240
+ query: { type: "string", description: "Search query text" },
241
+ limit: { type: "number", description: "Max results (default 5)" },
242
+ project: { type: "string", description: "Filter by project name" },
243
+ },
244
+ required: ["query"],
245
+ },
246
+ async execute(_callId, args, _ctx) {
247
+ if (!db)
248
+ return { error: "Hicortex not initialized" };
249
+ try {
250
+ const results = await retrieval.retrieve(db, embedder_js_1.embed, args.query, {
251
+ limit: args.limit,
252
+ project: args.project,
253
+ });
254
+ return formatToolResults(results);
255
+ }
256
+ catch (err) {
257
+ return { error: `Search failed: ${err instanceof Error ? err.message : String(err)}` };
258
+ }
259
+ },
260
+ }), { name: "hicortex_search" });
261
+ api.registerTool((_ctx) => ({
262
+ name: "hicortex_context",
263
+ description: "Get recent context memories, optionally filtered by project. Useful to recall what happened recently.",
264
+ parameters: {
265
+ type: "object",
266
+ properties: {
267
+ project: { type: "string", description: "Filter by project name" },
268
+ limit: { type: "number", description: "Max results (default 10)" },
269
+ },
270
+ },
271
+ execute(_callId, args, _ctx) {
272
+ if (!db)
273
+ return { error: "Hicortex not initialized" };
274
+ try {
275
+ const results = retrieval.searchContext(db, {
276
+ project: args?.project,
277
+ limit: args?.limit,
278
+ });
279
+ return formatToolResults(results);
280
+ }
281
+ catch (err) {
282
+ return { error: `Context search failed: ${err instanceof Error ? err.message : String(err)}` };
283
+ }
284
+ },
285
+ }), { name: "hicortex_context" });
286
+ api.registerTool((_ctx) => ({
287
+ name: "hicortex_ingest",
288
+ description: "Store a new memory in long-term storage. Use for important facts, decisions, or lessons.",
289
+ parameters: {
290
+ type: "object",
291
+ properties: {
292
+ content: { type: "string", description: "Memory content to store" },
293
+ project: { type: "string", description: "Project this memory belongs to" },
294
+ memory_type: {
295
+ type: "string",
296
+ enum: ["episode", "lesson", "fact", "decision"],
297
+ description: "Type of memory (default: episode)",
298
+ },
299
+ },
300
+ required: ["content"],
301
+ },
302
+ async execute(_callId, args, context) {
303
+ if (!db)
304
+ return { error: "Hicortex not initialized" };
305
+ const features = (0, license_js_1.getFeatures)(stateDir);
306
+ if (features.maxMemories > 0 && storage.countMemories(db) >= features.maxMemories) {
307
+ return {
308
+ content: [{
309
+ type: "text",
310
+ text: `Free tier limit reached (${features.maxMemories} memories). ` +
311
+ `Your existing memories and lessons still work — search and recall are unaffected. ` +
312
+ `New memories won't be saved until you upgrade.\n\n` +
313
+ `Upgrade for unlimited usage: https://hicortex.gamaze.com/`
314
+ }],
315
+ };
316
+ }
317
+ try {
318
+ const embedding = await (0, embedder_js_1.embed)(args.content);
319
+ const id = storage.insertMemory(db, args.content, embedding, {
320
+ sourceAgent: `openclaw/${context?.agentId ?? "manual"}`,
321
+ project: args.project,
322
+ memoryType: args.memory_type ?? "episode",
323
+ privacy: "WORK",
324
+ });
325
+ return { content: [{ type: "text", text: `Memory stored (id: ${id.slice(0, 8)})` }] };
326
+ }
327
+ catch (err) {
328
+ return { error: `Ingest failed: ${err instanceof Error ? err.message : String(err)}` };
329
+ }
330
+ },
331
+ }), { name: "hicortex_ingest" });
332
+ api.registerTool((_ctx) => ({
333
+ name: "hicortex_lessons",
334
+ description: "Get actionable lessons learned from past sessions. Auto-generated insights about mistakes to avoid.",
335
+ parameters: {
336
+ type: "object",
337
+ properties: {
338
+ days: { type: "number", description: "Look back N days (default 7)" },
339
+ project: { type: "string", description: "Filter by project name" },
340
+ },
341
+ },
342
+ execute(_callId, args, _ctx) {
343
+ if (!db)
344
+ return { error: "Hicortex not initialized" };
345
+ try {
346
+ const lessons = storage.getLessons(db, args.days ?? 7, args.project);
347
+ if (lessons.length === 0) {
348
+ return { content: [{ type: "text", text: "No lessons found for the specified period." }] };
349
+ }
350
+ const text = lessons.map((l) => `- ${l.content.slice(0, 500)}`).join("\n");
351
+ return { content: [{ type: "text", text }] };
352
+ }
353
+ catch (err) {
354
+ return { error: `Lessons fetch failed: ${err instanceof Error ? err.message : String(err)}` };
355
+ }
356
+ },
357
+ }), { name: "hicortex_lessons" });
358
+ },
359
+ };
360
+ // ---------------------------------------------------------------------------
361
+ // Helpers
362
+ // ---------------------------------------------------------------------------
363
+ // ---------------------------------------------------------------------------
364
+ // Auto-configure LLM: resolve config → test connection → persist if new
365
+ // ---------------------------------------------------------------------------
366
+ async function autoConfigureLlm(pluginConfig, log) {
367
+ // Step 1: Resolve LLM config from all sources
368
+ const llmConfig = (0, llm_js_1.resolveLlmConfig)({
369
+ llmBaseUrl: pluginConfig.llmBaseUrl,
370
+ llmApiKey: pluginConfig.llmApiKey,
371
+ llmModel: pluginConfig.llmModel,
372
+ reflectModel: pluginConfig.reflectModel,
373
+ });
374
+ // Step 2: Test the connection
375
+ log(`[hicortex] Testing LLM connection: ${llmConfig.provider}/${llmConfig.model} @ ${llmConfig.baseUrl}`);
376
+ const testClient = new llm_js_1.LlmClient(llmConfig);
377
+ try {
378
+ const response = await testClient.completeFast("Respond with just the word OK", 10);
379
+ if (response && response.length > 0) {
380
+ log(`[hicortex] LLM connection verified`);
381
+ // Step 3: Persist to OC config so future startups skip detection
382
+ persistProviderConfig(llmConfig, log);
383
+ return llmConfig;
384
+ }
385
+ }
386
+ catch (err) {
387
+ const msg = err instanceof Error ? err.message : String(err);
388
+ log(`[hicortex] LLM test failed (${llmConfig.baseUrl}): ${msg}`);
389
+ }
390
+ // Step 4: If test failed and this is z.ai, try the alternate endpoint
391
+ if (llmConfig.provider === "zai") {
392
+ const altUrl = llmConfig.baseUrl.includes("/coding/")
393
+ ? llmConfig.baseUrl.replace("/coding/", "/")
394
+ : llmConfig.baseUrl.replace("/api/paas/", "/api/coding/paas/");
395
+ log(`[hicortex] Trying alternate z.ai endpoint: ${altUrl}`);
396
+ llmConfig.baseUrl = altUrl;
397
+ const altClient = new llm_js_1.LlmClient(llmConfig);
398
+ try {
399
+ const response = await altClient.completeFast("Respond with just the word OK", 10);
400
+ if (response && response.length > 0) {
401
+ log(`[hicortex] LLM connection verified on alternate endpoint`);
402
+ persistProviderConfig(llmConfig, log);
403
+ return llmConfig;
404
+ }
405
+ }
406
+ catch (err) {
407
+ const msg = err instanceof Error ? err.message : String(err);
408
+ log(`[hicortex] Alternate z.ai endpoint also failed: ${msg}`);
409
+ }
410
+ }
411
+ // Step 5: Fall back — return the config anyway, log instructions
412
+ log(`[hicortex] WARNING: Could not verify LLM connection. ` +
413
+ `Distillation and consolidation may fail. ` +
414
+ `To fix: add models.providers.${llmConfig.provider}.baseUrl to ~/.openclaw/openclaw.json ` +
415
+ `or set llmBaseUrl in the hicortex plugin config.`);
416
+ return llmConfig;
417
+ }
418
+ /**
419
+ * Persist verified provider config to openclaw.json so future startups
420
+ * skip detection and go straight to the verified URL.
421
+ */
422
+ function persistProviderConfig(llmConfig, log) {
423
+ try {
424
+ const configPath = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "openclaw.json");
425
+ const raw = (0, node_fs_2.readFileSync)(configPath, "utf-8");
426
+ const config = JSON.parse(raw);
427
+ // Check if baseUrl already stored for this provider
428
+ const existing = config?.models?.providers?.[llmConfig.provider]?.baseUrl;
429
+ if (existing === llmConfig.baseUrl)
430
+ return; // Already persisted
431
+ // Write the provider config
432
+ if (!config.models)
433
+ config.models = {};
434
+ if (!config.models.providers)
435
+ config.models.providers = {};
436
+ if (!config.models.providers[llmConfig.provider]) {
437
+ config.models.providers[llmConfig.provider] = {};
438
+ }
439
+ const prov = config.models.providers[llmConfig.provider];
440
+ prov.baseUrl = llmConfig.baseUrl;
441
+ // OC requires a models array — preserve existing or add the active model
442
+ if (!prov.models || !Array.isArray(prov.models)) {
443
+ prov.models = [{
444
+ id: llmConfig.model,
445
+ name: llmConfig.model,
446
+ input: ["text"],
447
+ contextWindow: 128000,
448
+ maxTokens: 8192,
449
+ }];
450
+ }
451
+ (0, node_fs_2.writeFileSync)(configPath, JSON.stringify(config, null, 2));
452
+ log(`[hicortex] Persisted LLM config: ${llmConfig.provider} → ${llmConfig.baseUrl}`);
453
+ }
454
+ catch {
455
+ // Non-fatal — config works in memory even if we can't persist
456
+ }
457
+ }
458
+ /**
459
+ * Track when the memory cap was first hit. Returns days since cap was reached.
460
+ * Stores timestamp in stateDir/cap-hit.txt on first detection.
461
+ */
462
+ function getDaysSinceCapHit(dir) {
463
+ const capFile = (0, node_path_1.join)(dir, "cap-hit.txt");
464
+ try {
465
+ const ts = (0, node_fs_2.readFileSync)(capFile, "utf-8").trim();
466
+ const hitDate = new Date(ts);
467
+ return Math.floor((Date.now() - hitDate.getTime()) / (1000 * 60 * 60 * 24));
468
+ }
469
+ catch {
470
+ // First time hitting cap — record it
471
+ try {
472
+ (0, node_fs_2.writeFileSync)(capFile, new Date().toISOString());
473
+ }
474
+ catch { /* non-fatal */ }
475
+ return 0;
476
+ }
477
+ }
478
+ const HICORTEX_TOOLS = [
479
+ "hicortex_search",
480
+ "hicortex_context",
481
+ "hicortex_ingest",
482
+ "hicortex_lessons",
483
+ ];
484
+ /**
485
+ * Ensure hicortex tools are in tools.allow so they're visible to agents
486
+ * regardless of the tools.profile setting.
487
+ */
488
+ function ensureToolsAllowed(log) {
489
+ try {
490
+ const configPath = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "openclaw.json");
491
+ const raw = (0, node_fs_2.readFileSync)(configPath, "utf-8");
492
+ const config = JSON.parse(raw);
493
+ if (!config.tools)
494
+ config.tools = {};
495
+ if (!Array.isArray(config.tools.allow))
496
+ config.tools.allow = [];
497
+ const missing = HICORTEX_TOOLS.filter((t) => !config.tools.allow.includes(t));
498
+ if (missing.length === 0)
499
+ return;
500
+ config.tools.allow.push(...missing);
501
+ (0, node_fs_2.writeFileSync)(configPath, JSON.stringify(config, null, 2));
502
+ log(`[hicortex] Added tools to allow list: ${missing.join(", ")}`);
503
+ }
504
+ catch {
505
+ // Non-fatal
506
+ }
507
+ }
508
+ // ---------------------------------------------------------------------------
509
+ // Seed lesson — injected on first run, teaches the agent to self-improve
510
+ // ---------------------------------------------------------------------------
511
+ const SEED_LESSON = `## Lesson: 1% Daily Self-Improvement
512
+
513
+ Self-improvement protocol: Review your sessions daily. Scan for mistakes, gaps, user corrections, and friction. Identify 1-2 issues maximum. Apply one targeted fix to your instruction files (AGENT.md, IDENTITY.md, TOOLS.md, SOUL.md, or CLAUDE.md). Never overcorrect — feedback is guidance, not emergency. One fix per issue. Never rewrite entire systems. Before changing something, ask: is this a real pattern or am I reacting to noise?
514
+
515
+ The flywheel: Sessions → Hicortex captures → Nightly consolidation → Lessons generated → Lessons injected into your context → You apply one fix → Better sessions tomorrow.
516
+
517
+ Use hicortex_lessons to check for new insights. If a lesson has high confidence, apply it. If medium, observe for one more day. If nothing needs fixing today, skip — that's fine.
518
+
519
+ **Severity:** critical
520
+ **Confidence:** high
521
+ **Generated:** seed-lesson`;
522
+ async function injectSeedLesson(database, log) {
523
+ try {
524
+ // Check if seed lesson already exists (match on unique content)
525
+ const existing = storage.getLessons(database, 365);
526
+ const hasSeed = existing.some((l) => l.content.includes("1% Daily Self-Improvement") ||
527
+ l.source_agent === "hicortex/seed");
528
+ if (hasSeed)
529
+ return;
530
+ // Embed and store
531
+ const embedding = await (0, embedder_js_1.embed)(SEED_LESSON);
532
+ storage.insertMemory(database, SEED_LESSON, embedding, {
533
+ sourceAgent: "hicortex/seed",
534
+ project: "global",
535
+ memoryType: "lesson",
536
+ baseStrength: 0.95,
537
+ privacy: "WORK",
538
+ });
539
+ log("[hicortex] Seed lesson injected: Daily Self-Improvement Protocol");
540
+ }
541
+ catch (err) {
542
+ // Non-fatal — log but don't crash the plugin
543
+ const msg = err instanceof Error ? err.message : String(err);
544
+ log(`[hicortex] Warning: could not inject seed lesson: ${msg}`);
545
+ }
546
+ }
547
+ function formatToolResults(results) {
548
+ if (results.length === 0) {
549
+ return {
550
+ content: [{ type: "text", text: "No memories found." }],
551
+ };
552
+ }
553
+ const text = results
554
+ .map((r) => `[${r.memory_type}] (score: ${r.score.toFixed(3)}, strength: ${r.effective_strength.toFixed(3)}) ${r.content.slice(0, 500)}`)
555
+ .join("\n\n");
556
+ return { content: [{ type: "text", text }] };
557
+ }
@@ -0,0 +1,5 @@
1
+ import type { LicenseInfo } from "./types.js";
2
+ /** Validate a license key against the Hicortex API */
3
+ 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"];
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateLicense = validateLicense;
4
+ exports.getFeatures = getFeatures;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const VALIDATE_URL = "https://hicortex.gamaze.com/api/validate";
8
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
9
+ const OFFLINE_GRACE_DAYS = 7;
10
+ // In-memory cache
11
+ let cachedLicense = null;
12
+ let cacheTimestamp = 0;
13
+ const FREE_LICENSE = {
14
+ valid: false,
15
+ tier: "free",
16
+ features: {
17
+ reflection: true,
18
+ vectorSearch: true,
19
+ maxMemories: 250,
20
+ crossAgent: true,
21
+ },
22
+ };
23
+ /** Validate a license key against the Hicortex API */
24
+ async function validateLicense(key, stateDir) {
25
+ // No key = free tier
26
+ if (!key)
27
+ return FREE_LICENSE;
28
+ // Check in-memory cache
29
+ if (cachedLicense && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
30
+ return cachedLicense;
31
+ }
32
+ try {
33
+ const resp = await fetch(VALIDATE_URL, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify({ key }),
37
+ signal: AbortSignal.timeout(10_000),
38
+ });
39
+ if (!resp.ok) {
40
+ throw new Error(`HTTP ${resp.status}`);
41
+ }
42
+ const data = (await resp.json());
43
+ // Cache result
44
+ cachedLicense = data;
45
+ cacheTimestamp = Date.now();
46
+ // Persist last successful validation timestamp for offline grace
47
+ if (data.valid) {
48
+ persistValidationTimestamp(stateDir);
49
+ }
50
+ return data;
51
+ }
52
+ catch {
53
+ // Network failure — check offline grace period
54
+ return offlineFallback(key, stateDir);
55
+ }
56
+ }
57
+ /** Get current features, using cache or free tier defaults */
58
+ function getFeatures(stateDir) {
59
+ if (cachedLicense)
60
+ return cachedLicense.features;
61
+ return FREE_LICENSE.features;
62
+ }
63
+ function persistValidationTimestamp(stateDir) {
64
+ try {
65
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(stateDir, "license-validated.txt"), new Date().toISOString());
66
+ }
67
+ catch {
68
+ // Non-critical
69
+ }
70
+ }
71
+ function offlineFallback(key, stateDir) {
72
+ const tsPath = (0, node_path_1.join)(stateDir, "license-validated.txt");
73
+ if (!(0, node_fs_1.existsSync)(tsPath))
74
+ return FREE_LICENSE;
75
+ try {
76
+ const lastValidated = new Date((0, node_fs_1.readFileSync)(tsPath, "utf-8").trim());
77
+ const daysSince = (Date.now() - lastValidated.getTime()) / (1000 * 60 * 60 * 24);
78
+ if (daysSince <= OFFLINE_GRACE_DAYS) {
79
+ // Within grace period — assume last known state was valid
80
+ return cachedLicense ?? {
81
+ valid: true,
82
+ tier: "pro",
83
+ features: {
84
+ reflection: true,
85
+ vectorSearch: true,
86
+ maxMemories: -1,
87
+ crossAgent: true,
88
+ },
89
+ };
90
+ }
91
+ }
92
+ catch {
93
+ // Corrupted file
94
+ }
95
+ return FREE_LICENSE;
96
+ }