@gamaze/hicortex 0.7.1 → 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.
- package/README.md +57 -39
- package/dist/claude-md.d.ts +9 -21
- package/dist/claude-md.js +9 -241
- package/dist/cli.d.ts +3 -2
- package/dist/cli.js +29 -11
- package/dist/consolidate.js +0 -7
- package/dist/db.js +24 -0
- package/dist/embedder.d.ts +11 -0
- package/dist/embedder.js +27 -0
- package/dist/extensions.d.ts +41 -88
- package/dist/extensions.js +36 -61
- package/dist/features.d.ts +21 -25
- package/dist/features.js +47 -83
- package/dist/hermes-transcript-reader.d.ts +27 -0
- package/dist/hermes-transcript-reader.js +134 -0
- package/dist/index.d.ts +16 -4
- package/dist/index.js +252 -344
- package/dist/init.d.ts +41 -1
- package/dist/init.js +545 -190
- package/dist/lesson-selection.d.ts +62 -0
- package/dist/lesson-selection.js +159 -0
- package/dist/lessons-context.d.ts +17 -0
- package/dist/lessons-context.js +96 -0
- package/dist/llm.d.ts +42 -29
- package/dist/llm.js +89 -270
- package/dist/mcp-server.d.ts +0 -1
- package/dist/mcp-server.js +404 -86
- package/dist/nightly.d.ts +9 -6
- package/dist/nightly.js +197 -357
- package/dist/oc-transcript-reader.d.ts +20 -0
- package/dist/oc-transcript-reader.js +61 -0
- package/dist/pi-transcript-reader.d.ts +1 -0
- package/dist/status.js +22 -2
- package/dist/storage.d.ts +7 -1
- package/dist/storage.js +28 -7
- package/dist/transcript-reader.d.ts +19 -0
- package/dist/transcript-reader.js +17 -3
- package/dist/types.d.ts +10 -0
- package/dist/uninstall.js +31 -1
- package/hermes-plugin/hicortex/README.md +77 -0
- package/hermes-plugin/hicortex/__init__.py +17 -0
- package/hermes-plugin/hicortex/client.py +162 -0
- package/hermes-plugin/hicortex/config.py +105 -0
- package/hermes-plugin/hicortex/plugin.yaml +12 -0
- package/hermes-plugin/hicortex/provider.py +432 -0
- package/openclaw.plugin.json +17 -44
- package/package.json +7 -5
- package/dist/pro-loader.d.ts +0 -33
- package/dist/pro-loader.js +0 -187
package/dist/index.js
CHANGED
|
@@ -1,238 +1,184 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Hicortex OpenClaw Plugin — Long-term Memory That Learns.
|
|
3
|
+
* Hicortex OpenClaw Plugin — Long-term Memory That Learns. (0.10.0)
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Thin-client model: the plugin requires a Hicortex server (co-located at
|
|
6
|
+
* http://127.0.0.1:8787 by default, or a remote URL via `serverUrl` config).
|
|
7
|
+
* No local database, no local LLM, no embedder, no consolidation scheduler.
|
|
8
|
+
*
|
|
9
|
+
* Install once: `openclaw plugins install @gamaze/hicortex`
|
|
10
|
+
* Run server: `npx @gamaze/hicortex init`
|
|
11
|
+
*
|
|
12
|
+
* Responsibilities (recall-only adapter, like the Hermes plugin):
|
|
13
|
+
* - before_agent_start → GET /lessons (fail-soft, 3s timeout) → inject context
|
|
14
|
+
* - Tools → HTTP proxies to /search, /context, /ingest, /lessons
|
|
15
|
+
*
|
|
16
|
+
* CAPTURE IS NOT THIS PLUGIN'S JOB. OpenClaw persists sessions at
|
|
17
|
+
* ~/.openclaw/agents/<agentId>/sessions/*.jsonl in the Pi v3 format; the
|
|
18
|
+
* machine's Hicortex nightly reads them via oc-transcript-reader.ts —
|
|
19
|
+
* canonical nightly-from-logs, same as CC JSONL and Hermes state.db.
|
|
8
20
|
*/
|
|
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
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
-
const node_path_1 = require("node:path");
|
|
44
|
-
const db_js_1 = require("./db.js");
|
|
45
22
|
const features_js_1 = require("./features.js");
|
|
46
23
|
const extensions_js_1 = require("./extensions.js");
|
|
47
24
|
const state_js_1 = require("./state.js");
|
|
48
|
-
const llm_js_1 = require("./llm.js");
|
|
49
25
|
const node_fs_1 = require("node:fs");
|
|
26
|
+
const node_path_1 = require("node:path");
|
|
50
27
|
const node_os_1 = require("node:os");
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Constants
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
const DEFAULT_SERVER_URL = "http://127.0.0.1:8787";
|
|
32
|
+
const LESSONS_TIMEOUT_MS = 3000;
|
|
33
|
+
const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
57
34
|
// ---------------------------------------------------------------------------
|
|
58
35
|
// Module state — initialized in registerService.start()
|
|
59
36
|
// ---------------------------------------------------------------------------
|
|
60
|
-
let
|
|
61
|
-
let
|
|
62
|
-
let
|
|
63
|
-
|
|
37
|
+
let serverUrl = DEFAULT_SERVER_URL;
|
|
38
|
+
let authToken;
|
|
39
|
+
let hicortexHome = HICORTEX_HOME;
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// HTTP helpers
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
function authHeaders() {
|
|
44
|
+
return authToken ? { Authorization: `Bearer ${authToken}` } : {};
|
|
45
|
+
}
|
|
46
|
+
async function serverGet(path, timeoutMs) {
|
|
47
|
+
try {
|
|
48
|
+
const resp = await fetch(`${serverUrl}${path}`, {
|
|
49
|
+
headers: authHeaders(),
|
|
50
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
51
|
+
});
|
|
52
|
+
if (!resp.ok)
|
|
53
|
+
return null;
|
|
54
|
+
return await resp.json();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function serverPost(path, body, timeoutMs) {
|
|
61
|
+
try {
|
|
62
|
+
const resp = await fetch(`${serverUrl}${path}`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json", ...authHeaders() },
|
|
65
|
+
body: JSON.stringify(body),
|
|
66
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
67
|
+
});
|
|
68
|
+
let data = null;
|
|
69
|
+
try {
|
|
70
|
+
data = await resp.json();
|
|
71
|
+
}
|
|
72
|
+
catch { /* non-JSON body */ }
|
|
73
|
+
return { ok: resp.ok, status: resp.status, data };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return { ok: false, status: 0, data: null };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Tool result formatter
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
function formatToolResults(results) {
|
|
83
|
+
if (results.length === 0) {
|
|
84
|
+
return { content: [{ type: "text", text: "No memories found." }] };
|
|
85
|
+
}
|
|
86
|
+
const text = results
|
|
87
|
+
.map((r) => `[${r.memory_type}] (score: ${r.score.toFixed(3)}, strength: ${r.effective_strength.toFixed(3)}) ${r.content.slice(0, 500)}`)
|
|
88
|
+
.join("\n\n");
|
|
89
|
+
return { content: [{ type: "text", text }] };
|
|
90
|
+
}
|
|
64
91
|
// ---------------------------------------------------------------------------
|
|
65
92
|
// Plugin export
|
|
66
93
|
// ---------------------------------------------------------------------------
|
|
67
94
|
exports.default = {
|
|
68
95
|
id: "hicortex",
|
|
69
|
-
name: "Hicortex
|
|
96
|
+
name: "Hicortex — Long-term Memory That Learns",
|
|
70
97
|
kind: "lifecycle",
|
|
71
98
|
register(api) {
|
|
72
99
|
// -----------------------------------------------------------------------
|
|
73
|
-
// Background service:
|
|
100
|
+
// Background service: resolve config, verify server, init features
|
|
74
101
|
// -----------------------------------------------------------------------
|
|
75
102
|
api.registerService({
|
|
76
103
|
id: "hicortex-service",
|
|
77
104
|
async start(ctx) {
|
|
78
105
|
const config = (ctx.config ?? {});
|
|
79
|
-
stateDir = ctx.stateDir ?? (0, node_path_1.join)(process.env.HOME ?? "~", ".hicortex");
|
|
80
106
|
const log = ctx.logger
|
|
81
107
|
? (msg) => ctx.logger.info(msg)
|
|
82
108
|
: console.log;
|
|
83
|
-
// Resolve
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
// Resolve server URL and auth token from plugin config
|
|
110
|
+
serverUrl = (config.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
|
|
111
|
+
authToken = config.authToken;
|
|
112
|
+
// Use stateDir from context so tests can redirect state writes
|
|
113
|
+
hicortexHome = ctx.stateDir ?? HICORTEX_HOME;
|
|
114
|
+
log(`[hicortex] Thin-client mode — server: ${serverUrl}`);
|
|
115
|
+
// License: init feature cache (only needs licenseKey, no DB access)
|
|
116
|
+
await (0, features_js_1.initFeatures)(config.licenseKey, hicortexHome);
|
|
117
|
+
// Verify server reachability at startup (non-fatal — warn only)
|
|
118
|
+
try {
|
|
119
|
+
const resp = await fetch(`${serverUrl}/health`, {
|
|
120
|
+
signal: AbortSignal.timeout(5000),
|
|
121
|
+
});
|
|
122
|
+
if (resp.ok) {
|
|
123
|
+
const data = await resp.json();
|
|
124
|
+
log(`[hicortex] Server OK: v${data.version}, ${data.memories} memories`);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
log(`[hicortex] Server returned HTTP ${resp.status} — capture and tools may fail`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
132
|
+
log(`[hicortex] WARNING: Server unreachable at ${serverUrl}: ${msg}. ` +
|
|
133
|
+
`Run \`npx @gamaze/hicortex init\` to start the server. ` +
|
|
134
|
+
`Capture and tool calls will fail until the server is available.`);
|
|
135
|
+
}
|
|
102
136
|
ensureToolsAllowed(log);
|
|
103
|
-
// Log stats
|
|
104
|
-
const stats = (0, db_js_1.getStats)(db, dbPath);
|
|
105
|
-
log(`[hicortex] Ready: ${stats.memories} memories, ${stats.links} links, ` +
|
|
106
|
-
`${Math.round(stats.db_size_bytes / 1024)} KB`);
|
|
107
137
|
},
|
|
108
138
|
async stop() {
|
|
109
|
-
|
|
110
|
-
cancelConsolidation();
|
|
111
|
-
cancelConsolidation = null;
|
|
112
|
-
}
|
|
113
|
-
if (db) {
|
|
114
|
-
db.close();
|
|
115
|
-
db = null;
|
|
116
|
-
}
|
|
117
|
-
llm = null;
|
|
139
|
+
// Nothing to clean up — no DB, no LLM, no timer
|
|
118
140
|
},
|
|
119
141
|
});
|
|
120
142
|
// -----------------------------------------------------------------------
|
|
121
|
-
// Hook: before_agent_start —
|
|
143
|
+
// Hook: before_agent_start — fetch lessons from server (fail-soft)
|
|
122
144
|
// -----------------------------------------------------------------------
|
|
123
|
-
api.on("before_agent_start", async (
|
|
124
|
-
if (!db)
|
|
125
|
-
return {};
|
|
145
|
+
api.on("before_agent_start", async (_event, ctx) => {
|
|
126
146
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
147
|
+
// One fetch — build context and check cap from the same response.
|
|
148
|
+
const data = await serverGet("/lessons", LESSONS_TIMEOUT_MS);
|
|
149
|
+
if (!data || !data.lessons || data.lessons.length === 0)
|
|
129
150
|
return {};
|
|
130
151
|
const maxLessons = (0, features_js_1.lessonsLimit)();
|
|
131
|
-
const
|
|
152
|
+
const state = (0, state_js_1.loadState)(hicortexHome);
|
|
153
|
+
const moduleIndex = data.moduleIndex ?? state.moduleIndex;
|
|
154
|
+
const selected = await (0, extensions_js_1.getLessonSelector)().select(data.lessons, {
|
|
132
155
|
maxLessons,
|
|
133
156
|
project: ctx.project,
|
|
134
|
-
|
|
157
|
+
moduleIndex,
|
|
135
158
|
});
|
|
159
|
+
if (selected.length === 0)
|
|
160
|
+
return {};
|
|
136
161
|
const formatted = selected.map((l) => {
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
162
|
+
const titleMatch = l.content.match(/## Lesson: (.+)/);
|
|
163
|
+
const typeMatch = l.content.match(/\*\*Type:\*\* (\w+)/);
|
|
164
|
+
const severityMatch = l.content.match(/\*\*Severity:\*\* (\w+)/);
|
|
165
|
+
const title = titleMatch ? titleMatch[1] : l.content.slice(0, 150);
|
|
166
|
+
const meta = [severityMatch?.[1], typeMatch?.[1]].filter(Boolean).join(", ");
|
|
167
|
+
return `- ${title}${meta ? ` (${meta})` : ""}`;
|
|
140
168
|
});
|
|
141
|
-
|
|
169
|
+
const context = `\n\n## Hicortex Lessons (auto-injected from long-term memory)\n` +
|
|
142
170
|
`These are actionable lessons learned from past sessions:\n\n` +
|
|
143
171
|
formatted.join("\n") +
|
|
144
172
|
"\n";
|
|
145
|
-
// Daily friendly reminder when at memory cap
|
|
146
|
-
const memCount = storage.countMemories(db);
|
|
147
|
-
if ((0, features_js_1.memoryCapReached)(memCount)) {
|
|
148
|
-
context +=
|
|
149
|
-
`\n---\nHicortex free tier: ${(0, features_js_1.maxMemoriesAllowed)()} memories stored. ` +
|
|
150
|
-
`New memories can no longer be saved, and your agent has stopped learning and self-improving from new sessions. ` +
|
|
151
|
-
`Existing memories, lessons, and search still work. ` +
|
|
152
|
-
`Upgrade for unlimited usage: https://hicortex.gamaze.com/ ` +
|
|
153
|
-
`— after purchase, tell me your key and I'll activate it for you.\n`;
|
|
154
|
-
}
|
|
155
173
|
return { appendSystemContext: context };
|
|
156
174
|
}
|
|
157
175
|
catch {
|
|
176
|
+
// Fail-soft — a broken lessons fetch must not block the agent
|
|
158
177
|
return {};
|
|
159
178
|
}
|
|
160
179
|
});
|
|
161
180
|
// -----------------------------------------------------------------------
|
|
162
|
-
//
|
|
163
|
-
// -----------------------------------------------------------------------
|
|
164
|
-
api.on("agent_end", async (event, ctx) => {
|
|
165
|
-
if (!db || !llm)
|
|
166
|
-
return;
|
|
167
|
-
if (!event.success || event.messages.length < 4)
|
|
168
|
-
return;
|
|
169
|
-
try {
|
|
170
|
-
const transcript = (0, distiller_js_1.extractConversationText)(event.messages);
|
|
171
|
-
if (transcript.length < 200)
|
|
172
|
-
return;
|
|
173
|
-
const date = new Date().toISOString().slice(0, 10);
|
|
174
|
-
const projectName = ctx.project ?? "unknown";
|
|
175
|
-
const sourceAgent = `openclaw/${ctx.agentId ?? "unknown"}`;
|
|
176
|
-
const entries = await (0, distiller_js_1.distillSession)(llm, transcript, projectName, date);
|
|
177
|
-
if (entries.length === 0)
|
|
178
|
-
return;
|
|
179
|
-
// Check license cap
|
|
180
|
-
if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
|
|
181
|
-
console.warn(`[hicortex] Free tier limit reached (${(0, features_js_1.maxMemoriesAllowed)()} memories). ` +
|
|
182
|
-
`Search and lessons still work, but new memories won't be saved. ` +
|
|
183
|
-
`Upgrade for unlimited usage: https://hicortex.gamaze.com/`);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
// Embed and ingest each entry
|
|
187
|
-
for (const entry of entries) {
|
|
188
|
-
try {
|
|
189
|
-
const embedding = await (0, embedder_js_1.embed)(entry);
|
|
190
|
-
storage.insertMemory(db, entry, embedding, {
|
|
191
|
-
sourceAgent,
|
|
192
|
-
sourceSession: ctx.sessionKey,
|
|
193
|
-
project: projectName,
|
|
194
|
-
privacy: "WORK",
|
|
195
|
-
memoryType: "episode",
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
catch (err) {
|
|
199
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
200
|
-
console.error(`[hicortex] Failed to ingest entry: ${msg}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
// Non-fatal — session capture failing shouldn't break the agent
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
// -----------------------------------------------------------------------
|
|
209
|
-
// Hook: session_end — check if consolidation is overdue
|
|
210
|
-
// -----------------------------------------------------------------------
|
|
211
|
-
api.on("session_end", async (event, _ctx) => {
|
|
212
|
-
if (!db || !llm || event.messageCount < 4)
|
|
213
|
-
return;
|
|
214
|
-
// Opportunistic consolidation if no nightly run in 48h
|
|
215
|
-
try {
|
|
216
|
-
const { readFileSync: readFs } = await import("node:fs");
|
|
217
|
-
const { join: joinPath } = await import("node:path");
|
|
218
|
-
const { homedir: homeDir } = await import("node:os");
|
|
219
|
-
const lastPath = joinPath(homeDir(), ".hicortex", "last-consolidated.txt");
|
|
220
|
-
const lastTs = readFs(lastPath, "utf-8").trim();
|
|
221
|
-
const lastDate = new Date(lastTs);
|
|
222
|
-
const hoursSince = (Date.now() - lastDate.getTime()) / (1000 * 60 * 60);
|
|
223
|
-
if (hoursSince > 48) {
|
|
224
|
-
console.log("[hicortex] Consolidation overdue — triggering now");
|
|
225
|
-
(0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed).catch((err) => {
|
|
226
|
-
console.error("[hicortex] Opportunistic consolidation failed:", err);
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
// No timestamp file — first run, consolidation will happen on schedule
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
// -----------------------------------------------------------------------
|
|
235
|
-
// Tools — registered as factory functions per OC plugin API
|
|
181
|
+
// Tools — HTTP proxies to server REST API
|
|
236
182
|
// -----------------------------------------------------------------------
|
|
237
183
|
api.registerTool((_ctx) => ({
|
|
238
184
|
name: "hicortex_search",
|
|
@@ -247,14 +193,16 @@ exports.default = {
|
|
|
247
193
|
required: ["query"],
|
|
248
194
|
},
|
|
249
195
|
async execute(_callId, args, _ctx) {
|
|
250
|
-
if (!db)
|
|
251
|
-
return { error: "Hicortex not initialized" };
|
|
252
196
|
try {
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
197
|
+
const params = new URLSearchParams({ query: args.query });
|
|
198
|
+
if (args.limit)
|
|
199
|
+
params.set("limit", String(args.limit));
|
|
200
|
+
if (args.project)
|
|
201
|
+
params.set("project", args.project);
|
|
202
|
+
const data = await serverGet(`/search?${params}`, 10000);
|
|
203
|
+
if (!data)
|
|
204
|
+
return { error: "Search failed: server unreachable" };
|
|
205
|
+
return formatToolResults(data.results ?? []);
|
|
258
206
|
}
|
|
259
207
|
catch (err) {
|
|
260
208
|
return { error: `Search failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
@@ -271,15 +219,18 @@ exports.default = {
|
|
|
271
219
|
limit: { type: "number", description: "Max results (default 10)" },
|
|
272
220
|
},
|
|
273
221
|
},
|
|
274
|
-
execute(_callId, args, _ctx) {
|
|
275
|
-
if (!db)
|
|
276
|
-
return { error: "Hicortex not initialized" };
|
|
222
|
+
async execute(_callId, args, _ctx) {
|
|
277
223
|
try {
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
224
|
+
const params = new URLSearchParams();
|
|
225
|
+
if (args?.project)
|
|
226
|
+
params.set("project", args.project);
|
|
227
|
+
if (args?.limit)
|
|
228
|
+
params.set("limit", String(args.limit));
|
|
229
|
+
const qs = params.toString();
|
|
230
|
+
const data = await serverGet(`/context${qs ? `?${qs}` : ""}`, 10000);
|
|
231
|
+
if (!data)
|
|
232
|
+
return { error: "Context search failed: server unreachable" };
|
|
233
|
+
return formatToolResults(data.results ?? []);
|
|
283
234
|
}
|
|
284
235
|
catch (err) {
|
|
285
236
|
return { error: `Context search failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
@@ -303,27 +254,18 @@ exports.default = {
|
|
|
303
254
|
required: ["content"],
|
|
304
255
|
},
|
|
305
256
|
async execute(_callId, args, context) {
|
|
306
|
-
if (!db)
|
|
307
|
-
return { error: "Hicortex not initialized" };
|
|
308
|
-
if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
|
|
309
|
-
return {
|
|
310
|
-
content: [{
|
|
311
|
-
type: "text",
|
|
312
|
-
text: `Free tier limit reached (${(0, features_js_1.maxMemoriesAllowed)()} memories). ` +
|
|
313
|
-
`Your existing memories and lessons still work — search and recall are unaffected. ` +
|
|
314
|
-
`New memories won't be saved until you upgrade.\n\n` +
|
|
315
|
-
`Upgrade for unlimited usage: https://hicortex.gamaze.com/`
|
|
316
|
-
}],
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
257
|
try {
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
258
|
+
const result = await serverPost("/ingest", {
|
|
259
|
+
content: args.content,
|
|
260
|
+
source_agent: `openclaw/${context?.agentId ?? "manual"}`,
|
|
323
261
|
project: args.project,
|
|
324
|
-
|
|
262
|
+
memory_type: args.memory_type ?? "episode",
|
|
325
263
|
privacy: "WORK",
|
|
326
|
-
});
|
|
264
|
+
}, 15000);
|
|
265
|
+
if (!result.ok) {
|
|
266
|
+
return { error: `Ingest failed: ${result.data?.error ?? `HTTP ${result.status}`}` };
|
|
267
|
+
}
|
|
268
|
+
const id = result.data?.id ?? "unknown";
|
|
327
269
|
return { content: [{ type: "text", text: `Memory stored (id: ${id.slice(0, 8)})` }] };
|
|
328
270
|
}
|
|
329
271
|
catch (err) {
|
|
@@ -337,17 +279,17 @@ exports.default = {
|
|
|
337
279
|
parameters: {
|
|
338
280
|
type: "object",
|
|
339
281
|
properties: {
|
|
340
|
-
|
|
341
|
-
project: { type: "string", description: "Filter by project name" },
|
|
282
|
+
project: { type: "string", description: "Filter by project name (optional)" },
|
|
342
283
|
},
|
|
343
284
|
},
|
|
344
|
-
execute(_callId, args, _ctx) {
|
|
345
|
-
if (!db)
|
|
346
|
-
return { error: "Hicortex not initialized" };
|
|
285
|
+
async execute(_callId, args, _ctx) {
|
|
347
286
|
try {
|
|
348
|
-
const
|
|
287
|
+
const data = await serverGet("/lessons", LESSONS_TIMEOUT_MS);
|
|
288
|
+
if (!data)
|
|
289
|
+
return { error: "Lessons fetch failed: server unreachable" };
|
|
290
|
+
const lessons = data.lessons ?? [];
|
|
349
291
|
if (lessons.length === 0) {
|
|
350
|
-
return { content: [{ type: "text", text: "No lessons found
|
|
292
|
+
return { content: [{ type: "text", text: "No lessons found." }] };
|
|
351
293
|
}
|
|
352
294
|
const text = lessons.map((l) => `- ${l.content.slice(0, 500)}`).join("\n");
|
|
353
295
|
return { content: [{ type: "text", text }] };
|
|
@@ -357,6 +299,67 @@ exports.default = {
|
|
|
357
299
|
}
|
|
358
300
|
},
|
|
359
301
|
}), { name: "hicortex_lessons" });
|
|
302
|
+
api.registerTool((_ctx) => ({
|
|
303
|
+
name: "hicortex_index",
|
|
304
|
+
description: "Get the knowledge domain index — shows what topics and projects are stored in memory, grouped by domain.",
|
|
305
|
+
parameters: {
|
|
306
|
+
type: "object",
|
|
307
|
+
properties: {},
|
|
308
|
+
},
|
|
309
|
+
async execute(_callId, _args, _ctx) {
|
|
310
|
+
try {
|
|
311
|
+
const data = await serverGet("/index", 10000);
|
|
312
|
+
if (!data)
|
|
313
|
+
return { error: "Index fetch failed: server unreachable" };
|
|
314
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
return { error: `Index fetch failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
}), { name: "hicortex_index" });
|
|
321
|
+
api.registerTool((_ctx) => ({
|
|
322
|
+
name: "hicortex_graph",
|
|
323
|
+
description: "Query the memory knowledge graph — find connected memories, hub nodes, or paths between memories.",
|
|
324
|
+
parameters: {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {
|
|
327
|
+
operation: {
|
|
328
|
+
type: "string",
|
|
329
|
+
enum: ["neighbors", "hubs", "path"],
|
|
330
|
+
description: "Graph operation to perform",
|
|
331
|
+
},
|
|
332
|
+
id: { type: "string", description: "Memory ID (required for neighbors and path operations)" },
|
|
333
|
+
target_id: { type: "string", description: "Target memory ID (required for path operation)" },
|
|
334
|
+
limit: { type: "number", description: "Max results (default 10)" },
|
|
335
|
+
domain: { type: "string", description: "Filter hubs by domain" },
|
|
336
|
+
relationship: { type: "string", description: "Filter neighbors by relationship type (e.g., CONTRADICTS, SUPERSEDES, derives)" },
|
|
337
|
+
},
|
|
338
|
+
required: ["operation"],
|
|
339
|
+
},
|
|
340
|
+
async execute(_callId, args, _ctx) {
|
|
341
|
+
try {
|
|
342
|
+
const params = new URLSearchParams({ op: args.operation });
|
|
343
|
+
if (args.id)
|
|
344
|
+
params.set("id", args.id);
|
|
345
|
+
if (args.target_id)
|
|
346
|
+
params.set("target_id", args.target_id);
|
|
347
|
+
if (args.limit)
|
|
348
|
+
params.set("limit", String(args.limit));
|
|
349
|
+
if (args.domain)
|
|
350
|
+
params.set("domain", args.domain);
|
|
351
|
+
if (args.relationship)
|
|
352
|
+
params.set("relationship", args.relationship);
|
|
353
|
+
const data = await serverGet(`/graph?${params}`, 10000);
|
|
354
|
+
if (!data)
|
|
355
|
+
return { error: "Graph query failed: server unreachable" };
|
|
356
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
return { error: `Graph query failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
}), { name: "hicortex_graph" });
|
|
360
363
|
api.registerTool((_ctx) => ({
|
|
361
364
|
name: "hicortex_update",
|
|
362
365
|
description: "Update an existing memory. Use after searching to fix incorrect information. If content changes, the embedding is re-computed.",
|
|
@@ -366,33 +369,30 @@ exports.default = {
|
|
|
366
369
|
id: { type: "string", description: "Memory ID (from search results, first 8 chars or full UUID)" },
|
|
367
370
|
content: { type: "string", description: "New content text" },
|
|
368
371
|
project: { type: "string", description: "New project name" },
|
|
369
|
-
memory_type: {
|
|
372
|
+
memory_type: {
|
|
373
|
+
type: "string",
|
|
374
|
+
enum: ["episode", "lesson", "fact", "decision"],
|
|
375
|
+
description: "New memory type",
|
|
376
|
+
},
|
|
370
377
|
},
|
|
371
378
|
required: ["id"],
|
|
372
379
|
},
|
|
373
380
|
async execute(_callId, args, _ctx) {
|
|
374
|
-
if (!db)
|
|
375
|
-
return { error: "Hicortex not initialized" };
|
|
376
381
|
try {
|
|
377
|
-
const
|
|
378
|
-
|
|
382
|
+
const result = await serverPost("/update", {
|
|
383
|
+
id: args.id,
|
|
384
|
+
content: args.content,
|
|
385
|
+
project: args.project,
|
|
386
|
+
memory_type: args.memory_type,
|
|
387
|
+
}, 15000);
|
|
388
|
+
if (result.status === 404) {
|
|
379
389
|
return { error: `Memory not found: ${args.id}` };
|
|
380
|
-
const fields = {};
|
|
381
|
-
if (args.content !== undefined)
|
|
382
|
-
fields.content = args.content;
|
|
383
|
-
if (args.project !== undefined)
|
|
384
|
-
fields.project = args.project;
|
|
385
|
-
if (args.memory_type !== undefined)
|
|
386
|
-
fields.memory_type = args.memory_type;
|
|
387
|
-
if (Object.keys(fields).length === 0)
|
|
388
|
-
return { error: "No fields to update" };
|
|
389
|
-
storage.updateMemory(db, fullId, fields);
|
|
390
|
-
if (args.content !== undefined) {
|
|
391
|
-
const embedding = await (0, embedder_js_1.embed)(args.content);
|
|
392
|
-
db.prepare("DELETE FROM memory_vectors WHERE id = ?").run(fullId);
|
|
393
|
-
db.prepare("INSERT INTO memory_vectors (id, embedding) VALUES (?, ?)").run(fullId, Buffer.from(embedding.buffer));
|
|
394
390
|
}
|
|
395
|
-
|
|
391
|
+
if (!result.ok) {
|
|
392
|
+
return { error: `Update failed: ${result.data?.error ?? `HTTP ${result.status}`}` };
|
|
393
|
+
}
|
|
394
|
+
const id = result.data?.id ?? args.id;
|
|
395
|
+
return { content: [{ type: "text", text: `Memory updated (id: ${String(id).slice(0, 8)})` }] };
|
|
396
396
|
}
|
|
397
397
|
catch (err) {
|
|
398
398
|
return { error: `Update failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
@@ -409,15 +409,16 @@ exports.default = {
|
|
|
409
409
|
},
|
|
410
410
|
required: ["id"],
|
|
411
411
|
},
|
|
412
|
-
execute(_callId, args, _ctx) {
|
|
413
|
-
if (!db)
|
|
414
|
-
return { error: "Hicortex not initialized" };
|
|
412
|
+
async execute(_callId, args, _ctx) {
|
|
415
413
|
try {
|
|
416
|
-
const
|
|
417
|
-
if (
|
|
414
|
+
const result = await serverPost("/delete", { id: args.id }, 15000);
|
|
415
|
+
if (result.status === 404) {
|
|
418
416
|
return { error: `Memory not found: ${args.id}` };
|
|
419
|
-
|
|
420
|
-
|
|
417
|
+
}
|
|
418
|
+
if (!result.ok) {
|
|
419
|
+
return { error: `Delete failed: ${result.data?.error ?? `HTTP ${result.status}`}` };
|
|
420
|
+
}
|
|
421
|
+
return { content: [{ type: "text", text: `Memory deleted (id: ${String(args.id).slice(0, 8)})` }] };
|
|
421
422
|
}
|
|
422
423
|
catch (err) {
|
|
423
424
|
return { error: `Delete failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
@@ -429,95 +430,13 @@ exports.default = {
|
|
|
429
430
|
// ---------------------------------------------------------------------------
|
|
430
431
|
// Helpers
|
|
431
432
|
// ---------------------------------------------------------------------------
|
|
432
|
-
function resolveMemoryId(database, idPrefix) {
|
|
433
|
-
if (idPrefix.length >= 36) {
|
|
434
|
-
const row = database.prepare("SELECT id FROM memories WHERE id = ?").get(idPrefix);
|
|
435
|
-
return row?.id ?? null;
|
|
436
|
-
}
|
|
437
|
-
const rows = database.prepare("SELECT id FROM memories WHERE id LIKE ?").all(`${idPrefix}%`);
|
|
438
|
-
if (rows.length === 1)
|
|
439
|
-
return rows[0].id;
|
|
440
|
-
return null;
|
|
441
|
-
}
|
|
442
|
-
// ---------------------------------------------------------------------------
|
|
443
|
-
// Auto-configure LLM: resolve config → test connection → persist if new
|
|
444
|
-
// ---------------------------------------------------------------------------
|
|
445
|
-
async function autoConfigureLlm(pluginConfig, log) {
|
|
446
|
-
// Step 1: Resolve LLM config from all sources
|
|
447
|
-
const llmConfig = (0, llm_js_1.resolveLlmConfig)({
|
|
448
|
-
llmBaseUrl: pluginConfig.llmBaseUrl,
|
|
449
|
-
llmApiKey: pluginConfig.llmApiKey,
|
|
450
|
-
llmModel: pluginConfig.llmModel,
|
|
451
|
-
reflectModel: pluginConfig.reflectModel,
|
|
452
|
-
});
|
|
453
|
-
// Step 2: Test the connection
|
|
454
|
-
log(`[hicortex] Testing LLM connection: ${llmConfig.provider}/${llmConfig.model} @ ${llmConfig.baseUrl}`);
|
|
455
|
-
const testClient = new llm_js_1.LlmClient(llmConfig);
|
|
456
|
-
try {
|
|
457
|
-
const response = await testClient.completeFast("Respond with just the word OK", 10);
|
|
458
|
-
if (response && response.length > 0) {
|
|
459
|
-
log(`[hicortex] LLM connection verified`);
|
|
460
|
-
// Step 3: Persist to OC config so future startups skip detection
|
|
461
|
-
persistProviderConfig(llmConfig, log);
|
|
462
|
-
return llmConfig;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
catch (err) {
|
|
466
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
467
|
-
log(`[hicortex] LLM test failed (${llmConfig.baseUrl}): ${msg}`);
|
|
468
|
-
}
|
|
469
|
-
// Step 4: Fall back — return the config anyway, log instructions
|
|
470
|
-
log(`[hicortex] WARNING: Could not verify LLM connection. ` +
|
|
471
|
-
`Distillation and consolidation may fail. ` +
|
|
472
|
-
`To fix: add models.providers.${llmConfig.provider}.baseUrl to ~/.openclaw/openclaw.json ` +
|
|
473
|
-
`or set llmBaseUrl in the hicortex plugin config.`);
|
|
474
|
-
return llmConfig;
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Persist verified provider config to openclaw.json so future startups
|
|
478
|
-
* skip detection and go straight to the verified URL.
|
|
479
|
-
*/
|
|
480
|
-
function persistProviderConfig(llmConfig, log) {
|
|
481
|
-
try {
|
|
482
|
-
const configPath = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "openclaw.json");
|
|
483
|
-
const raw = (0, node_fs_1.readFileSync)(configPath, "utf-8");
|
|
484
|
-
const config = JSON.parse(raw);
|
|
485
|
-
// Check if baseUrl already stored for this provider
|
|
486
|
-
const existing = config?.models?.providers?.[llmConfig.provider]?.baseUrl;
|
|
487
|
-
if (existing === llmConfig.baseUrl)
|
|
488
|
-
return; // Already persisted
|
|
489
|
-
// Write the provider config
|
|
490
|
-
if (!config.models)
|
|
491
|
-
config.models = {};
|
|
492
|
-
if (!config.models.providers)
|
|
493
|
-
config.models.providers = {};
|
|
494
|
-
if (!config.models.providers[llmConfig.provider]) {
|
|
495
|
-
config.models.providers[llmConfig.provider] = {};
|
|
496
|
-
}
|
|
497
|
-
const prov = config.models.providers[llmConfig.provider];
|
|
498
|
-
prov.baseUrl = llmConfig.baseUrl;
|
|
499
|
-
// OC requires a models array — preserve existing or add the active model
|
|
500
|
-
if (!prov.models || !Array.isArray(prov.models)) {
|
|
501
|
-
prov.models = [{
|
|
502
|
-
id: llmConfig.model,
|
|
503
|
-
name: llmConfig.model,
|
|
504
|
-
input: ["text"],
|
|
505
|
-
contextWindow: 128000,
|
|
506
|
-
maxTokens: 8192,
|
|
507
|
-
}];
|
|
508
|
-
}
|
|
509
|
-
(0, node_fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
|
|
510
|
-
log(`[hicortex] Persisted LLM config: ${llmConfig.provider} → ${llmConfig.baseUrl}`);
|
|
511
|
-
}
|
|
512
|
-
catch {
|
|
513
|
-
// Non-fatal — config works in memory even if we can't persist
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
433
|
const HICORTEX_TOOLS = [
|
|
517
434
|
"hicortex_search",
|
|
518
435
|
"hicortex_context",
|
|
519
436
|
"hicortex_ingest",
|
|
520
437
|
"hicortex_lessons",
|
|
438
|
+
"hicortex_index",
|
|
439
|
+
"hicortex_graph",
|
|
521
440
|
"hicortex_update",
|
|
522
441
|
"hicortex_delete",
|
|
523
442
|
];
|
|
@@ -542,17 +461,6 @@ function ensureToolsAllowed(log) {
|
|
|
542
461
|
log(`[hicortex] Added tools to allow list: ${missing.join(", ")}`);
|
|
543
462
|
}
|
|
544
463
|
catch {
|
|
545
|
-
// Non-fatal
|
|
464
|
+
// Non-fatal — openclaw.json may not exist in test environments
|
|
546
465
|
}
|
|
547
466
|
}
|
|
548
|
-
function formatToolResults(results) {
|
|
549
|
-
if (results.length === 0) {
|
|
550
|
-
return {
|
|
551
|
-
content: [{ type: "text", text: "No memories found." }],
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
const text = results
|
|
555
|
-
.map((r) => `[${r.memory_type}] (score: ${r.score.toFixed(3)}, strength: ${r.effective_strength.toFixed(3)}) ${r.content.slice(0, 500)}`)
|
|
556
|
-
.join("\n\n");
|
|
557
|
-
return { content: [{ type: "text", text }] };
|
|
558
|
-
}
|