@gamaze/hicortex 0.17.1 → 0.17.3
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 +2 -1
- package/assets/context.html +44 -0
- package/assets/dashboard.html +129 -0
- package/assets/viz.html +35 -4
- package/dist/cli.d.ts +3 -2
- package/dist/cli.js +10 -3
- package/dist/consolidate.d.ts +67 -1
- package/dist/consolidate.js +198 -16
- package/dist/dashboard.d.ts +71 -2
- package/dist/dashboard.js +36 -1
- package/dist/distiller.js +1 -1
- package/dist/domain-classify.d.ts +8 -2
- package/dist/domain-classify.js +19 -5
- package/dist/init.js +1 -1
- package/dist/llm.d.ts +52 -4
- package/dist/llm.js +65 -5
- package/dist/mcp-server.js +5 -0
- package/dist/nightly.d.ts +7 -0
- package/dist/nightly.js +248 -121
- package/dist/state.d.ts +22 -0
- package/dist/telemetry.d.ts +12 -2
- package/dist/types.d.ts +52 -0
- package/hermes-plugin/hicortex/README.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -218,9 +218,10 @@ Config at `~/.hicortex/config.json`. Created by `init`. Key options:
|
|
|
218
218
|
| `captureCooldownHours` | Success-cooldown (hours) for the **capture watchdog** (0.17). The capture timer polls every ~20 min; the watchdog captures only if more than this has elapsed since the last *successful* capture (`state.lastNightly`). Default `6` (≈4 captures/day). A failed preflight retries on the next poll (~20 min) — so a transient fire-instant network miss costs minutes, not a day (#239) |
|
|
219
219
|
| `consolidationHours` | Hours (0–23, local) for the **consolidation** timer — the full nightly (capture + distill + score + reflect + link). Installed for **server/co-located only** (clients have no local DB). Default `[10, 22]`: the 22:00 evening slot runs after the day's capture waves (same-day results); the 10:00 morning slot runs *after* the morning capture so wake-up pushes are caught. Omitted on clients |
|
|
220
220
|
| `consolidateMaxLlmCalls` | Ceiling on total LLM calls across all classify-tier consolidation stages (content-domain, link discovery, supersession) per run. A runaway **backstop**, not a throughput throttle — on a free local model the binding constraint is the nightly unit's wall-clock timeout, not call count. Default `5000` (was a hard-coded 200 that starved link/supersession during a classification backlog) |
|
|
221
|
+
| `memorySoftCap` | Soft cap on the memory corpus (default 10000). When the corpus exceeds this, the nightly's capacity-eviction stage removes the lowest-`effectiveStrength` memories (ties broken by oldest access) until under the cap — the active forgetting mechanism that bounds DB size, vector-index RAM, and consolidation workload. `0` disables eviction (indefinite growth — the pre-#245 behaviour). The evicted tail is cold by construction (effectiveStrength is the same decay-weighted score the recall ranker uses, so these were not surfacing in the top-k anyway). At 10K memories the load + JS sort is <100 ms |
|
|
221
222
|
| `updateChannel` | Release channel pinned into the generated daemon/timer ExecStart for **npx-thin** installs (global-binary installs use the absolute binary and are unaffected). A dist-tag (`"rc"`, `"next"`) or an exact version (`"0.17.1"`). E.g. `"rc"` → the timer runs `npx -y @gamaze/hicortex@rc nightly`, so the host tracks the rc dist-tag (an internal fleet can ride rc through a pre-promotion soak). Validated as `[\w.\-]+` (rejects anything that'd break the unit/plist templates). Absent → auto-detect (bare on `latest`, else `@next`). (0.17.1) |
|
|
222
223
|
| `nightlyHour` | **Deprecated (0.17) single-slot fallback.** Local hour (0–23) honoured only when `consolidationHours` is absent — yields one daily consolidation slot at that hour (preserves the pre-0.17 "one daily job" intent). New installs should use `consolidationHours` |
|
|
223
|
-
| `preflightTimeoutMs` | **Client mode only.** Per-attempt timeout for the nightly's server-reachability check before it starts capturing (default: 20000 ms, bumped from 15000 in 0.17 to absorb a
|
|
224
|
+
| `preflightTimeoutMs` | **Client mode only.** Per-attempt timeout for the nightly's server-reachability check before it starts capturing (default: 20000 ms, bumped from 15000 in 0.17 to absorb a slow link re-establishing after the client wakes) |
|
|
224
225
|
| `preflightAttempts` | **Client mode only.** Reachability-check retries before the nightly aborts (default: 3; floored at 1). `1` = single try, no retry |
|
|
225
226
|
| `preflightRetryGapMs` | **Client mode only.** Delay between reachability retries (default: 60000 ms). Note: timers don't advance while the machine is asleep, so on a sleeping laptop this gap counts awake-time, not wall-clock |
|
|
226
227
|
| `scoreSimilarityWeight` | Weight of semantic similarity in the ranking score (default: 0.50) |
|
package/assets/context.html
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
--danger: #c0392b;
|
|
35
35
|
--ok: #2f8f4e;
|
|
36
36
|
--tab-active: #ffffff;
|
|
37
|
+
--nav-bg: #f6f7f9;
|
|
37
38
|
}
|
|
38
39
|
@media (prefers-color-scheme: dark) {
|
|
39
40
|
:root {
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
--danger: #e05555;
|
|
48
49
|
--ok: #5fbf77;
|
|
49
50
|
--tab-active: #0f1117;
|
|
51
|
+
--nav-bg: #010409;
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
/* Explicit theme override wins over the media default in BOTH directions. */
|
|
@@ -54,11 +56,13 @@
|
|
|
54
56
|
--bg: #ffffff; --panel: #f6f7f9; --panel-border: #d9dce2;
|
|
55
57
|
--text: #1c1f26; --text-dim: #656d7a; --accent: #2f6fb0;
|
|
56
58
|
--accent-text: #ffffff; --danger: #c0392b; --ok: #2f8f4e; --tab-active: #ffffff;
|
|
59
|
+
--nav-bg: #f6f7f9;
|
|
57
60
|
}
|
|
58
61
|
:root[data-theme="dark"] {
|
|
59
62
|
--bg: #0f1117; --panel: #161a23; --panel-border: #262c3a;
|
|
60
63
|
--text: #d5dae4; --text-dim: #8b93a5; --accent: #5b9dd9;
|
|
61
64
|
--accent-text: #0b0d12; --danger: #e05555; --ok: #5fbf77; --tab-active: #0f1117;
|
|
65
|
+
--nav-bg: #010409;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -165,9 +169,33 @@
|
|
|
165
169
|
padding: 8px; font: inherit; margin-bottom: 10px;
|
|
166
170
|
}
|
|
167
171
|
#token-err { color: var(--danger); font-size: 13px; margin-top: 8px; display: none; }
|
|
172
|
+
|
|
173
|
+
/* ---- shared console nav (#249) ---- */
|
|
174
|
+
.hc-nav {
|
|
175
|
+
display: flex; align-items: center; gap: 16px;
|
|
176
|
+
height: 48px; padding: 0 18px; flex: none;
|
|
177
|
+
background: var(--nav-bg);
|
|
178
|
+
border-bottom: 1px solid var(--panel-border);
|
|
179
|
+
font-size: 14px;
|
|
180
|
+
}
|
|
181
|
+
.hc-nav-logo { color: var(--text); font-weight: 600; text-decoration: none; letter-spacing: 0.02em; }
|
|
182
|
+
.hc-nav-links { margin-left: auto; display: flex; align-items: center; gap: 16px; }
|
|
183
|
+
.hc-nav-links a { color: var(--text-dim); text-decoration: none; transition: color 0.12s; }
|
|
184
|
+
.hc-nav-links a:hover { color: var(--accent); }
|
|
185
|
+
.hc-nav-links a[data-nav-active] { color: var(--text); }
|
|
186
|
+
.hc-nav-disabled { color: var(--text-dim); opacity: 0.4; cursor: not-allowed; user-select: none; }
|
|
168
187
|
</style>
|
|
169
188
|
</head>
|
|
170
189
|
<body>
|
|
190
|
+
<nav class="hc-nav">
|
|
191
|
+
<a class="hc-nav-logo" href="/dashboard">Hicortex</a>
|
|
192
|
+
<div class="hc-nav-links">
|
|
193
|
+
<a href="/dashboard">Dashboard</a>
|
|
194
|
+
<a href="/viz">Graph</a>
|
|
195
|
+
<a href="/context/ui" data-nav-active>Context</a>
|
|
196
|
+
<span class="hc-nav-disabled" title="Coming soon — #250">Self-improvement</span>
|
|
197
|
+
</div>
|
|
198
|
+
</nav>
|
|
171
199
|
<header>
|
|
172
200
|
<h1>Hicortex — context</h1>
|
|
173
201
|
<label class="meta" for="scope">scope</label>
|
|
@@ -249,6 +277,22 @@
|
|
|
249
277
|
}
|
|
250
278
|
})();
|
|
251
279
|
|
|
280
|
+
// Shared console nav (#249): append ?token= to internal nav links on click
|
|
281
|
+
// so navigation between pages doesn't re-authenticate. Reads token from any
|
|
282
|
+
// page-scoped localStorage key (each page historically used its own).
|
|
283
|
+
document.querySelectorAll(".hc-nav-links a, .hc-nav-logo").forEach(function (a) {
|
|
284
|
+
a.addEventListener("click", function (e) {
|
|
285
|
+
var t = token
|
|
286
|
+
|| localStorage.getItem("hicortexToken")
|
|
287
|
+
|| localStorage.getItem("hicortex-dashboard-token");
|
|
288
|
+
if (!t) return;
|
|
289
|
+
e.preventDefault();
|
|
290
|
+
var u = new URL(a.getAttribute("href"), location.origin);
|
|
291
|
+
u.searchParams.set("token", t);
|
|
292
|
+
location.href = u.toString();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
252
296
|
function authHeaders(extra) {
|
|
253
297
|
var h = extra ? Object.assign({}, extra) : {};
|
|
254
298
|
if (token) h["Authorization"] = "Bearer " + token;
|
package/assets/dashboard.html
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
--accent-2: #2f8f4e;
|
|
42
42
|
--warn: #b08030;
|
|
43
43
|
--danger: #c0392b;
|
|
44
|
+
--nav-bg: #f6f7f9;
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -89,9 +90,42 @@
|
|
|
89
90
|
table.digest td.k { color: var(--text-dim); width: 180px; }
|
|
90
91
|
.stage-pill { display: inline-block; padding: 1px 6px; border-radius: 10px; background: rgba(128,128,128,0.18); font-size: 11px; color: var(--text-dim); margin-right: 4px; }
|
|
91
92
|
.empty { color: var(--text-dim); font-style: italic; padding: 12px 0; }
|
|
93
|
+
|
|
94
|
+
/* ---- shared console nav (#249) ---- */
|
|
95
|
+
.hc-nav {
|
|
96
|
+
position: sticky; top: 0; z-index: 50;
|
|
97
|
+
display: flex; align-items: center; gap: 16px;
|
|
98
|
+
height: 48px; padding: 0 20px;
|
|
99
|
+
margin: -20px -20px 20px;
|
|
100
|
+
background: var(--nav-bg, #010409);
|
|
101
|
+
border-bottom: 1px solid var(--panel-border);
|
|
102
|
+
font-size: 14px;
|
|
103
|
+
}
|
|
104
|
+
.hc-nav-logo {
|
|
105
|
+
color: var(--text); font-weight: 600; text-decoration: none;
|
|
106
|
+
letter-spacing: 0.02em;
|
|
107
|
+
}
|
|
108
|
+
.hc-nav-links { margin-left: auto; display: flex; align-items: center; gap: 16px; }
|
|
109
|
+
.hc-nav-links a {
|
|
110
|
+
color: var(--text-dim); text-decoration: none; transition: color 0.12s;
|
|
111
|
+
}
|
|
112
|
+
.hc-nav-links a:hover { color: var(--accent); }
|
|
113
|
+
.hc-nav-links a[data-nav-active] { color: var(--text); }
|
|
114
|
+
.hc-nav-disabled {
|
|
115
|
+
color: var(--text-dim); opacity: 0.4; cursor: not-allowed; user-select: none;
|
|
116
|
+
}
|
|
92
117
|
</style>
|
|
93
118
|
</head>
|
|
94
119
|
<body>
|
|
120
|
+
<nav class="hc-nav">
|
|
121
|
+
<a class="hc-nav-logo" href="/dashboard">Hicortex</a>
|
|
122
|
+
<div class="hc-nav-links">
|
|
123
|
+
<a href="/dashboard" data-nav-active>Dashboard</a>
|
|
124
|
+
<a href="/viz">Graph</a>
|
|
125
|
+
<a href="/context/ui">Context</a>
|
|
126
|
+
<span class="hc-nav-disabled" title="Coming soon — #250">Self-improvement</span>
|
|
127
|
+
</div>
|
|
128
|
+
</nav>
|
|
95
129
|
<h1>Hicortex — memory dashboard</h1>
|
|
96
130
|
<div class="sub">View-only analytics. All metrics derived from the live corpus + nightly snapshots.</div>
|
|
97
131
|
|
|
@@ -147,6 +181,8 @@
|
|
|
147
181
|
<div id="digest-lessons"></div>
|
|
148
182
|
<h2 style="margin-top:16px;">Dedup merges</h2>
|
|
149
183
|
<div id="digest-dedup"></div>
|
|
184
|
+
<h2 id="digest-tokens-section" style="margin-top:16px;display:none;">Token usage breakdown by stage</h2>
|
|
185
|
+
<div id="digest-tokens"></div>
|
|
150
186
|
</div>
|
|
151
187
|
|
|
152
188
|
<div class="sub muted" style="margin-top:24px;">
|
|
@@ -205,13 +241,71 @@ function renderHeadline(h) {
|
|
|
205
241
|
const ups = h.uses_per_showing === null || h.uses_per_showing === undefined
|
|
206
242
|
? "—"
|
|
207
243
|
: (h.uses_per_showing * 100).toFixed(1) + "%";
|
|
244
|
+
// Capacity gauge (#245): X / cap memories. Amber at ≥85% of cap, red at ≥100%.
|
|
245
|
+
// Hidden when the cap is 0 (disabled — indefinite growth, the pre-#245 default).
|
|
246
|
+
let capStat = "";
|
|
247
|
+
if (h.memory_soft_cap && h.memory_soft_cap > 0) {
|
|
248
|
+
const cap = h.memory_soft_cap;
|
|
249
|
+
const n = h.total_memories;
|
|
250
|
+
const pct = Math.min(100, (n / cap) * 100);
|
|
251
|
+
const over = n >= cap;
|
|
252
|
+
const near = pct >= 85 && !over;
|
|
253
|
+
const barColor = over ? "var(--bad, #e05555)" : near ? "var(--warn, #e0b055)" : "var(--ok, #61c98f)";
|
|
254
|
+
capStat = `
|
|
255
|
+
<div class="stat" style="flex-basis:240px;">
|
|
256
|
+
<div class="v" style="font-size:18px;">${n.toLocaleString()} <span class="muted" style="font-size:13px;">/ ${cap.toLocaleString()}</span></div>
|
|
257
|
+
<div class="l">Memories vs soft cap ${over ? "(over cap)" : ""}</div>
|
|
258
|
+
<div style="margin-top:6px;height:6px;background:rgba(128,128,128,0.2);border-radius:3px;overflow:hidden;">
|
|
259
|
+
<div style="width:${pct.toFixed(1)}%;height:100%;background:${barColor};"></div>
|
|
260
|
+
</div>
|
|
261
|
+
</div>`;
|
|
262
|
+
}
|
|
263
|
+
// LLM tokens this period (#246): hidden when `used` is 0 (no metered run yet
|
|
264
|
+
// — nothing to show). When capped (cap > 0), render a usage bar with the
|
|
265
|
+
// percentage; amber at ≥85%, red at ≥100%. When uncapped (self-hosted
|
|
266
|
+
// default), show just the count. Both shapes include "this month" so the
|
|
267
|
+
// billing-period framing is uniform.
|
|
268
|
+
let tokenStat = "";
|
|
269
|
+
const tok = h.tokens;
|
|
270
|
+
if (tok && tok.used > 0) {
|
|
271
|
+
const usedStr = formatTokens(tok.used);
|
|
272
|
+
if (tok.cap > 0) {
|
|
273
|
+
const pct = Math.min(100, (tok.used / tok.cap) * 100);
|
|
274
|
+
const over = tok.used >= tok.cap;
|
|
275
|
+
const near = pct >= 85 && !over;
|
|
276
|
+
const barColor = over ? "var(--bad, #e05555)" : near ? "var(--warn, #e0b055)" : "var(--ok, #61c98f)";
|
|
277
|
+
tokenStat = `
|
|
278
|
+
<div class="stat" style="flex-basis:240px;">
|
|
279
|
+
<div class="v" style="font-size:18px;">${escapeHtml(usedStr)} <span class="muted" style="font-size:13px;">/ ${escapeHtml(formatTokens(tok.cap))}</span></div>
|
|
280
|
+
<div class="l">LLM tokens this month ${over ? "(over cap)" : ""}</div>
|
|
281
|
+
<div style="margin-top:6px;height:6px;background:rgba(128,128,128,0.2);border-radius:3px;overflow:hidden;">
|
|
282
|
+
<div style="width:${pct.toFixed(1)}%;height:100%;background:${barColor};"></div>
|
|
283
|
+
</div>
|
|
284
|
+
</div>`;
|
|
285
|
+
} else {
|
|
286
|
+
tokenStat = `
|
|
287
|
+
<div class="stat">
|
|
288
|
+
<div class="v">${escapeHtml(usedStr)}</div>
|
|
289
|
+
<div class="l">LLM tokens this month</div>
|
|
290
|
+
</div>`;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
208
293
|
$("headline").innerHTML = `
|
|
209
294
|
<div class="stat"><div class="v">${h.total_memories}</div><div class="l">Total memories</div></div>
|
|
210
295
|
<div class="stat"><div class="v">${ups}</div><div class="l">Uses per showing (headline)</div></div>
|
|
211
296
|
<div class="stat"><div class="v warn">${h.cold_count}</div><div class="l">Cold long-tail (never shown/used)</div></div>
|
|
297
|
+
${capStat}
|
|
298
|
+
${tokenStat}
|
|
212
299
|
`;
|
|
213
300
|
}
|
|
214
301
|
|
|
302
|
+
/** Compact human-readable token count: 1234 → "1.2K", 1_500_000 → "1.5M". */
|
|
303
|
+
function formatTokens(n) {
|
|
304
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
|
|
305
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + "K";
|
|
306
|
+
return String(n);
|
|
307
|
+
}
|
|
308
|
+
|
|
215
309
|
// ---------------------------------------------------------------------------
|
|
216
310
|
// Render: growth chart (hand-rolled inline SVG line chart)
|
|
217
311
|
//
|
|
@@ -348,8 +442,27 @@ function renderDigest(digest) {
|
|
|
348
442
|
s.lessonsGenerated !== undefined ? `<span class="stage-pill">lessons: ${s.lessonsGenerated}</span>` : "",
|
|
349
443
|
`<span class="stage-pill">dedup: ${s.dedup}</span>`,
|
|
350
444
|
`<span class="stage-pill">supersession: ${s.supersession}</span>`,
|
|
445
|
+
s.evicted !== undefined && s.evicted > 0 ? `<span class="stage-pill warn">evicted: ${s.evicted}</span>` : "",
|
|
446
|
+
s.tokens !== undefined ? `<span class="stage-pill">tokens: ${escapeHtml(formatTokens(s.tokens))}</span>` : "",
|
|
351
447
|
].join(" ");
|
|
352
448
|
$("digest-stages").innerHTML = stages;
|
|
449
|
+
// Per-stage token breakdown (#246) — only when the day's run metered tokens
|
|
450
|
+
// and the breakdown was carried (always present together with `tokens`).
|
|
451
|
+
const tbs = s.tokens_by_stage;
|
|
452
|
+
if (tbs && Object.keys(tbs).length > 0) {
|
|
453
|
+
const rows = Object.entries(tbs)
|
|
454
|
+
.sort((a, b) => (b[1].total - a[1].total))
|
|
455
|
+
.map(([k, v]) =>
|
|
456
|
+
`<tr><td class="k">${escapeHtml(k)}</td>` +
|
|
457
|
+
`<td>${escapeHtml(formatTokens(v.total))}</td>` +
|
|
458
|
+
`<td class="muted">${escapeHtml(formatTokens(v.prompt))} in / ${escapeHtml(formatTokens(v.completion))} out</td></tr>`
|
|
459
|
+
).join("");
|
|
460
|
+
$("digest-tokens").innerHTML = `<table class="digest"><tbody>${rows}</tbody></table>`;
|
|
461
|
+
$("digest-tokens-section").style.display = "";
|
|
462
|
+
} else {
|
|
463
|
+
$("digest-tokens").innerHTML = `<div class="empty">No token usage that day (no metered LLM run).</div>`;
|
|
464
|
+
$("digest-tokens-section").style.display = "none";
|
|
465
|
+
}
|
|
353
466
|
|
|
354
467
|
if (digest.sample.length === 0) {
|
|
355
468
|
$("digest-sample").innerHTML = `<div class="empty">No new memories that day.</div>`;
|
|
@@ -427,6 +540,22 @@ async function load() {
|
|
|
427
540
|
}
|
|
428
541
|
|
|
429
542
|
document.addEventListener("DOMContentLoaded", () => {
|
|
543
|
+
// Shared console nav (#249): append ?token= to internal nav links on click
|
|
544
|
+
// so navigation between pages doesn't re-authenticate. Reads the token from
|
|
545
|
+
// any of the page-scoped localStorage keys (each page historically used its
|
|
546
|
+
// own key) — keeps the snippet identical across the three served pages.
|
|
547
|
+
document.querySelectorAll(".hc-nav-links a, .hc-nav-logo").forEach(function (a) {
|
|
548
|
+
a.addEventListener("click", function (e) {
|
|
549
|
+
var t = token
|
|
550
|
+
|| localStorage.getItem("hicortexToken")
|
|
551
|
+
|| localStorage.getItem("hicortex-dashboard-token");
|
|
552
|
+
if (!t) return;
|
|
553
|
+
e.preventDefault();
|
|
554
|
+
var u = new URL(a.getAttribute("href"), location.origin);
|
|
555
|
+
u.searchParams.set("token", t);
|
|
556
|
+
location.href = u.toString();
|
|
557
|
+
});
|
|
558
|
+
});
|
|
430
559
|
// Seed the date picker to the digest date from the first load (today).
|
|
431
560
|
$("reload").addEventListener("click", load);
|
|
432
561
|
$("range").addEventListener("change", load);
|
package/assets/viz.html
CHANGED
|
@@ -55,17 +55,26 @@
|
|
|
55
55
|
#topbar {
|
|
56
56
|
position: fixed; top: 0; left: 0; right: 0;
|
|
57
57
|
display: flex; align-items: center; gap: 10px;
|
|
58
|
-
|
|
58
|
+
height: 48px;
|
|
59
|
+
padding: 0 12px;
|
|
59
60
|
background: rgba(22, 26, 35, 0.94);
|
|
60
61
|
border-bottom: 1px solid var(--panel-border);
|
|
61
62
|
z-index: 10;
|
|
62
63
|
}
|
|
63
64
|
#topbar h1 { font-size: 14px; font-weight: 600; white-space: nowrap; }
|
|
64
|
-
#meta { color: var(--text-dim);
|
|
65
|
+
#meta { color: var(--text-dim); white-space: nowrap; margin-left: auto; }
|
|
66
|
+
|
|
67
|
+
/* ---- shared console nav links (#249), integrated into the viz topbar ---- */
|
|
68
|
+
.hc-nav-logo { color: var(--text); text-decoration: none; letter-spacing: 0.02em; }
|
|
69
|
+
.hc-nav-links { margin-left: 16px; display: flex; align-items: center; gap: 16px; font-size: 14px; }
|
|
70
|
+
.hc-nav-links a { color: var(--text-dim); text-decoration: none; transition: color 0.12s; }
|
|
71
|
+
.hc-nav-links a:hover { color: var(--accent); }
|
|
72
|
+
.hc-nav-links a[data-nav-active] { color: var(--text); }
|
|
73
|
+
.hc-nav-disabled { color: var(--text-dim); opacity: 0.4; cursor: not-allowed; user-select: none; }
|
|
65
74
|
|
|
66
75
|
/* ---- left control panel ---- */
|
|
67
76
|
#panel {
|
|
68
|
-
position: fixed; top:
|
|
77
|
+
position: fixed; top: 48px; left: 12px; width: 236px;
|
|
69
78
|
background: rgba(22, 26, 35, 0.94);
|
|
70
79
|
border: 1px solid var(--panel-border); border-radius: 8px;
|
|
71
80
|
padding: 12px; z-index: 10;
|
|
@@ -235,8 +244,14 @@
|
|
|
235
244
|
<div id="graph-2d"></div>
|
|
236
245
|
|
|
237
246
|
<div id="topbar">
|
|
238
|
-
<h1
|
|
247
|
+
<h1 class="hc-nav-logo"><a href="/dashboard" style="color:inherit;text-decoration:none">Hicortex</a></h1>
|
|
239
248
|
<span id="meta"></span>
|
|
249
|
+
<nav class="hc-nav-links">
|
|
250
|
+
<a href="/dashboard">Dashboard</a>
|
|
251
|
+
<a href="/viz" data-nav-active>Graph</a>
|
|
252
|
+
<a href="/context/ui">Context</a>
|
|
253
|
+
<span class="hc-nav-disabled" title="Coming soon — #250">Self-improvement</span>
|
|
254
|
+
</nav>
|
|
240
255
|
</div>
|
|
241
256
|
|
|
242
257
|
<div id="panel">
|
|
@@ -343,6 +358,22 @@
|
|
|
343
358
|
}
|
|
344
359
|
})();
|
|
345
360
|
|
|
361
|
+
// Shared console nav (#249): append ?token= to internal nav links on click
|
|
362
|
+
// so navigation between pages doesn't re-authenticate. Reads token from any
|
|
363
|
+
// page-scoped localStorage key (each page historically used its own).
|
|
364
|
+
document.querySelectorAll(".hc-nav-links a, .hc-nav-logo").forEach(function (a) {
|
|
365
|
+
a.addEventListener("click", function (e) {
|
|
366
|
+
var t = token
|
|
367
|
+
|| localStorage.getItem("hicortexToken")
|
|
368
|
+
|| localStorage.getItem("hicortex-dashboard-token");
|
|
369
|
+
if (!t) return;
|
|
370
|
+
e.preventDefault();
|
|
371
|
+
var u = new URL(a.getAttribute("href"), location.origin);
|
|
372
|
+
u.searchParams.set("token", t);
|
|
373
|
+
location.href = u.toString();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
346
377
|
// =========================================================================
|
|
347
378
|
// DOM references
|
|
348
379
|
// =========================================================================
|
package/dist/cli.d.ts
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* server Start the MCP HTTP/SSE server (persistent daemon)
|
|
7
7
|
* init Detect existing setup and configure for CC/OC
|
|
8
8
|
* nightly Run capture + consolidate (manual trigger)
|
|
9
|
-
* nightly --capture-only
|
|
10
|
-
* nightly --
|
|
9
|
+
* nightly --capture-only Capture only, skip consolidation
|
|
10
|
+
* nightly --consolidate-only Consolidate only, skip capture (hosted service)
|
|
11
|
+
* nightly --status Show nightly pipeline health check
|
|
11
12
|
* relink Resumable link-discovery pass over the entire corpus (issue #143)
|
|
12
13
|
* dedup Cluster + merge near-duplicate memories (issue #100)
|
|
13
14
|
* dedup --apply Execute the merge (default: dry run)
|
package/dist/cli.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* server Start the MCP HTTP/SSE server (persistent daemon)
|
|
8
8
|
* init Detect existing setup and configure for CC/OC
|
|
9
9
|
* nightly Run capture + consolidate (manual trigger)
|
|
10
|
-
* nightly --capture-only
|
|
11
|
-
* nightly --
|
|
10
|
+
* nightly --capture-only Capture only, skip consolidation
|
|
11
|
+
* nightly --consolidate-only Consolidate only, skip capture (hosted service)
|
|
12
|
+
* nightly --status Show nightly pipeline health check
|
|
12
13
|
* relink Resumable link-discovery pass over the entire corpus (issue #143)
|
|
13
14
|
* dedup Cluster + merge near-duplicate memories (issue #100)
|
|
14
15
|
* dedup --apply Execute the merge (default: dry run)
|
|
@@ -69,7 +70,12 @@ switch (command) {
|
|
|
69
70
|
else {
|
|
70
71
|
const dryRun = args.includes("--dry-run");
|
|
71
72
|
const captureOnly = args.includes("--capture-only");
|
|
73
|
+
const consolidateOnly = args.includes("--consolidate-only");
|
|
72
74
|
const watchdog = args.includes("--watchdog");
|
|
75
|
+
if (captureOnly && consolidateOnly) {
|
|
76
|
+
console.error("[hicortex] nightly: --capture-only and --consolidate-only are mutually exclusive");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
73
79
|
// Timestamp every log line. The nightly writes to a file (launchd /
|
|
74
80
|
// systemd StandardOutput append) with NO per-line timestamp, which made
|
|
75
81
|
// diagnosing capture gaps impossible (the #239 investigation couldn't
|
|
@@ -92,7 +98,7 @@ switch (command) {
|
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
100
|
import("./nightly.js").then(({ runNightly }) => {
|
|
95
|
-
runNightly({ dryRun, captureOnly, watchdog, recaptureWindowDays }).catch((err) => {
|
|
101
|
+
runNightly({ dryRun, captureOnly, consolidateOnly, watchdog, recaptureWindowDays }).catch((err) => {
|
|
96
102
|
console.error("[hicortex] Nightly pipeline failed:", err);
|
|
97
103
|
process.exit(1);
|
|
98
104
|
});
|
|
@@ -285,6 +291,7 @@ Options:
|
|
|
285
291
|
server --host <h> Host (default: 127.0.0.1)
|
|
286
292
|
nightly --dry-run Preview without changes
|
|
287
293
|
nightly --capture-only Capture only, skip consolidation (safe to run multiple times/day)
|
|
294
|
+
nightly --consolidate-only Consolidate only, skip capture (hosted-service per-tenant runs)
|
|
288
295
|
nightly --recapture-window <days> Re-discover sessions quiet since <days> ago (one-shot #189 recovery)
|
|
289
296
|
nightly --status Show nightly pipeline health
|
|
290
297
|
relink --dry-run Discovery + counts only, zero writes, cursor untouched
|
package/dist/consolidate.d.ts
CHANGED
|
@@ -63,12 +63,63 @@ export declare class BudgetTracker {
|
|
|
63
63
|
maxCalls: number;
|
|
64
64
|
callsUsed: number;
|
|
65
65
|
callsByStage: Record<string, number>;
|
|
66
|
+
/**
|
|
67
|
+
* Token usage per stage (#246). Keys are the same stage labels passed to
|
|
68
|
+
* `use()`. A stage that made no metered calls (no usage returned — never the
|
|
69
|
+
* path on a healthy openai/ollama endpoint) is absent, NOT zero, so the
|
|
70
|
+
* dashboard can distinguish "nothing spent" from "no signal".
|
|
71
|
+
*/
|
|
72
|
+
tokensByStage: Record<string, {
|
|
73
|
+
prompt: number;
|
|
74
|
+
completion: number;
|
|
75
|
+
total: number;
|
|
76
|
+
}>;
|
|
77
|
+
/** Run-wide totals — the sum of every recordUsage() call this run. */
|
|
78
|
+
totalTokens: {
|
|
79
|
+
prompt: number;
|
|
80
|
+
completion: number;
|
|
81
|
+
total: number;
|
|
82
|
+
};
|
|
66
83
|
constructor(maxCalls: number);
|
|
67
84
|
get exhausted(): boolean;
|
|
68
85
|
get remaining(): number;
|
|
69
86
|
use(stage: string, count?: number): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Record token usage from one LLM call (#246). Called by the consolidation
|
|
89
|
+
* stages after each metered completion. `undefined` usage (claude-cli path,
|
|
90
|
+
* or a non-conforming endpoint that returned no usage object) is a no-op —
|
|
91
|
+
* never recorded as zero, which would silently undercount real spend.
|
|
92
|
+
*/
|
|
93
|
+
recordUsage(stage: string, usage: {
|
|
94
|
+
prompt_tokens: number;
|
|
95
|
+
completion_tokens: number;
|
|
96
|
+
total_tokens: number;
|
|
97
|
+
} | undefined): void;
|
|
70
98
|
summary(): NonNullable<ConsolidationReport["budget"]>;
|
|
71
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Decide whether consolidation should be throttled this run based on the
|
|
102
|
+
* `llmTokensPerMonth` fair-use cap. Pure (no I/O) so it can be unit-tested
|
|
103
|
+
* independently of the nightly wiring.
|
|
104
|
+
*
|
|
105
|
+
* Returns `{ throttle: true, used, cap }` when the projected post-run total
|
|
106
|
+
* would exceed the cap; `{ throttle: false }` otherwise. The estimate is the
|
|
107
|
+
* previous run's actual usage (`llmTokensLastRun`, 0/absent on the first
|
|
108
|
+
* metered run = never throttle the first run — no baseline yet).
|
|
109
|
+
*
|
|
110
|
+
* `cap = 0` (the self-hosted default) → never throttle (unlimited).
|
|
111
|
+
* `periodStart` in a previous calendar month → period resets to 0 first
|
|
112
|
+
* (mirrors the reset logic in nightly.ts; both sides agree because both read
|
|
113
|
+
* the same state + clock).
|
|
114
|
+
*/
|
|
115
|
+
export declare function shouldThrottleTokens(cap: number, period: {
|
|
116
|
+
total: number;
|
|
117
|
+
periodStart: string;
|
|
118
|
+
} | undefined, lastRunTokens: number, now?: Date): {
|
|
119
|
+
throttle: boolean;
|
|
120
|
+
used?: number;
|
|
121
|
+
cap?: number;
|
|
122
|
+
};
|
|
72
123
|
/**
|
|
73
124
|
* Parse JSON from LLM output, tolerating markdown fences and indexed formats.
|
|
74
125
|
*/
|
|
@@ -203,6 +254,17 @@ export declare function stageDecayPrune(db: Database.Database, dryRun: boolean):
|
|
|
203
254
|
pruned: number;
|
|
204
255
|
failed: number;
|
|
205
256
|
};
|
|
257
|
+
/**
|
|
258
|
+
* Default soft cap on the memory corpus (#245). Above this the lowest-value
|
|
259
|
+
* memories are evicted each nightly. 10000 balances headroom for a busy
|
|
260
|
+
* self-hosted install against the noise cost of a bloated vector index
|
|
261
|
+
* (recall top-k competes against the long tail). Override via `memorySoftCap`.
|
|
262
|
+
*/
|
|
263
|
+
export declare const DEFAULT_MEMORY_SOFT_CAP = 10000;
|
|
264
|
+
export declare function stageMemoryCapEviction(db: Database.Database, dryRun: boolean, cap: number): {
|
|
265
|
+
cap: number;
|
|
266
|
+
evicted: number;
|
|
267
|
+
};
|
|
206
268
|
/**
|
|
207
269
|
* Run the full consolidation pipeline. Returns a structured report.
|
|
208
270
|
*/
|
|
@@ -232,7 +294,11 @@ export declare function runConsolidation(db: Database.Database, llm: LlmClient,
|
|
|
232
294
|
/** Total LLM-call ceiling across classify-tier stages (#241). The caller
|
|
233
295
|
* reads `consolidateMaxLlmCalls` from config and passes it; unset → the
|
|
234
296
|
* exported `CONSOLIDATE_MAX_LLM_CALLS` default (5000). */
|
|
235
|
-
budgetMaxCalls?: number
|
|
297
|
+
budgetMaxCalls?: number,
|
|
298
|
+
/** Soft cap on the corpus (#245). Nightly.ts reads `memorySoftCap` from
|
|
299
|
+
* config and passes it; unset → `DEFAULT_MEMORY_SOFT_CAP` (10000). `0`
|
|
300
|
+
* disables eviction (indefinite growth). */
|
|
301
|
+
memorySoftCap?: number): Promise<ConsolidationReport>;
|
|
236
302
|
/**
|
|
237
303
|
* Calculate milliseconds until the next occurrence of a given hour (local time).
|
|
238
304
|
*/
|