@gamaze/hicortex 0.20.7 → 0.20.10
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 +18 -41
- package/assets/dashboard.html +3989 -836
- package/dist/calibration.d.ts +293 -0
- package/dist/calibration.js +379 -0
- package/dist/capture-health.d.ts +87 -0
- package/dist/capture-health.js +106 -0
- package/dist/capture-pause.d.ts +86 -0
- package/dist/capture-pause.js +127 -0
- package/dist/capture.d.ts +24 -3
- package/dist/capture.js +11 -1
- package/dist/classify-domains.d.ts +6 -0
- package/dist/classify-domains.js +7 -1
- package/dist/cli.js +38 -3
- package/dist/config-read.d.ts +1 -1
- package/dist/config-read.js +96 -9
- package/dist/consolidate.d.ts +114 -68
- package/dist/consolidate.js +302 -182
- package/dist/dashboard.d.ts +326 -6
- package/dist/dashboard.js +592 -7
- package/dist/db.js +105 -0
- package/dist/dedup.d.ts +34 -26
- package/dist/dedup.js +91 -57
- package/dist/distiller.js +1 -1
- package/dist/domain-classify.d.ts +7 -6
- package/dist/domain-classify.js +12 -10
- package/dist/eval/decay-eval.d.ts +3 -3
- package/dist/eval/decay-eval.js +4 -4
- package/dist/eval/importance-eval.d.ts +85 -0
- package/dist/eval/importance-eval.js +286 -0
- package/dist/eval/planted-eval.d.ts +26 -0
- package/dist/eval/planted-eval.js +97 -0
- package/dist/eval/planted-fixtures.d.ts +107 -0
- package/dist/eval/planted-fixtures.js +283 -0
- package/dist/eval/planted-harness.d.ts +176 -0
- package/dist/eval/planted-harness.js +649 -0
- package/dist/eval/ranking-battery.d.ts +78 -0
- package/dist/eval/ranking-battery.js +181 -0
- package/dist/eval/ranking-eval.d.ts +41 -0
- package/dist/eval/ranking-eval.js +391 -0
- package/dist/eval/ranking-fixtures.d.ts +77 -0
- package/dist/eval/ranking-fixtures.js +226 -0
- package/dist/identity-store.d.ts +21 -0
- package/dist/identity-store.js +49 -0
- package/dist/index.js +4 -3
- package/dist/init.d.ts +23 -3
- package/dist/init.js +84 -9
- package/dist/llm.d.ts +43 -58
- package/dist/llm.js +87 -101
- package/dist/mcp-server.d.ts +12 -0
- package/dist/mcp-server.js +213 -32
- package/dist/nightly.d.ts +9 -1
- package/dist/nightly.js +164 -110
- package/dist/nofit.d.ts +4 -11
- package/dist/nofit.js +6 -23
- package/dist/prompts.d.ts +10 -0
- package/dist/prompts.js +28 -5
- package/dist/recall-index.d.ts +30 -28
- package/dist/recall-index.js +21 -18
- package/dist/recall-registry.d.ts +2 -1
- package/dist/recall-registry.js +35 -1
- package/dist/reconsolidation.d.ts +168 -87
- package/dist/reconsolidation.js +818 -377
- package/dist/relink.js +3 -4
- package/dist/rescore-importance.d.ts +80 -0
- package/dist/rescore-importance.js +236 -0
- package/dist/retrieval.d.ts +80 -35
- package/dist/retrieval.js +322 -105
- package/dist/run-deadline.d.ts +62 -0
- package/dist/run-deadline.js +73 -0
- package/dist/schema-prototypes.d.ts +3 -3
- package/dist/schema-prototypes.js +3 -3
- package/dist/stages.d.ts +37 -0
- package/dist/stages.js +51 -0
- package/dist/state.d.ts +34 -9
- package/dist/storage.d.ts +50 -18
- package/dist/storage.js +125 -30
- package/dist/telemetry.d.ts +8 -7
- package/dist/token-budget.js +3 -4
- package/dist/type-classify.js +4 -4
- package/dist/types.d.ts +143 -155
- package/domains.example.json +4 -5
- package/hermes-plugin/hicortex/README.md +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -1
- package/pi-extension/hicortex/README.md +1 -1
- package/server.json +3 -3
package/dist/mcp-server.js
CHANGED
|
@@ -51,6 +51,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
51
51
|
exports.resolveDistillProbeGate = resolveDistillProbeGate;
|
|
52
52
|
exports.createMcpServer = createMcpServer;
|
|
53
53
|
exports.resolveBodyLimitMb = resolveBodyLimitMb;
|
|
54
|
+
exports.resolveSearchSimilarityFloor = resolveSearchSimilarityFloor;
|
|
54
55
|
exports.makeBodyLimitErrorHandler = makeBodyLimitErrorHandler;
|
|
55
56
|
exports.makeContentLengthGate = makeContentLengthGate;
|
|
56
57
|
exports.startServer = startServer;
|
|
@@ -76,6 +77,7 @@ const viz_js_1 = require("./viz.js");
|
|
|
76
77
|
const dashboard_js_1 = require("./dashboard.js");
|
|
77
78
|
const identity_store_js_1 = require("./identity-store.js");
|
|
78
79
|
const retrieval = __importStar(require("./retrieval.js"));
|
|
80
|
+
const CALIBRATION = __importStar(require("./calibration.js"));
|
|
79
81
|
const recall_registry_js_1 = require("./recall-registry.js");
|
|
80
82
|
const memory_instructions_js_1 = require("./memory-instructions.js");
|
|
81
83
|
const recall_index_js_1 = require("./recall-index.js");
|
|
@@ -87,6 +89,8 @@ const distiller_js_1 = require("./distiller.js");
|
|
|
87
89
|
const dedup_js_1 = require("./dedup.js");
|
|
88
90
|
const reconsolidation_js_1 = require("./reconsolidation.js");
|
|
89
91
|
const redact_js_1 = require("./redact.js");
|
|
92
|
+
const capture_health_js_1 = require("./capture-health.js");
|
|
93
|
+
const capture_pause_js_1 = require("./capture-pause.js");
|
|
90
94
|
const init_js_1 = require("./init.js");
|
|
91
95
|
// ---------------------------------------------------------------------------
|
|
92
96
|
// Server state
|
|
@@ -180,7 +184,7 @@ function createMcpServer() {
|
|
|
180
184
|
// -- hicortex_search --
|
|
181
185
|
server.tool("hicortex_search", "Search shared long-term memory (all agents, all sessions). CALL THIS BEFORE assuming, guessing, or asking the user about anything that may have come up before: prior decisions, preferences, project facts, people, hardware, past incidents. If you are about to write 'I don't have information about…', search first.", {
|
|
182
186
|
query: zod_1.z.string().describe("Search query text"),
|
|
183
|
-
limit: zod_1.z.coerce.number().optional().describe("Max results (default: server
|
|
187
|
+
limit: zod_1.z.coerce.number().optional().describe("Max results (default: the server default)"),
|
|
184
188
|
project: zod_1.z.string().optional().describe("Filter by project name"),
|
|
185
189
|
}, async ({ query, limit, project }) => {
|
|
186
190
|
if (!db)
|
|
@@ -214,7 +218,7 @@ function createMcpServer() {
|
|
|
214
218
|
// -- hicortex_recent --
|
|
215
219
|
server.tool("hicortex_recent", "Get recent memories, optionally filtered by project. CALL THIS AT THE START of substantive work on a project to catch up on its latest state — cheaper than asking the user what happened.", {
|
|
216
220
|
project: zod_1.z.string().optional().describe("Filter by project name"),
|
|
217
|
-
limit: zod_1.z.coerce.number().optional().describe("Max results (default: server
|
|
221
|
+
limit: zod_1.z.coerce.number().optional().describe("Max results (default: the server default)"),
|
|
218
222
|
}, async ({ project, limit }) => {
|
|
219
223
|
if (!db)
|
|
220
224
|
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
@@ -497,6 +501,25 @@ function resolveBodyLimitMb(configVal, hostedMode) {
|
|
|
497
501
|
return cfg;
|
|
498
502
|
return hostedMode ? 5 : 25;
|
|
499
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* Resolve the OPTIONAL /search relevance floor (`minSimilarity` query param,
|
|
506
|
+
* #409 console polish). Pure — exported for tests. Absent/blank/invalid →
|
|
507
|
+
* undefined = NO gate (byte-identical to every pre-existing caller: agents,
|
|
508
|
+
* plugins, MCP tools never send the param). A finite number in [0, 1] → that
|
|
509
|
+
* floor, clamped into range so a hostile `?minSimilarity=42` cannot widen or
|
|
510
|
+
* invert the gate. Applied AFTER retrieve() with the exported recall gate
|
|
511
|
+
* (passesRelevanceGate: FTS/`both` hits pass regardless — a token match is
|
|
512
|
+
* real evidence; vector-only hits must clear the floor) — the same post-hoc
|
|
513
|
+
* shape /recall-index uses, so the two recall surfaces gate identically.
|
|
514
|
+
*/
|
|
515
|
+
function resolveSearchSimilarityFloor(raw) {
|
|
516
|
+
if (typeof raw !== "string" || raw.trim() === "")
|
|
517
|
+
return undefined;
|
|
518
|
+
const v = Number(raw);
|
|
519
|
+
if (!Number.isFinite(v))
|
|
520
|
+
return undefined;
|
|
521
|
+
return Math.min(1, Math.max(0, v));
|
|
522
|
+
}
|
|
500
523
|
/**
|
|
501
524
|
* Express error middleware (#7): translate express.json's default HTML 413
|
|
502
525
|
* (entity.too.large) into a consistent JSON response. Catches body-parser
|
|
@@ -674,9 +697,10 @@ async function startServer(options = {}) {
|
|
|
674
697
|
});
|
|
675
698
|
}
|
|
676
699
|
if (llmConfig) {
|
|
677
|
-
// Tuning (#220: maxTokens + enableThinking
|
|
678
|
-
//
|
|
679
|
-
// the nightly
|
|
700
|
+
// Tuning (#220: maxTokens + enableThinking; #408: the diagnostic env tier
|
|
701
|
+
// for numCtx + ollama flush), validated + resolved via the shared overlay
|
|
702
|
+
// (also applied in resolveSavedLlmConfig for the nightly — the ONE
|
|
703
|
+
// resolution point per process). Wrong-typed values warn + drop.
|
|
680
704
|
(0, llm_js_1.applyTierTuningOverlay)(llmConfig, savedConfig);
|
|
681
705
|
llm = new llm_js_1.LlmClient(llmConfig);
|
|
682
706
|
console.log(`[hicortex] LLM (one model, all phases): ${llmConfig.provider}/${llmConfig.model}`);
|
|
@@ -749,34 +773,29 @@ async function startServer(options = {}) {
|
|
|
749
773
|
console.warn("[hicortex] Config uses the legacy 'contextAgents' key — renamed to 'identityAgents' in 0.18 (#264). " +
|
|
750
774
|
"The legacy key still works; update your config to silence this warning.");
|
|
751
775
|
}
|
|
752
|
-
// #
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
retrieval.
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const
|
|
759
|
-
|
|
776
|
+
// #408: recall/decay calibration is RELEASE-MANAGED (calibration.ts) — no
|
|
777
|
+
// config keys are read here anymore. The plain configure*() calls pin this
|
|
778
|
+
// process to the shipped constants (they remain the eval/test seam; see
|
|
779
|
+
// retrieval.ts). The boot log still prints the active values — now sourced
|
|
780
|
+
// from the calibration constants via the configure*() returns.
|
|
781
|
+
retrieval.configureDecay();
|
|
782
|
+
const recallCfg = retrieval.configureRecall();
|
|
783
|
+
const scoringCfg = retrieval.configureScoring();
|
|
784
|
+
const sessionIntentCfg = retrieval.configureSessionIntent();
|
|
785
|
+
console.log(`[hicortex] Recall (release-managed calibration): k=${recallCfg.searchLimit}/recent=${recallCfg.recentLimit}` +
|
|
760
786
|
`/window=${recallCfg.recentWindowDays}d/cold=${recallCfg.coldExposureSlots}` +
|
|
761
|
-
`/novelty=${
|
|
787
|
+
`/novelty=${CALIBRATION.NOVELTY_FLOOR_SLOTS}` +
|
|
762
788
|
` · ` +
|
|
763
789
|
`score sim=${scoringCfg.similarity}/str=${scoringCfg.strength}/conn=${scoringCfg.connections}` +
|
|
764
790
|
`/rec=${scoringCfg.recency}, fresh=${scoringCfg.freshnessBoostWeight}@${scoringCfg.freshnessBoostDays}d, ` +
|
|
765
791
|
`superseded×${scoringCfg.supersededDemotion}` +
|
|
766
792
|
`, intent w=${sessionIntentCfg.weight}` +
|
|
767
793
|
(sessionIntentCfg.weight === 0 ? " (disabled)" : ""));
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
maxItems: savedConfig?.recallMaxItems,
|
|
774
|
-
minPromptLength: savedConfig?.recallMinPromptChars,
|
|
775
|
-
titleChars: savedConfig?.recallTitleChars,
|
|
776
|
-
// #324 novelty floor: slots of recallMaxItems guaranteed to the
|
|
777
|
-
// pure-prompt (unblended) search's top passing hit(s). 0 disables.
|
|
778
|
-
noveltyFloorSlots: savedConfig?.noveltyFloorSlots,
|
|
779
|
-
};
|
|
794
|
+
// Defaults resolve inside recall-index.ts / recall-registry.ts from the
|
|
795
|
+
// calibration constants (#408) — the options objects stay as the seams the
|
|
796
|
+
// eval injects through.
|
|
797
|
+
recallRegistry = new recall_registry_js_1.SessionRecallRegistry();
|
|
798
|
+
recallIndexOptions = {};
|
|
780
799
|
memoryInstructionsEnabled = savedConfig?.memoryInstructions !== false;
|
|
781
800
|
if (resolvedAgents.dropped.length > 0) {
|
|
782
801
|
console.warn(`[hicortex] Ignoring invalid identityAgents entries: ${resolvedAgents.dropped.join(", ")} ` +
|
|
@@ -918,7 +937,7 @@ async function startServer(options = {}) {
|
|
|
918
937
|
res.status(503).json({ error: "Server not initialized" });
|
|
919
938
|
return;
|
|
920
939
|
}
|
|
921
|
-
const { content, source_agent, source_agent_id, source_domain, project, memory_type, privacy, source_session, session_date, corrects, supersedes } = req.body ?? {};
|
|
940
|
+
const { content, source_agent, source_agent_id, source_domain, source_machine, project, memory_type, privacy, source_session, session_date, corrects, supersedes } = req.body ?? {};
|
|
922
941
|
if (!content || typeof content !== "string") {
|
|
923
942
|
res.status(400).json({ error: "Missing or invalid 'content' field" });
|
|
924
943
|
return;
|
|
@@ -969,6 +988,7 @@ async function startServer(options = {}) {
|
|
|
969
988
|
// Attribution + provenance passthrough (0.16.x); null when absent.
|
|
970
989
|
sourceAgentId: typeof source_agent_id === "string" ? source_agent_id : null,
|
|
971
990
|
sourceDomain: typeof source_domain === "string" ? source_domain : null,
|
|
991
|
+
sourceMachine: storage.sanitizeSourceMachine(source_machine),
|
|
972
992
|
sourceSession: source_session ?? undefined,
|
|
973
993
|
project: project ?? undefined,
|
|
974
994
|
memoryType: normalizedType ?? "experience",
|
|
@@ -1002,15 +1022,24 @@ async function startServer(options = {}) {
|
|
|
1002
1022
|
res.status(400).json({ error: "Missing 'query'" });
|
|
1003
1023
|
return;
|
|
1004
1024
|
}
|
|
1005
|
-
// No hardcoded default: absent limit →
|
|
1025
|
+
// No hardcoded default: absent limit → the release-managed calibration
|
|
1026
|
+
// default (#408 — was the searchLimit config key).
|
|
1006
1027
|
const limit = req.query.limit ? Number(req.query.limit) : undefined;
|
|
1007
1028
|
const project = typeof req.query.project === "string" && req.query.project ? req.query.project : undefined;
|
|
1008
1029
|
// 0.16.x: `privacy` query param is ACCEPTED for backward compat (old
|
|
1009
1030
|
// clients/plugins still send it) but no longer read — retrieval ignores
|
|
1010
1031
|
// privacy entirely (the column is vestigial, never filtered).
|
|
1011
1032
|
warnDeprecatedPrivacyParamIfPresent(req.query, "search");
|
|
1033
|
+
// #409 console polish: OPTIONAL relevance floor. Absent (every legacy
|
|
1034
|
+
// caller) = ungated, byte-identical behavior. The console sends the
|
|
1035
|
+
// recall floor (RECALL_MIN_SIMILARITY, via the /dashboard/field echo) so
|
|
1036
|
+
// vector nearest-neighbor junk below it never renders as "results".
|
|
1037
|
+
const minSimilarity = resolveSearchSimilarityFloor(req.query.minSimilarity);
|
|
1012
1038
|
try {
|
|
1013
|
-
|
|
1039
|
+
let results = await retrieval.retrieve(db, embedder_js_1.embed, query, { limit, project });
|
|
1040
|
+
if (minSimilarity !== undefined) {
|
|
1041
|
+
results = results.filter((r) => (0, recall_index_js_1.passesRelevanceGate)(r, minSimilarity));
|
|
1042
|
+
}
|
|
1014
1043
|
res.json({ results });
|
|
1015
1044
|
}
|
|
1016
1045
|
catch (err) {
|
|
@@ -1074,7 +1103,8 @@ async function startServer(options = {}) {
|
|
|
1074
1103
|
return;
|
|
1075
1104
|
}
|
|
1076
1105
|
const project = typeof req.query.project === "string" && req.query.project ? req.query.project : undefined;
|
|
1077
|
-
// No hardcoded default: absent limit →
|
|
1106
|
+
// No hardcoded default: absent limit → the release-managed calibration
|
|
1107
|
+
// default (#408 — was the recentLimit config key).
|
|
1078
1108
|
// 0.16.x: `privacy` query param accepted but ignored (vestigial column).
|
|
1079
1109
|
const limit = req.query.limit ? Number(req.query.limit) : undefined;
|
|
1080
1110
|
warnDeprecatedPrivacyParamIfPresent(req.query, "recent");
|
|
@@ -1172,6 +1202,35 @@ async function startServer(options = {}) {
|
|
|
1172
1202
|
(0, health_js_1.logAndSendInternalError)(res, "context", err);
|
|
1173
1203
|
}
|
|
1174
1204
|
});
|
|
1205
|
+
// PUT /identity/mode — switch ONE agent's identity scope (#423 phase 3).
|
|
1206
|
+
// No /context/mode alias: this is a NEW endpoint with no legacy callers.
|
|
1207
|
+
//
|
|
1208
|
+
// Single-writer discipline: the daemon's boot-time identityAgents map is
|
|
1209
|
+
// normally read-once-at-boot — THIS route is the one live writer. The
|
|
1210
|
+
// adapter reads the FRESH config (not the boot snapshot) so two switches in
|
|
1211
|
+
// a row can never drop each other's writes, then on success (1) persists
|
|
1212
|
+
// the merged map as config `identityAgents` (survives restarts) and (2)
|
|
1213
|
+
// sets the module-level map to the SAME map — the switch is live on the
|
|
1214
|
+
// very next GET /identity?agent= (applies: "immediate"). Externally
|
|
1215
|
+
// hand-edited config still needs a restart (pre-existing posture). Note
|
|
1216
|
+
// the interplay: PUT /identity's black-hole guard 409s section writes while
|
|
1217
|
+
// config forces off/global — switching to 'override' here first unblocks
|
|
1218
|
+
// section editing. Bearer-only (standard auth middleware, no exemption).
|
|
1219
|
+
app.put("/identity/mode", (req, res) => {
|
|
1220
|
+
try {
|
|
1221
|
+
const fresh = readConfigFile(stateDir) ?? {};
|
|
1222
|
+
const r = (0, identity_store_js_1.handleIdentityModePut)(identityDir, req.body ?? null, req.query, (0, identity_store_js_1.resolveIdentityAgentsConfig)(fresh).agents);
|
|
1223
|
+
if (r.status === 200 && r.agents) {
|
|
1224
|
+
const merged = r.agents;
|
|
1225
|
+
(0, init_js_1.persistConfigUpdates)((0, node_path_1.join)(stateDir, "config.json"), { identityAgents: merged });
|
|
1226
|
+
identityAgents = merged;
|
|
1227
|
+
}
|
|
1228
|
+
res.status(r.status).json(r.body);
|
|
1229
|
+
}
|
|
1230
|
+
catch (err) {
|
|
1231
|
+
(0, health_js_1.logAndSendInternalError)(res, "identity/mode", err);
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1175
1234
|
// REST /distill — canonical capture endpoint (0.9.0+).
|
|
1176
1235
|
// Every machine (including the server itself) POSTs denoised session text here.
|
|
1177
1236
|
// The server distills, embeds, stores. Body limit: see `distillBodyLimitMb`
|
|
@@ -1185,11 +1244,42 @@ async function startServer(options = {}) {
|
|
|
1185
1244
|
res.status(503).json({ error: "Server not initialized" });
|
|
1186
1245
|
return;
|
|
1187
1246
|
}
|
|
1247
|
+
// #422: destructured ABOVE the no-LLM guard (it used to sit below) so the
|
|
1248
|
+
// attribution fields exist at EVERY exit incl. the 503 — capture-health
|
|
1249
|
+
// accounting records held posts there too. Nothing else reads them before
|
|
1250
|
+
// the guard, so semantics are unchanged.
|
|
1251
|
+
const { text, messages, source_agent, source_agent_id, source_domain, source_machine, project, session_id, segment_id, session_date, privacy } = req.body ?? {};
|
|
1252
|
+
// #422 capture health: normalize the wire fields ONCE so every exit below
|
|
1253
|
+
// records with a one-liner (recordDistillActivity re-sanitizes machine/
|
|
1254
|
+
// agent — the builder only forwards + picks the byte count). bytes = the
|
|
1255
|
+
// resolved (post-redaction) conversationText length, 0 while unresolved.
|
|
1256
|
+
const capEntry = (bytes, outcome) => ({
|
|
1257
|
+
machine: source_machine,
|
|
1258
|
+
agent: source_agent,
|
|
1259
|
+
sessionId: session_id,
|
|
1260
|
+
segmentId: segment_id,
|
|
1261
|
+
bytes,
|
|
1262
|
+
outcome,
|
|
1263
|
+
});
|
|
1264
|
+
// #423 phase 3 (D3): operator capture pause — server-side 200-skip. The
|
|
1265
|
+
// pause table is read per POST, so a pause takes effect on the very next
|
|
1266
|
+
// /distill (no restart). Deliberately ABOVE the no-LLM guard: a paused
|
|
1267
|
+
// bundle must skip regardless of LLM state — a 503 there would hold the
|
|
1268
|
+
// client's cursor on a box that is deliberately not capturing. The 200 is
|
|
1269
|
+
// the point: capture.ts treats every 200 as confirmed and advances its
|
|
1270
|
+
// cursor, so paused sessions are deliberately NOT captured and never
|
|
1271
|
+
// re-sent/backfilled. Zero client changes.
|
|
1272
|
+
const pause = (0, capture_pause_js_1.capturePauseKey)(source_machine, source_agent);
|
|
1273
|
+
if ((0, capture_pause_js_1.isCapturePaused)(db, pause.machine, pause.harness)) {
|
|
1274
|
+
(0, capture_health_js_1.recordDistillActivity)(db, capEntry(0, "paused"));
|
|
1275
|
+
res.status(200).json({ skipped: true, paused: true, machine: pause.machine, harness: pause.harness });
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1188
1278
|
if (!llm || !llmConfig) {
|
|
1279
|
+
(0, capture_health_js_1.recordDistillActivity)(db, capEntry(0, "held"));
|
|
1189
1280
|
res.status(503).json({ error: "No LLM configured — run npx @gamaze/hicortex init. Session will be retried." });
|
|
1190
1281
|
return;
|
|
1191
1282
|
}
|
|
1192
|
-
const { text, messages, source_agent, source_agent_id, source_domain, project, session_id, segment_id, session_date, privacy } = req.body ?? {};
|
|
1193
1283
|
// Resolve the conversation text from either the pre-denoised string or raw messages array.
|
|
1194
1284
|
// `fromTextBranch` is captured once so the redaction gate below uses the SAME
|
|
1195
1285
|
// discriminator as the resolution (avoids re-redacting the messages-derived
|
|
@@ -1239,6 +1329,7 @@ async function startServer(options = {}) {
|
|
|
1239
1329
|
if (session_id && segment_id) {
|
|
1240
1330
|
const existingCount = (0, dedup_js_1.countExistingSegment)(db, session_id, segment_id);
|
|
1241
1331
|
if (existingCount > 0) {
|
|
1332
|
+
(0, capture_health_js_1.recordDistillActivity)(db, capEntry(conversationText.length, "skipped"));
|
|
1242
1333
|
res.status(200).json({ skipped: true, existing_count: existingCount });
|
|
1243
1334
|
return;
|
|
1244
1335
|
}
|
|
@@ -1250,6 +1341,7 @@ async function startServer(options = {}) {
|
|
|
1250
1341
|
if (session_id && !segment_id) {
|
|
1251
1342
|
const existingCount = (0, dedup_js_1.countExistingSession)(db, session_id);
|
|
1252
1343
|
if (existingCount > 0) {
|
|
1344
|
+
(0, capture_health_js_1.recordDistillActivity)(db, capEntry(conversationText.length, "skipped"));
|
|
1253
1345
|
res.status(200).json({ skipped: true, existing_count: existingCount });
|
|
1254
1346
|
return;
|
|
1255
1347
|
}
|
|
@@ -1261,6 +1353,7 @@ async function startServer(options = {}) {
|
|
|
1261
1353
|
// diagnosis — the capture client treats non-201/200 as transient and holds
|
|
1262
1354
|
// its cursor (capture.ts), so the segment is retried next run, never lost.
|
|
1263
1355
|
if (!(await resolveDistillProbeGate(llm, llmConfig))) {
|
|
1356
|
+
(0, capture_health_js_1.recordDistillActivity)(db, capEntry(conversationText.length, "held"));
|
|
1264
1357
|
res.status(503).json({ error: "LLM endpoint not generating — session will be retried" });
|
|
1265
1358
|
return;
|
|
1266
1359
|
}
|
|
@@ -1286,6 +1379,7 @@ async function startServer(options = {}) {
|
|
|
1286
1379
|
// a skipped duplicate neither trips the gate nor consumes budget. The client
|
|
1287
1380
|
// capture loop holds its cursor on 429 (dup-over-loss, capture.ts:303).
|
|
1288
1381
|
if ((0, token_budget_js_1.isTokenBudgetExceeded)(stateDir)) {
|
|
1382
|
+
(0, capture_health_js_1.recordDistillActivity)(db, capEntry(conversationText.length, "held"));
|
|
1289
1383
|
res.status(429).json({ error: "token budget exceeded", retry: "next billing period" });
|
|
1290
1384
|
return;
|
|
1291
1385
|
}
|
|
@@ -1334,6 +1428,7 @@ async function startServer(options = {}) {
|
|
|
1334
1428
|
// filtered. Default null for older clients that don't send them.
|
|
1335
1429
|
sourceAgentId: typeof source_agent_id === "string" ? source_agent_id : null,
|
|
1336
1430
|
sourceDomain: typeof source_domain === "string" ? source_domain : null,
|
|
1431
|
+
sourceMachine: storage.sanitizeSourceMachine(source_machine),
|
|
1337
1432
|
// Per-chunk key: "<session_id>[#<segment_id>]#<i>". The prefix
|
|
1338
1433
|
// matches the dedup checks above, so a re-run is idempotent.
|
|
1339
1434
|
sourceSession: sourcePrefix ? `${sourcePrefix}#${i}` : undefined,
|
|
@@ -1352,6 +1447,7 @@ async function startServer(options = {}) {
|
|
|
1352
1447
|
return out;
|
|
1353
1448
|
});
|
|
1354
1449
|
const ids = insertAll();
|
|
1450
|
+
(0, capture_health_js_1.recordDistillActivity)(db, capEntry(conversationText.length, "ok"));
|
|
1355
1451
|
res.status(201).json({
|
|
1356
1452
|
ids,
|
|
1357
1453
|
distilled: ids.length,
|
|
@@ -1365,6 +1461,7 @@ async function startServer(options = {}) {
|
|
|
1365
1461
|
});
|
|
1366
1462
|
}
|
|
1367
1463
|
catch (err) {
|
|
1464
|
+
(0, capture_health_js_1.recordDistillActivity)(db, capEntry(conversationText.length, "held"));
|
|
1368
1465
|
res.status(500).json({ error: "Distillation failed" });
|
|
1369
1466
|
console.error(`[hicortex] /distill: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
|
1370
1467
|
}
|
|
@@ -1447,6 +1544,52 @@ async function startServer(options = {}) {
|
|
|
1447
1544
|
}
|
|
1448
1545
|
});
|
|
1449
1546
|
// -------------------------------------------------------------------------
|
|
1547
|
+
// REST /enrich — owner corroboration (#423 phase 3).
|
|
1548
|
+
//
|
|
1549
|
+
// An enrich is EVIDENCE ABOUT IMPORTANCE: it bumps corroboration_count and
|
|
1550
|
+
// base_strength (+the calibration delta, capped at 1.0) — the same anchor
|
|
1551
|
+
// the nightly's LLM scoring and hub-boost write. It must NOT touch
|
|
1552
|
+
// access_count (reserved for real recall use) or shown_count (index
|
|
1553
|
+
// exposure) — faking either corrupts the uses-per-showing adoption metric.
|
|
1554
|
+
// Never a stage write: stages are derived presentation (the E-reframe).
|
|
1555
|
+
// An absorbed memory is invisible evidence and cannot be corroborated
|
|
1556
|
+
// (409, same posture as /update).
|
|
1557
|
+
// -------------------------------------------------------------------------
|
|
1558
|
+
app.post("/enrich", (req, res) => {
|
|
1559
|
+
if (!db) {
|
|
1560
|
+
res.status(503).json({ error: "Server not initialized" });
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
const { id } = req.body ?? {};
|
|
1564
|
+
if (!id || typeof id !== "string") {
|
|
1565
|
+
res.status(400).json({ error: "Missing or invalid 'id' field" });
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
const fullId = resolveMemoryId(db, id);
|
|
1569
|
+
if (!fullId) {
|
|
1570
|
+
res.status(404).json({ error: "Memory not found" });
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
// Same absorbed guard as /update: corroborating an invisible row would
|
|
1574
|
+
// strengthen evidence the store deliberately folded into another memory.
|
|
1575
|
+
const target = storage.getMemory(db, fullId);
|
|
1576
|
+
if (target?.status === "absorbed") {
|
|
1577
|
+
res.status(409).json({ error: "Memory is absorbed — invisible evidence cannot be corroborated" });
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
try {
|
|
1581
|
+
const r = storage.enrichMemory(db, fullId, new Date().toISOString());
|
|
1582
|
+
if (!r) {
|
|
1583
|
+
res.status(404).json({ error: "Memory not found" });
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
res.status(200).json({ id: fullId, corroboration_count: r.corroborationCount, base_strength: r.baseStrength });
|
|
1587
|
+
}
|
|
1588
|
+
catch (err) {
|
|
1589
|
+
(0, health_js_1.logAndSendInternalError)(res, "enrich", err);
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
// -------------------------------------------------------------------------
|
|
1450
1593
|
// REST /delete — permanently delete a memory and its links.
|
|
1451
1594
|
//
|
|
1452
1595
|
// NOTE for #124: returns {deleted: true, id} — clean JSON for future /viz.
|
|
@@ -1663,6 +1806,43 @@ async function startServer(options = {}) {
|
|
|
1663
1806
|
// express adapter that injects the live db + config. STRICTLY view-only —
|
|
1664
1807
|
// no mutation endpoints on the dashboard surface.
|
|
1665
1808
|
app.get("/dashboard/data", (0, dashboard_js_1.dashboardDataHandler)(() => db, () => readConfigFile(stateDir)));
|
|
1809
|
+
// GET /dashboard/field — the console flight-field payload (#409/#421
|
|
1810
|
+
// Phase 1): the whole live store as minimal fields (titles ≤100 via the
|
|
1811
|
+
// production memoryTitle, derived stage + effective strength, no content
|
|
1812
|
+
// bodies) plus every link edge {a, b, rel}. Bearer-only (auth middleware,
|
|
1813
|
+
// no shell exemption — it carries data); localhost bypass applies. Handler
|
|
1814
|
+
// + gzip adapter live in src/dashboard.ts next to its /data sibling; the
|
|
1815
|
+
// field is one row per memory, so Accept-Encoding: gzip clients get the
|
|
1816
|
+
// compressed wire form.
|
|
1817
|
+
app.get("/dashboard/field", (0, dashboard_js_1.dashboardFieldHandler)(() => db));
|
|
1818
|
+
// GET /dashboard/events?days=N — the console replay ledger (#409/#421
|
|
1819
|
+
// Phase 1): night-resolution synthesis over existing tables (created_at /
|
|
1820
|
+
// memory_history / dedup_log / memory_links; no schema change, no
|
|
1821
|
+
// event-sourcing store). ids only. Bearer-only like /dashboard/field;
|
|
1822
|
+
// localhost bypass applies.
|
|
1823
|
+
app.get("/dashboard/events", (0, dashboard_js_1.dashboardEventsHandler)(() => db));
|
|
1824
|
+
// GET/PUT /dashboard/model — the console's model-settings surface (#422
|
|
1825
|
+
// Phase 2): the ONE scoped writer on the dashboard (view-only everywhere
|
|
1826
|
+
// else). GET echoes the config's model knobs raw (null = unset) + the
|
|
1827
|
+
// boot-resolved provider from the daemon's in-memory llmConfig; api_key_set
|
|
1828
|
+
// carries ONLY the key's presence — no key material on the wire, ever. PUT
|
|
1829
|
+
// validates an allowlisted subset (null clears a config key) and persists
|
|
1830
|
+
// via init.ts persistConfigUpdates — strict load, so a malformed config.json
|
|
1831
|
+
// throws → 500 with the file untouched. Bearer-only (auth middleware, no
|
|
1832
|
+
// shell exemption — it carries install config); localhost bypass applies.
|
|
1833
|
+
// Applies on restart: the daemon resolves config at boot (llmConfig is the
|
|
1834
|
+
// boot snapshot; the card footnotes this).
|
|
1835
|
+
app.get("/dashboard/model", (0, dashboard_js_1.dashboardModelGetHandler)(() => readConfigFile(stateDir), () => llmConfig));
|
|
1836
|
+
app.put("/dashboard/model", (0, dashboard_js_1.dashboardModelPutHandler)((updates) => (0, init_js_1.persistConfigUpdates)((0, node_path_1.join)(stateDir, "config.json"), updates), () => llmConfig));
|
|
1837
|
+
// PUT /dashboard/capture-pause — the console's pause/resume toggle (#423
|
|
1838
|
+
// phase 3, D3). Body {machine?, harness, paused}: a pause makes /distill
|
|
1839
|
+
// 200-skip the bundle's posts — deliberate NON-capture, the sessions are
|
|
1840
|
+
// not backfilled (the client cursor advances on the 200, by design). The
|
|
1841
|
+
// effect is IMMEDIATE — no restart — because the /distill handler reads the
|
|
1842
|
+
// capture_pauses table on every post. Bearer-only like /dashboard/model
|
|
1843
|
+
// (auth middleware, no shell exemption — it mutates operator state);
|
|
1844
|
+
// localhost bypass applies. Handler + adapter live in src/dashboard.ts.
|
|
1845
|
+
app.put("/dashboard/capture-pause", (0, dashboard_js_1.dashboardCapturePausePutHandler)(() => db));
|
|
1666
1846
|
// GET /account — account identity for the console nav (name/org/plan from
|
|
1667
1847
|
// config). The LIGHTWEIGHT twin of the account block inside /dashboard/data:
|
|
1668
1848
|
// the /viz and /identity/ui pages need only this, not the metric payload;
|
|
@@ -1789,7 +1969,8 @@ function readConfigFile(stateDir) {
|
|
|
1789
1969
|
}
|
|
1790
1970
|
catch (e) {
|
|
1791
1971
|
console.warn(`[hicortex] ${configPath} exists but could not be parsed — server booting degraded ` +
|
|
1792
|
-
`(config-driven LLM
|
|
1972
|
+
`(config-driven LLM knobs and agentId self-heal will not apply; decay/recall ` +
|
|
1973
|
+
`calibration is release-managed and unaffected). ` +
|
|
1793
1974
|
`Fix the JSON and restart. Cause: ${e instanceof Error ? e.message : String(e)}`);
|
|
1794
1975
|
return null;
|
|
1795
1976
|
}
|
package/dist/nightly.d.ts
CHANGED
|
@@ -20,6 +20,14 @@
|
|
|
20
20
|
* (#189 review, fix 3). Per-session cursors keep the wide re-scan cheap: an
|
|
21
21
|
* already-captured session yields an empty delta.
|
|
22
22
|
*
|
|
23
|
+
* First run (#436): with NO watermark yet, since = now − firstRunLookbackDays
|
|
24
|
+
* (default 7 — owner ruling 2026-09-14: full-history first-run ingestion is
|
|
25
|
+
* not feasible; a long-term AI user's entire session store must not be
|
|
26
|
+
* discovered night one). The widening-only invariant composes unchanged:
|
|
27
|
+
* min(now−7d, now−N) — a recapture window wider than the default widens, a
|
|
28
|
+
* narrower one leaves the floor. Installing more history is the deliberate
|
|
29
|
+
* `--recapture-window` act, not a default.
|
|
30
|
+
*
|
|
23
31
|
* Clock-jump clamp (#327): a FUTURE-dated lastNightly (client clock error —
|
|
24
32
|
* NTP not yet synced at write time) would, once the clock corrects, sit ahead
|
|
25
33
|
* of every session mtime and permanently skip quiet sessions (their mtimes
|
|
@@ -27,7 +35,7 @@
|
|
|
27
35
|
* fires once per affected run (this function runs once per nightly).
|
|
28
36
|
* `now` is injectable for tests.
|
|
29
37
|
*/
|
|
30
|
-
export declare function computeSince(stateDir: string, recaptureWindowDays?: number, now?: Date): Date;
|
|
38
|
+
export declare function computeSince(stateDir: string, recaptureWindowDays?: number, now?: Date, firstRunLookbackDays?: number): Date;
|
|
31
39
|
/**
|
|
32
40
|
* Parse a `Retry-After` header into ms (#327). Handles both RFC forms —
|
|
33
41
|
* delay-seconds (`"30"`) and HTTP-date — and returns undefined for anything
|