@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/README.md +119 -0
- package/dist/consolidate.d.ts +36 -0
- package/dist/consolidate.js +482 -0
- package/dist/db.d.ts +19 -0
- package/dist/db.js +140 -0
- package/dist/distiller.d.ts +15 -0
- package/dist/distiller.js +186 -0
- package/dist/embedder.d.ts +20 -0
- package/dist/embedder.js +85 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +557 -0
- package/dist/license.d.ts +5 -0
- package/dist/license.js +96 -0
- package/dist/llm.d.ts +66 -0
- package/dist/llm.js +421 -0
- package/dist/prompts.d.ts +16 -0
- package/dist/prompts.js +117 -0
- package/dist/retrieval.d.ts +47 -0
- package/dist/retrieval.js +320 -0
- package/dist/storage.d.ts +98 -0
- package/dist/storage.js +326 -0
- package/dist/types.d.ts +132 -0
- package/dist/types.js +6 -0
- package/openclaw.plugin.json +70 -0
- package/package.json +42 -0
- package/skills/hicortex-activate/SKILL.md +53 -0
- package/skills/hicortex-learn/SKILL.md +40 -0
- package/skills/hicortex-memory/SKILL.md +39 -0
package/dist/storage.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Storage layer — CRUD operations for the SQLite + sqlite-vec database.
|
|
4
|
+
* Ported from hicortex/storage.py. All functions are synchronous (better-sqlite3).
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.embedToBlob = embedToBlob;
|
|
8
|
+
exports.insertMemory = insertMemory;
|
|
9
|
+
exports.getMemory = getMemory;
|
|
10
|
+
exports.updateMemory = updateMemory;
|
|
11
|
+
exports.strengthenMemory = strengthenMemory;
|
|
12
|
+
exports.deleteMemory = deleteMemory;
|
|
13
|
+
exports.vectorSearch = vectorSearch;
|
|
14
|
+
exports.searchFts = searchFts;
|
|
15
|
+
exports.addLink = addLink;
|
|
16
|
+
exports.getLinks = getLinks;
|
|
17
|
+
exports.deleteLinks = deleteLinks;
|
|
18
|
+
exports.insertMemoriesBatch = insertMemoriesBatch;
|
|
19
|
+
exports.countMemories = countMemories;
|
|
20
|
+
exports.getRecentMemories = getRecentMemories;
|
|
21
|
+
exports.getMemoriesSince = getMemoriesSince;
|
|
22
|
+
exports.getLessons = getLessons;
|
|
23
|
+
exports.getPruneCandidates = getPruneCandidates;
|
|
24
|
+
exports.getAllLinkCounts = getAllLinkCounts;
|
|
25
|
+
exports.getUnscoredMemories = getUnscoredMemories;
|
|
26
|
+
const node_crypto_1 = require("node:crypto");
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Helpers
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
function nowIso() {
|
|
31
|
+
return new Date().toISOString();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Serialize a Float32Array embedding to a Buffer for sqlite-vec.
|
|
35
|
+
*/
|
|
36
|
+
function embedToBlob(embedding) {
|
|
37
|
+
return Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
38
|
+
}
|
|
39
|
+
function rowToMemory(row) {
|
|
40
|
+
return row;
|
|
41
|
+
}
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Single memory CRUD
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
/**
|
|
46
|
+
* Insert a memory and its vector embedding. Returns the generated UUID.
|
|
47
|
+
*/
|
|
48
|
+
function insertMemory(db, content, embedding, opts = {}) {
|
|
49
|
+
const id = (0, node_crypto_1.randomUUID)();
|
|
50
|
+
const ts = opts.createdAt ?? nowIso();
|
|
51
|
+
const ingestedTs = nowIso();
|
|
52
|
+
db.prepare(`INSERT INTO memories
|
|
53
|
+
(id, content, base_strength, last_accessed, access_count,
|
|
54
|
+
created_at, ingested_at, source_agent, source_session, project,
|
|
55
|
+
privacy, memory_type)
|
|
56
|
+
VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)`).run(id, content, opts.baseStrength ?? 0.5, ts, ts, ingestedTs, opts.sourceAgent ?? "default", opts.sourceSession ?? null, opts.project ?? null, opts.privacy ?? "WORK", opts.memoryType ?? "episode");
|
|
57
|
+
db.prepare("INSERT INTO memory_vectors (id, embedding) VALUES (?, ?)").run(id, embedToBlob(embedding));
|
|
58
|
+
return id;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get a single memory by ID. Returns null if not found.
|
|
62
|
+
*/
|
|
63
|
+
function getMemory(db, memoryId) {
|
|
64
|
+
const row = db
|
|
65
|
+
.prepare("SELECT * FROM memories WHERE id = ?")
|
|
66
|
+
.get(memoryId);
|
|
67
|
+
return row ? rowToMemory(row) : null;
|
|
68
|
+
}
|
|
69
|
+
// Allowed columns for updateMemory
|
|
70
|
+
const ALLOWED_UPDATE_FIELDS = new Set([
|
|
71
|
+
"content",
|
|
72
|
+
"base_strength",
|
|
73
|
+
"last_accessed",
|
|
74
|
+
"access_count",
|
|
75
|
+
"source_agent",
|
|
76
|
+
"source_session",
|
|
77
|
+
"project",
|
|
78
|
+
"privacy",
|
|
79
|
+
"memory_type",
|
|
80
|
+
]);
|
|
81
|
+
/**
|
|
82
|
+
* Update specific fields on a memory.
|
|
83
|
+
*/
|
|
84
|
+
function updateMemory(db, memoryId, fields) {
|
|
85
|
+
const keys = Object.keys(fields);
|
|
86
|
+
if (keys.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
for (const k of keys) {
|
|
89
|
+
if (!ALLOWED_UPDATE_FIELDS.has(k)) {
|
|
90
|
+
throw new Error(`Cannot update field: ${k}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const setClause = keys.map((k) => `"${k}" = ?`).join(", ");
|
|
94
|
+
const values = keys.map((k) => fields[k]);
|
|
95
|
+
values.push(memoryId);
|
|
96
|
+
db.prepare(`UPDATE memories SET ${setClause} WHERE id = ?`).run(...values);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Atomically increment access_count and reset last_accessed.
|
|
100
|
+
*/
|
|
101
|
+
function strengthenMemory(db, memoryId, nowIsoStr) {
|
|
102
|
+
db.prepare(`UPDATE memories
|
|
103
|
+
SET access_count = access_count + 1, last_accessed = ?
|
|
104
|
+
WHERE id = ?`).run(nowIsoStr, memoryId);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Delete a memory, its vector, and all its links.
|
|
108
|
+
*/
|
|
109
|
+
function deleteMemory(db, memoryId) {
|
|
110
|
+
db.prepare("DELETE FROM memory_links WHERE source_id = ? OR target_id = ?").run(memoryId, memoryId);
|
|
111
|
+
db.prepare("DELETE FROM memory_vectors WHERE id = ?").run(memoryId);
|
|
112
|
+
db.prepare("DELETE FROM memories WHERE id = ?").run(memoryId);
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Vector search
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
/**
|
|
118
|
+
* Find similar memories by vector distance. Returns memories with distance field.
|
|
119
|
+
*/
|
|
120
|
+
function vectorSearch(db, queryEmbedding, limit = 10, excludeIds = []) {
|
|
121
|
+
const rows = db
|
|
122
|
+
.prepare("SELECT id, distance FROM memory_vectors WHERE embedding MATCH ? AND k = ? ORDER BY distance")
|
|
123
|
+
.all(embedToBlob(queryEmbedding), limit);
|
|
124
|
+
const excludeSet = new Set(excludeIds);
|
|
125
|
+
const results = [];
|
|
126
|
+
for (const row of rows) {
|
|
127
|
+
if (excludeSet.has(row.id))
|
|
128
|
+
continue;
|
|
129
|
+
const mem = db
|
|
130
|
+
.prepare("SELECT * FROM memories WHERE id = ?")
|
|
131
|
+
.get(row.id);
|
|
132
|
+
if (!mem)
|
|
133
|
+
continue;
|
|
134
|
+
results.push({ ...rowToMemory(mem), distance: row.distance });
|
|
135
|
+
}
|
|
136
|
+
return results;
|
|
137
|
+
}
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// FTS5 search
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
/**
|
|
142
|
+
* Full-text search using FTS5 BM25 ranking.
|
|
143
|
+
* Returns memories with a rank field (lower is better).
|
|
144
|
+
*/
|
|
145
|
+
function searchFts(db, query, limit = 10, privacy, sourceAgent) {
|
|
146
|
+
const conditions = ["memories_fts MATCH ?"];
|
|
147
|
+
const params = [query];
|
|
148
|
+
if (privacy && privacy.length > 0) {
|
|
149
|
+
const placeholders = privacy.map(() => "?").join(", ");
|
|
150
|
+
conditions.push(`m.privacy IN (${placeholders})`);
|
|
151
|
+
params.push(...privacy);
|
|
152
|
+
}
|
|
153
|
+
if (sourceAgent) {
|
|
154
|
+
conditions.push("m.source_agent = ?");
|
|
155
|
+
params.push(sourceAgent);
|
|
156
|
+
}
|
|
157
|
+
const where = conditions.join(" AND ");
|
|
158
|
+
params.push(limit);
|
|
159
|
+
const rows = db
|
|
160
|
+
.prepare(`SELECT m.*, fts.rank
|
|
161
|
+
FROM memories_fts fts
|
|
162
|
+
JOIN memories m ON m.rowid = fts.rowid
|
|
163
|
+
WHERE ${where}
|
|
164
|
+
ORDER BY fts.rank
|
|
165
|
+
LIMIT ?`)
|
|
166
|
+
.all(...params);
|
|
167
|
+
return rows.map((r) => {
|
|
168
|
+
const rank = r.rank;
|
|
169
|
+
const mem = rowToMemory(r);
|
|
170
|
+
return { ...mem, rank };
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Links
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
/**
|
|
177
|
+
* Create a link between two memories.
|
|
178
|
+
*/
|
|
179
|
+
function addLink(db, sourceId, targetId, relationship, strength = 0.5) {
|
|
180
|
+
db.prepare(`INSERT OR REPLACE INTO memory_links
|
|
181
|
+
(source_id, target_id, relationship, strength, created_at)
|
|
182
|
+
VALUES (?, ?, ?, ?, ?)`).run(sourceId, targetId, relationship, strength, nowIso());
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get links for a memory. direction: 'outgoing', 'incoming', or 'both'.
|
|
186
|
+
*/
|
|
187
|
+
function getLinks(db, memoryId, direction = "both") {
|
|
188
|
+
let rows;
|
|
189
|
+
if (direction === "outgoing") {
|
|
190
|
+
rows = db
|
|
191
|
+
.prepare("SELECT * FROM memory_links WHERE source_id = ?")
|
|
192
|
+
.all(memoryId);
|
|
193
|
+
}
|
|
194
|
+
else if (direction === "incoming") {
|
|
195
|
+
rows = db
|
|
196
|
+
.prepare("SELECT * FROM memory_links WHERE target_id = ?")
|
|
197
|
+
.all(memoryId);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
rows = db
|
|
201
|
+
.prepare("SELECT * FROM memory_links WHERE source_id = ? OR target_id = ?")
|
|
202
|
+
.all(memoryId, memoryId);
|
|
203
|
+
}
|
|
204
|
+
return rows;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Delete all links involving a memory.
|
|
208
|
+
*/
|
|
209
|
+
function deleteLinks(db, memoryId) {
|
|
210
|
+
db.prepare("DELETE FROM memory_links WHERE source_id = ? OR target_id = ?").run(memoryId, memoryId);
|
|
211
|
+
}
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Batch & query helpers
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
/**
|
|
216
|
+
* Batch insert memories. Returns count inserted.
|
|
217
|
+
*/
|
|
218
|
+
function insertMemoriesBatch(db, memories) {
|
|
219
|
+
const insertMem = db.prepare(`INSERT INTO memories
|
|
220
|
+
(id, content, base_strength, last_accessed, access_count,
|
|
221
|
+
created_at, ingested_at, source_agent, source_session, project,
|
|
222
|
+
privacy, memory_type)
|
|
223
|
+
VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)`);
|
|
224
|
+
const insertVec = db.prepare("INSERT INTO memory_vectors (id, embedding) VALUES (?, ?)");
|
|
225
|
+
const tx = db.transaction(() => {
|
|
226
|
+
let count = 0;
|
|
227
|
+
for (const mem of memories) {
|
|
228
|
+
const id = (0, node_crypto_1.randomUUID)();
|
|
229
|
+
const ts = nowIso();
|
|
230
|
+
insertMem.run(id, mem.content, mem.baseStrength ?? 0.5, ts, ts, ts, mem.sourceAgent ?? "default", mem.sourceSession ?? null, mem.project ?? null, mem.privacy ?? "WORK", mem.memoryType ?? "episode");
|
|
231
|
+
insertVec.run(id, embedToBlob(mem.embedding));
|
|
232
|
+
count++;
|
|
233
|
+
}
|
|
234
|
+
return count;
|
|
235
|
+
});
|
|
236
|
+
return tx();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Return total memory count.
|
|
240
|
+
*/
|
|
241
|
+
function countMemories(db) {
|
|
242
|
+
return db.prepare("SELECT count(*) as cnt FROM memories").get().cnt;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Get memories created in the last N days, newest first.
|
|
246
|
+
*/
|
|
247
|
+
function getRecentMemories(db, days = 7, limit = 50) {
|
|
248
|
+
const rows = db
|
|
249
|
+
.prepare(`SELECT * FROM memories
|
|
250
|
+
WHERE created_at >= datetime('now', ?)
|
|
251
|
+
ORDER BY created_at DESC LIMIT ?`)
|
|
252
|
+
.all(`-${days} days`, limit);
|
|
253
|
+
return rows.map(rowToMemory);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get all memories ingested after a timestamp.
|
|
257
|
+
* Uses ingested_at (when the memory entered the DB) for consolidation correctness.
|
|
258
|
+
*/
|
|
259
|
+
function getMemoriesSince(db, sinceIso) {
|
|
260
|
+
const rows = db
|
|
261
|
+
.prepare(`SELECT * FROM memories
|
|
262
|
+
WHERE ingested_at > ?
|
|
263
|
+
ORDER BY ingested_at ASC`)
|
|
264
|
+
.all(sinceIso);
|
|
265
|
+
return rows.map(rowToMemory);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get lesson-type memories from the last N days.
|
|
269
|
+
*/
|
|
270
|
+
function getLessons(db, days = 7, project) {
|
|
271
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
272
|
+
if (project) {
|
|
273
|
+
const rows = db
|
|
274
|
+
.prepare(`SELECT * FROM memories
|
|
275
|
+
WHERE memory_type = 'lesson' AND created_at > ? AND project = ?
|
|
276
|
+
ORDER BY created_at DESC`)
|
|
277
|
+
.all(cutoff, project);
|
|
278
|
+
return rows.map(rowToMemory);
|
|
279
|
+
}
|
|
280
|
+
const rows = db
|
|
281
|
+
.prepare(`SELECT * FROM memories
|
|
282
|
+
WHERE memory_type = 'lesson' AND created_at > ?
|
|
283
|
+
ORDER BY created_at DESC`)
|
|
284
|
+
.all(cutoff);
|
|
285
|
+
return rows.map(rowToMemory);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get memories older than cutoff with zero access (prune candidates).
|
|
289
|
+
*/
|
|
290
|
+
function getPruneCandidates(db, cutoffIso) {
|
|
291
|
+
const rows = db
|
|
292
|
+
.prepare(`SELECT * FROM memories
|
|
293
|
+
WHERE created_at < ? AND access_count = 0
|
|
294
|
+
ORDER BY created_at ASC`)
|
|
295
|
+
.all(cutoffIso);
|
|
296
|
+
return rows.map(rowToMemory);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get link counts for all memories in a single query.
|
|
300
|
+
* Returns a map of memory_id -> total link count (both directions).
|
|
301
|
+
*/
|
|
302
|
+
function getAllLinkCounts(db) {
|
|
303
|
+
const rows = db
|
|
304
|
+
.prepare(`SELECT id, COUNT(*) as cnt FROM (
|
|
305
|
+
SELECT source_id AS id FROM memory_links
|
|
306
|
+
UNION ALL
|
|
307
|
+
SELECT target_id AS id FROM memory_links
|
|
308
|
+
) GROUP BY id`)
|
|
309
|
+
.all();
|
|
310
|
+
const counts = new Map();
|
|
311
|
+
for (const row of rows) {
|
|
312
|
+
counts.set(row.id, row.cnt);
|
|
313
|
+
}
|
|
314
|
+
return counts;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get all memories with default base_strength (never scored).
|
|
318
|
+
*/
|
|
319
|
+
function getUnscoredMemories(db) {
|
|
320
|
+
const rows = db
|
|
321
|
+
.prepare(`SELECT * FROM memories
|
|
322
|
+
WHERE base_strength = 0.5
|
|
323
|
+
ORDER BY ingested_at ASC`)
|
|
324
|
+
.all();
|
|
325
|
+
return rows.map(rowToMemory);
|
|
326
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Hicortex OpenClaw plugin.
|
|
3
|
+
* Ported from the Python hicortex codebase.
|
|
4
|
+
*/
|
|
5
|
+
/** A stored memory record. */
|
|
6
|
+
export interface Memory {
|
|
7
|
+
id: string;
|
|
8
|
+
content: string;
|
|
9
|
+
base_strength: number;
|
|
10
|
+
last_accessed: string | null;
|
|
11
|
+
access_count: number;
|
|
12
|
+
created_at: string;
|
|
13
|
+
ingested_at: string;
|
|
14
|
+
source_agent: string;
|
|
15
|
+
source_session: string | null;
|
|
16
|
+
project: string | null;
|
|
17
|
+
privacy: "PUBLIC" | "WORK" | "PERSONAL" | "SENSITIVE";
|
|
18
|
+
memory_type: "episode" | "lesson" | "fact" | "decision";
|
|
19
|
+
}
|
|
20
|
+
/** A link between two memories. */
|
|
21
|
+
export interface MemoryLink {
|
|
22
|
+
source_id: string;
|
|
23
|
+
target_id: string;
|
|
24
|
+
relationship: string;
|
|
25
|
+
strength: number;
|
|
26
|
+
created_at: string;
|
|
27
|
+
}
|
|
28
|
+
/** A search result with scoring metadata. */
|
|
29
|
+
export interface MemorySearchResult {
|
|
30
|
+
id: string;
|
|
31
|
+
content: string;
|
|
32
|
+
score: number;
|
|
33
|
+
effective_strength: number;
|
|
34
|
+
access_count: number;
|
|
35
|
+
memory_type: string;
|
|
36
|
+
project: string | null;
|
|
37
|
+
created_at: string;
|
|
38
|
+
connections: number;
|
|
39
|
+
}
|
|
40
|
+
/** Report returned by the consolidation pipeline. */
|
|
41
|
+
export interface ConsolidationReport {
|
|
42
|
+
started_at: string;
|
|
43
|
+
completed_at?: string;
|
|
44
|
+
dry_run: boolean;
|
|
45
|
+
status: "completed" | "skipped" | "failed";
|
|
46
|
+
elapsed_seconds?: number;
|
|
47
|
+
stages: {
|
|
48
|
+
precheck?: {
|
|
49
|
+
skip: boolean;
|
|
50
|
+
reason: string;
|
|
51
|
+
new_memory_count: number;
|
|
52
|
+
unscored_count: number;
|
|
53
|
+
};
|
|
54
|
+
importance?: {
|
|
55
|
+
scored: number;
|
|
56
|
+
failed: number;
|
|
57
|
+
skipped_budget: number;
|
|
58
|
+
};
|
|
59
|
+
reflection?: {
|
|
60
|
+
lessons_generated: number;
|
|
61
|
+
failed?: boolean;
|
|
62
|
+
skipped?: boolean;
|
|
63
|
+
reason?: string;
|
|
64
|
+
};
|
|
65
|
+
links?: {
|
|
66
|
+
auto_linked: number;
|
|
67
|
+
failed: number;
|
|
68
|
+
};
|
|
69
|
+
decay_prune?: {
|
|
70
|
+
candidates: number;
|
|
71
|
+
pruned: number;
|
|
72
|
+
failed: number;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
budget?: {
|
|
76
|
+
max_calls: number;
|
|
77
|
+
calls_used: number;
|
|
78
|
+
calls_remaining: number;
|
|
79
|
+
calls_by_stage: Record<string, number>;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Plugin configuration from openclaw.plugin.json configSchema. */
|
|
83
|
+
export interface HicortexConfig {
|
|
84
|
+
licenseKey?: string;
|
|
85
|
+
llmBaseUrl?: string;
|
|
86
|
+
llmApiKey?: string;
|
|
87
|
+
llmModel?: string;
|
|
88
|
+
reflectModel?: string;
|
|
89
|
+
consolidateHour?: number;
|
|
90
|
+
dbPath?: string;
|
|
91
|
+
}
|
|
92
|
+
/** Response from license validation API. */
|
|
93
|
+
export interface LicenseInfo {
|
|
94
|
+
valid: boolean;
|
|
95
|
+
tier: "free" | "pro" | "lifetime" | "team";
|
|
96
|
+
features: {
|
|
97
|
+
reflection: boolean;
|
|
98
|
+
vectorSearch: boolean;
|
|
99
|
+
maxMemories: number;
|
|
100
|
+
crossAgent: boolean;
|
|
101
|
+
};
|
|
102
|
+
email?: string;
|
|
103
|
+
expires_at?: string;
|
|
104
|
+
}
|
|
105
|
+
/** Options for inserting a memory. */
|
|
106
|
+
export interface InsertMemoryOptions {
|
|
107
|
+
sourceAgent?: string;
|
|
108
|
+
sourceSession?: string | null;
|
|
109
|
+
project?: string | null;
|
|
110
|
+
privacy?: string;
|
|
111
|
+
memoryType?: string;
|
|
112
|
+
baseStrength?: number;
|
|
113
|
+
createdAt?: string;
|
|
114
|
+
}
|
|
115
|
+
/** Options for vector search. */
|
|
116
|
+
export interface VectorSearchOptions {
|
|
117
|
+
limit?: number;
|
|
118
|
+
excludeIds?: string[];
|
|
119
|
+
}
|
|
120
|
+
/** Options for FTS search. */
|
|
121
|
+
export interface FtsSearchOptions {
|
|
122
|
+
limit?: number;
|
|
123
|
+
privacy?: string[];
|
|
124
|
+
sourceAgent?: string;
|
|
125
|
+
}
|
|
126
|
+
/** Options for retrieval. */
|
|
127
|
+
export interface RetrievalOptions {
|
|
128
|
+
limit?: number;
|
|
129
|
+
project?: string | null;
|
|
130
|
+
privacy?: string[];
|
|
131
|
+
sourceAgent?: string;
|
|
132
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "hicortex",
|
|
3
|
+
"name": "Hicortex — Long-term Memory That Learns",
|
|
4
|
+
"description": "Your agents remember past decisions, avoid repeated mistakes, and get smarter every day. Nightly reflection generates actionable lessons that automatically update agent behavior.",
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"kind": "lifecycle",
|
|
7
|
+
"skills": ["./skills/hicortex-memory", "./skills/hicortex-learn", "./skills/hicortex-activate"],
|
|
8
|
+
"configSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"licenseKey": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Hicortex license key (hctx-...). Leave empty for free tier (100 memory cap)."
|
|
14
|
+
},
|
|
15
|
+
"llmBaseUrl": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "LLM API base URL. Auto-detected from OpenClaw config or env vars if omitted."
|
|
18
|
+
},
|
|
19
|
+
"llmApiKey": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "LLM API key. Auto-detected from OpenClaw config or env vars if omitted."
|
|
22
|
+
},
|
|
23
|
+
"llmModel": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"default": "qwen3.5:4b",
|
|
26
|
+
"description": "Model for importance scoring and distillation (local recommended)"
|
|
27
|
+
},
|
|
28
|
+
"reflectModel": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"default": "qwen3.5:cloud",
|
|
31
|
+
"description": "Model for nightly reflection (cloud recommended)"
|
|
32
|
+
},
|
|
33
|
+
"consolidateHour": {
|
|
34
|
+
"type": "number",
|
|
35
|
+
"default": 2,
|
|
36
|
+
"description": "Hour (0-23, local time) to run nightly consolidation"
|
|
37
|
+
},
|
|
38
|
+
"dbPath": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Custom path for the SQLite database file"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"required": []
|
|
44
|
+
},
|
|
45
|
+
"uiHints": {
|
|
46
|
+
"licenseKey": {
|
|
47
|
+
"label": "License Key",
|
|
48
|
+
"placeholder": "hctx-... (optional, free tier without key)",
|
|
49
|
+
"sensitive": true
|
|
50
|
+
},
|
|
51
|
+
"llmBaseUrl": {
|
|
52
|
+
"label": "LLM API URL",
|
|
53
|
+
"placeholder": "Auto-detected if empty"
|
|
54
|
+
},
|
|
55
|
+
"llmApiKey": {
|
|
56
|
+
"label": "LLM API Key",
|
|
57
|
+
"sensitive": true,
|
|
58
|
+
"placeholder": "Auto-detected if empty"
|
|
59
|
+
},
|
|
60
|
+
"llmModel": {
|
|
61
|
+
"label": "Scoring Model"
|
|
62
|
+
},
|
|
63
|
+
"reflectModel": {
|
|
64
|
+
"label": "Reflection Model"
|
|
65
|
+
},
|
|
66
|
+
"consolidateHour": {
|
|
67
|
+
"label": "Consolidation Hour"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gamaze/hicortex",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Long-term memory for AI agents \u2014 local embeddings, semantic search, and self-improving reflection. OpenClaw plugin.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"openclaw": {
|
|
7
|
+
"extensions": ["./dist/index.js"]
|
|
8
|
+
},
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"skills/",
|
|
13
|
+
"openclaw.plugin.json",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^5.4.0",
|
|
24
|
+
"vitest": "^3.0.0",
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
|
+
"@types/better-sqlite3": "^7.6.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"license": "UNLICENSED",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/mha33/hicortex.git",
|
|
35
|
+
"directory": "packages/openclaw-plugin"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"better-sqlite3": "^11.0.0",
|
|
39
|
+
"sqlite-vec": "^0.1.7",
|
|
40
|
+
"@huggingface/transformers": "^3.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hicortex-activate
|
|
3
|
+
description: Activate a Hicortex license key. Use when the user says they purchased Hicortex, has a license key, or wants to upgrade from the free tier.
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
user-invocable: true
|
|
6
|
+
disable-model-invocation: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Activate Hicortex License
|
|
10
|
+
|
|
11
|
+
When the user wants to activate their license key, guide them through it.
|
|
12
|
+
|
|
13
|
+
## If they provide the key (e.g. `/hicortex-activate hctx-abc123`)
|
|
14
|
+
|
|
15
|
+
Run this command to apply the key:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
openclaw config set plugins.entries.hicortex.config.licenseKey "THE_KEY_HERE"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then restart the gateway:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
openclaw gateway restart
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Tell the user: "License activated! Hicortex now has unlimited memory. Your agent will keep learning and improving from every session."
|
|
28
|
+
|
|
29
|
+
## If they don't have a key yet
|
|
30
|
+
|
|
31
|
+
Tell them:
|
|
32
|
+
|
|
33
|
+
"You can get a license key at https://hicortex.gamaze.com/ — after purchase, you'll receive your key by email. Then come back and tell me the key, and I'll activate it for you."
|
|
34
|
+
|
|
35
|
+
## If activation fails
|
|
36
|
+
|
|
37
|
+
If the `openclaw config set` command fails, fall back to telling the user to manually add it:
|
|
38
|
+
|
|
39
|
+
"Open ~/.openclaw/openclaw.json, find the hicortex plugin entry, and add your key:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
"config": {
|
|
43
|
+
"licenseKey": "hctx-your-key-here"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then restart: `openclaw gateway restart`"
|
|
48
|
+
|
|
49
|
+
## Rules
|
|
50
|
+
|
|
51
|
+
- Never ask the user to open a terminal or edit files unless the automatic method fails
|
|
52
|
+
- Always confirm the key was applied by checking the gateway log after restart
|
|
53
|
+
- Be encouraging — they just bought the product
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hicortex-learn
|
|
3
|
+
description: Save an explicit learning or insight to Hicortex long-term memory. Use when you discover something worth remembering across sessions — a lesson, a correction, a pattern, a decision.
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
user-invocable: true
|
|
6
|
+
disable-model-invocation: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Save Learning to Hicortex
|
|
10
|
+
|
|
11
|
+
When invoked with `/learn <text>`, store the learning in long-term memory via hicortex_ingest.
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
1. Parse the text after `/learn`
|
|
16
|
+
2. Clean it up into a clear, self-contained statement that will make sense months from now
|
|
17
|
+
3. Add today's date
|
|
18
|
+
4. Call the `hicortex_ingest` tool with:
|
|
19
|
+
- `content`: The learning text
|
|
20
|
+
- `project`: "global" (unless clearly project-specific)
|
|
21
|
+
- `memory_type`: "lesson"
|
|
22
|
+
|
|
23
|
+
## Example
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
/learn z.ai API uses Bearer auth on all endpoints, not x-api-key
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Becomes:
|
|
30
|
+
```
|
|
31
|
+
hicortex_ingest(content="LEARNING: z.ai API uses Bearer auth on all three endpoints (paas, coding, anthropic). Not x-api-key. (2026-03-24)", project="global", memory_type="lesson")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Rules
|
|
35
|
+
|
|
36
|
+
- Keep it concise — one clear statement
|
|
37
|
+
- Include the "why" when relevant
|
|
38
|
+
- Include the date for temporal context
|
|
39
|
+
- Prefix with "LEARNING:" so it's identifiable in search
|
|
40
|
+
- Confirm to the user what was saved (title + confirmation)
|