@gamaze/hicortex 0.17.5 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -25
- 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/{lessons-context.d.ts → learnings-identity.d.ts} +32 -21
- package/dist/{lessons-context.js → learnings-identity.js} +50 -39
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +168 -58
- 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.d.ts +13 -9
- package/dist/type-classify.js +26 -12
- 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/cli.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
const cli_args_js_1 = require("./cli-args.js");
|
|
21
|
-
const command = process.argv[2];
|
|
21
|
+
const command = (0, cli_args_js_1.resolveCommandAlias)(process.argv[2]);
|
|
22
22
|
switch (command) {
|
|
23
23
|
case "server": {
|
|
24
24
|
const portArg = process.argv.indexOf("--port");
|
|
@@ -218,16 +218,19 @@ switch (command) {
|
|
|
218
218
|
});
|
|
219
219
|
break;
|
|
220
220
|
}
|
|
221
|
-
case "
|
|
222
|
-
// Standing
|
|
223
|
-
// configured server. Secondary to the
|
|
221
|
+
case "identity": {
|
|
222
|
+
// Standing identity layer edit surface (spec §6; renamed from context in
|
|
223
|
+
// 0.18 #264): show|edit against the configured server. Secondary to the
|
|
224
|
+
// /identity/ui Web UI; for headless boxes. The legacy `context` command is
|
|
225
|
+
// kept as a hidden backcompat alias via resolveCommandAlias so old scripts
|
|
226
|
+
// and muscle memory keep working.
|
|
224
227
|
const args = process.argv.slice(3);
|
|
225
|
-
import("./
|
|
226
|
-
|
|
227
|
-
if (err instanceof
|
|
228
|
+
import("./identity-cli.js").then(({ runIdentityCommand, IdentityCliError }) => {
|
|
229
|
+
runIdentityCommand(args).catch((err) => {
|
|
230
|
+
if (err instanceof IdentityCliError)
|
|
228
231
|
console.error(err.message);
|
|
229
232
|
else
|
|
230
|
-
console.error("[hicortex]
|
|
233
|
+
console.error("[hicortex] identity command failed:", err instanceof Error ? err.message : err);
|
|
231
234
|
process.exit(1);
|
|
232
235
|
});
|
|
233
236
|
});
|
|
@@ -271,12 +274,14 @@ switch (command) {
|
|
|
271
274
|
.catch(() => process.exit(0));
|
|
272
275
|
}).catch(() => process.exit(0));
|
|
273
276
|
break;
|
|
274
|
-
case "
|
|
275
|
-
// CC SessionStart hook: fetch lessons from the configured server
|
|
276
|
-
// a Markdown block to stdout.
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
case "learnings-identity": {
|
|
278
|
+
// CC SessionStart hook: fetch identity + lessons from the configured server
|
|
279
|
+
// and print a Markdown block to stdout. Canonical command name since #264;
|
|
280
|
+
// the legacy `lessons-context` subcommand is kept as a backcompat alias via
|
|
281
|
+
// resolveCommandAlias so existing installed hooks keep working. Fail-soft —
|
|
282
|
+
// any error = silent exit 0 so a broken hook never blocks a CC session.
|
|
283
|
+
import("./learnings-identity.js").then(({ fetchLessonsIdentity }) => {
|
|
284
|
+
fetchLessonsIdentity()
|
|
280
285
|
.then((block) => {
|
|
281
286
|
if (block)
|
|
282
287
|
process.stdout.write(block + "\n");
|
|
@@ -285,6 +290,7 @@ switch (command) {
|
|
|
285
290
|
.catch(() => process.exit(0));
|
|
286
291
|
}).catch(() => process.exit(0));
|
|
287
292
|
break;
|
|
293
|
+
}
|
|
288
294
|
default:
|
|
289
295
|
console.log(`Hicortex — Human-like memory for self-improving AI agents
|
|
290
296
|
|
|
@@ -296,7 +302,7 @@ Commands:
|
|
|
296
302
|
Scaffolds 5 editable default memory domains (Work, Personal,
|
|
297
303
|
People, Health, Finance) in ~/.hicortex/config.json
|
|
298
304
|
init --server <url> Set up as client (remote server)
|
|
299
|
-
init --agent-name <name> Opt in to a per-agent
|
|
305
|
+
init --agent-name <name> Opt in to a per-agent identity id (default: unset — shared global identity)
|
|
300
306
|
Pass --agent-name "" to clear it back to global
|
|
301
307
|
init --repair-config Recover from a malformed ~/.hicortex/config.json: move it to
|
|
302
308
|
config.json.corrupt-<timestamp> and rebuild. Nothing is deleted.
|
|
@@ -306,9 +312,11 @@ Commands:
|
|
|
306
312
|
dedup Cluster + merge near-duplicate memories (server mode; dry run by default)
|
|
307
313
|
classify-domains Backfill content-based domain tags over the corpus (server mode, needs config.domains)
|
|
308
314
|
classify-types Backfill episode→fact/decision type tags over the corpus (server mode)
|
|
309
|
-
|
|
315
|
+
learnings-identity Fetch identity + lessons and print Markdown to stdout (CC SessionStart hook)
|
|
316
|
+
(alias: lessons-context — the pre-#264 name, kept for backcompat)
|
|
310
317
|
recall-hook Pushed recall index for the current prompt (CC UserPromptSubmit/SessionStart hook)
|
|
311
|
-
|
|
318
|
+
identity Standing identity layer (show|edit) against the configured server
|
|
319
|
+
(alias: context — the pre-0.18 name, kept for backcompat)
|
|
312
320
|
telemetry Show exactly what anonymous telemetry sends (read-only)
|
|
313
321
|
status Show current configuration and stats
|
|
314
322
|
uninstall Remove CC integration (preserves DB)
|
|
@@ -333,9 +341,10 @@ Options:
|
|
|
333
341
|
classify-types --all Reclassify every memory (default: only episodes)
|
|
334
342
|
classify-types --batch <n> Memories per batch (default: 200)
|
|
335
343
|
classify-types --reset Restart from the beginning (ignore saved cursor)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
344
|
+
identity show [name] Print all identity sections, or just <name> (raw, pipeable)
|
|
345
|
+
identity edit <name> Edit a section in $EDITOR; PUT only if changed
|
|
346
|
+
identity … --agent <id> Target a per-agent scope instead of the global set
|
|
347
|
+
(the legacy 'context' command remains as a hidden alias for 'identity')
|
|
339
348
|
|
|
340
349
|
Examples:
|
|
341
350
|
npx @gamaze/hicortex server
|
package/dist/consolidate.d.ts
CHANGED
|
@@ -63,6 +63,21 @@ export declare class BudgetTracker {
|
|
|
63
63
|
maxCalls: number;
|
|
64
64
|
callsUsed: number;
|
|
65
65
|
callsByStage: Record<string, number>;
|
|
66
|
+
/**
|
|
67
|
+
* Per-stage count of LLM-call REQUESTS refused because the budget was
|
|
68
|
+
* exhausted (#255). Keys are the same stage labels passed to `use()`. The
|
|
69
|
+
* value is the SUM of the `count` args passed to each refused `use()` call
|
|
70
|
+
* in that stage (in production every `use()` call passes count=1, so each
|
|
71
|
+
* refused call adds 1 — but the API accepts a batch count, so a single
|
|
72
|
+
* refused batch request accrues its full count). Stages break on the first
|
|
73
|
+
* refusal, so a stage's value is the count of the one request that crossed
|
|
74
|
+
* the boundary. For item-level skip counts (how many memories or pairs were
|
|
75
|
+
* left unprocessed), see the per-stage reports — e.g.
|
|
76
|
+
* `stages.importance.skipped_budget` — which count MEMORIES, not call
|
|
77
|
+
* requests. Surfaced in summary() and ConsolidationReport as
|
|
78
|
+
* `deferred_by_stage`.
|
|
79
|
+
*/
|
|
80
|
+
deferredByStage: Record<string, number>;
|
|
66
81
|
/**
|
|
67
82
|
* Token usage per stage (#246). Keys are the same stage labels passed to
|
|
68
83
|
* `use()`. A stage that made no metered calls (no usage returned — never the
|
package/dist/consolidate.js
CHANGED
|
@@ -127,6 +127,21 @@ class BudgetTracker {
|
|
|
127
127
|
maxCalls;
|
|
128
128
|
callsUsed = 0;
|
|
129
129
|
callsByStage = {};
|
|
130
|
+
/**
|
|
131
|
+
* Per-stage count of LLM-call REQUESTS refused because the budget was
|
|
132
|
+
* exhausted (#255). Keys are the same stage labels passed to `use()`. The
|
|
133
|
+
* value is the SUM of the `count` args passed to each refused `use()` call
|
|
134
|
+
* in that stage (in production every `use()` call passes count=1, so each
|
|
135
|
+
* refused call adds 1 — but the API accepts a batch count, so a single
|
|
136
|
+
* refused batch request accrues its full count). Stages break on the first
|
|
137
|
+
* refusal, so a stage's value is the count of the one request that crossed
|
|
138
|
+
* the boundary. For item-level skip counts (how many memories or pairs were
|
|
139
|
+
* left unprocessed), see the per-stage reports — e.g.
|
|
140
|
+
* `stages.importance.skipped_budget` — which count MEMORIES, not call
|
|
141
|
+
* requests. Surfaced in summary() and ConsolidationReport as
|
|
142
|
+
* `deferred_by_stage`.
|
|
143
|
+
*/
|
|
144
|
+
deferredByStage = {};
|
|
130
145
|
/**
|
|
131
146
|
* Token usage per stage (#246). Keys are the same stage labels passed to
|
|
132
147
|
* `use()`. A stage that made no metered calls (no usage returned — never the
|
|
@@ -151,8 +166,15 @@ class BudgetTracker {
|
|
|
151
166
|
}
|
|
152
167
|
use(stage, count = 1) {
|
|
153
168
|
if (this.callsUsed + count > this.maxCalls) {
|
|
154
|
-
|
|
155
|
-
|
|
169
|
+
// #255: emit as a STRUCTURED event (not a bare prose warn) so a monitor
|
|
170
|
+
// can grep/parse `event=budget_exhausted` from journald. The line stays
|
|
171
|
+
// human-readable (key=value tokens after the [hicortex] prefix). Deferred
|
|
172
|
+
// counts are accrued BEFORE the log so the line reflects the up-to-date
|
|
173
|
+
// per-stage toll — the refused count is added to this stage's slot.
|
|
174
|
+
this.deferredByStage[stage] = (this.deferredByStage[stage] ?? 0) + count;
|
|
175
|
+
console.warn(`[hicortex] event=budget_exhausted stage=${stage} ` +
|
|
176
|
+
`calls_used=${this.callsUsed} max_calls=${this.maxCalls} ` +
|
|
177
|
+
`deferred_by_stage=${JSON.stringify(this.deferredByStage)}`);
|
|
156
178
|
return false;
|
|
157
179
|
}
|
|
158
180
|
this.callsUsed += count;
|
|
@@ -183,6 +205,11 @@ class BudgetTracker {
|
|
|
183
205
|
calls_used: this.callsUsed,
|
|
184
206
|
calls_remaining: this.remaining,
|
|
185
207
|
calls_by_stage: { ...this.callsByStage },
|
|
208
|
+
// #255: exhaustion + per-stage deferred counts flow into the report →
|
|
209
|
+
// telemetry + dashboard. Always present (post-#255); absent implies a
|
|
210
|
+
// pre-#255 report (treat as false / no deferrals).
|
|
211
|
+
exhausted: this.exhausted,
|
|
212
|
+
deferred_by_stage: { ...this.deferredByStage },
|
|
186
213
|
tokens_by_stage: Object.fromEntries(Object.entries(this.tokensByStage).map(([k, v]) => [k, { ...v }])),
|
|
187
214
|
tokens_total: { ...this.totalTokens },
|
|
188
215
|
};
|
|
@@ -393,7 +420,7 @@ async function stageReflection(db, memories, llm, budget, embedFn, dryRun) {
|
|
|
393
420
|
const sourcePattern = String(lo.source_pattern ?? "");
|
|
394
421
|
// No `## Lesson:` prefix: memory_type='lesson' carries the type, and the
|
|
395
422
|
// text is the topic-first first line (display reads the first line, not a
|
|
396
|
-
// header parse — see
|
|
423
|
+
// header parse — see learnings-identity.ts / index.ts).
|
|
397
424
|
let content = `${lessonText}\n\n`;
|
|
398
425
|
content += `**Type:** ${lessonType}\n`;
|
|
399
426
|
content += `**Severity:** ${severity}\n`;
|
package/dist/dashboard.d.ts
CHANGED
|
@@ -53,6 +53,32 @@ export interface DashboardMetrics {
|
|
|
53
53
|
completion: number;
|
|
54
54
|
total: number;
|
|
55
55
|
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Always-on consolidation-budget usage metric (#255 CR). `calls_used` is
|
|
58
|
+
* how many LLM calls the run actually spent; `max_calls` is the configured
|
|
59
|
+
* `consolidateMaxLlmCalls` ceiling. Forwarded whenever consolidation ran
|
|
60
|
+
* (the digest renders a continuous used/max bar, like the token-usage
|
|
61
|
+
* metric, so you can see the budget climbing before exhaustion). Undefined
|
|
62
|
+
* on backfill rows and on runs where consolidation didn't execute
|
|
63
|
+
* (capture-only / no_llm / throttled / skipped) — the page renders no bar.
|
|
64
|
+
*/
|
|
65
|
+
budget_calls_used?: number;
|
|
66
|
+
budget_max_calls?: number;
|
|
67
|
+
/**
|
|
68
|
+
* True when this run's consolidation budget (`consolidateMaxLlmCalls`)
|
|
69
|
+
* was exhausted — LLM-bound stages deferred remaining work (#255).
|
|
70
|
+
* Forwarded ONLY on exhaustion (the alert state on top of the always-on
|
|
71
|
+
* usage bar). Undefined on healthy runs (where budget_calls_used is still
|
|
72
|
+
* present), backfill rows, and non-run nights.
|
|
73
|
+
*/
|
|
74
|
+
budget_exhausted?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Per-stage count of refused LLM-call requests on an exhausted run (#255).
|
|
77
|
+
* Mirrors ConsolidationReport.budget.deferred_by_stage. Forwarded in
|
|
78
|
+
* lockstep with `budget_exhausted` (exhausted runs only) so the snapshot
|
|
79
|
+
* shape is clean on healthy runs.
|
|
80
|
+
*/
|
|
81
|
+
budget_deferred_by_stage?: Record<string, number>;
|
|
56
82
|
};
|
|
57
83
|
/** Corpus capacity (#245). `memory_soft_cap` is the configured ceiling (0 =
|
|
58
84
|
* disabled); always present in real snapshots, undefined on backfilled
|
|
@@ -131,6 +157,17 @@ export interface DashboardData {
|
|
|
131
157
|
completion: number;
|
|
132
158
|
total: number;
|
|
133
159
|
}>;
|
|
160
|
+
/**
|
|
161
|
+
* Always-on consolidation-budget usage (#255 CR). Present whenever the
|
|
162
|
+
* day's snapshot carries them (consolidation ran). The page renders a
|
|
163
|
+
* used/max bar in the same style as the token-usage metric.
|
|
164
|
+
*/
|
|
165
|
+
budget_calls_used?: number;
|
|
166
|
+
budget_max_calls?: number;
|
|
167
|
+
/** True when the run exhausted its consolidation budget (#255). */
|
|
168
|
+
budget_exhausted?: boolean;
|
|
169
|
+
/** Per-stage refused-request counts on an exhausted run (#255). */
|
|
170
|
+
budget_deferred_by_stage?: Record<string, number>;
|
|
134
171
|
};
|
|
135
172
|
dedup_merges: {
|
|
136
173
|
loser_id: string;
|
|
@@ -170,6 +207,26 @@ export interface NightlyDelta {
|
|
|
170
207
|
completion: number;
|
|
171
208
|
total: number;
|
|
172
209
|
}>;
|
|
210
|
+
/**
|
|
211
|
+
* Always-on consolidation-budget usage (#255 CR). Forwarded whenever
|
|
212
|
+
* consolidation ran so the dashboard renders a continuous used/max bar.
|
|
213
|
+
* `callsUsed` = LLM calls spent this run; `maxCalls` = configured ceiling.
|
|
214
|
+
* Undefined in lockstep (both set together when consolidation ran).
|
|
215
|
+
*/
|
|
216
|
+
budgetCallsUsed?: number;
|
|
217
|
+
budgetMaxCalls?: number;
|
|
218
|
+
/**
|
|
219
|
+
* True when this run's consolidation budget was exhausted (#255) — same as
|
|
220
|
+
* ConsolidationReport.budget.exhausted. Undefined when consolidation didn't
|
|
221
|
+
* run (capture-only / no_llm / throttled / skipped) or didn't exhaust.
|
|
222
|
+
*/
|
|
223
|
+
budgetExhausted?: boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Per-stage refused-request counts on an exhausted run (#255) — same shape
|
|
226
|
+
* as ConsolidationReport.budget.deferred_by_stage. Undefined in lockstep
|
|
227
|
+
* with `budgetExhausted`.
|
|
228
|
+
*/
|
|
229
|
+
budgetDeferredByStage?: Record<string, number>;
|
|
173
230
|
}
|
|
174
231
|
/**
|
|
175
232
|
* Write one snapshot row for `runAt` (an ISO timestamp the caller chooses —
|
|
@@ -225,7 +282,7 @@ export declare function handleDashboardData(db: Database.Database, query: {
|
|
|
225
282
|
};
|
|
226
283
|
/**
|
|
227
284
|
* Express adapter for GET /dashboard/data. Wraps the pure handler so the
|
|
228
|
-
* route stays thin (same convention as
|
|
285
|
+
* route stays thin (same convention as identity-store handlers in viz.ts).
|
|
229
286
|
* Failures surface as a 500 with the usual {error} shape — no silent degrade.
|
|
230
287
|
*/
|
|
231
288
|
export declare function dashboardDataHandler(getDb: () => Database.Database, getConfig: () => Record<string, unknown> | null | undefined): express.RequestHandler;
|
package/dist/dashboard.js
CHANGED
|
@@ -103,6 +103,24 @@ function writeSnapshot(db, runAt, delta, memorySoftCap) {
|
|
|
103
103
|
// page treats undefined as "no data for this day", matching adoption.
|
|
104
104
|
...(delta.tokensThisRun !== undefined ? { tokens: delta.tokensThisRun } : {}),
|
|
105
105
|
...(delta.tokensByStage !== undefined ? { tokens_by_stage: delta.tokensByStage } : {}),
|
|
106
|
+
// #255 CR: always-on usage metric — forward calls_used + max_calls
|
|
107
|
+
// whenever consolidation ran (regardless of exhaustion) so the page can
|
|
108
|
+
// render a continuous used/max bar. Presence = a run happened; absence =
|
|
109
|
+
// consolidation didn't execute (capture-only / no_llm / throttled / skipped).
|
|
110
|
+
...(delta.budgetCallsUsed !== undefined ? { budget_calls_used: delta.budgetCallsUsed } : {}),
|
|
111
|
+
...(delta.budgetMaxCalls !== undefined ? { budget_max_calls: delta.budgetMaxCalls } : {}),
|
|
112
|
+
// #255: the alert state. budget_exhausted + budget_deferred_by_stage are
|
|
113
|
+
// gated on the SAME condition (exhausted=true) so the snapshot shape is
|
|
114
|
+
// clean on healthy runs — no empty {} leaking through (W4). The healthy
|
|
115
|
+
// signal is carried by budget_calls_used above, not by a false flag here.
|
|
116
|
+
...(delta.budgetExhausted
|
|
117
|
+
? {
|
|
118
|
+
budget_exhausted: true,
|
|
119
|
+
...(delta.budgetDeferredByStage !== undefined
|
|
120
|
+
? { budget_deferred_by_stage: delta.budgetDeferredByStage }
|
|
121
|
+
: {}),
|
|
122
|
+
}
|
|
123
|
+
: {}),
|
|
106
124
|
};
|
|
107
125
|
if (memorySoftCap !== undefined) {
|
|
108
126
|
metrics.capacity = { memory_soft_cap: memorySoftCap };
|
|
@@ -404,6 +422,14 @@ function handleDashboardData(db, query, config) {
|
|
|
404
422
|
// are forwarded together — the page renders either the breakdown or nothing.
|
|
405
423
|
tokens: dayMetrics?.new_this_run?.tokens,
|
|
406
424
|
tokens_by_stage: dayMetrics?.new_this_run?.tokens_by_stage,
|
|
425
|
+
// #255 CR: always-on usage metric — present whenever consolidation ran
|
|
426
|
+
// (under-cap OR exhausted). The page renders a continuous used/max bar.
|
|
427
|
+
budget_calls_used: dayMetrics?.new_this_run?.budget_calls_used,
|
|
428
|
+
budget_max_calls: dayMetrics?.new_this_run?.budget_max_calls,
|
|
429
|
+
// #255: budget exhaustion — only present when the run actually hit the
|
|
430
|
+
// cap (undefined on backfill / non-run / under-cap nights).
|
|
431
|
+
budget_exhausted: dayMetrics?.new_this_run?.budget_exhausted,
|
|
432
|
+
budget_deferred_by_stage: dayMetrics?.new_this_run?.budget_deferred_by_stage,
|
|
407
433
|
},
|
|
408
434
|
dedup_merges: dedupRows.map((r) => ({
|
|
409
435
|
loser_id: r.loser_id,
|
|
@@ -429,7 +455,7 @@ function handleDashboardData(db, query, config) {
|
|
|
429
455
|
}
|
|
430
456
|
/**
|
|
431
457
|
* Express adapter for GET /dashboard/data. Wraps the pure handler so the
|
|
432
|
-
* route stays thin (same convention as
|
|
458
|
+
* route stays thin (same convention as identity-store handlers in viz.ts).
|
|
433
459
|
* Failures surface as a 500 with the usual {error} shape — no silent degrade.
|
|
434
460
|
*/
|
|
435
461
|
function dashboardDataHandler(getDb, getConfig) {
|
package/dist/extensions.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* still be swapped in (tests, future experiments).
|
|
8
8
|
*
|
|
9
9
|
* LessonSelector — lesson selection sites:
|
|
10
|
-
*
|
|
10
|
+
* learnings-identity.ts:fetchLessonsIdentity (CC SessionStart hook),
|
|
11
11
|
* index.ts:before_agent_start (OC in-process plugin).
|
|
12
12
|
* The default is the domain-aware scoring selector (lesson-selection.ts):
|
|
13
13
|
* ranks lessons by project match + domain affinity + recency +
|
package/dist/extensions.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* still be swapped in (tests, future experiments).
|
|
9
9
|
*
|
|
10
10
|
* LessonSelector — lesson selection sites:
|
|
11
|
-
*
|
|
11
|
+
* learnings-identity.ts:fetchLessonsIdentity (CC SessionStart hook),
|
|
12
12
|
* index.ts:before_agent_start (OC in-process plugin).
|
|
13
13
|
* The default is the domain-aware scoring selector (lesson-selection.ts):
|
|
14
14
|
* ranks lessons by project match + domain affinity + recency +
|
package/dist/health.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health-endpoint + REST error-sanitisation helpers (#253).
|
|
3
|
+
*
|
|
4
|
+
* Two concerns live here:
|
|
5
|
+
*
|
|
6
|
+
* 1. The public `GET /health` probe is UNAUTHENTICATED and was previously
|
|
7
|
+
* echoing tenant/install business-intelligence (memory count, link count,
|
|
8
|
+
* DB size, version, the full LLM backend string) plus running `COUNT(*)`
|
|
9
|
+
* on every hit. The public response is now just `{status:"ok"}`. The
|
|
10
|
+
* diagnostics moved to `GET /health/detail`, which goes through the normal
|
|
11
|
+
* bearer-token auth middleware (localhost bypasses as usual) so an
|
|
12
|
+
* operator running `hicortex status` on the server box, or a co-located
|
|
13
|
+
* nightly preflight, still gets them — but a remote/anonymous caller does
|
|
14
|
+
* not. Spec: `specs/2026-07-27-hosted-service.md` §6, Phase 0a item 5a/b.
|
|
15
|
+
*
|
|
16
|
+
* 2. REST `res.status(500).json({error: err.message})` sites were echoing
|
|
17
|
+
* internal detail (LLM upstream URLs, hostnames, stack frames) to the HTTP
|
|
18
|
+
* caller. `logAndSendInternalError` logs the full detail server-side and
|
|
19
|
+
* returns a generic `{error:"Internal error"}` body. Validation errors
|
|
20
|
+
* (400 with a useful, non-leaking message) stay specific at their call
|
|
21
|
+
* sites — only the catch blocks that could leak infrastructure route
|
|
22
|
+
* through here.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Public, unauthenticated health probe. Carries NO data — just liveness.
|
|
26
|
+
* Anyone hitting `/health` (load balancer, watchdog, anonymous prober) gets
|
|
27
|
+
* this and nothing else.
|
|
28
|
+
*/
|
|
29
|
+
export declare function publicHealthResponse(): {
|
|
30
|
+
status: "ok";
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Operator-only diagnostics. Returned by `GET /health/detail` behind the
|
|
34
|
+
* standard auth middleware (localhost bypasses auth, so co-located tooling
|
|
35
|
+
* — `hicortex status`, nightly preflight, `init` detect — sees it without a
|
|
36
|
+
* token; a remote caller needs the bearer token).
|
|
37
|
+
*/
|
|
38
|
+
export declare function detailedHealthResponse(opts: {
|
|
39
|
+
memories: number;
|
|
40
|
+
links: number;
|
|
41
|
+
dbSizeBytes: number;
|
|
42
|
+
version: string;
|
|
43
|
+
llmLabel: string;
|
|
44
|
+
}): {
|
|
45
|
+
status: "ok";
|
|
46
|
+
version: string;
|
|
47
|
+
memories: number;
|
|
48
|
+
links: number;
|
|
49
|
+
db_size_kb: number;
|
|
50
|
+
llm: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Log the full internal error detail server-side and return a generic
|
|
54
|
+
* client-facing 500 body. Use this in every REST catch block whose
|
|
55
|
+
* `err.message` could leak infrastructure (LLM upstream URLs, hostnames,
|
|
56
|
+
* stack frames, DB paths). Validation errors (400) with a useful,
|
|
57
|
+
* non-leaking message stay inline at the call site — this is for the
|
|
58
|
+
* catch-all 500 paths only.
|
|
59
|
+
*
|
|
60
|
+
* The `route` arg (e.g. "lessons", "distill") scopes the log line so an
|
|
61
|
+
* operator can tell which endpoint failed without the response body
|
|
62
|
+
* carrying that detail to the caller.
|
|
63
|
+
*/
|
|
64
|
+
export declare function logAndSendInternalError(res: {
|
|
65
|
+
status(code: number): {
|
|
66
|
+
json(body: unknown): void;
|
|
67
|
+
};
|
|
68
|
+
}, route: string, err: unknown): void;
|
package/dist/health.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Health-endpoint + REST error-sanitisation helpers (#253).
|
|
4
|
+
*
|
|
5
|
+
* Two concerns live here:
|
|
6
|
+
*
|
|
7
|
+
* 1. The public `GET /health` probe is UNAUTHENTICATED and was previously
|
|
8
|
+
* echoing tenant/install business-intelligence (memory count, link count,
|
|
9
|
+
* DB size, version, the full LLM backend string) plus running `COUNT(*)`
|
|
10
|
+
* on every hit. The public response is now just `{status:"ok"}`. The
|
|
11
|
+
* diagnostics moved to `GET /health/detail`, which goes through the normal
|
|
12
|
+
* bearer-token auth middleware (localhost bypasses as usual) so an
|
|
13
|
+
* operator running `hicortex status` on the server box, or a co-located
|
|
14
|
+
* nightly preflight, still gets them — but a remote/anonymous caller does
|
|
15
|
+
* not. Spec: `specs/2026-07-27-hosted-service.md` §6, Phase 0a item 5a/b.
|
|
16
|
+
*
|
|
17
|
+
* 2. REST `res.status(500).json({error: err.message})` sites were echoing
|
|
18
|
+
* internal detail (LLM upstream URLs, hostnames, stack frames) to the HTTP
|
|
19
|
+
* caller. `logAndSendInternalError` logs the full detail server-side and
|
|
20
|
+
* returns a generic `{error:"Internal error"}` body. Validation errors
|
|
21
|
+
* (400 with a useful, non-leaking message) stay specific at their call
|
|
22
|
+
* sites — only the catch blocks that could leak infrastructure route
|
|
23
|
+
* through here.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.publicHealthResponse = publicHealthResponse;
|
|
27
|
+
exports.detailedHealthResponse = detailedHealthResponse;
|
|
28
|
+
exports.logAndSendInternalError = logAndSendInternalError;
|
|
29
|
+
/**
|
|
30
|
+
* Public, unauthenticated health probe. Carries NO data — just liveness.
|
|
31
|
+
* Anyone hitting `/health` (load balancer, watchdog, anonymous prober) gets
|
|
32
|
+
* this and nothing else.
|
|
33
|
+
*/
|
|
34
|
+
function publicHealthResponse() {
|
|
35
|
+
return { status: "ok" };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Operator-only diagnostics. Returned by `GET /health/detail` behind the
|
|
39
|
+
* standard auth middleware (localhost bypasses auth, so co-located tooling
|
|
40
|
+
* — `hicortex status`, nightly preflight, `init` detect — sees it without a
|
|
41
|
+
* token; a remote caller needs the bearer token).
|
|
42
|
+
*/
|
|
43
|
+
function detailedHealthResponse(opts) {
|
|
44
|
+
return {
|
|
45
|
+
status: "ok",
|
|
46
|
+
version: opts.version,
|
|
47
|
+
memories: opts.memories,
|
|
48
|
+
links: opts.links,
|
|
49
|
+
db_size_kb: Math.round(opts.dbSizeBytes / 1024),
|
|
50
|
+
llm: opts.llmLabel,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Log the full internal error detail server-side and return a generic
|
|
55
|
+
* client-facing 500 body. Use this in every REST catch block whose
|
|
56
|
+
* `err.message` could leak infrastructure (LLM upstream URLs, hostnames,
|
|
57
|
+
* stack frames, DB paths). Validation errors (400) with a useful,
|
|
58
|
+
* non-leaking message stay inline at the call site — this is for the
|
|
59
|
+
* catch-all 500 paths only.
|
|
60
|
+
*
|
|
61
|
+
* The `route` arg (e.g. "lessons", "distill") scopes the log line so an
|
|
62
|
+
* operator can tell which endpoint failed without the response body
|
|
63
|
+
* carrying that detail to the caller.
|
|
64
|
+
*/
|
|
65
|
+
function logAndSendInternalError(res, route, err) {
|
|
66
|
+
// Stack when available (most informative), else name+message for Errors,
|
|
67
|
+
// else String(). NEVER echo this to the response body.
|
|
68
|
+
const detail = err instanceof Error
|
|
69
|
+
? (err.stack ?? `${err.name}: ${err.message}`)
|
|
70
|
+
: String(err);
|
|
71
|
+
console.error(`[hicortex] /${route}: ${detail}`);
|
|
72
|
+
res.status(500).json({ error: "Internal error" });
|
|
73
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* identity-cli — `hicortex identity show|edit`, the secondary/headless edit
|
|
3
|
+
* surface for the standing identity layer (spec 2026-07-12 §6; renamed 0.18
|
|
4
|
+
* #264 from `context`). The Web UI (`/identity/ui`) is primary; this exists
|
|
5
|
+
* for boxes without a browser.
|
|
6
|
+
*
|
|
7
|
+
* hicortex identity show [name] GET /identity → print all sections, or one
|
|
8
|
+
* hicortex identity edit <name> GET section → $EDITOR → PUT if changed
|
|
9
|
+
*
|
|
10
|
+
* The legacy `hicortex context ...` command remains as a hidden alias so old
|
|
11
|
+
* scripts and muscle memory keep working (#264 backcompat).
|
|
12
|
+
*
|
|
13
|
+
* URL/token resolution mirrors learnings-identity.ts:44-49 (client mode →
|
|
14
|
+
* config.serverUrl; server mode → http://127.0.0.1:<port>; token from
|
|
15
|
+
* config.authToken) — explicitly NOT the hardcoded 127.0.0.1:8787 of
|
|
16
|
+
* status.ts. Fails soft with a clear message + non-zero exit on any server
|
|
17
|
+
* error, distinguishing a down server from an HTTP error (esp. 404 = server
|
|
18
|
+
* too old / wrong endpoint), like the OC plugin's describeGetFailure.
|
|
19
|
+
*
|
|
20
|
+
* The CLI targets the new `/identity` endpoint. A 0.18+ server (this package)
|
|
21
|
+
* also keeps `/context` as an alias, so a new CLI against a not-yet-upgraded
|
|
22
|
+
* server falls back through that alias via describeFailure's 404 hint.
|
|
23
|
+
*/
|
|
24
|
+
/** Thrown for any expected, user-facing failure. cli.ts prints .message + exits 1. */
|
|
25
|
+
export declare class IdentityCliError extends Error {
|
|
26
|
+
}
|
|
27
|
+
/** Backcompat alias (#264). */
|
|
28
|
+
export declare const ContextCliError: typeof IdentityCliError;
|
|
29
|
+
export interface IdentityServerTarget {
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
authToken?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the server URL + token from a parsed config object. Pure + exported
|
|
35
|
+
* so it is unit-testable without a live config. Follows learnings-identity.ts.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveIdentityTarget(config: Record<string, unknown>): IdentityServerTarget;
|
|
38
|
+
/** Backcompat alias (#264). */
|
|
39
|
+
export declare const resolveContextTarget: typeof resolveIdentityTarget;
|
|
40
|
+
/** Backcompat alias (#264). */
|
|
41
|
+
export type ContextServerTarget = IdentityServerTarget;
|
|
42
|
+
/** Read ~/.hicortex/config.json (or $HICORTEX_HOME/config.json). Missing → {}. */
|
|
43
|
+
export declare function loadConfig(): Record<string, unknown>;
|
|
44
|
+
/** The Save decision: PUT only when the edited content differs. Pure + tested. */
|
|
45
|
+
export declare function sectionChanged(before: string, after: string): boolean;
|
|
46
|
+
export interface IdentityGetResponse {
|
|
47
|
+
sections: Record<string, string>;
|
|
48
|
+
updated_at: string | null;
|
|
49
|
+
clients: string[];
|
|
50
|
+
/** Present only for an agent-scoped read (0.13). */
|
|
51
|
+
agent?: string;
|
|
52
|
+
mode?: string;
|
|
53
|
+
}
|
|
54
|
+
/** Backcompat alias (#264). */
|
|
55
|
+
export type ContextGetResponse = IdentityGetResponse;
|
|
56
|
+
/** GET /identity. Throws IdentityCliError with a clear message on any failure. */
|
|
57
|
+
export declare function getIdentity(target: IdentityServerTarget, agent?: string): Promise<IdentityGetResponse>;
|
|
58
|
+
/** Backcompat alias (#264). */
|
|
59
|
+
export declare const getContext: typeof getIdentity;
|
|
60
|
+
/** PUT one section. Throws IdentityCliError with a clear message on any failure. */
|
|
61
|
+
export declare function putSection(target: IdentityServerTarget, name: string, content: string, agent?: string): Promise<void>;
|
|
62
|
+
/** Readable rendering of every section + the resolved clients line (show, no name). */
|
|
63
|
+
export declare function formatAllSections(data: IdentityGetResponse): string;
|
|
64
|
+
/** Raw markdown for one section (show <name>), or null if it does not exist. */
|
|
65
|
+
export declare function formatOneSection(data: IdentityGetResponse, name: string): string | null;
|
|
66
|
+
/**
|
|
67
|
+
* Default editor spawn: opens `file` in the first available editor, blocking
|
|
68
|
+
* until it exits. Returns true if an editor ran, false if none was found.
|
|
69
|
+
* Injectable so tests never actually spawn an editor.
|
|
70
|
+
*/
|
|
71
|
+
export type EditorSpawn = (file: string) => boolean;
|
|
72
|
+
/**
|
|
73
|
+
* `edit <name>`: validate name (fast-fail; the server enforces too) → fetch
|
|
74
|
+
* current content → $EDITOR on a temp file → PUT only if changed. Temp file is
|
|
75
|
+
* always cleaned up. `spawn` is injectable for tests.
|
|
76
|
+
*/
|
|
77
|
+
export declare function runEdit(name: string, spawn?: EditorSpawn, agent?: string): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Split out a `--agent <id>` flag (anywhere in argv) from the positional args.
|
|
80
|
+
* The flag omitted → the global scope.
|
|
81
|
+
*/
|
|
82
|
+
export declare function extractAgentFlag(args: string[]): {
|
|
83
|
+
agent?: string;
|
|
84
|
+
rest: string[];
|
|
85
|
+
};
|
|
86
|
+
/** Dispatch for `hicortex identity <sub>` (and the hidden `context` alias).
|
|
87
|
+
* Throws IdentityCliError on bad usage/failure. */
|
|
88
|
+
export declare function runIdentityCommand(args: string[]): Promise<void>;
|
|
89
|
+
/** Backcompat alias (#264). */
|
|
90
|
+
export declare const runContextCommand: typeof runIdentityCommand;
|