@gamaze/hicortex 0.17.6 → 0.18.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 +30 -28
- package/assets/dashboard.html +121 -5
- package/assets/{context.html → identity.html} +18 -18
- package/assets/viz.html +19 -7
- package/dist/claude-md.d.ts +2 -1
- package/dist/claude-md.js +2 -1
- package/dist/cli-args.d.ts +9 -0
- package/dist/cli-args.js +16 -0
- package/dist/cli.js +29 -20
- package/dist/consolidate.d.ts +15 -0
- package/dist/consolidate.js +30 -3
- package/dist/dashboard.d.ts +58 -1
- package/dist/dashboard.js +27 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/health.d.ts +68 -0
- package/dist/health.js +73 -0
- package/dist/identity-cli.d.ts +90 -0
- package/dist/{context-cli.js → identity-cli.js} +66 -48
- package/dist/{context-store.d.ts → identity-store.d.ts} +94 -31
- package/dist/{context-store.js → identity-store.js} +212 -71
- package/dist/index.d.ts +12 -5
- package/dist/index.js +57 -29
- package/dist/init.d.ts +44 -8
- package/dist/init.js +142 -37
- package/dist/learnings-identity.d.ts +149 -0
- package/dist/{lessons-context.js → learnings-identity.js} +96 -52
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +217 -68
- package/dist/memory-instructions.d.ts +6 -6
- package/dist/memory-instructions.js +6 -6
- package/dist/nightly.js +65 -6
- package/dist/paths.js +1 -1
- package/dist/recall-hook-cli.d.ts +1 -1
- package/dist/recall-hook-cli.js +3 -3
- package/dist/recall-index.js +5 -2
- package/dist/status.d.ts +2 -2
- package/dist/status.js +11 -9
- package/dist/telemetry.d.ts +10 -0
- package/dist/type-classify.js +4 -1
- package/dist/type-labels.d.ts +30 -0
- package/dist/type-labels.js +43 -0
- package/dist/types.d.ts +28 -0
- package/dist/uninstall.d.ts +12 -0
- package/dist/uninstall.js +21 -3
- package/dist/viz.d.ts +24 -11
- package/dist/viz.js +97 -32
- package/hermes-plugin/hicortex/README.md +4 -2
- package/package.json +2 -2
- package/dist/context-cli.d.ts +0 -69
- package/dist/lessons-context.d.ts +0 -102
package/dist/nightly.js
CHANGED
|
@@ -69,6 +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 retrieval_js_1 = require("./retrieval.js");
|
|
71
71
|
const state_js_1 = require("./state.js");
|
|
72
|
+
const identity_store_js_1 = require("./identity-store.js");
|
|
72
73
|
const capture_cursors_js_1 = require("./capture-cursors.js");
|
|
73
74
|
const capture_js_1 = require("./capture.js");
|
|
74
75
|
const dashboard_js_1 = require("./dashboard.js");
|
|
@@ -132,12 +133,16 @@ function computeSince(stateDir, recaptureWindowDays) {
|
|
|
132
133
|
}
|
|
133
134
|
return lastRun;
|
|
134
135
|
}
|
|
135
|
-
/** POST /distill transport for server mode — localhost
|
|
136
|
-
|
|
136
|
+
/** POST /distill transport for server mode — localhost. Sends authToken so
|
|
137
|
+
* self-capture works regardless of the localhost-bypass marker (#271 root-cause fix). */
|
|
138
|
+
function makeLocalPost(port, authToken) {
|
|
137
139
|
return async (body) => {
|
|
138
140
|
const resp = await fetch(`http://127.0.0.1:${port}/distill`, {
|
|
139
141
|
method: "POST",
|
|
140
|
-
headers: {
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/json",
|
|
144
|
+
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
|
|
145
|
+
},
|
|
141
146
|
body: JSON.stringify(body),
|
|
142
147
|
// Synchronous 35B distillation of a large segment can take minutes.
|
|
143
148
|
signal: AbortSignal.timeout(20 * 60 * 1000),
|
|
@@ -223,6 +228,16 @@ async function runNightly(options = {}) {
|
|
|
223
228
|
rotateNightlyLog(stateDir);
|
|
224
229
|
// One-time migration of legacy state files (no-op if state.json exists)
|
|
225
230
|
(0, state_js_1.migrateLegacyState)(stateDir);
|
|
231
|
+
// #264: rename <home>/context/ → identity/ on the next nightly run when only
|
|
232
|
+
// the legacy dir exists. The identity-store fallback read is the safety net
|
|
233
|
+
// for a partial/no migration. No-op when neither dir exists (fresh install).
|
|
234
|
+
const idMig = (0, identity_store_js_1.migrateIdentityDir)(stateDir);
|
|
235
|
+
if (idMig.renamed) {
|
|
236
|
+
console.log(`[hicortex] Migrated identity dir: ${idMig.from} → ${idMig.to}`);
|
|
237
|
+
}
|
|
238
|
+
else if (idMig.reason && idMig.reason !== "no legacy context/ dir" && !idMig.reason.startsWith("identity/ already exists")) {
|
|
239
|
+
console.warn(`[hicortex] Identity dir migration skipped: ${idMig.reason}`);
|
|
240
|
+
}
|
|
226
241
|
// Check mode: client or server
|
|
227
242
|
const savedConfig = readNightlyConfig(stateDir);
|
|
228
243
|
// 0.16.8 upgrade guard: warn if ignored per-stage keys are still present.
|
|
@@ -401,7 +416,7 @@ async function runNightly(options = {}) {
|
|
|
401
416
|
// source_agent_id / source_domain are per-client provenance from
|
|
402
417
|
// config.json (agentId / sourceDomain) — attribution only, no filtering.
|
|
403
418
|
const result = await (0, capture_js_1.captureBatches)(batches, {
|
|
404
|
-
post: makeLocalPost(port),
|
|
419
|
+
post: makeLocalPost(port, savedConfig?.authToken),
|
|
405
420
|
cursorStore,
|
|
406
421
|
dryRun,
|
|
407
422
|
sourceAgentId: savedConfig?.agentId,
|
|
@@ -450,6 +465,18 @@ async function runNightly(options = {}) {
|
|
|
450
465
|
let tokensThisRun;
|
|
451
466
|
// #246: per-stage token breakdown (hoisted for the dashboard snapshot).
|
|
452
467
|
let tokensByStage;
|
|
468
|
+
// #255: budget-exhaustion flag + per-stage deferred counts (hoisted for
|
|
469
|
+
// telemetry + the dashboard snapshot). Undefined when consolidation didn't
|
|
470
|
+
// run at all (capture-only / no_llm / throttled) — the optional fields are
|
|
471
|
+
// omitted so the aggregate treats absent as "not measurable".
|
|
472
|
+
let budgetExhausted;
|
|
473
|
+
let budgetDeferredByStage;
|
|
474
|
+
// #255 CR: always-on usage metric — hoisted for the dashboard snapshot so
|
|
475
|
+
// the digest renders a continuous used/max bar (consolidation
|
|
476
|
+
// completeness as a health metric), not just an amber pill at exhaustion.
|
|
477
|
+
// Undefined when consolidation didn't run; presence = a run happened.
|
|
478
|
+
let budgetCallsUsed;
|
|
479
|
+
let budgetMaxCalls;
|
|
453
480
|
if (!dryRun && !captureOnly) {
|
|
454
481
|
if (!llm || !llmConfig) {
|
|
455
482
|
console.error("[hicortex] consolidation skipped: no LLM configured — run npx @gamaze/hicortex init");
|
|
@@ -540,6 +567,19 @@ async function runNightly(options = {}) {
|
|
|
540
567
|
tokensThisRun = tokensTotal.total;
|
|
541
568
|
tokensByStage = report.budget?.tokens_by_stage;
|
|
542
569
|
}
|
|
570
|
+
// #255: budget exhaustion — always populated when consolidation ran
|
|
571
|
+
// (report.budget.exhausted is a boolean). The dashboard + telemetry
|
|
572
|
+
// treat true as a quality-degradation health signal. The
|
|
573
|
+
// ran-vs-didn't-run distinction is carried by `budgetCallsUsed`/
|
|
574
|
+
// `budgetMaxCalls` (forwarded whenever consolidation ran), NOT by a
|
|
575
|
+
// false `budget_exhausted` flag — the snapshot forwards
|
|
576
|
+
// `budget_exhausted` only on exhaustion (alert state), so the
|
|
577
|
+
// aggregate reads: calls_used present + budget_exhausted undefined
|
|
578
|
+
// = "ran and didn't exhaust"; calls_used undefined = "didn't run".
|
|
579
|
+
budgetExhausted = report.budget?.exhausted;
|
|
580
|
+
budgetDeferredByStage = report.budget?.deferred_by_stage;
|
|
581
|
+
budgetCallsUsed = report.budget?.calls_used;
|
|
582
|
+
budgetMaxCalls = report.budget?.max_calls;
|
|
543
583
|
// #246: accrue to state.json (monthly reset + last-run estimate for
|
|
544
584
|
// the next throttle check). Written even on a failed run — a partial
|
|
545
585
|
// run that made metered calls before the failure still spent tokens,
|
|
@@ -633,6 +673,15 @@ async function runNightly(options = {}) {
|
|
|
633
673
|
// when consolidation didn't run or made no metered calls).
|
|
634
674
|
tokensThisRun,
|
|
635
675
|
tokensByStage,
|
|
676
|
+
// #255 CR: always-on budget usage — undefined when consolidation
|
|
677
|
+
// didn't run (capture-only / no_llm / throttled). Forwarded whenever
|
|
678
|
+
// consolidation ran so the digest renders a continuous used/max bar.
|
|
679
|
+
budgetCallsUsed,
|
|
680
|
+
budgetMaxCalls,
|
|
681
|
+
// #255: budget exhaustion — undefined when consolidation didn't run
|
|
682
|
+
// (capture-only / no_llm / throttled) or didn't exhaust.
|
|
683
|
+
budgetExhausted,
|
|
684
|
+
budgetDeferredByStage,
|
|
636
685
|
}, memorySoftCapResolved);
|
|
637
686
|
}
|
|
638
687
|
catch (snapErr) {
|
|
@@ -675,6 +724,10 @@ async function runNightly(options = {}) {
|
|
|
675
724
|
// #246: total tokens consumed by this run's consolidation (absent on
|
|
676
725
|
// capture-only / throttled / no_llm / skipped — no metered calls).
|
|
677
726
|
tokens_this_run: tokensThisRun,
|
|
727
|
+
// #255: budget exhaustion — forwarded only when consolidation ran AND
|
|
728
|
+
// exhausted (false is omitted to keep the ping minimal; the aggregate
|
|
729
|
+
// treats absent as "not exhausted / not measurable").
|
|
730
|
+
...(budgetExhausted ? { budget_exhausted: true } : {}),
|
|
678
731
|
sessions: batches.length,
|
|
679
732
|
ok: !hadTransientFailure,
|
|
680
733
|
shown: adoption.shown,
|
|
@@ -720,11 +773,17 @@ async function runClientNightly(config, dryRun, stateDir = HICORTEX_HOME, recapt
|
|
|
720
773
|
let reachable = false;
|
|
721
774
|
for (let attempt = 1; attempt <= PREFLIGHT_ATTEMPTS; attempt++) {
|
|
722
775
|
try {
|
|
776
|
+
// PUBLIC /health probe — liveness only, no auth required. Client-mode
|
|
777
|
+
// preflight runs against a REMOTE server over Tailscale, and the client
|
|
778
|
+
// has NO bearer token to hand on this path (the auth token is the
|
|
779
|
+
// server's, not the client's; /distill uses the configured authToken
|
|
780
|
+
// but the liveness check must work even before that resolves). The
|
|
781
|
+
// public /health returns only {status:"ok"} (#253), so we log
|
|
782
|
+
// reachability without a version/memory count.
|
|
723
783
|
const resp = await fetch(`${serverUrl}/health`, { signal: AbortSignal.timeout(PREFLIGHT_TIMEOUT_MS) });
|
|
724
784
|
if (!resp.ok)
|
|
725
785
|
throw new Error(`HTTP ${resp.status}`);
|
|
726
|
-
|
|
727
|
-
console.log(`[hicortex] Server OK: v${data.version}, ${data.memories} memories`);
|
|
786
|
+
console.log(`[hicortex] Server reachable at ${serverUrl}`);
|
|
728
787
|
reachable = true;
|
|
729
788
|
break;
|
|
730
789
|
}
|
package/dist/paths.js
CHANGED
|
@@ -8,7 +8,7 @@ exports.hicortexHome = hicortexHome;
|
|
|
8
8
|
* HICORTEX_DB_PATH convention in db.ts); otherwise defaults to ~/.hicortex.
|
|
9
9
|
* Every module that needs the home dir routes through here, so the override
|
|
10
10
|
* behaves consistently across all commands instead of being honored by some
|
|
11
|
-
* (
|
|
11
|
+
* (identity-cli, learnings-identity) and hardcoded away by others.
|
|
12
12
|
*/
|
|
13
13
|
const node_os_1 = require("node:os");
|
|
14
14
|
const node_path_1 = require("node:path");
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - SessionStart (startup/resume/clear/compact): POST a reset so the
|
|
9
9
|
* server's per-session shown-set matches the fresh context window.
|
|
10
10
|
*
|
|
11
|
-
* Fail-soft like
|
|
11
|
+
* Fail-soft like learnings-identity: ANY failure (no config, timeout, non-2xx,
|
|
12
12
|
* parse error) prints nothing and exits 0 — a broken hook must never block or
|
|
13
13
|
* slow a CC session beyond the fetch timeout (1000 ms, owner-set).
|
|
14
14
|
*/
|
package/dist/recall-hook-cli.js
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* - SessionStart (startup/resume/clear/compact): POST a reset so the
|
|
10
10
|
* server's per-session shown-set matches the fresh context window.
|
|
11
11
|
*
|
|
12
|
-
* Fail-soft like
|
|
12
|
+
* Fail-soft like learnings-identity: ANY failure (no config, timeout, non-2xx,
|
|
13
13
|
* parse error) prints nothing and exits 0 — a broken hook must never block or
|
|
14
14
|
* slow a CC session beyond the fetch timeout (1000 ms, owner-set).
|
|
15
15
|
*/
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.buildHookRequest = buildHookRequest;
|
|
18
18
|
exports.runRecallHook = runRecallHook;
|
|
19
|
-
const
|
|
19
|
+
const learnings_identity_js_1 = require("./learnings-identity.js");
|
|
20
20
|
const node_path_1 = require("node:path");
|
|
21
21
|
const FETCH_TIMEOUT_MS = 1000;
|
|
22
22
|
/** Read all of stdin (CC pipes the hook payload JSON). */
|
|
@@ -51,7 +51,7 @@ function buildHookRequest(payload, cwd = process.cwd()) {
|
|
|
51
51
|
return { session_id: sessionId, prompt, project: (0, node_path_1.basename)(cwd) };
|
|
52
52
|
}
|
|
53
53
|
async function runRecallHook() {
|
|
54
|
-
const cfg = (0,
|
|
54
|
+
const cfg = (0, learnings_identity_js_1.resolveConfig)();
|
|
55
55
|
if (!cfg)
|
|
56
56
|
return;
|
|
57
57
|
let payload;
|
package/dist/recall-index.js
CHANGED
|
@@ -62,6 +62,7 @@ exports.handleRecallIndex = handleRecallIndex;
|
|
|
62
62
|
exports.handleMemoryGet = handleMemoryGet;
|
|
63
63
|
exports.formatMemoryGetText = formatMemoryGetText;
|
|
64
64
|
const storage = __importStar(require("./storage.js"));
|
|
65
|
+
const type_labels_js_1 = require("./type-labels.js");
|
|
65
66
|
/** Relevance-gate floor for vector-only candidates (config `recallMinSimilarity`).
|
|
66
67
|
* 0.62 (was 0.55; raised 2026-08-03 on the fine-grain floor sweep — see the
|
|
67
68
|
* minSimilarity doc above). */
|
|
@@ -116,7 +117,7 @@ function formatIndexLine(r, maxLen = DEFAULT_TITLE_CHARS) {
|
|
|
116
117
|
formatDate(r.created_at),
|
|
117
118
|
r.domain ?? r.project ?? undefined,
|
|
118
119
|
r.source_agent ?? undefined,
|
|
119
|
-
r.memory_type,
|
|
120
|
+
(0, type_labels_js_1.labelForType)(r.memory_type),
|
|
120
121
|
]
|
|
121
122
|
.filter(Boolean)
|
|
122
123
|
.join(", ");
|
|
@@ -281,7 +282,9 @@ function formatMemoryGetText(db, query) {
|
|
|
281
282
|
const mem = r.body.memory;
|
|
282
283
|
const citation = r.body.citation; // carries FETCHED (#204)
|
|
283
284
|
const date = (mem.created_at ?? "").slice(0, 10);
|
|
284
|
-
|
|
285
|
+
// #264 WS2: render the human-term label (Knowledge/Experience/...), not the
|
|
286
|
+
// internal enum, in the citation header shown to the agent/user.
|
|
287
|
+
const header = `[memory ${mem.id} | ${(0, type_labels_js_1.labelForType)(mem.memory_type ?? "episode")} | ${mem.project ?? "-"} | from ${mem.source_agent ?? "unknown"} | ${date}]\n` +
|
|
285
288
|
`Cite as ${citation} where this shapes your answer; it may be stale — newer memories supersede older.`;
|
|
286
289
|
return { status: 200, text: `${header}\n\n${mem.content ?? ""}` };
|
|
287
290
|
}
|
package/dist/status.d.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* The value shown after "Agent name:" in `hicortex status` (#179, A3). Reports
|
|
6
6
|
* EXACTLY what the CC hook resolves (shared `resolveAgentIdentity`), so the
|
|
7
|
-
* operator never keys `
|
|
7
|
+
* operator never keys `identityAgents`/`agents/<id>/` on an id the install does
|
|
8
8
|
* not actually send. Unset → the install sends no `?agent=` and shares the
|
|
9
|
-
* global
|
|
9
|
+
* global identity (CC default). A configured-but-unsanitizable value is called
|
|
10
10
|
* out as invalid (the hook sends none) rather than silently accepted.
|
|
11
11
|
*/
|
|
12
12
|
export declare function statusAgentLine(config: Record<string, unknown>): string;
|
package/dist/status.js
CHANGED
|
@@ -13,27 +13,27 @@ const node_child_process_1 = require("node:child_process");
|
|
|
13
13
|
const db_js_1 = require("./db.js");
|
|
14
14
|
const features_js_1 = require("./features.js");
|
|
15
15
|
const state_js_1 = require("./state.js");
|
|
16
|
-
const
|
|
16
|
+
const identity_store_js_1 = require("./identity-store.js");
|
|
17
17
|
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
18
18
|
const CC_SETTINGS = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "settings.json");
|
|
19
19
|
const OC_CONFIG = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "openclaw.json");
|
|
20
20
|
/**
|
|
21
21
|
* The value shown after "Agent name:" in `hicortex status` (#179, A3). Reports
|
|
22
22
|
* EXACTLY what the CC hook resolves (shared `resolveAgentIdentity`), so the
|
|
23
|
-
* operator never keys `
|
|
23
|
+
* operator never keys `identityAgents`/`agents/<id>/` on an id the install does
|
|
24
24
|
* not actually send. Unset → the install sends no `?agent=` and shares the
|
|
25
|
-
* global
|
|
25
|
+
* global identity (CC default). A configured-but-unsanitizable value is called
|
|
26
26
|
* out as invalid (the hook sends none) rather than silently accepted.
|
|
27
27
|
*/
|
|
28
28
|
function statusAgentLine(config) {
|
|
29
|
-
const id = (0,
|
|
29
|
+
const id = (0, identity_store_js_1.resolveAgentIdentity)(config);
|
|
30
30
|
switch (id.source) {
|
|
31
31
|
case "configured":
|
|
32
32
|
return id.agentId;
|
|
33
33
|
case "invalid-config":
|
|
34
34
|
return `(invalid configured value "${id.rawConfigured}" — fix config.agentName; hook sends none)`;
|
|
35
35
|
default: // unset
|
|
36
|
-
return "(not set — global
|
|
36
|
+
return "(not set — global identity)";
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
async function runStatus() {
|
|
@@ -87,8 +87,8 @@ async function runStatus() {
|
|
|
87
87
|
else if (!isClientMode && !savedAuthToken) {
|
|
88
88
|
console.log(`Auth token: not configured (run: npx @gamaze/hicortex init)`);
|
|
89
89
|
}
|
|
90
|
-
// Per-agent
|
|
91
|
-
// key operators use for
|
|
90
|
+
// Per-agent identity id (#179) — the id this install sends as ?agent= and the
|
|
91
|
+
// key operators use for identityAgents / agents/<id>/ dirs.
|
|
92
92
|
console.log(`Agent name: ${statusAgentLine(parsedConfig)}`);
|
|
93
93
|
console.log();
|
|
94
94
|
// Adapters
|
|
@@ -119,11 +119,13 @@ async function runStatus() {
|
|
|
119
119
|
catch { /* no CC settings */ }
|
|
120
120
|
console.log(` CC MCP: ${ccRegistered ? `registered → ${ccUrl}` : "not registered"}`);
|
|
121
121
|
console.log();
|
|
122
|
-
// Server status
|
|
122
|
+
// Server status. /health/detail carries the diagnostics (version, memories,
|
|
123
|
+
// llm) — /health itself is the public minimal {status:"ok"} probe (#253).
|
|
124
|
+
// localhost bypasses auth, so on-box `hicortex status` gets the fields.
|
|
123
125
|
console.log("Server:");
|
|
124
126
|
let serverRunning = false;
|
|
125
127
|
try {
|
|
126
|
-
const resp = await fetch("http://127.0.0.1:8787/health", {
|
|
128
|
+
const resp = await fetch("http://127.0.0.1:8787/health/detail", {
|
|
127
129
|
signal: AbortSignal.timeout(2000),
|
|
128
130
|
});
|
|
129
131
|
if (resp.ok) {
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -106,6 +106,16 @@ export interface TelemetryPayload {
|
|
|
106
106
|
* wire (that lives in the dashboard snapshot, not the telemetry ping).
|
|
107
107
|
*/
|
|
108
108
|
tokens_this_run?: number;
|
|
109
|
+
/**
|
|
110
|
+
* True when the per-tenant consolidation budget (`consolidateMaxLlmCalls`)
|
|
111
|
+
* was exhausted this run (#255) — LLM-bound stages deferred remaining work.
|
|
112
|
+
* A quality-degradation signal concentrated on heavy users; absent on a
|
|
113
|
+
* pre-#255 ping, a capture-only/no-LLM/throttled/skipped run, or when the
|
|
114
|
+
* budget was NOT exhausted (additive optional — the aggregate treats absent
|
|
115
|
+
* as "no exhaustion / not measurable"). Per-stage deferred counts live in
|
|
116
|
+
* the dashboard snapshot, not on the wire (mirrors `tokens_this_run`).
|
|
117
|
+
*/
|
|
118
|
+
budget_exhausted?: boolean;
|
|
109
119
|
}
|
|
110
120
|
/**
|
|
111
121
|
* Check if telemetry is enabled. Disabled by:
|
package/dist/type-classify.js
CHANGED
|
@@ -45,6 +45,7 @@ const node_path_1 = require("node:path");
|
|
|
45
45
|
const db_js_1 = require("./db.js");
|
|
46
46
|
const state_js_1 = require("./state.js");
|
|
47
47
|
const llm_js_1 = require("./llm.js");
|
|
48
|
+
const type_labels_js_1 = require("./type-labels.js");
|
|
48
49
|
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
49
50
|
/** Max chars of memory content fed to the classify prompt. */
|
|
50
51
|
const CLASSIFY_CONTENT_MAX_CHARS = 1500;
|
|
@@ -293,7 +294,9 @@ async function runClassifyTypes(options = {}) {
|
|
|
293
294
|
.all();
|
|
294
295
|
for (const c of counts)
|
|
295
296
|
report.byType[c.memory_type] = c.cnt;
|
|
296
|
-
|
|
297
|
+
// #264 WS2: display the human-term label (Knowledge/Experience/...), not
|
|
298
|
+
// the internal enum — the operator reading the log sees human terms.
|
|
299
|
+
const breakdown = counts.map((c) => `${(0, type_labels_js_1.labelForType)(c.memory_type)}=${c.cnt}`).join(", ") || "none";
|
|
297
300
|
console.log(`[hicortex] classify-types ${report.aborted ? "ABORTED" : "complete"}: ` +
|
|
298
301
|
`${report.scanned} classified, ${report.reclassified} reclassified, ` +
|
|
299
302
|
`${report.unchanged} unchanged, ${report.failed} infra-skipped`);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-term labels for the internal `memory_type` enum (#264 WS2).
|
|
3
|
+
*
|
|
4
|
+
* The data model stores four types under unintuitive internal names
|
|
5
|
+
* (`fact` / `episode` / `decision` / `lesson`). Product-facing surfaces show
|
|
6
|
+
* the human terms instead — Knowledge / Experience / Decisions / Learnings —
|
|
7
|
+
* via this single label map. The enum values themselves are NEVER changed:
|
|
8
|
+
* no DB column, query, distiller tag, or storage path is altered. This is a
|
|
9
|
+
* DISPLAY rename only; every user-facing rendering routes through
|
|
10
|
+
* `labelForType`.
|
|
11
|
+
*
|
|
12
|
+
* Mapping (decided 2026-08-11, research/2026-08-11-identity-reframe-brainstorm.md):
|
|
13
|
+
* fact → Knowledge
|
|
14
|
+
* episode → Experience
|
|
15
|
+
* decision → Decisions
|
|
16
|
+
* lesson → Learnings
|
|
17
|
+
*
|
|
18
|
+
* Unknown / future types fall back to the raw value (never silently remapped).
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The four documented memory types mapped to their human-term labels.
|
|
22
|
+
* Kept as a plain record so it can be iterated for coverage assertions.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MEMORY_TYPE_LABELS: Record<string, string>;
|
|
25
|
+
/**
|
|
26
|
+
* Return the human-term label for a `memory_type` enum value. Unknown or
|
|
27
|
+
* future types (including null/undefined) fall back to the raw input so new
|
|
28
|
+
* types are visible rather than silently mislabeled.
|
|
29
|
+
*/
|
|
30
|
+
export declare function labelForType(t: string | null | undefined): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Human-term labels for the internal `memory_type` enum (#264 WS2).
|
|
4
|
+
*
|
|
5
|
+
* The data model stores four types under unintuitive internal names
|
|
6
|
+
* (`fact` / `episode` / `decision` / `lesson`). Product-facing surfaces show
|
|
7
|
+
* the human terms instead — Knowledge / Experience / Decisions / Learnings —
|
|
8
|
+
* via this single label map. The enum values themselves are NEVER changed:
|
|
9
|
+
* no DB column, query, distiller tag, or storage path is altered. This is a
|
|
10
|
+
* DISPLAY rename only; every user-facing rendering routes through
|
|
11
|
+
* `labelForType`.
|
|
12
|
+
*
|
|
13
|
+
* Mapping (decided 2026-08-11, research/2026-08-11-identity-reframe-brainstorm.md):
|
|
14
|
+
* fact → Knowledge
|
|
15
|
+
* episode → Experience
|
|
16
|
+
* decision → Decisions
|
|
17
|
+
* lesson → Learnings
|
|
18
|
+
*
|
|
19
|
+
* Unknown / future types fall back to the raw value (never silently remapped).
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.MEMORY_TYPE_LABELS = void 0;
|
|
23
|
+
exports.labelForType = labelForType;
|
|
24
|
+
/**
|
|
25
|
+
* The four documented memory types mapped to their human-term labels.
|
|
26
|
+
* Kept as a plain record so it can be iterated for coverage assertions.
|
|
27
|
+
*/
|
|
28
|
+
exports.MEMORY_TYPE_LABELS = {
|
|
29
|
+
fact: "Knowledge",
|
|
30
|
+
episode: "Experience",
|
|
31
|
+
decision: "Decisions",
|
|
32
|
+
lesson: "Learnings",
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Return the human-term label for a `memory_type` enum value. Unknown or
|
|
36
|
+
* future types (including null/undefined) fall back to the raw input so new
|
|
37
|
+
* types are visible rather than silently mislabeled.
|
|
38
|
+
*/
|
|
39
|
+
function labelForType(t) {
|
|
40
|
+
if (!t)
|
|
41
|
+
return "—";
|
|
42
|
+
return exports.MEMORY_TYPE_LABELS[t] ?? t;
|
|
43
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -161,6 +161,26 @@ export interface ConsolidationReport {
|
|
|
161
161
|
calls_used: number;
|
|
162
162
|
calls_remaining: number;
|
|
163
163
|
calls_by_stage: Record<string, number>;
|
|
164
|
+
/**
|
|
165
|
+
* True when `calls_used >= max_calls` at run end (#255). The run continued
|
|
166
|
+
* to completion (no abort) but LLM-bound stages past the boundary deferred
|
|
167
|
+
* their remaining work — a quality-degradation signal, not a failure.
|
|
168
|
+
* Absent on pre-#255 reports; treat as false.
|
|
169
|
+
*/
|
|
170
|
+
exhausted?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Per-stage count of LLM-call REQUESTS refused because the budget was
|
|
173
|
+
* exhausted (#255). Keys are the same stage labels passed to
|
|
174
|
+
* `BudgetTracker.use()`. The value is the SUM of the `count` args passed
|
|
175
|
+
* to each refused `use()` call in that stage — in production every `use()`
|
|
176
|
+
* call passes count=1, so a stage present here with count N hit the
|
|
177
|
+
* boundary and had N further single-call requests denied (stages break on
|
|
178
|
+
* first refusal, so N is small per stage). For item-level "how many
|
|
179
|
+
* memories/pairs were skipped" see the per-stage reports (e.g.
|
|
180
|
+
* stages.importance.skipped_budget), which count MEMORIES not call
|
|
181
|
+
* requests. Absent on pre-#255 reports.
|
|
182
|
+
*/
|
|
183
|
+
deferred_by_stage?: Record<string, number>;
|
|
164
184
|
/**
|
|
165
185
|
* Token usage per stage (#246). Each value sums prompt + completion +
|
|
166
186
|
* total across every metered LLM call in that stage this run. A stage with
|
|
@@ -187,6 +207,14 @@ export interface HicortexConfig {
|
|
|
187
207
|
serverUrl?: string;
|
|
188
208
|
/** Bearer token for the Hicortex server. Localhost bypasses auth by default. */
|
|
189
209
|
authToken?: string;
|
|
210
|
+
/**
|
|
211
|
+
* Optional PRIOR bearer token kept around during rotation (#254). When set,
|
|
212
|
+
* BOTH `authToken` and `authTokenPrevious` are accepted (constant-time,
|
|
213
|
+
* zero-downtime rotation). Absent/empty → single-token behaviour. Rotate by
|
|
214
|
+
* writing the new value to `authToken` and the old value here, then later
|
|
215
|
+
* clearing this key once all clients have switched.
|
|
216
|
+
*/
|
|
217
|
+
authTokenPrevious?: string;
|
|
190
218
|
/**
|
|
191
219
|
* Stable per-install UUID generated by `init` (see ensureAgentId in init.ts;
|
|
192
220
|
* never rotated). Attribution identity of the capturing client — stored on
|
package/dist/uninstall.d.ts
CHANGED
|
@@ -2,4 +2,16 @@
|
|
|
2
2
|
* Hicortex uninstall — clean removal of CC integration.
|
|
3
3
|
* Preserves the database (user data).
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Matches a CC SessionStart hook `command` that runs the Hicortex
|
|
7
|
+
* identity/learnings hook — the canonical `learnings-identity` (#264) OR the
|
|
8
|
+
* legacy `lessons-context` alias. Exported so the uninstall behavior (which
|
|
9
|
+
* name variants get cleaned up) is unit-testable without spinning up CC.
|
|
10
|
+
* Word-boundary guard so "learnings-identity" never matches a hypothetical
|
|
11
|
+
* "learnings-identity-foo", the two names stay distinct from each other, and
|
|
12
|
+
* neither collides with the sibling `recall-hook` SessionStart hook.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SESSION_START_HOOK_COMMAND_RE: RegExp;
|
|
15
|
+
/** True when a CC hook `command` string runs the Hicortex SessionStart hook. */
|
|
16
|
+
export declare function isHicortexSessionStartHook(command: string): boolean;
|
|
5
17
|
export declare function runUninstall(): Promise<void>;
|
package/dist/uninstall.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Preserves the database (user data).
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.SESSION_START_HOOK_COMMAND_RE = void 0;
|
|
8
|
+
exports.isHicortexSessionStartHook = isHicortexSessionStartHook;
|
|
7
9
|
exports.runUninstall = runUninstall;
|
|
8
10
|
const paths_js_1 = require("./paths.js");
|
|
9
11
|
const node_fs_1 = require("node:fs");
|
|
@@ -17,6 +19,20 @@ const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
|
17
19
|
const CC_SETTINGS = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "settings.json");
|
|
18
20
|
const CC_COMMANDS_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "commands");
|
|
19
21
|
const CLAUDE_MD = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "CLAUDE.md");
|
|
22
|
+
/**
|
|
23
|
+
* Matches a CC SessionStart hook `command` that runs the Hicortex
|
|
24
|
+
* identity/learnings hook — the canonical `learnings-identity` (#264) OR the
|
|
25
|
+
* legacy `lessons-context` alias. Exported so the uninstall behavior (which
|
|
26
|
+
* name variants get cleaned up) is unit-testable without spinning up CC.
|
|
27
|
+
* Word-boundary guard so "learnings-identity" never matches a hypothetical
|
|
28
|
+
* "learnings-identity-foo", the two names stay distinct from each other, and
|
|
29
|
+
* neither collides with the sibling `recall-hook` SessionStart hook.
|
|
30
|
+
*/
|
|
31
|
+
exports.SESSION_START_HOOK_COMMAND_RE = /(^|\s)(?:learnings-identity|lessons-context)(\s|$)/;
|
|
32
|
+
/** True when a CC hook `command` string runs the Hicortex SessionStart hook. */
|
|
33
|
+
function isHicortexSessionStartHook(command) {
|
|
34
|
+
return typeof command === "string" && exports.SESSION_START_HOOK_COMMAND_RE.test(command);
|
|
35
|
+
}
|
|
20
36
|
async function ask(question) {
|
|
21
37
|
const rl = (0, node_readline_1.createInterface)({ input: process.stdin, output: process.stdout });
|
|
22
38
|
return new Promise((resolve) => {
|
|
@@ -130,7 +146,9 @@ async function runUninstall() {
|
|
|
130
146
|
}
|
|
131
147
|
if (removedCmds > 0)
|
|
132
148
|
console.log(` ✓ Removed ${removedCmds} legacy CC command${removedCmds > 1 ? "s" : ""} (/learn, /hicortex-activate)`);
|
|
133
|
-
// 4. Remove SessionStart hook (JSON merge — filter out entries containing
|
|
149
|
+
// 4. Remove SessionStart hook (JSON merge — filter out entries containing
|
|
150
|
+
// EITHER the canonical "learnings-identity" OR the legacy "lessons-context"
|
|
151
|
+
// alias, #264 backcompat: an install may have written either name.)
|
|
134
152
|
try {
|
|
135
153
|
const raw = (0, node_fs_1.readFileSync)(CC_SETTINGS, "utf-8");
|
|
136
154
|
const settings = JSON.parse(raw);
|
|
@@ -147,7 +165,7 @@ async function runUninstall() {
|
|
|
147
165
|
if (typeof h !== "object" || h === null)
|
|
148
166
|
return false;
|
|
149
167
|
const hook = h;
|
|
150
|
-
return typeof hook.command === "string" && hook.command
|
|
168
|
+
return typeof hook.command === "string" && isHicortexSessionStartHook(hook.command);
|
|
151
169
|
});
|
|
152
170
|
}
|
|
153
171
|
return true;
|
|
@@ -155,7 +173,7 @@ async function runUninstall() {
|
|
|
155
173
|
if (filtered.length < before) {
|
|
156
174
|
hooks.SessionStart = filtered;
|
|
157
175
|
(0, node_fs_1.writeFileSync)(CC_SETTINGS, JSON.stringify(settings, null, 2));
|
|
158
|
-
console.log(" ✓ Removed SessionStart
|
|
176
|
+
console.log(" ✓ Removed SessionStart learnings-identity hook");
|
|
159
177
|
}
|
|
160
178
|
}
|
|
161
179
|
}
|
package/dist/viz.d.ts
CHANGED
|
@@ -37,8 +37,16 @@ export declare const VIZ_VENDOR_FILES: ReadonlySet<string>;
|
|
|
37
37
|
* middleware before this runs). With no token configured, remote requests are
|
|
38
38
|
* REJECTED (not open): the default bind is 0.0.0.0, so "no token = no auth"
|
|
39
39
|
* would expose the whole memory store to the network.
|
|
40
|
+
*
|
|
41
|
+
* `authTokenPrevious` (optional, #254) is the prior token kept around during
|
|
42
|
+
* rotation. Both tokens are accepted; this gives a zero-downtime rotation
|
|
43
|
+
* window — in-flight clients configured with the old token keep working until
|
|
44
|
+
* they pick up the new one. BOTH comparisons are constant-time and both are
|
|
45
|
+
* always evaluated (no short-circuit), so a caller cannot learn WHICH token
|
|
46
|
+
* matched from the response timing. Absent/empty `authTokenPrevious` behaves
|
|
47
|
+
* exactly as the single-token middleware always has.
|
|
40
48
|
*/
|
|
41
|
-
export declare function createAuthMiddleware(authToken: string | undefined): express.RequestHandler;
|
|
49
|
+
export declare function createAuthMiddleware(authToken: string | undefined, authTokenPrevious?: string): express.RequestHandler;
|
|
42
50
|
/**
|
|
43
51
|
* Resolve the on-disk path of the viz page. Throws (fail explicitly) when the
|
|
44
52
|
* asset is missing — a broken install should surface, not degrade silently.
|
|
@@ -52,24 +60,29 @@ export declare function readVizHtml(): string;
|
|
|
52
60
|
*/
|
|
53
61
|
export declare function vizHandler(): express.RequestHandler;
|
|
54
62
|
/**
|
|
55
|
-
* Resolve the on-disk path of the
|
|
63
|
+
* Resolve the on-disk path of the identity-layer editor page. Throws (fail
|
|
56
64
|
* explicitly) when the asset is missing — same contract as resolveVizHtmlPath.
|
|
57
65
|
* assets/ sits next to both dist/ (dist/viz.js → ../assets/) and src/
|
|
58
66
|
* (src/viz.ts → ../assets/ under tsx), so one sibling candidate covers both.
|
|
59
67
|
*/
|
|
60
|
-
export declare function
|
|
61
|
-
/** Read the
|
|
62
|
-
export declare function
|
|
68
|
+
export declare function resolveIdentityHtmlPath(): string;
|
|
69
|
+
/** Read the identity editor page. Read at request time so a reinstall is live. */
|
|
70
|
+
export declare function readIdentityHtml(): string;
|
|
63
71
|
/**
|
|
64
|
-
* Express handler for GET /
|
|
65
|
-
* standing
|
|
66
|
-
* cannot be read, exactly like vizHandler.
|
|
72
|
+
* Express handler for GET /identity/ui — the PRIMARY edit surface for the
|
|
73
|
+
* standing identity layer. 503 with the usual {error} shape when the asset
|
|
74
|
+
* cannot be read, exactly like vizHandler. Also serves the legacy
|
|
75
|
+
* /context/ui URL (#264 backcompat).
|
|
67
76
|
*/
|
|
68
|
-
export declare function
|
|
77
|
+
export declare function identityUiHandler(): express.RequestHandler;
|
|
78
|
+
/** Backcompat aliases (#264). */
|
|
79
|
+
export declare const resolveContextHtmlPath: typeof resolveIdentityHtmlPath;
|
|
80
|
+
export declare const readContextHtml: typeof readIdentityHtml;
|
|
81
|
+
export declare const contextUiHandler: typeof identityUiHandler;
|
|
69
82
|
/**
|
|
70
83
|
* Resolve the on-disk path of the dashboard page. Throws (fail explicitly)
|
|
71
84
|
* when the asset is missing — same contract as resolveVizHtmlPath and
|
|
72
|
-
*
|
|
85
|
+
* resolveIdentityHtmlPath. assets/ sits next to both dist/ and src/ (the
|
|
73
86
|
* sibling layout the other resolvers rely on).
|
|
74
87
|
*/
|
|
75
88
|
export declare function resolveDashboardHtmlPath(): string;
|
|
@@ -78,7 +91,7 @@ export declare function readDashboardHtml(): string;
|
|
|
78
91
|
/**
|
|
79
92
|
* Express handler for GET /dashboard — the view-only analytics page (#224).
|
|
80
93
|
* 503 with the usual {error} shape when the asset cannot be read, exactly like
|
|
81
|
-
* vizHandler and
|
|
94
|
+
* vizHandler and identityUiHandler. The page SHELL is public (exempted in
|
|
82
95
|
* createAuthMiddleware); all data comes from GET /dashboard/data (bearer-only).
|
|
83
96
|
*/
|
|
84
97
|
export declare function dashboardHandler(): express.RequestHandler;
|