@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/features.js CHANGED
@@ -1,19 +1,20 @@
1
1
  "use strict";
2
2
  /**
3
- * Centralized feature gating — single source of truth for tier-dependent values.
3
+ * Feature gating — all gates removed as of 0.10.0.
4
4
  *
5
- * Why this exists:
6
- * - getFeatures() in license.ts was sync but validateLicense was async, creating
7
- * a race where Pro users got free-tier features during the validation window.
8
- * - License checks were scattered across 8+ call sites with subtly different
9
- * handling (e.g., consolidate.ts:308 had a dynamic import in a hot loop to
10
- * dodge a circular import).
5
+ * Personal and noncommercial use is fully featured under the
6
+ * PolyForm Noncommercial License. Commercial use requires a per-seat
7
+ * license (see COMMERCIAL.md). There is no technical feature gating;
8
+ * the license key's only remaining role is the "licensed to <org>"
9
+ * display in `hicortex status`.
11
10
  *
12
- * All feature decisions now flow through this module. Call initFeatures() once at
13
- * process boot before serving any requests; sync getters become deterministic.
11
+ * The functions below are kept as trivial wrappers so call sites
12
+ * compile without churn. They will be removed entirely in a future
13
+ * cleanup pass once callers have been audited.
14
14
  */
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.initFeatures = initFeatures;
17
+ exports.getValidatedLicense = getValidatedLicense;
17
18
  exports.isPro = isPro;
18
19
  exports.maxMemoriesAllowed = maxMemoriesAllowed;
19
20
  exports.memoryCapReached = memoryCapReached;
@@ -25,117 +26,80 @@ const node_os_1 = require("node:os");
25
26
  const license_js_1 = require("./license.js");
26
27
  const state_js_1 = require("./state.js");
27
28
  const DEFAULT_STATE_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
