@gamaze/hicortex 0.11.1 → 0.12.1
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/classify-domains.js +2 -2
- package/dist/cli.js +18 -0
- package/dist/context-cli.d.ts +58 -0
- package/dist/context-cli.js +240 -0
- package/dist/context-store.d.ts +120 -0
- package/dist/context-store.js +321 -0
- package/dist/db.js +2 -1
- package/dist/features.js +2 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +39 -21
- package/dist/init.d.ts +9 -0
- package/dist/init.js +41 -11
- package/dist/lessons-context.d.ts +20 -7
- package/dist/lessons-context.js +120 -30
- package/dist/mcp-server.js +71 -9
- package/dist/nightly-status.js +8 -1
- package/dist/nightly.js +21 -2
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +17 -0
- package/dist/relink.js +2 -2
- package/dist/retrieval.d.ts +1 -1
- package/dist/retrieval.js +2 -2
- package/dist/state.js +2 -2
- package/dist/status.js +2 -1
- package/dist/uninstall.js +31 -14
- 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 +2 -2
- package/skills/hicortex-memory/SKILL.md +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -50,6 +50,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
50
50
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
51
|
exports.startServer = startServer;
|
|
52
52
|
const express_1 = __importDefault(require("express"));
|
|
53
|
+
const node_path_1 = require("node:path");
|
|
53
54
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
54
55
|
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
55
56
|
const zod_1 = require("zod");
|
|
@@ -61,6 +62,7 @@ const embedder_js_1 = require("./embedder.js");
|
|
|
61
62
|
const storage = __importStar(require("./storage.js"));
|
|
62
63
|
const graph_js_1 = require("./graph.js");
|
|
63
64
|
const viz_js_1 = require("./viz.js");
|
|
65
|
+
const context_store_js_1 = require("./context-store.js");
|
|
64
66
|
const retrieval = __importStar(require("./retrieval.js"));
|
|
65
67
|
const seed_lesson_js_1 = require("./seed-lesson.js");
|
|
66
68
|
const distiller_js_1 = require("./distiller.js");
|
|
@@ -76,6 +78,9 @@ let llmConfig = null;
|
|
|
76
78
|
// immediate abort ("strict", default) or a fallback to the base model ("local").
|
|
77
79
|
let distillFallbackMode = "strict";
|
|
78
80
|
let stateDir = "";
|
|
81
|
+
// Resolved contextClients list (spec §2) — the harness names allowed to inject
|
|
82
|
+
// the standing context layer. Echoed by GET /context so each hook self-gates.
|
|
83
|
+
let contextClients = ["cc"];
|
|
79
84
|
// Cache detectChunkSize results keyed by "<provider>/<model>@<baseUrl>" so we
|
|
80
85
|
// probe each endpoint once per server boot rather than once per /distill request.
|
|
81
86
|
const chunkSizeCache = new Map();
|
|
@@ -109,19 +114,19 @@ function createMcpServer() {
|
|
|
109
114
|
return { content: [{ type: "text", text: `Search failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
110
115
|
}
|
|
111
116
|
});
|
|
112
|
-
// --
|
|
113
|
-
server.tool("
|
|
117
|
+
// -- hicortex_recent --
|
|
118
|
+
server.tool("hicortex_recent", "Get recent memories, optionally filtered by project. Queryless recall of the latest memories by project, ranked by importance. Useful to catch up on what happened recently.", {
|
|
114
119
|
project: zod_1.z.string().optional().describe("Filter by project name"),
|
|
115
120
|
limit: zod_1.z.coerce.number().optional().describe("Max results (default 10)"),
|
|
116
121
|
}, async ({ project, limit }) => {
|
|
117
122
|
if (!db)
|
|
118
123
|
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
119
124
|
try {
|
|
120
|
-
const results = retrieval.
|
|
125
|
+
const results = retrieval.searchRecent(db, { project, limit });
|
|
121
126
|
return { content: [{ type: "text", text: formatResults(results) }] };
|
|
122
127
|
}
|
|
123
128
|
catch (err) {
|
|
124
|
-
return { content: [{ type: "text", text: `
|
|
129
|
+
return { content: [{ type: "text", text: `Recent recall failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
125
130
|
}
|
|
126
131
|
});
|
|
127
132
|
// -- hicortex_ingest --
|
|
@@ -387,7 +392,7 @@ async function startServer(options = {}) {
|
|
|
387
392
|
console.warn("╔══════════════════════════════════════════════════════════════╗");
|
|
388
393
|
console.warn("║ NO LLM CONFIGURED — running in recall-only mode ║");
|
|
389
394
|
console.warn("║ ║");
|
|
390
|
-
console.warn("║ search / lessons /
|
|
395
|
+
console.warn("║ search / lessons / recent: ENABLED ║");
|
|
391
396
|
console.warn("║ /distill (capture) and consolidation: DISABLED ║");
|
|
392
397
|
console.warn("║ ║");
|
|
393
398
|
console.warn("║ To enable capture, run: ║");
|
|
@@ -422,6 +427,15 @@ async function startServer(options = {}) {
|
|
|
422
427
|
console.warn("[hicortex] WARNING: no authToken configured — remote connections will be rejected " +
|
|
423
428
|
"(localhost still works). Run `npx @gamaze/hicortex init` to generate a token.");
|
|
424
429
|
}
|
|
430
|
+
// Context layer (0.12): resolve which harnesses may inject the standing
|
|
431
|
+
// context. Warn once per boot on unknown names so typos (e.g. "herms")
|
|
432
|
+
// surface instead of silently dropping.
|
|
433
|
+
const resolvedClients = (0, context_store_js_1.resolveContextClients)(savedConfig?.contextClients);
|
|
434
|
+
contextClients = resolvedClients.clients;
|
|
435
|
+
if (resolvedClients.dropped.length > 0) {
|
|
436
|
+
console.warn(`[hicortex] Ignoring unknown contextClients: ${resolvedClients.dropped.join(", ")} ` +
|
|
437
|
+
`(known: cc, hermes, oc)`);
|
|
438
|
+
}
|
|
425
439
|
// Express app
|
|
426
440
|
const app = (0, express_1.default)();
|
|
427
441
|
// Raise the body limit — whole-session denoised transcripts exceed the 100 kB default.
|
|
@@ -431,7 +445,7 @@ async function startServer(options = {}) {
|
|
|
431
445
|
const origin = req.headers.origin;
|
|
432
446
|
if (origin) {
|
|
433
447
|
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
434
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
448
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, POST, OPTIONS");
|
|
435
449
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization");
|
|
436
450
|
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
437
451
|
res.setHeader("Vary", "Origin");
|
|
@@ -567,8 +581,8 @@ async function startServer(options = {}) {
|
|
|
567
581
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
568
582
|
}
|
|
569
583
|
});
|
|
570
|
-
// REST /
|
|
571
|
-
app.get("/
|
|
584
|
+
// REST /recent — recent memories, optionally filtered by project.
|
|
585
|
+
app.get("/recent", (req, res) => {
|
|
572
586
|
if (!db) {
|
|
573
587
|
res.status(503).json({ error: "Server not initialized" });
|
|
574
588
|
return;
|
|
@@ -579,13 +593,51 @@ async function startServer(options = {}) {
|
|
|
579
593
|
? req.query.privacy.split(",").map((s) => s.trim()).filter(Boolean)
|
|
580
594
|
: undefined;
|
|
581
595
|
try {
|
|
582
|
-
const results = retrieval.
|
|
596
|
+
const results = retrieval.searchRecent(db, { project, limit, privacy });
|
|
583
597
|
res.json({ results });
|
|
584
598
|
}
|
|
585
599
|
catch (err) {
|
|
586
600
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
587
601
|
}
|
|
588
602
|
});
|
|
603
|
+
// -------------------------------------------------------------------------
|
|
604
|
+
// REST /context — standing context layer (0.12, spec 2026-07-12).
|
|
605
|
+
//
|
|
606
|
+
// GET → { sections, updated_at, clients } read from <hicortex-home>/context/.
|
|
607
|
+
// PUT → partial upsert of named sections (allowlisted names, atomic).
|
|
608
|
+
//
|
|
609
|
+
// This is NOT recall. The recall endpoint that previously held this name is
|
|
610
|
+
// now /recent (§Naming). Stale-client tripwire: old recall callers always
|
|
611
|
+
// send project/limit/privacy query params; context-layer callers never do —
|
|
612
|
+
// so those params on GET /context return a loud, self-explaining 400 instead
|
|
613
|
+
// of silently degrading recall to an empty {sections} response.
|
|
614
|
+
//
|
|
615
|
+
// Auth is the standard model (bearer; localhost bypass) via the shared
|
|
616
|
+
// middleware — no special-casing here.
|
|
617
|
+
// -------------------------------------------------------------------------
|
|
618
|
+
// Thin adapters: all logic (tripwire, validation, allowlist, atomicity,
|
|
619
|
+
// symlink safety, size warn) lives in the pure handlers in context-store.ts,
|
|
620
|
+
// which the tests exercise directly — no mirror-app drift.
|
|
621
|
+
app.get("/context", (req, res) => {
|
|
622
|
+
try {
|
|
623
|
+
const r = (0, context_store_js_1.handleContextGet)((0, node_path_1.join)(stateDir, "context"), contextClients, req.query);
|
|
624
|
+
res.status(r.status).json(r.body);
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
app.put("/context", (req, res) => {
|
|
631
|
+
try {
|
|
632
|
+
const r = (0, context_store_js_1.handleContextPut)((0, node_path_1.join)(stateDir, "context"), req.body);
|
|
633
|
+
if (r.warn)
|
|
634
|
+
console.warn(`[hicortex] ${r.warn}`);
|
|
635
|
+
res.status(r.status).json(r.body);
|
|
636
|
+
}
|
|
637
|
+
catch (err) {
|
|
638
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
639
|
+
}
|
|
640
|
+
});
|
|
589
641
|
// REST /distill — canonical capture endpoint (0.9.0+).
|
|
590
642
|
// Every machine (including the server itself) POSTs denoised session text here.
|
|
591
643
|
// The server distills, embeds, stores. Body limit: 25 MB (raised at app init).
|
|
@@ -916,6 +968,16 @@ async function startServer(options = {}) {
|
|
|
916
968
|
// (static third-party code from the npm tarball, no data) — the exemption
|
|
917
969
|
// lives in createAuthMiddleware next to the /viz one.
|
|
918
970
|
app.get("/viz/vendor/:file", (0, viz_js_1.vizVendorHandler)());
|
|
971
|
+
// GET /context/ui — standing-context editor page (0.12, spec 2026-07-12 §5).
|
|
972
|
+
//
|
|
973
|
+
// The PRIMARY edit surface for the context layer. Self-contained HTML (inline
|
|
974
|
+
// CSS/JS, zero external requests) served from assets/context.html; builds one
|
|
975
|
+
// tab per section from GET /context and saves via PUT /context. The page
|
|
976
|
+
// SHELL is public (exempted in createAuthMiddleware, like /viz — it carries
|
|
977
|
+
// no data); the GET/PUT /context data calls stay bearer-only (localhost
|
|
978
|
+
// bypass). The page collects the token client-side: ?token= URL param
|
|
979
|
+
// (stripped on load) or an in-page prompt on 401, persisted in localStorage.
|
|
980
|
+
app.get("/context/ui", (0, viz_js_1.contextUiHandler)());
|
|
919
981
|
// SSE endpoint — each connection gets its own McpServer + transport
|
|
920
982
|
app.get("/sse", async (req, res) => {
|
|
921
983
|
const transport = new sse_js_1.SSEServerTransport("/messages", res);
|
package/dist/nightly-status.js
CHANGED
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.showNightlyStatus = showNightlyStatus;
|
|
14
|
+
const paths_js_1 = require("./paths.js");
|
|
14
15
|
const node_fs_1 = require("node:fs");
|
|
15
16
|
const node_path_1 = require("node:path");
|
|
16
17
|
const node_os_1 = require("node:os");
|
|
17
18
|
const node_child_process_1 = require("node:child_process");
|
|
18
19
|
const db_js_1 = require("./db.js");
|
|
19
20
|
const state_js_1 = require("./state.js");
|
|
20
|
-
const HICORTEX_HOME = (0,
|
|
21
|
+
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
21
22
|
const CONFIG_PATH = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
|
|
22
23
|
async function showNightlyStatus() {
|
|
23
24
|
console.log("Hicortex Nightly Pipeline Status");
|
|
@@ -92,6 +93,12 @@ async function showNightlyStatus() {
|
|
|
92
93
|
catch { /* not installed */ }
|
|
93
94
|
}
|
|
94
95
|
console.log(`Timer: ${timerInfo}${!timerActive ? " ⚠ Pipeline will NOT run automatically" : ""}`);
|
|
96
|
+
// Scheduled runs log here (launchd plist / systemd unit both append) —
|
|
97
|
+
// point operators at it, since the runs' output is not in journalctl.
|
|
98
|
+
const nightlyLogPath = (0, node_path_1.join)(HICORTEX_HOME, "nightly.log");
|
|
99
|
+
if ((0, node_fs_1.existsSync)(nightlyLogPath)) {
|
|
100
|
+
console.log(`Log: ${nightlyLogPath}`);
|
|
101
|
+
}
|
|
95
102
|
// DB stats
|
|
96
103
|
const dbPath = (0, db_js_1.resolveDbPath)();
|
|
97
104
|
if ((0, node_fs_1.existsSync)(dbPath)) {
|
package/dist/nightly.js
CHANGED
|
@@ -46,9 +46,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
46
46
|
})();
|
|
47
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
48
|
exports.runNightly = runNightly;
|
|
49
|
+
const paths_js_1 = require("./paths.js");
|
|
49
50
|
const node_fs_1 = require("node:fs");
|
|
50
51
|
const node_path_1 = require("node:path");
|
|
51
|
-
const node_os_1 = require("node:os");
|
|
52
52
|
let VERSION = "0.0.0";
|
|
53
53
|
try {
|
|
54
54
|
VERSION = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "..", "package.json"), "utf-8")).version;
|
|
@@ -69,7 +69,7 @@ const oc_transcript_reader_js_1 = require("./oc-transcript-reader.js");
|
|
|
69
69
|
const features_js_1 = require("./features.js");
|
|
70
70
|
const state_js_1 = require("./state.js");
|
|
71
71
|
const telemetry_js_1 = require("./telemetry.js");
|
|
72
|
-
const HICORTEX_HOME = (0,
|
|
72
|
+
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
73
73
|
function readNightlyConfig(stateDir) {
|
|
74
74
|
try {
|
|
75
75
|
const configPath = (0, node_path_1.join)(stateDir, "config.json");
|
|
@@ -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/paths.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function hicortexHome(): string;
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hicortexHome = hicortexHome;
|
|
4
|
+
/**
|
|
5
|
+
* Canonical Hicortex home resolution — the single source of truth (#174).
|
|
6
|
+
*
|
|
7
|
+
* Honors the HICORTEX_HOME env override (a headless/test seam, mirroring the
|
|
8
|
+
* HICORTEX_DB_PATH convention in db.ts); otherwise defaults to ~/.hicortex.
|
|
9
|
+
* Every module that needs the home dir routes through here, so the override
|
|
10
|
+
* behaves consistently across all commands instead of being honored by some
|
|
11
|
+
* (context-cli, lessons-context) and hardcoded away by others.
|
|
12
|
+
*/
|
|
13
|
+
const node_os_1 = require("node:os");
|
|
14
|
+
const node_path_1 = require("node:path");
|
|
15
|
+
function hicortexHome() {
|
|
16
|
+
return process.env.HICORTEX_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
17
|
+
}
|
package/dist/relink.js
CHANGED
|
@@ -74,14 +74,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
74
74
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
75
75
|
exports.getStoredEmbedding = getStoredEmbedding;
|
|
76
76
|
exports.runRelink = runRelink;
|
|
77
|
+
const paths_js_1 = require("./paths.js");
|
|
77
78
|
const node_fs_1 = require("node:fs");
|
|
78
79
|
const node_path_1 = require("node:path");
|
|
79
|
-
const node_os_1 = require("node:os");
|
|
80
80
|
const db_js_1 = require("./db.js");
|
|
81
81
|
const storage = __importStar(require("./storage.js"));
|
|
82
82
|
const consolidate_js_1 = require("./consolidate.js");
|
|
83
83
|
const state_js_1 = require("./state.js");
|
|
84
|
-
const HICORTEX_HOME = (0,
|
|
84
|
+
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
85
85
|
function readConfig(stateDir) {
|
|
86
86
|
try {
|
|
87
87
|
return JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(stateDir, "config.json"), "utf-8"));
|
package/dist/retrieval.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export declare function retrieve(db: Database.Database, embedFn: EmbedFn, query:
|
|
|
55
55
|
/**
|
|
56
56
|
* Get recent context, optionally filtered by project and privacy.
|
|
57
57
|
*/
|
|
58
|
-
export declare function
|
|
58
|
+
export declare function searchRecent(db: Database.Database, options?: {
|
|
59
59
|
project?: string | null;
|
|
60
60
|
limit?: number;
|
|
61
61
|
privacy?: string[];
|
package/dist/retrieval.js
CHANGED
|
@@ -52,7 +52,7 @@ exports.l2ToCosine = l2ToCosine;
|
|
|
52
52
|
exports.effectiveStrength = effectiveStrength;
|
|
53
53
|
exports.computeScore = computeScore;
|
|
54
54
|
exports.retrieve = retrieve;
|
|
55
|
-
exports.
|
|
55
|
+
exports.searchRecent = searchRecent;
|
|
56
56
|
const storage = __importStar(require("./storage.js"));
|
|
57
57
|
const BASE_DECAY = 0.0005;
|
|
58
58
|
/**
|
|
@@ -315,7 +315,7 @@ async function retrieve(db, embedFn, query, options) {
|
|
|
315
315
|
/**
|
|
316
316
|
* Get recent context, optionally filtered by project and privacy.
|
|
317
317
|
*/
|
|
318
|
-
function
|
|
318
|
+
function searchRecent(db, options) {
|
|
319
319
|
const limit = options?.limit ?? 10;
|
|
320
320
|
const project = options?.project;
|
|
321
321
|
const privacy = options?.privacy;
|
package/dist/state.js
CHANGED
|
@@ -23,10 +23,10 @@ exports.saveState = saveState;
|
|
|
23
23
|
exports.updateState = updateState;
|
|
24
24
|
exports.migrateLegacyState = migrateLegacyState;
|
|
25
25
|
exports.describeLastNightly = describeLastNightly;
|
|
26
|
+
const paths_js_1 = require("./paths.js");
|
|
26
27
|
const node_fs_1 = require("node:fs");
|
|
27
28
|
const node_path_1 = require("node:path");
|
|
28
|
-
const
|
|
29
|
-
const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
29
|
+
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
30
30
|
const STATE_FILE = "state.json";
|
|
31
31
|
/**
|
|
32
32
|
* Load the state file. Returns an empty state if the file is missing
|
package/dist/status.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runStatus = runStatus;
|
|
7
|
+
const paths_js_1 = require("./paths.js");
|
|
7
8
|
const node_fs_1 = require("node:fs");
|
|
8
9
|
const node_path_1 = require("node:path");
|
|
9
10
|
const node_os_1 = require("node:os");
|
|
@@ -11,7 +12,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
11
12
|
const db_js_1 = require("./db.js");
|
|
12
13
|
const features_js_1 = require("./features.js");
|
|
13
14
|
const state_js_1 = require("./state.js");
|
|
14
|
-
const HICORTEX_HOME = (0,
|
|
15
|
+
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
15
16
|
const CC_SETTINGS = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "settings.json");
|
|
16
17
|
const OC_CONFIG = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "openclaw.json");
|
|
17
18
|
async function runStatus() {
|
package/dist/uninstall.js
CHANGED
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.runUninstall = runUninstall;
|
|
8
|
+
const paths_js_1 = require("./paths.js");
|
|
8
9
|
const node_fs_1 = require("node:fs");
|
|
9
10
|
const node_path_1 = require("node:path");
|
|
10
11
|
const node_os_1 = require("node:os");
|
|
11
12
|
const node_child_process_1 = require("node:child_process");
|
|
12
13
|
const node_readline_1 = require("node:readline");
|
|
13
14
|
const claude_md_js_1 = require("./claude-md.js");
|
|
14
|
-
const HICORTEX_HOME = (0,
|
|
15
|
+
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
15
16
|
const CC_SETTINGS = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "settings.json");
|
|
16
17
|
const CC_COMMANDS_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "commands");
|
|
17
18
|
const CLAUDE_MD = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "CLAUDE.md");
|
|
@@ -32,29 +33,45 @@ async function runUninstall() {
|
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
34
35
|
console.log();
|
|
35
|
-
// 1. Stop and remove daemon
|
|
36
|
+
// 1. Stop and remove daemon + nightly timer (both units, or the timer
|
|
37
|
+
// keeps firing against a half-removed install)
|
|
36
38
|
const os = (0, node_os_1.platform)();
|
|
37
39
|
if (os === "darwin") {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
for (const name of ["com.gamaze.hicortex.plist", "com.gamaze.hicortex-nightly.plist"]) {
|
|
41
|
+
const plistPath = (0, node_path_1.join)((0, node_os_1.homedir)(), "Library", "LaunchAgents", name);
|
|
42
|
+
if ((0, node_fs_1.existsSync)(plistPath)) {
|
|
43
|
+
try {
|
|
44
|
+
(0, node_child_process_1.execSync)(`launchctl unload ${plistPath} 2>/dev/null`);
|
|
45
|
+
}
|
|
46
|
+
catch { /* not loaded */ }
|
|
47
|
+
(0, node_fs_1.unlinkSync)(plistPath);
|
|
48
|
+
console.log(` ✓ Removed ${name}`);
|
|
42
49
|
}
|
|
43
|
-
catch { /* not loaded */ }
|
|
44
|
-
(0, node_fs_1.unlinkSync)(plistPath);
|
|
45
|
-
console.log(" ✓ Removed launchd daemon");
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
else if (os === "linux") {
|
|
49
53
|
try {
|
|
50
54
|
(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
55
|
}
|
|
57
56
|
catch { /* not installed */ }
|
|
57
|
+
try {
|
|
58
|
+
(0, node_child_process_1.execSync)("systemctl --user disable --now hicortex-nightly.timer 2>/dev/null");
|
|
59
|
+
}
|
|
60
|
+
catch { /* not installed */ }
|
|
61
|
+
const unitDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".config", "systemd", "user");
|
|
62
|
+
for (const name of ["hicortex.service", "hicortex-nightly.timer", "hicortex-nightly.service"]) {
|
|
63
|
+
const unitPath = (0, node_path_1.join)(unitDir, name);
|
|
64
|
+
try {
|
|
65
|
+
if ((0, node_fs_1.existsSync)(unitPath))
|
|
66
|
+
(0, node_fs_1.unlinkSync)(unitPath);
|
|
67
|
+
}
|
|
68
|
+
catch { /* leave it */ }
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
(0, node_child_process_1.execSync)("systemctl --user daemon-reload 2>/dev/null");
|
|
72
|
+
}
|
|
73
|
+
catch { /* fine */ }
|
|
74
|
+
console.log(" ✓ Removed systemd service + nightly timer");
|
|
58
75
|
}
|
|
59
76
|
// 2. Remove MCP from CC
|
|
60
77
|
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
|
)
|