@gamaze/hicortex 0.11.1 → 0.12.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 +16 -2
- package/assets/context.html +487 -0
- package/dist/cli.js +18 -0
- package/dist/context-cli.d.ts +58 -0
- package/dist/context-cli.js +243 -0
- package/dist/context-store.d.ts +120 -0
- package/dist/context-store.js +321 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +37 -20
- package/dist/init.js +22 -9
- package/dist/lessons-context.d.ts +20 -7
- package/dist/lessons-context.js +127 -29
- package/dist/mcp-server.js +69 -7
- package/dist/nightly-status.js +6 -0
- package/dist/nightly.js +19 -0
- package/dist/uninstall.js +29 -13
- package/dist/viz.d.ts +15 -0
- package/dist/viz.js +48 -0
- package/hermes-plugin/hicortex/README.md +4 -5
- package/hermes-plugin/hicortex/__init__.py +1 -1
- package/hermes-plugin/hicortex/client.py +2 -2
- package/hermes-plugin/hicortex/plugin.yaml +2 -2
- package/hermes-plugin/hicortex/provider.py +8 -21
- package/package.json +1 -1
- package/skills/hicortex-memory/SKILL.md +1 -1
package/dist/nightly.js
CHANGED
|
@@ -103,10 +103,29 @@ function writeLastRun(stateDir = HICORTEX_HOME) {
|
|
|
103
103
|
return s;
|
|
104
104
|
}, stateDir);
|
|
105
105
|
}
|
|
106
|
+
const NIGHTLY_LOG_MAX_BYTES = 1024 * 1024; // 1 MB — years of normal runs
|
|
107
|
+
/**
|
|
108
|
+
* Keep ~/.hicortex/nightly.log bounded. The launchd plist and systemd unit
|
|
109
|
+
* both append to it forever with no rotation, and the typical volatile-journal
|
|
110
|
+
* target is a Raspberry Pi on a small SD card. Copy-then-truncate (not rename)
|
|
111
|
+
* because the process's own stdout may hold an O_APPEND fd on this very file —
|
|
112
|
+
* truncation keeps that fd valid and subsequent writes land at the new end.
|
|
113
|
+
*/
|
|
114
|
+
function rotateNightlyLog(stateDir = HICORTEX_HOME) {
|
|
115
|
+
const logPath = (0, node_path_1.join)(stateDir, "nightly.log");
|
|
116
|
+
try {
|
|
117
|
+
if ((0, node_fs_1.statSync)(logPath).size <= NIGHTLY_LOG_MAX_BYTES)
|
|
118
|
+
return;
|
|
119
|
+
(0, node_fs_1.copyFileSync)(logPath, `${logPath}.old`);
|
|
120
|
+
(0, node_fs_1.truncateSync)(logPath);
|
|
121
|
+
}
|
|
122
|
+
catch { /* no log file, or unreadable — nothing to rotate */ }
|
|
123
|
+
}
|
|
106
124
|
async function runNightly(options = {}) {
|
|
107
125
|
const dryRun = options.dryRun ?? false;
|
|
108
126
|
const captureOnly = options.captureOnly ?? false;
|
|
109
127
|
const stateDir = options.stateDir ?? HICORTEX_HOME;
|
|
128
|
+
rotateNightlyLog(stateDir);
|
|
110
129
|
// One-time migration of legacy state files (no-op if state.json exists)
|
|
111
130
|
(0, state_js_1.migrateLegacyState)(stateDir);
|
|
112
131
|
// Check mode: client or server
|
package/dist/uninstall.js
CHANGED
|
@@ -32,29 +32,45 @@ async function runUninstall() {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
console.log();
|
|
35
|
-
// 1. Stop and remove daemon
|
|
35
|
+
// 1. Stop and remove daemon + nightly timer (both units, or the timer
|
|
36
|
+
// keeps firing against a half-removed install)
|
|
36
37
|
const os = (0, node_os_1.platform)();
|
|
37
38
|
if (os === "darwin") {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
for (const name of ["com.gamaze.hicortex.plist", "com.gamaze.hicortex-nightly.plist"]) {
|
|
40
|
+
const plistPath = (0, node_path_1.join)((0, node_os_1.homedir)(), "Library", "LaunchAgents", name);
|
|
41
|
+
if ((0, node_fs_1.existsSync)(plistPath)) {
|
|
42
|
+
try {
|
|
43
|
+
(0, node_child_process_1.execSync)(`launchctl unload ${plistPath} 2>/dev/null`);
|
|
44
|
+
}
|
|
45
|
+
catch { /* not loaded */ }
|
|
46
|
+
(0, node_fs_1.unlinkSync)(plistPath);
|
|
47
|
+
console.log(` ✓ Removed ${name}`);
|
|
42
48
|
}
|
|
43
|
-
catch { /* not loaded */ }
|
|
44
|
-
(0, node_fs_1.unlinkSync)(plistPath);
|
|
45
|
-
console.log(" ✓ Removed launchd daemon");
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
else if (os === "linux") {
|
|
49
52
|
try {
|
|
50
53
|
(0, node_child_process_1.execSync)("systemctl --user disable --now hicortex.service 2>/dev/null");
|
|
51
|
-
const servicePath = (0, node_path_1.join)((0, node_os_1.homedir)(), ".config", "systemd", "user", "hicortex.service");
|
|
52
|
-
if ((0, node_fs_1.existsSync)(servicePath))
|
|
53
|
-
(0, node_fs_1.unlinkSync)(servicePath);
|
|
54
|
-
(0, node_child_process_1.execSync)("systemctl --user daemon-reload 2>/dev/null");
|
|
55
|
-
console.log(" ✓ Removed systemd service");
|
|
56
54
|
}
|
|
57
55
|
catch { /* not installed */ }
|
|
56
|
+
try {
|
|
57
|
+
(0, node_child_process_1.execSync)("systemctl --user disable --now hicortex-nightly.timer 2>/dev/null");
|
|
58
|
+
}
|
|
59
|
+
catch { /* not installed */ }
|
|
60
|
+
const unitDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".config", "systemd", "user");
|
|
61
|
+
for (const name of ["hicortex.service", "hicortex-nightly.timer", "hicortex-nightly.service"]) {
|
|
62
|
+
const unitPath = (0, node_path_1.join)(unitDir, name);
|
|
63
|
+
try {
|
|
64
|
+
if ((0, node_fs_1.existsSync)(unitPath))
|
|
65
|
+
(0, node_fs_1.unlinkSync)(unitPath);
|
|
66
|
+
}
|
|
67
|
+
catch { /* leave it */ }
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
(0, node_child_process_1.execSync)("systemctl --user daemon-reload 2>/dev/null");
|
|
71
|
+
}
|
|
72
|
+
catch { /* fine */ }
|
|
73
|
+
console.log(" ✓ Removed systemd service + nightly timer");
|
|
58
74
|
}
|
|
59
75
|
// 2. Remove MCP from CC
|
|
60
76
|
try {
|
package/dist/viz.d.ts
CHANGED
|
@@ -51,6 +51,21 @@ export declare function readVizHtml(): string;
|
|
|
51
51
|
* when the asset cannot be read.
|
|
52
52
|
*/
|
|
53
53
|
export declare function vizHandler(): express.RequestHandler;
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the on-disk path of the context-layer editor page. Throws (fail
|
|
56
|
+
* explicitly) when the asset is missing — same contract as resolveVizHtmlPath.
|
|
57
|
+
* assets/ sits next to both dist/ (dist/viz.js → ../assets/) and src/
|
|
58
|
+
* (src/viz.ts → ../assets/ under tsx), so one sibling candidate covers both.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveContextHtmlPath(): string;
|
|
61
|
+
/** Read the context editor page. Read at request time so a reinstall is live. */
|
|
62
|
+
export declare function readContextHtml(): string;
|
|
63
|
+
/**
|
|
64
|
+
* Express handler for GET /context/ui — the PRIMARY edit surface for the
|
|
65
|
+
* standing context layer. 503 with the usual {error} shape when the asset
|
|
66
|
+
* cannot be read, exactly like vizHandler.
|
|
67
|
+
*/
|
|
68
|
+
export declare function contextUiHandler(): express.RequestHandler;
|
|
54
69
|
/**
|
|
55
70
|
* Resolve the on-disk path of an allowlisted vendor bundle, or null when the
|
|
56
71
|
* requested name is not on the allowlist. The filesystem path is built ONLY
|
package/dist/viz.js
CHANGED
|
@@ -31,6 +31,9 @@ exports.createAuthMiddleware = createAuthMiddleware;
|
|
|
31
31
|
exports.resolveVizHtmlPath = resolveVizHtmlPath;
|
|
32
32
|
exports.readVizHtml = readVizHtml;
|
|
33
33
|
exports.vizHandler = vizHandler;
|
|
34
|
+
exports.resolveContextHtmlPath = resolveContextHtmlPath;
|
|
35
|
+
exports.readContextHtml = readContextHtml;
|
|
36
|
+
exports.contextUiHandler = contextUiHandler;
|
|
34
37
|
exports.resolveVizVendorPath = resolveVizVendorPath;
|
|
35
38
|
exports.vizVendorHandler = vizVendorHandler;
|
|
36
39
|
const node_fs_1 = require("node:fs");
|
|
@@ -70,6 +73,14 @@ function createAuthMiddleware(authToken) {
|
|
|
70
73
|
// normal Authorization header on its data fetches.
|
|
71
74
|
if (req.method === "GET" && req.path === "/viz")
|
|
72
75
|
return next();
|
|
76
|
+
// The /context/ui page SHELL is public for the same reason as /viz: a
|
|
77
|
+
// self-contained static editor page, no data and no secrets (it ships
|
|
78
|
+
// verbatim in the npm tarball). The standing-context DATA it edits comes
|
|
79
|
+
// from GET/PUT /context, which stay bearer-only (localhost bypass) like
|
|
80
|
+
// every other data route; the page collects the token client-side and
|
|
81
|
+
// sends it as a normal Authorization header on its /context fetches.
|
|
82
|
+
if (req.method === "GET" && req.path === "/context/ui")
|
|
83
|
+
return next();
|
|
73
84
|
// The pinned renderer bundles the /viz page loads (#139) are public for
|
|
74
85
|
// the same reason as the shell: static third-party code shipped verbatim
|
|
75
86
|
// in the npm tarball, zero data. Kept tight: GET only, and ONLY names on
|
|
@@ -136,6 +147,43 @@ function vizHandler() {
|
|
|
136
147
|
}
|
|
137
148
|
};
|
|
138
149
|
}
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Context layer editor page (/context/ui, 0.12 — spec 2026-07-12 §5)
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
/**
|
|
154
|
+
* Resolve the on-disk path of the context-layer editor page. Throws (fail
|
|
155
|
+
* explicitly) when the asset is missing — same contract as resolveVizHtmlPath.
|
|
156
|
+
* assets/ sits next to both dist/ (dist/viz.js → ../assets/) and src/
|
|
157
|
+
* (src/viz.ts → ../assets/ under tsx), so one sibling candidate covers both.
|
|
158
|
+
*/
|
|
159
|
+
function resolveContextHtmlPath() {
|
|
160
|
+
const candidates = [(0, node_path_1.join)(__dirname, "..", "assets", "context.html")];
|
|
161
|
+
for (const candidate of candidates) {
|
|
162
|
+
if ((0, node_fs_1.existsSync)(candidate))
|
|
163
|
+
return candidate;
|
|
164
|
+
}
|
|
165
|
+
throw new Error(`context.html asset not found — looked in: ${candidates.join(", ")}. ` +
|
|
166
|
+
`The package install is incomplete (assets/ missing).`);
|
|
167
|
+
}
|
|
168
|
+
/** Read the context editor page. Read at request time so a reinstall is live. */
|
|
169
|
+
function readContextHtml() {
|
|
170
|
+
return (0, node_fs_1.readFileSync)(resolveContextHtmlPath(), "utf-8");
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Express handler for GET /context/ui — the PRIMARY edit surface for the
|
|
174
|
+
* standing context layer. 503 with the usual {error} shape when the asset
|
|
175
|
+
* cannot be read, exactly like vizHandler.
|
|
176
|
+
*/
|
|
177
|
+
function contextUiHandler() {
|
|
178
|
+
return (_req, res) => {
|
|
179
|
+
try {
|
|
180
|
+
res.type("html").send(readContextHtml());
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
res.status(503).json({ error: err instanceof Error ? err.message : String(err) });
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
139
187
|
/**
|
|
140
188
|
* Resolve the on-disk path of an allowlisted vendor bundle, or null when the
|
|
141
189
|
* requested name is not on the allowlist. The filesystem path is built ONLY
|
|
@@ -15,28 +15,27 @@ Gives [Hermes](https://github.com/nousresearch/hermes-agent) agents self-learnin
|
|
|
15
15
|
| `prefetch(query)` | recall relevant memories before each turn | `GET /search` |
|
|
16
16
|
| `queue_prefetch(query)` | background recall for the next turn | `GET /search` |
|
|
17
17
|
| `system_prompt_block()` | inject distilled lessons + memory index | `GET /lessons` |
|
|
18
|
-
| `get_tool_schemas()` | exposes the 8 unified tools
|
|
18
|
+
| `get_tool_schemas()` | exposes the 8 unified tools | see tool table below |
|
|
19
19
|
|
|
20
20
|
That's the whole surface. No `sync_turn`, no compaction/session-end capture — those are intentionally absent.
|
|
21
21
|
|
|
22
|
-
### Tools (unified 8
|
|
22
|
+
### Tools (unified 8)
|
|
23
23
|
|
|
24
24
|
| Tool | REST call | Description |
|
|
25
25
|
|---|---|---|
|
|
26
26
|
| `hicortex_search` | `GET /search` | Semantic search over long-term memory |
|
|
27
|
-
| `
|
|
27
|
+
| `hicortex_recent` | `GET /recent` | Recent memories by project (queryless recall; was `hicortex_context`/`hicortex_recall_recent` before 0.12) |
|
|
28
28
|
| `hicortex_ingest` | `POST /ingest` | Store a new memory |
|
|
29
29
|
| `hicortex_lessons` | `GET /lessons` | Get distilled lessons |
|
|
30
30
|
| `hicortex_index` | `GET /index` | Knowledge domain index |
|
|
31
31
|
| `hicortex_graph` | `GET /graph` | Graph queries (neighbors/hubs/path) |
|
|
32
32
|
| `hicortex_update` | `POST /update` | Update a memory (re-embeds on content change) |
|
|
33
33
|
| `hicortex_delete` | `POST /delete` | Permanently delete a memory and its links |
|
|
34
|
-
| `hicortex_recall_recent` | `GET /context` | Hermes-specific alias for context recall |
|
|
35
34
|
|
|
36
35
|
## Prerequisites
|
|
37
36
|
|
|
38
37
|
- A reachable Hicortex server (default `http://localhost:8787`). Stand one up with `npx @gamaze/hicortex init`.
|
|
39
|
-
- The server needs the REST `/search`, `/
|
|
38
|
+
- The server needs the REST `/search`, `/recent`, `/lessons` endpoints (Hicortex ≥ 0.12 — this plugin version does not talk to older servers; upgrade the server first).
|
|
40
39
|
|
|
41
40
|
## Install
|
|
42
41
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Hicortex memory provider plugin for Hermes — recall-only.
|
|
2
2
|
|
|
3
3
|
Recall: prefetch() -> GET /search (relevant memories before each turn)
|
|
4
|
-
tools -> hicortex_search /
|
|
4
|
+
tools -> hicortex_search / hicortex_recent
|
|
5
5
|
system_prompt_block -> lessons injected into the system prompt
|
|
6
6
|
|
|
7
7
|
Capture is NOT the plugin's job. A nightly reader on the Hicortex server
|
|
@@ -83,14 +83,14 @@ class HicortexClient:
|
|
|
83
83
|
{"query": query, "limit": limit, "project": project, "privacy": privacy},
|
|
84
84
|
).get("results", [])
|
|
85
85
|
|
|
86
|
-
def
|
|
86
|
+
def recent(
|
|
87
87
|
self,
|
|
88
88
|
project: Optional[str] = None,
|
|
89
89
|
limit: int = 10,
|
|
90
90
|
privacy: Optional[str] = None,
|
|
91
91
|
) -> list[dict]:
|
|
92
92
|
return self._get(
|
|
93
|
-
"/
|
|
93
|
+
"/recent", {"project": project, "limit": limit, "privacy": privacy}
|
|
94
94
|
).get("results", [])
|
|
95
95
|
|
|
96
96
|
def lessons(self) -> dict[str, Any]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: hicortex
|
|
2
|
-
version: 0.
|
|
3
|
-
description: "Self-learning memory for Hermes agents — every session is distilled into lessons overnight, and your agent wakes up wiser. Injects fresh lessons each turn and exposes the full 8-tool memory surface (search,
|
|
2
|
+
version: 0.5.0
|
|
3
|
+
description: "Self-learning memory for Hermes agents — every session is distilled into lessons overnight, and your agent wakes up wiser. Injects fresh lessons each turn and exposes the full 8-tool memory surface (search, recent, ingest, lessons, index, graph, update, delete) via a shared Hicortex server. Stdlib-only."
|
|
4
4
|
pip_dependencies: []
|
|
5
5
|
hooks: []
|
|
6
6
|
requires_env:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Recall: prefetch() -> GET /search (relevant memories before each turn)
|
|
4
4
|
queue_prefetch() -> GET /search (background recall for the next turn)
|
|
5
|
-
tools -> hicortex_search /
|
|
5
|
+
tools -> hicortex_search / hicortex_recent
|
|
6
6
|
system_prompt_block -> lessons + memory index injected into the prompt
|
|
7
7
|
|
|
8
8
|
Capture is NOT the plugin's job. A nightly reader on the Hicortex server
|
|
@@ -152,7 +152,7 @@ class HicortexProvider(MemoryProvider):
|
|
|
152
152
|
lines = [
|
|
153
153
|
"## Hicortex long-term memory",
|
|
154
154
|
"You have shared long-term memory across sessions. Use `hicortex_search` "
|
|
155
|
-
"for specific recall and `
|
|
155
|
+
"for specific recall and `hicortex_recent` for recent memories by project.",
|
|
156
156
|
]
|
|
157
157
|
if lessons:
|
|
158
158
|
lines.append("Lessons:")
|
|
@@ -188,24 +188,11 @@ class HicortexProvider(MemoryProvider):
|
|
|
188
188
|
},
|
|
189
189
|
},
|
|
190
190
|
{
|
|
191
|
-
"name": "
|
|
192
|
-
"description": "Recall recent context memories, optionally filtered by project.",
|
|
193
|
-
"parameters": {
|
|
194
|
-
"type": "object",
|
|
195
|
-
"properties": {
|
|
196
|
-
"project": {"type": "string"},
|
|
197
|
-
"limit": {
|
|
198
|
-
"type": "number",
|
|
199
|
-
"description": "Max results (default 10)",
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
"name": "hicortex_context",
|
|
191
|
+
"name": "hicortex_recent",
|
|
206
192
|
"description": (
|
|
207
|
-
"Get recent
|
|
208
|
-
"
|
|
193
|
+
"Get recent memories, optionally filtered by project. Queryless recall "
|
|
194
|
+
"of the latest memories by project, ranked by importance. Useful to "
|
|
195
|
+
"catch up on what happened recently."
|
|
209
196
|
),
|
|
210
197
|
"parameters": {
|
|
211
198
|
"type": "object",
|
|
@@ -335,8 +322,8 @@ class HicortexProvider(MemoryProvider):
|
|
|
335
322
|
)
|
|
336
323
|
return json.dumps(hits)
|
|
337
324
|
|
|
338
|
-
elif tool_name
|
|
339
|
-
hits = client.
|
|
325
|
+
elif tool_name == "hicortex_recent":
|
|
326
|
+
hits = client.recent(
|
|
340
327
|
project=args.get("project") or self._project,
|
|
341
328
|
limit=int(args.get("limit", 10)),
|
|
342
329
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Self-learning memory for AI agents — experience captured automatically, distilled into lessons overnight, shared across your whole fleet. Works with Hermes, OpenClaw, Claude Code, and Pi.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@ You have access to a long-term memory system that stores knowledge from past ses
|
|
|
16
16
|
- You're about to make a decision that might contradict past work
|
|
17
17
|
- Starting work on a project you've worked on before
|
|
18
18
|
|
|
19
|
-
## When to use
|
|
19
|
+
## When to use hicortex_recent
|
|
20
20
|
|
|
21
21
|
- At the start of a session to recall recent project state
|
|
22
22
|
- When switching between projects to load relevant context
|