28
- const FREE_FEATURES = {
29
+ // A single canonical "full" feature set — no tiers.
30
+ const FULL_FEATURES = {
29
31
  reflection: true,
30
32
  vectorSearch: true,
31
- maxMemories: 250,
33
+ maxMemories: -1,
32
34
  crossAgent: true,
33
35
  remoteIngest: true,
34
36
  };
35
- let currentFeatures = FREE_FEATURES;
37
+ let currentFeatures = FULL_FEATURES;
36
38
  let initialized = false;
39
+ // Validated license info for display purposes only (no feature gating).
40
+ let validatedLicenseInfo = null;
37
41
  function persistTier(stateDir, info) {
38
42
  (0, state_js_1.updateState)((s) => {
39
43
  s.tier = {
40
44
  tier: info.tier,
41
45
  validatedAt: new Date().toISOString(),
42
- features: info.features,
46
+ features: FULL_FEATURES,
43
47
  };
44
48
  return s;
45
49
  }, stateDir);
46
50
  }
47
51
  /**
48
- * Initialize the feature cache. Call ONCE at process boot before any feature
49
- * gating queries. Race fix:
50
- * 1. Synchronously load persisted tier from disk (instant, deterministic)
51
- * 2. If no persisted tier and we have a key, AWAIT first validation
52
- * 3. If persisted tier exists, kick off background re-validation
53
- *
54
- * After this returns, sync getters (isPro, lessonsLimit, etc.) are deterministic
55
- * and reflect the user's actual tier — no more "free during validation window".
52
+ * Initialize license display. Call ONCE at process boot.
53
+ * No feature gates are applied regardless of the validation result.
56
54
  */
57
- async function initFeatures(licenseKey, stateDir = DEFAULT_STATE_DIR, hostVersion = "0.0.0") {
55
+ async function initFeatures(licenseKey, stateDir = DEFAULT_STATE_DIR, _hostVersion = "0.0.0") {
58
56
  if (initialized)
59
57
  return;
60
58
  initialized = true;
61
- // Step 1: Load persisted tier from state.json (instant)
62
- const persisted = (0, state_js_1.loadState)(stateDir).tier;
63
- if (persisted) {
64
- currentFeatures = persisted.features;
65
- }
66
- else {
67
- currentFeatures = FREE_FEATURES;
68
- }
69
- // Step 2: No key → free tier, done. Pro loader is only run for paid tiers.
59
+ currentFeatures = FULL_FEATURES;
70
60
  if (!licenseKey)
71
61
  return;
72
- // Step 3: Validate
73
- if (!persisted) {
74
- // First-time: AWAIT validation so the very first request sees the right tier
75
- try {
76
- const info = await (0, license_js_1.validateLicense)(licenseKey, stateDir);
77
- currentFeatures = info.features;
78
- if (info.valid) {
79
- persistTier(stateDir, info);
80
- }
81
- }
82
- catch {
83
- // Validation failed (network, etc.) — stay on free
62
+ // Validate the key for display purposes only — a failure keeps the server
63
+ // fully functional.
64
+ try {
65
+ const info = await (0, license_js_1.validateLicense)(licenseKey, stateDir);
66
+ validatedLicenseInfo = info;
67
+ if (info.valid) {
68
+ persistTier(stateDir, info);
84
69
  }
85
70
  }
86
- else {
87
- // Already have a persisted tier; re-validate in background
88
- (0, license_js_1.validateLicense)(licenseKey, stateDir)
89
- .then((info) => {
90
- currentFeatures = info.features;
91
- if (info.valid) {
92
- persistTier(stateDir, info);
93
- }
94
- })
95
- .catch(() => {
96
- // Keep persisted features
97
- });
98
- }
99
- // Step 4: If the current tier is paid, try to load the Pro extension bundle.
100
- // This is best-effort — if loading fails (network, missing tarball, bad
101
- // extraction, Pro package throws on activate), OSS defaults remain in effect
102
- // and the host keeps running. No user-visible crash.
103
- if (isPro()) {
104
- try {
105
- const { loadPro } = await import("./pro-loader.js");
106
- await loadPro(licenseKey, stateDir, hostVersion);
107
- }
108
- catch (err) {
109
- const msg = err instanceof Error ? err.message : String(err);
110
- console.warn(`[hicortex][pro] Pro loader failed to import: ${msg}`);
111
- }
71
+ catch {
72
+ // Non-fatal continue fully functional without a validated display tier
112
73
  }
113
74
  }
75
+ /** Returns the validated license info if a key was supplied and validated. */
76
+ function getValidatedLicense() {
77
+ return validatedLicenseInfo;
78
+ }
114
79
  // ---------------------------------------------------------------------------
115
- // Public API — sync getters used everywhere in the codebase
80
+ // Public API — kept for call-site compatibility; all return "fully unlocked"
116
81
  // ---------------------------------------------------------------------------
117
- /** Are we on a paid tier (Pro, Team, Lifetime)? */
82
+ /** Always false no memory cap. */
118
83
  function isPro() {
119
- return currentFeatures.maxMemories === -1;
84
+ return true;
120
85
  }
121
- /** Memory count cap. -1 = unlimited (paid). */
86
+ /** Always -1 (unlimited). */
122
87
  function maxMemoriesAllowed() {
123
- return currentFeatures.maxMemories;
88
+ return -1;
124
89
  }
125
- /** Has the memory cap been hit? Pass current count from caller. */
126
- function memoryCapReached(currentCount) {
127
- const max = maxMemoriesAllowed();
128
- return max > 0 && currentCount >= max;
90
+ /** Always false no cap is ever reached. */
91
+ function memoryCapReached(_currentCount) {
92
+ return false;
129
93
  }
130
- /** Number of lessons to inject into CLAUDE.md / before_agent_start. */
94
+ /** Always 20. */
131
95
  function lessonsLimit() {
132
- return isPro() ? 20 : 10;
96
+ return 20;
133
97
  }
134
- /** Is remote /ingest allowed? Free + Team yes, Pro (single-machine) no. */
98
+ /** Always true — remote ingest is always allowed. */
135
99
  function remoteIngestAllowed() {
136
- return currentFeatures.remoteIngest !== false;
100
+ return true;
137
101
  }
138
- /** Direct read of the underlying features (for callers that need the full record). */
102
+ /** Direct read of the underlying features record. */
139
103
  function getCurrentFeatures() {
140
104
  return currentFeatures;
141
105
  }
package/dist/graph.d.ts CHANGED
@@ -50,5 +50,5 @@ export interface GraphNeighbor {
50
50
  content: string;
51
51
  project: string | null;
52
52
  }
53
- export declare function getNeighbors(db: Database.Database, memoryId: string, limit?: number): GraphNeighbor[];
53
+ export declare function getNeighbors(db: Database.Database, memoryId: string, limit?: number, relationship?: string): GraphNeighbor[];
54
54
  export declare function shortestPath(db: Database.Database, fromId: string, toId: string, maxDepth?: number): string[] | null;
package/dist/graph.js CHANGED
@@ -175,14 +175,20 @@ function detectHubs(db, thresholdMultiplier = 2, minLinks = 3) {
175
175
  }
176
176
  return hubs;
177
177
  }
178
- function getNeighbors(db, memoryId, limit = 10) {
179
- const rows = db
180
- .prepare(`SELECT source_id, target_id, relationship, strength
178
+ function getNeighbors(db, memoryId, limit = 10, relationship) {
179
+ let sql = `SELECT source_id, target_id, relationship, strength
181
180
  FROM memory_links
182
- WHERE source_id = ? OR target_id = ?
183
- ORDER BY strength DESC
184
- LIMIT ?`)
185
- .all(memoryId, memoryId, limit);
181
+ WHERE (source_id = ? OR target_id = ?)`;
182
+ const params = [memoryId, memoryId];
183
+ if (relationship) {
184
+ sql += ` AND relationship = ?`;
185
+ params.push(relationship);
186
+ }
187
+ sql += ` ORDER BY strength DESC LIMIT ?`;
188
+ params.push(limit);
189
+ const rows = db
190
+ .prepare(sql)
191
+ .all(...params);
186
192
  const results = [];
187
193
  for (const row of rows) {
188
194
  const isOutgoing = row.source_id === memoryId;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Hermes transcript reader — the nightly capture path for Nous Research Hermes.
3
+ *
4
+ * Hermes stores conversation in a SQLite state DB, one per profile:
5
+ * ~/.hermes/profiles/<profile>/state.db (per-profile agents: lenny, raider, nano)
6
+ * ~/.hermes/state.db (global, non-profile setups)
7
+ *
8
+ * Schema (relevant columns):
9
+ * sessions(id TEXT PK, started_at REAL, ended_at REAL, ...)
10
+ * messages(session_id TEXT, role TEXT, content TEXT, tool_name TEXT, timestamp REAL, active INT, ...)
11
+ *
12
+ * Compaction is NON-DESTRUCTIVE (compacted-out turns kept with active=0) and
13
+ * sessions are retained ~90 days — so the full history of any ended session is
14
+ * readable here nightly. No runtime plugin capture is needed; the Hermes plugin
15
+ * is recall-only.
16
+ *
17
+ * We process only ENDED sessions (ended_at set) that ended since the last run.
18
+ * A live session is distilled after it ends — this avoids partial-session
19
+ * distillation and keeps per-session dedup clean (chunks are stored as
20
+ * `<sessionId>#<chunkIndex>`; see nightly.ts).
21
+ */
22
+ import type { TranscriptBatch } from "./transcript-reader.js";
23
+ /**
24
+ * Read Hermes sessions that ended since `since`, across all profiles.
25
+ * Returns one batch per session, parallel to readCcTranscripts().
26
+ */
27
+ export declare function readHermesSessions(since: Date, hermesHome?: string): TranscriptBatch[];
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ /**
3
+ * Hermes transcript reader — the nightly capture path for Nous Research Hermes.
4
+ *
5
+ * Hermes stores conversation in a SQLite state DB, one per profile:
6
+ * ~/.hermes/profiles/<profile>/state.db (per-profile agents: lenny, raider, nano)
7
+ * ~/.hermes/state.db (global, non-profile setups)
8
+ *
9
+ * Schema (relevant columns):
10
+ * sessions(id TEXT PK, started_at REAL, ended_at REAL, ...)
11
+ * messages(session_id TEXT, role TEXT, content TEXT, tool_name TEXT, timestamp REAL, active INT, ...)
12
+ *
13
+ * Compaction is NON-DESTRUCTIVE (compacted-out turns kept with active=0) and
14
+ * sessions are retained ~90 days — so the full history of any ended session is
15
+ * readable here nightly. No runtime plugin capture is needed; the Hermes plugin
16
+ * is recall-only.
17
+ *
18
+ * We process only ENDED sessions (ended_at set) that ended since the last run.
19
+ * A live session is distilled after it ends — this avoids partial-session
20
+ * distillation and keeps per-session dedup clean (chunks are stored as
21
+ * `<sessionId>#<chunkIndex>`; see nightly.ts).
22
+ */
23
+ var __importDefault = (this && this.__importDefault) || function (mod) {
24
+ return (mod && mod.__esModule) ? mod : { "default": mod };
25
+ };
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.readHermesSessions = readHermesSessions;
28
+ const node_fs_1 = require("node:fs");
29
+ const node_path_1 = require("node:path");
30
+ const node_os_1 = require("node:os");
31
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
32
+ const HERMES_HOME = process.env.HERMES_HOME || (0, node_path_1.join)((0, node_os_1.homedir)(), ".hermes");
33
+ /**
34
+ * Session `source` values that are NOT primary conversations and must not be
35
+ * captured — capturing them would pollute long-term memory with automated,
36
+ * non-conversational runs. `cron` is Hermes' scheduled-task source (garmin
37
+ * syncs, daily scans, self-reflection loops). This mirrors the MemoryProvider
38
+ * contract's own guidance to skip non-primary `agent_context` (e.g. "cron").
39
+ * Everything else (cli, discord, telegram, slack, …) is a real conversation.
40
+ */
41
+ const NON_PRIMARY_SOURCES = new Set(["cron"]);
42
+ /**
43
+ * Message roles that are metadata/noise, not conversation — mapped so the
44
+ * distiller's extractConversationText drops them (it skips "tool_result").
45
+ */
46
+ const NOISE_ROLES = new Set(["tool", "session_meta"]);
47
+ /**
48
+ * Read Hermes sessions that ended since `since`, across all profiles.
49
+ * Returns one batch per session, parallel to readCcTranscripts().
50
+ */
51
+ function readHermesSessions(since, hermesHome = HERMES_HOME) {
52
+ const batches = [];
53
+ const sinceEpoch = since.getTime() / 1000; // Hermes timestamps are unix seconds (REAL)
54
+ for (const { profile, dbPath } of discoverProfileDbs(hermesHome)) {
55
+ let db;
56
+ try {
57
+ db = new better_sqlite3_1.default(dbPath, { readonly: true, fileMustExist: true });
58
+ }
59
+ catch {
60
+ continue; // locked / unreadable / wrong owner — skip, retry next run
61
+ }
62
+ try {
63
+ const sessions = db
64
+ .prepare("SELECT id, ended_at, source FROM sessions WHERE ended_at IS NOT NULL AND ended_at > ? ORDER BY ended_at")
65
+ .all(sinceEpoch);
66
+ const msgStmt = db.prepare("SELECT role, content, tool_name, timestamp FROM messages WHERE session_id = ? ORDER BY timestamp, id");
67
+ for (const s of sessions) {
68
+ // Skip automated (non-primary) sessions — cron runs are not
69
+ // conversations and would pollute memory. Checked before pulling
70
+ // messages so we don't even read them.
71
+ if (NON_PRIMARY_SOURCES.has(s.source))
72
+ continue;
73
+ const rows = msgStmt.all(s.id);
74
+ // Skip only genuinely empty sessions. Do NOT gate on message count —
75
+ // a short 2-message exchange can carry a real decision. Meaningful-
76
+ // content is gated downstream by the post-denoise 200-char check in
77
+ // nightly.ts, so short-but-dense sessions aren't dropped here.
78
+ if (rows.length === 0)
79
+ continue;
80
+ // Map Hermes rows to the shape extractConversationText() understands
81
+ // (it reads m.role + m.content). Metadata/noise roles (tool results,
82
+ // session_meta) are relabelled "tool_result" so the distiller drops them.
83
+ const entries = rows.map((r) => ({
84
+ role: NOISE_ROLES.has(r.role) ? "tool_result" : r.role,
85
+ content: r.content ?? "",
86
+ }));
87
+ const endTs = s.ended_at ?? rows[rows.length - 1].timestamp;
88
+ batches.push({
89
+ sessionId: s.id,
90
+ projectName: profile,
91
+ sourceAgent: `hermes/${profile}`,
92
+ date: new Date(endTs * 1000).toISOString().slice(0, 10),
93
+ entries,
94
+ });
95
+ }
96
+ }
97
+ catch {
98
+ // Query failed (schema drift on a Hermes upgrade) — skip this DB, don't crash the run.
99
+ }
100
+ finally {
101
+ db.close();
102
+ }
103
+ }
104
+ return batches;
105
+ }
106
+ /**
107
+ * Find every Hermes state DB: one per profile, plus the global DB for
108
+ * non-profile installs. The profile name becomes the provenance label.
109
+ */
110
+ function discoverProfileDbs(hermesHome) {
111
+ const out = [];
112
+ const profilesDir = (0, node_path_1.join)(hermesHome, "profiles");
113
+ try {
114
+ for (const entry of (0, node_fs_1.readdirSync)(profilesDir)) {
115
+ if (entry.startsWith(".") || entry.startsWith("_"))
116
+ continue;
117
+ const dbPath = (0, node_path_1.join)(profilesDir, entry, "state.db");
118
+ try {
119
+ if ((0, node_fs_1.statSync)(dbPath).isFile())
120
+ out.push({ profile: entry, dbPath });
121
+ }
122
+ catch {
123
+ // no state.db for this profile
124
+ }
125
+ }
126
+ }
127
+ catch {
128
+ // no profiles dir
129
+ }
130
+ const globalDb = (0, node_path_1.join)(hermesHome, "state.db");
131
+ if ((0, node_fs_1.existsSync)(globalDb))
132
+ out.push({ profile: "default", dbPath: globalDb });
133
+ return out;
134
+ }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,21 @@
1
1
  /**
2
- * Hicortex OpenClaw Plugin — Long-term Memory That Learns.
2
+ * Hicortex OpenClaw Plugin — Long-term Memory That Learns. (0.10.0)
3
3
  *
4
- * Pure in-process plugin: no sidecar, no HTTP. Uses better-sqlite3 + sqlite-vec
5
- * for storage, @huggingface/transformers for embeddings, and multi-provider LLM
6
- * for distillation and consolidation.
4
+ * Thin-client model: the plugin requires a Hicortex server (co-located at
5
+ * http://127.0.0.1:8787 by default, or a remote URL via `serverUrl` config).
6
+ * No local database, no local LLM, no embedder, no consolidation scheduler.
7
+ *
8
+ * Install once: `openclaw plugins install @gamaze/hicortex`
9
+ * Run server: `npx @gamaze/hicortex init`
10
+ *
11
+ * Responsibilities (recall-only adapter, like the Hermes plugin):
12
+ * - before_agent_start → GET /lessons (fail-soft, 3s timeout) → inject context
13
+ * - Tools → HTTP proxies to /search, /context, /ingest, /lessons
14
+ *
15
+ * CAPTURE IS NOT THIS PLUGIN'S JOB. OpenClaw persists sessions at
16
+ * ~/.openclaw/agents/<agentId>/sessions/*.jsonl in the Pi v3 format; the
17
+ * machine's Hicortex nightly reads them via oc-transcript-reader.ts —
18
+ * canonical nightly-from-logs, same as CC JSONL and Hermes state.db.
7
19
  */
8
20
  declare const _default: {
9
21
  id: string;