@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
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Planted ranking fixtures for the search-trust calibration gate (#425).
|
|
4
|
+
*
|
|
5
|
+
* Two fixture cases, both shaped like the field failure that opened the
|
|
6
|
+
* issue (the owner's live console test, 2026-09-13): a user searches a
|
|
7
|
+
* DISTINCTIVE PROPER NOUN they know exists; the one memory carrying that
|
|
8
|
+
* token has the best similarity of all candidates AND the literal FTS hit,
|
|
9
|
+
* yet ranks behind hardened but less-relevant memories that win on
|
|
10
|
+
* effective strength + graph connections.
|
|
11
|
+
*
|
|
12
|
+
* (a) sirnas_exact_match — the AC1 fixture: a low-strength, unlinked,
|
|
13
|
+
* never-accessed memory carrying an invented proper noun against
|
|
14
|
+
* base-≥0.9 rivals with links among themselves, access history, and
|
|
15
|
+
* older creation dates. The rivals share the query's DOMAIN
|
|
16
|
+
* vocabulary (harbours, docks, boats) but never the proper noun.
|
|
17
|
+
* Declared expectation: the proper-noun row ranks #1.
|
|
18
|
+
* (b) no_match_control — the AC2 fixture: a "mamma"-class query with no
|
|
19
|
+
* relevant match and NO FTS hits anywhere in the corpus. Whatever
|
|
20
|
+
* nearest-neighbor junk vector search returns must be byte-stable
|
|
21
|
+
* under the ranking work (no both-channel candidates exist, so the
|
|
22
|
+
* boost can never fire — the case pins that the no-regression
|
|
23
|
+
* guarantee is structural, not lucky).
|
|
24
|
+
*
|
|
25
|
+
* Honesty rules (the planted-pairs #393 A discipline):
|
|
26
|
+
* - The texts are synthetic and generic (this package publishes to npm —
|
|
27
|
+
* no real infrastructure, people, or fleet detail), but SHAPED like the
|
|
28
|
+
* field failure: the proper noun is invented (it must appear nowhere
|
|
29
|
+
* else — verified by an invariant test), the rivals are hardened with
|
|
30
|
+
* metadata the production pipeline itself writes (base strength, access
|
|
31
|
+
* hardening, link counts).
|
|
32
|
+
* - Similarities are MEASURED at run time with the real bge-small-en-v1.5
|
|
33
|
+
* embedder (ranking-eval.ts) — never asserted into existence. The
|
|
34
|
+
* declared expectation is about ORDER under the shipped ranking, and
|
|
35
|
+
* the report prints the measured similarity/score of every top row.
|
|
36
|
+
* - Strength metadata is written through the production helpers
|
|
37
|
+
* (insertMemory + updateMemory + addLink), not hand-rolled SQL, so the
|
|
38
|
+
* fixture exercises the same rows retrieval() will see.
|
|
39
|
+
*/
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.REAL_SIRNAS_MEMORY_ID = exports.NO_MATCH_FIXTURE = exports.SIRNAS_FIXTURE = exports.RANKING_SOURCE_AGENT = exports.RANKING_PROJECT = void 0;
|
|
42
|
+
exports.checkFixtureInvariants = checkFixtureInvariants;
|
|
43
|
+
/** All planted rows share project + source_agent (metadata never refuses). */
|
|
44
|
+
exports.RANKING_PROJECT = "ranking-eval";
|
|
45
|
+
exports.RANKING_SOURCE_AGENT = "ranking-eval";
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Case 1 — sirnas_exact_match (AC1)
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
//
|
|
50
|
+
// The target mirrors the field case's SHAPE with invented names: a home
|
|
51
|
+
// harbour memory carrying one distinctive proper noun, born weak (base 0.35,
|
|
52
|
+
// the "routine-but-curated" band), never accessed, unlinked, a few months
|
|
53
|
+
// old. The rivals are what the decay model produces for early-hardened
|
|
54
|
+
// technical rows: base 0.90-0.95, mutual links, access history, created
|
|
55
|
+
// BEFORE the target. They share the harbour/boating domain vocabulary — so
|
|
56
|
+
// a bare proper-noun query lands in their neighborhood — but none carries
|
|
57
|
+
// the proper noun.
|
|
58
|
+
const SIRNAS_ROWS = [
|
|
59
|
+
// --- rivals: hardened, connected, older, domain-adjacent ---
|
|
60
|
+
{
|
|
61
|
+
key: "rival_fees",
|
|
62
|
+
content: "Harbour fees and guest dock rules: pay at the machine by the fuel quay, electricity on the guest dock is metered, " +
|
|
63
|
+
"and visiting boats must clear their berth by ten in the morning during the summer season.",
|
|
64
|
+
createdAt: "2026-03-02T09:00:00.000Z",
|
|
65
|
+
memoryType: "knowledge",
|
|
66
|
+
baseStrength: 0.9,
|
|
67
|
+
accessCount: 4,
|
|
68
|
+
lastAccessedDaysAgo: 6,
|
|
69
|
+
linksTo: ["rival_channel", "rival_maintenance"],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: "rival_channel",
|
|
73
|
+
content: "Sailing notes for the home bay: the channel is marked with cardinal buoys, the shallow spit near the outer dock " +
|
|
74
|
+
"dries at low water, and the ferry has right of way inside the harbour basin.",
|
|
75
|
+
createdAt: "2026-03-16T09:00:00.000Z",
|
|
76
|
+
memoryType: "knowledge",
|
|
77
|
+
baseStrength: 0.95,
|
|
78
|
+
accessCount: 6,
|
|
79
|
+
lastAccessedDaysAgo: 2,
|
|
80
|
+
linksTo: ["rival_fees", "rival_maintenance"],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: "rival_maintenance",
|
|
84
|
+
content: "Boat maintenance log: scrub the hull and check the anodes before launch, service the winch every spring, and " +
|
|
85
|
+
"book the boatyard crane early — the lifting slot list fills up in May.",
|
|
86
|
+
createdAt: "2026-03-30T09:00:00.000Z",
|
|
87
|
+
memoryType: "experience",
|
|
88
|
+
baseStrength: 0.9,
|
|
89
|
+
accessCount: 3,
|
|
90
|
+
lastAccessedDaysAgo: 11,
|
|
91
|
+
linksTo: ["rival_fees"],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "rival_passage",
|
|
95
|
+
content: "Passage plan habits: file the float plan before leaving the dock, plot the tidal gate times, and always take the " +
|
|
96
|
+
"safe water mark side of the reef when the light is obscured.",
|
|
97
|
+
createdAt: "2026-04-13T09:00:00.000Z",
|
|
98
|
+
memoryType: "knowledge",
|
|
99
|
+
baseStrength: 0.9,
|
|
100
|
+
accessCount: 2,
|
|
101
|
+
lastAccessedDaysAgo: 20,
|
|
102
|
+
linksTo: ["rival_channel"],
|
|
103
|
+
},
|
|
104
|
+
// --- the target: the exact proper-noun match, born humble ---
|
|
105
|
+
{
|
|
106
|
+
key: "target_harbour",
|
|
107
|
+
content: "Home harbour: Vindarsvik (near Stensundet). The guest berth is east of the ferry pier; hold slack when the " +
|
|
108
|
+
"fishing boats unload.",
|
|
109
|
+
createdAt: "2026-05-25T09:00:00.000Z",
|
|
110
|
+
memoryType: "knowledge",
|
|
111
|
+
baseStrength: 0.35,
|
|
112
|
+
accessCount: 0,
|
|
113
|
+
lastAccessedDaysAgo: 118,
|
|
114
|
+
linksTo: [],
|
|
115
|
+
},
|
|
116
|
+
// --- neutral filler: keeps neighborhoods non-trivial, off-domain ---
|
|
117
|
+
{
|
|
118
|
+
key: "fill_recipe",
|
|
119
|
+
content: "Batch-cooked a tomato and butter sauce from the Rome cookbook: San Marzano tomatoes, one whole onion halved, and far more butter than seems reasonable.",
|
|
120
|
+
createdAt: "2026-04-01T09:00:00.000Z",
|
|
121
|
+
memoryType: "experience",
|
|
122
|
+
baseStrength: 0.5,
|
|
123
|
+
accessCount: 0,
|
|
124
|
+
lastAccessedDaysAgo: 160,
|
|
125
|
+
linksTo: [],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
key: "fill_books",
|
|
129
|
+
content: "Finished the third novel in the frontier trilogy. The middle book dragged through the mining-town chapters, but the finale pays off when the surveyor reads her grandmother's letters.",
|
|
130
|
+
createdAt: "2026-04-20T09:00:00.000Z",
|
|
131
|
+
memoryType: "experience",
|
|
132
|
+
baseStrength: 0.5,
|
|
133
|
+
accessCount: 0,
|
|
134
|
+
lastAccessedDaysAgo: 140,
|
|
135
|
+
linksTo: [],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
key: "fill_lang",
|
|
139
|
+
content: "Language study notes: the dative prepositions finally clicked after drilling them as a sung list; irregular verbs still need spaced repetition.",
|
|
140
|
+
createdAt: "2026-05-10T09:00:00.000Z",
|
|
141
|
+
memoryType: "experience",
|
|
142
|
+
baseStrength: 0.5,
|
|
143
|
+
accessCount: 0,
|
|
144
|
+
lastAccessedDaysAgo: 120,
|
|
145
|
+
linksTo: [],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
key: "fill_garden",
|
|
149
|
+
content: "The raised beds need refreshing before spring: compost the spent tomato vines, rotate the legume row, and prune the apple espalier before the buds swell.",
|
|
150
|
+
createdAt: "2026-06-02T09:00:00.000Z",
|
|
151
|
+
memoryType: "experience",
|
|
152
|
+
baseStrength: 0.5,
|
|
153
|
+
accessCount: 0,
|
|
154
|
+
lastAccessedDaysAgo: 100,
|
|
155
|
+
linksTo: [],
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
exports.SIRNAS_FIXTURE = {
|
|
159
|
+
key: "sirnas_exact_match",
|
|
160
|
+
description: "AC1: a distinctive proper-noun query must rank the exact-match (both-channel) memory first, " +
|
|
161
|
+
"ahead of hardened domain-adjacent rivals that win on strength + connections.",
|
|
162
|
+
rows: SIRNAS_ROWS,
|
|
163
|
+
query: "Vindarsvik",
|
|
164
|
+
distinctiveToken: "vindarsvik",
|
|
165
|
+
expectedTop1: "target_harbour",
|
|
166
|
+
};
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Case 2 — no_match_control (AC2)
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
//
|
|
171
|
+
// A "mamma"-class query: a common household word in the user's language that
|
|
172
|
+
// appears in NO corpus row — no FTS token, no semantic neighbor. Vector
|
|
173
|
+
// search still returns nearest-neighbor junk (the field's 0.53-similarity
|
|
174
|
+
// technical rows); the case pins the FULL returned list so any drift in
|
|
175
|
+
// how unrelated junk is ordered is caught, and asserts structurally that
|
|
176
|
+
// the ranking work cannot touch it (no both-channel candidates exist).
|
|
177
|
+
exports.NO_MATCH_FIXTURE = {
|
|
178
|
+
key: "no_match_control",
|
|
179
|
+
description: "AC2: a no-match query (no FTS hits anywhere) returns a byte-stable list — no both-channel " +
|
|
180
|
+
"candidates exist, so the boost is structurally inert.",
|
|
181
|
+
// Same corpus as case 1 (minus nothing): the query matches none of it.
|
|
182
|
+
rows: SIRNAS_ROWS,
|
|
183
|
+
query: "mamma",
|
|
184
|
+
distinctiveToken: "",
|
|
185
|
+
// No declared top-1 — the assertion is stability, not order (the photo
|
|
186
|
+
// records the list; the gate compares runs).
|
|
187
|
+
expectedTop1: "",
|
|
188
|
+
};
|
|
189
|
+
/** The battery runs against a real snapshot copy (ranking-eval.ts case 3). */
|
|
190
|
+
exports.REAL_SIRNAS_MEMORY_ID = "12980c9d-0eba-4963-95eb-0548bc2aa93e";
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Fixture invariants (checked by ranking-eval at run time AND pinned in
|
|
193
|
+
// tests/ranking-eval-tooling.test.ts — a corpus that silently drifts from
|
|
194
|
+
// its declared shape would fake a pass/fail)
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
function checkFixtureInvariants(f) {
|
|
197
|
+
const problems = [];
|
|
198
|
+
const keys = new Set(f.rows.map((r) => r.key));
|
|
199
|
+
if (keys.size !== f.rows.length)
|
|
200
|
+
problems.push("duplicate row keys");
|
|
201
|
+
for (const r of f.rows) {
|
|
202
|
+
for (const to of r.linksTo) {
|
|
203
|
+
if (!keys.has(to))
|
|
204
|
+
problems.push(`${r.key} links to unknown key ${to}`);
|
|
205
|
+
if (to === r.key)
|
|
206
|
+
problems.push(`${r.key} links to itself`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (f.expectedTop1 && !keys.has(f.expectedTop1)) {
|
|
210
|
+
problems.push(`expectedTop1 ${f.expectedTop1} is not a row key`);
|
|
211
|
+
}
|
|
212
|
+
if (f.distinctiveToken) {
|
|
213
|
+
const token = f.distinctiveToken.toLowerCase();
|
|
214
|
+
const carriers = f.rows.filter((r) => r.content.toLowerCase().includes(token));
|
|
215
|
+
if (carriers.length !== 1) {
|
|
216
|
+
problems.push(`distinctive token "${f.distinctiveToken}" carried by ${carriers.length} rows (expected exactly 1)`);
|
|
217
|
+
}
|
|
218
|
+
else if (f.expectedTop1 && carriers[0].key !== f.expectedTop1) {
|
|
219
|
+
problems.push(`distinctive token carried by ${carriers[0].key}, not expectedTop1 ${f.expectedTop1}`);
|
|
220
|
+
}
|
|
221
|
+
if (!f.query.toLowerCase().includes(token)) {
|
|
222
|
+
problems.push(`query does not contain the distinctive token`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return problems;
|
|
226
|
+
}
|
package/dist/identity-store.d.ts
CHANGED
|
@@ -351,3 +351,24 @@ export declare function serveIdentityBody(handlerResult: HandlerResult, memoryEn
|
|
|
351
351
|
* symlink safety). Throws are left to the adapter to turn into a 500.
|
|
352
352
|
*/
|
|
353
353
|
export declare function handleIdentityPut(identityDir: string, body: unknown, query?: Record<string, unknown>, identityAgents?: Record<string, AgentMode>): HandlerResult;
|
|
354
|
+
/**
|
|
355
|
+
* PUT /identity/mode (#423 phase 3): switch ONE agent's scope to
|
|
356
|
+
* override/global/off. Pure validation + merge — the ADAPTER (mcp-server.ts)
|
|
357
|
+
* does three things with the result: (1) persists `agents` as the config's
|
|
358
|
+
* `identityAgents` (survives restarts), (2) sets the daemon's live boot-time
|
|
359
|
+
* map to the same merged map (single-writer: the map changes at boot or
|
|
360
|
+
* here — never diverges), so the switch is live on the very next
|
|
361
|
+
* GET /identity?agent= (applies: "immediate"), and (3) responds with this
|
|
362
|
+
* body. Externally hand-edited config still needs a restart (pre-existing
|
|
363
|
+
* posture — only this endpoint updates the live map).
|
|
364
|
+
*
|
|
365
|
+
* Interplay with PUT /identity: its black-hole guard 409s section writes
|
|
366
|
+
* while config forces off/global for the agent (sections written under
|
|
367
|
+
* agents/<id>/ would never be served); switching to 'override' here FIRST
|
|
368
|
+
* unblocks section editing. `dir_present` tells the caller whether an agent
|
|
369
|
+
* section dir already exists ('real' — agent content to serve) or not
|
|
370
|
+
* (override on a fresh dir = global content until sections are written).
|
|
371
|
+
*/
|
|
372
|
+
export declare function handleIdentityModePut(identityDir: string, body: unknown, query: Record<string, unknown>, currentAgents: Record<string, AgentMode>): HandlerResult & {
|
|
373
|
+
agents?: Record<string, AgentMode>;
|
|
374
|
+
};
|
package/dist/identity-store.js
CHANGED
|
@@ -56,6 +56,7 @@ exports.readResolvedSections = readResolvedSections;
|
|
|
56
56
|
exports.handleIdentityGet = handleIdentityGet;
|
|
57
57
|
exports.serveIdentityBody = serveIdentityBody;
|
|
58
58
|
exports.handleIdentityPut = handleIdentityPut;
|
|
59
|
+
exports.handleIdentityModePut = handleIdentityModePut;
|
|
59
60
|
const node_fs_1 = require("node:fs");
|
|
60
61
|
const node_path_1 = require("node:path");
|
|
61
62
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -875,3 +876,51 @@ function handleIdentityPut(identityDir, body, query = {}, identityAgents = {}) {
|
|
|
875
876
|
}
|
|
876
877
|
return { status: 200, body: respBody, warn };
|
|
877
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* PUT /identity/mode (#423 phase 3): switch ONE agent's scope to
|
|
881
|
+
* override/global/off. Pure validation + merge — the ADAPTER (mcp-server.ts)
|
|
882
|
+
* does three things with the result: (1) persists `agents` as the config's
|
|
883
|
+
* `identityAgents` (survives restarts), (2) sets the daemon's live boot-time
|
|
884
|
+
* map to the same merged map (single-writer: the map changes at boot or
|
|
885
|
+
* here — never diverges), so the switch is live on the very next
|
|
886
|
+
* GET /identity?agent= (applies: "immediate"), and (3) responds with this
|
|
887
|
+
* body. Externally hand-edited config still needs a restart (pre-existing
|
|
888
|
+
* posture — only this endpoint updates the live map).
|
|
889
|
+
*
|
|
890
|
+
* Interplay with PUT /identity: its black-hole guard 409s section writes
|
|
891
|
+
* while config forces off/global for the agent (sections written under
|
|
892
|
+
* agents/<id>/ would never be served); switching to 'override' here FIRST
|
|
893
|
+
* unblocks section editing. `dir_present` tells the caller whether an agent
|
|
894
|
+
* section dir already exists ('real' — agent content to serve) or not
|
|
895
|
+
* (override on a fresh dir = global content until sections are written).
|
|
896
|
+
*/
|
|
897
|
+
function handleIdentityModePut(identityDir, body, query, currentAgents) {
|
|
898
|
+
const { agentId, error } = extractAgentParam(query);
|
|
899
|
+
if (error)
|
|
900
|
+
return { status: 400, body: { error } };
|
|
901
|
+
if (agentId === null) {
|
|
902
|
+
return {
|
|
903
|
+
status: 400,
|
|
904
|
+
body: { error: "The ?agent= query parameter is required (PUT /identity/mode sets ONE agent's scope)" },
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
908
|
+
return { status: 400, body: { error: "Body must be a JSON object {mode}" } };
|
|
909
|
+
}
|
|
910
|
+
const { mode } = body;
|
|
911
|
+
if (mode !== "override" && mode !== "global" && mode !== "off") {
|
|
912
|
+
return { status: 400, body: { error: "Invalid 'mode' — expected one of \"override\", \"global\", \"off\"" } };
|
|
913
|
+
}
|
|
914
|
+
return {
|
|
915
|
+
status: 200,
|
|
916
|
+
body: {
|
|
917
|
+
agent: agentId,
|
|
918
|
+
mode,
|
|
919
|
+
dir_present: agentDirState(identityDir, agentId) === "real",
|
|
920
|
+
applies: "immediate",
|
|
921
|
+
},
|
|
922
|
+
// The merged map the adapter persists AND applies to the live boot-time
|
|
923
|
+
// map — other agents' entries are preserved untouched.
|
|
924
|
+
agents: { ...currentAgents, [agentId]: mode },
|
|
925
|
+
};
|
|
926
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -80,9 +80,10 @@ const SEARCH_FALLBACK_TIMEOUT_MS = 5000;
|
|
|
80
80
|
* instruction points at a 0.14+ endpoint that 404s on these servers). */
|
|
81
81
|
const LEGACY_CONTENT_CAP = 500;
|
|
82
82
|
/** Default max memories per recall on the legacy /search fallback (config
|
|
83
|
-
* `recallLimit`, #316). The pushed /recall-index is sized by
|
|
84
|
-
* (
|
|
85
|
-
* applies where a client limit actually exists: the pre-0.14
|
|
83
|
+
* `recallLimit`, #316). The pushed /recall-index is sized by the server's
|
|
84
|
+
* release-managed calibration (#408) — the server accepts no client limit —
|
|
85
|
+
* so this knob applies where a client limit actually exists: the pre-0.14
|
|
86
|
+
* fallback. */
|
|
86
87
|
const DEFAULT_RECALL_LIMIT = 8;
|
|
87
88
|
/** Cap for the per-session LRU trackers (#316 CR: raised from 50 — a
|
|
88
89
|
* multi-agent gateway fans sessions across agents; evicting a live session's
|
package/dist/init.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ export declare function persistLlmConfig(configPath?: string): Promise<void>;
|
|
|
69
69
|
* and the writer then OVERWROTE the file — `persistAuthToken` minted a fresh
|
|
70
70
|
* token (fleet-wide 401), `scaffoldDefaultDomains` re-seeded the generic
|
|
71
71
|
* vocabulary over the owner list, etc. `authToken` / `licenseKey` /
|
|
72
|
-
* `llmApiKey` / `domains` / `
|
|
72
|
+
* `llmApiKey` / `domains` / `identityClients` (was
|
|
73
73
|
* `contextClients`) all gone.
|
|
74
74
|
* The early-return guards (existing-key checks) did NOT save them: those only
|
|
75
75
|
* fire on a VALID parse that reads the key, not on a corrupted file.
|
|
@@ -94,6 +94,20 @@ export declare function loadConfigStrict(configPath: string): {
|
|
|
94
94
|
config: Record<string, unknown>;
|
|
95
95
|
hadFile: boolean;
|
|
96
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* Apply a sparse set of config updates atomically-ish (strict load → merge →
|
|
99
|
+
* save): the HTTP-side sibling of persistLlmConfig (which drives the
|
|
100
|
+
* interactive init flow). `null` values DELETE the key — the console's
|
|
101
|
+
* "clear field" semantic. Values are assumed VALIDATED by the caller (the
|
|
102
|
+
* /dashboard/model PUT validates its allowlisted subset BEFORE calling); this
|
|
103
|
+
* function only owns load-strictly + merge + save, so a malformed existing
|
|
104
|
+
* config throws here and the file is left UNTOUCHED (the 0.16.x contract —
|
|
105
|
+
* see loadConfigStrict). Returns the freshly persisted config so the caller
|
|
106
|
+
* can answer with the post-write truth (re-read, not echo).
|
|
107
|
+
*
|
|
108
|
+
* Exported for dashboard.ts's PUT handler + tests.
|
|
109
|
+
*/
|
|
110
|
+
export declare function persistConfigUpdates(configPath: string, updates: Record<string, unknown>): Record<string, unknown>;
|
|
97
111
|
/**
|
|
98
112
|
* `init --repair-config` escape hatch: move a malformed config.json aside so
|
|
99
113
|
* init can rebuild, instead of dead-ending on loadConfigStrict's throw.
|
|
@@ -174,7 +188,7 @@ export declare function ensureAgentId(config: Record<string, unknown>): {
|
|
|
174
188
|
* + unconditional save WIPES config.json when the file exists but is
|
|
175
189
|
* unparseable (a hand-edit syntax slip) — the catch swallows the parse error,
|
|
176
190
|
* {} is seeded, and the save overwrites the file with just {"agentId": ...},
|
|
177
|
-
* destroying authToken / licenseKey / domains
|
|
191
|
+
* destroying authToken / licenseKey / domains, then
|
|
178
192
|
* cascades into scaffoldDefaultDomains re-seeding the generic vocabulary.
|
|
179
193
|
* This wrapper refuses that path:
|
|
180
194
|
* - ENOENT (file genuinely absent) → seed {} is correct (new install).
|
|
@@ -348,7 +362,8 @@ export declare function runInit(options?: {
|
|
|
348
362
|
}): Promise<void>;
|
|
349
363
|
/**
|
|
350
364
|
* Resolve the timer-jitter spread (seconds) from config. 0 = disabled. Uses
|
|
351
|
-
* readNonNegativeConfig (0 is a valid "off",
|
|
365
|
+
* readNonNegativeConfig (0 is a valid "off", like memorySoftCap's eviction
|
|
366
|
+
* opt-out).
|
|
352
367
|
*/
|
|
353
368
|
export declare function resolveTimerJitterSeconds(configDir?: string): number;
|
|
354
369
|
/**
|
|
@@ -403,3 +418,8 @@ export declare function formatLaunchdIntervals(hours: number[], minuteOffset?: n
|
|
|
403
418
|
* applies to every OnCalendar entry AND to OnUnitActiveSec (systemd semantics).
|
|
404
419
|
*/
|
|
405
420
|
export declare function formatSystemdTimerBody(isInterval: boolean, intervalSec: number, hours: number[], jitterSec: number): string;
|
|
421
|
+
/**
|
|
422
|
+
* Install the CONSOLIDATION timer (full `nightly`). Reuses the existing
|
|
423
|
+
* `hicortex-nightly` unit name (repurposed from the pre-0.17 single full-nightly).
|
|
424
|
+
*/
|
|
425
|
+
export declare function installConsolidationTimer(hours: number[]): void;
|
package/dist/init.js
CHANGED
|
@@ -30,6 +30,7 @@ exports.parseEnvFile = parseEnvFile;
|
|
|
30
30
|
exports.isLlmConfigured = isLlmConfigured;
|
|
31
31
|
exports.persistLlmConfig = persistLlmConfig;
|
|
32
32
|
exports.loadConfigStrict = loadConfigStrict;
|
|
33
|
+
exports.persistConfigUpdates = persistConfigUpdates;
|
|
33
34
|
exports.quarantineMalformedConfig = quarantineMalformedConfig;
|
|
34
35
|
exports.generateAuthToken = generateAuthToken;
|
|
35
36
|
exports.persistAuthToken = persistAuthToken;
|
|
@@ -52,6 +53,7 @@ exports.resolveNightlyHour = resolveNightlyHour;
|
|
|
52
53
|
exports.formatOnCalendarLines = formatOnCalendarLines;
|
|
53
54
|
exports.formatLaunchdIntervals = formatLaunchdIntervals;
|
|
54
55
|
exports.formatSystemdTimerBody = formatSystemdTimerBody;
|
|
56
|
+
exports.installConsolidationTimer = installConsolidationTimer;
|
|
55
57
|
const paths_js_1 = require("./paths.js");
|
|
56
58
|
const localhost_bypass_js_1 = require("./localhost-bypass.js");
|
|
57
59
|
const telemetry_js_1 = require("./telemetry.js");
|
|
@@ -64,6 +66,8 @@ const node_crypto_1 = require("node:crypto");
|
|
|
64
66
|
const claude_md_js_1 = require("./claude-md.js");
|
|
65
67
|
const claude_desktop_js_1 = require("./claude-desktop.js");
|
|
66
68
|
const config_read_js_1 = require("./config-read.js");
|
|
69
|
+
const calibration_js_1 = require("./calibration.js");
|
|
70
|
+
const run_deadline_js_1 = require("./run-deadline.js");
|
|
67
71
|
const identity_store_js_1 = require("./identity-store.js");
|
|
68
72
|
const HICORTEX_HOME = (0, paths_js_1.hicortexHome)();
|
|
69
73
|
/** This package's version, for the install lifecycle ping (0.15.2). */
|
|
@@ -994,7 +998,7 @@ function saveConfig(configPath, config) {
|
|
|
994
998
|
* and the writer then OVERWROTE the file — `persistAuthToken` minted a fresh
|
|
995
999
|
* token (fleet-wide 401), `scaffoldDefaultDomains` re-seeded the generic
|
|
996
1000
|
* vocabulary over the owner list, etc. `authToken` / `licenseKey` /
|
|
997
|
-
* `llmApiKey` / `domains` / `
|
|
1001
|
+
* `llmApiKey` / `domains` / `identityClients` (was
|
|
998
1002
|
* `contextClients`) all gone.
|
|
999
1003
|
* The early-return guards (existing-key checks) did NOT save them: those only
|
|
1000
1004
|
* fire on a VALID parse that reads the key, not on a corrupted file.
|
|
@@ -1050,6 +1054,30 @@ function loadConfigStrict(configPath) {
|
|
|
1050
1054
|
}
|
|
1051
1055
|
return { config: parsed, hadFile: true };
|
|
1052
1056
|
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Apply a sparse set of config updates atomically-ish (strict load → merge →
|
|
1059
|
+
* save): the HTTP-side sibling of persistLlmConfig (which drives the
|
|
1060
|
+
* interactive init flow). `null` values DELETE the key — the console's
|
|
1061
|
+
* "clear field" semantic. Values are assumed VALIDATED by the caller (the
|
|
1062
|
+
* /dashboard/model PUT validates its allowlisted subset BEFORE calling); this
|
|
1063
|
+
* function only owns load-strictly + merge + save, so a malformed existing
|
|
1064
|
+
* config throws here and the file is left UNTOUCHED (the 0.16.x contract —
|
|
1065
|
+
* see loadConfigStrict). Returns the freshly persisted config so the caller
|
|
1066
|
+
* can answer with the post-write truth (re-read, not echo).
|
|
1067
|
+
*
|
|
1068
|
+
* Exported for dashboard.ts's PUT handler + tests.
|
|
1069
|
+
*/
|
|
1070
|
+
function persistConfigUpdates(configPath, updates) {
|
|
1071
|
+
const { config } = loadConfigStrict(configPath);
|
|
1072
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
1073
|
+
if (v === null)
|
|
1074
|
+
delete config[k];
|
|
1075
|
+
else
|
|
1076
|
+
config[k] = v;
|
|
1077
|
+
}
|
|
1078
|
+
saveConfig(configPath, config);
|
|
1079
|
+
return config;
|
|
1080
|
+
}
|
|
1053
1081
|
/**
|
|
1054
1082
|
* `init --repair-config` escape hatch: move a malformed config.json aside so
|
|
1055
1083
|
* init can rebuild, instead of dead-ending on loadConfigStrict's throw.
|
|
@@ -1099,7 +1127,7 @@ function quarantineMalformedConfig(configPath) {
|
|
|
1099
1127
|
console.log(` Keys found in the old file: ${keys.join(", ")}`);
|
|
1100
1128
|
}
|
|
1101
1129
|
console.log(` ACTION REQUIRED: copy any of licenseKey / llmApiKey /`);
|
|
1102
|
-
console.log(` domains
|
|
1130
|
+
console.log(` domains back from the backup by hand.`);
|
|
1103
1131
|
console.log(` A NEW authToken will be generated — every thin client pointing at this`);
|
|
1104
1132
|
console.log(` server must be updated, or their recall will 401 (silently, fail-soft).`);
|
|
1105
1133
|
return { quarantined: true, backupPath, keys };
|
|
@@ -1167,7 +1195,7 @@ function ensureAgentId(config) {
|
|
|
1167
1195
|
* + unconditional save WIPES config.json when the file exists but is
|
|
1168
1196
|
* unparseable (a hand-edit syntax slip) — the catch swallows the parse error,
|
|
1169
1197
|
* {} is seeded, and the save overwrites the file with just {"agentId": ...},
|
|
1170
|
-
* destroying authToken / licenseKey / domains
|
|
1198
|
+
* destroying authToken / licenseKey / domains, then
|
|
1171
1199
|
* cascades into scaffoldDefaultDomains re-seeding the generic vocabulary.
|
|
1172
1200
|
* This wrapper refuses that path:
|
|
1173
1201
|
* - ENOENT (file genuinely absent) → seed {} is correct (new install).
|
|
@@ -1941,6 +1969,9 @@ async function runInit(options = {}) {
|
|
|
1941
1969
|
// the install → first-nightly → retained funnel is measurable. Never blocks:
|
|
1942
1970
|
// failures are swallowed inside sendLifecycleEvent.
|
|
1943
1971
|
await (0, telemetry_js_1.sendLifecycleEvent)("install", HICORTEX_HOME, readHomeConfig(HICORTEX_HOME), pkgVersion());
|
|
1972
|
+
// #436: the first-run lookback cap is invisible until it bites — tell the
|
|
1973
|
+
// long-history user where the rest went and how to get it, once, at install.
|
|
1974
|
+
console.log(`History import: the first nightly captures the last ${calibration_js_1.DEFAULT_FIRST_RUN_LOOKBACK_DAYS} days of your agents' sessions by default — run \`hicortex nightly --recapture-window <days>\` once to import more.`);
|
|
1944
1975
|
console.log("Next steps:");
|
|
1945
1976
|
// Counter-based so the list stays contiguous (1,2,3,4) whether or not Hermes
|
|
1946
1977
|
// was detected — a conditional middle step used to leave a "1, 3, 4" gap.
|
|
@@ -2126,6 +2157,9 @@ async function runClientInit(serverUrl, agentName) {
|
|
|
2126
2157
|
// the install → first-nightly → retained funnel is measurable. Never blocks:
|
|
2127
2158
|
// failures are swallowed inside sendLifecycleEvent.
|
|
2128
2159
|
await (0, telemetry_js_1.sendLifecycleEvent)("install", HICORTEX_HOME, readHomeConfig(HICORTEX_HOME), pkgVersion());
|
|
2160
|
+
// #436: the first-run lookback cap is invisible until it bites — tell the
|
|
2161
|
+
// long-history user where the rest went and how to get it, once, at install.
|
|
2162
|
+
console.log(`History import: the first nightly captures the last ${calibration_js_1.DEFAULT_FIRST_RUN_LOOKBACK_DAYS} days of your agents' sessions on this machine by default — run \`hicortex nightly --recapture-window <days>\` once to import more.`);
|
|
2129
2163
|
console.log("How it works:");
|
|
2130
2164
|
console.log(" • MCP tools (search, identity, ingest) talk to the remote server");
|
|
2131
2165
|
console.log(" • Nightly pipeline denoises CC transcripts, POSTs to server for distillation");
|
|
@@ -2181,7 +2215,8 @@ const DEFAULT_CONSOLIDATION_HOURS = [10, 22];
|
|
|
2181
2215
|
const DEFAULT_TIMER_JITTER_SEC = 3600;
|
|
2182
2216
|
/**
|
|
2183
2217
|
* Resolve the timer-jitter spread (seconds) from config. 0 = disabled. Uses
|
|
2184
|
-
* readNonNegativeConfig (0 is a valid "off",
|
|
2218
|
+
* readNonNegativeConfig (0 is a valid "off", like memorySoftCap's eviction
|
|
2219
|
+
* opt-out).
|
|
2185
2220
|
*/
|
|
2186
2221
|
function resolveTimerJitterSeconds(configDir = HICORTEX_HOME) {
|
|
2187
2222
|
let config = {};
|
|
@@ -2471,6 +2506,12 @@ function installCaptureWatchdogTimer() {
|
|
|
2471
2506
|
* `hicortex-nightly` unit name (repurposed from the pre-0.17 single full-nightly).
|
|
2472
2507
|
*/
|
|
2473
2508
|
function installConsolidationTimer(hours) {
|
|
2509
|
+
// #405: DERIVED from the run deadline — nightlyTimeBudgetMinutes + 60 min
|
|
2510
|
+
// slack. The old fixed 360 was prose-coupled to the old call budget (raise
|
|
2511
|
+
// together!); now the unit backstop tracks the deadline it is backing.
|
|
2512
|
+
// Backstop only (NOT an operating limit) — the slack absorbs backup +
|
|
2513
|
+
// telemetry + a slow shutdown after the deadline stops the stages.
|
|
2514
|
+
const timeoutMin = (0, run_deadline_js_1.resolveNightlyTimeBudgetMinutes)(readInstallConfig()) + 60;
|
|
2474
2515
|
writeScheduleUnit({
|
|
2475
2516
|
unitBase: "hicortex-nightly",
|
|
2476
2517
|
plistLabel: "com.gamaze.hicortex-nightly",
|
|
@@ -2478,12 +2519,46 @@ function installConsolidationTimer(hours) {
|
|
|
2478
2519
|
timerDesc: "Hicortex Consolidation Timer",
|
|
2479
2520
|
nightlyArgs: ["nightly"],
|
|
2480
2521
|
hours,
|
|
2481
|
-
//
|
|
2482
|
-
|
|
2483
|
-
// one. ~5000 LLM calls × ~1–3s/call ≈ 1.4–4.2h → 6h clears it with margin.
|
|
2484
|
-
// Coupled to the consolidateMaxLlmCalls budget (#241): raise together.
|
|
2485
|
-
timeoutMin: 360,
|
|
2522
|
+
// nightlyTimeBudgetMinutes (default 240) + 60 min slack — see above.
|
|
2523
|
+
timeoutMin,
|
|
2486
2524
|
});
|
|
2525
|
+
// #405 shadow detection: a systemd drop-in can override the derived
|
|
2526
|
+
// TimeoutStartSec silently (a stale pre-#405 drop-in pinning a shorter
|
|
2527
|
+
// timeout would hard-kill legitimate deadline-bounded runs). Detect and
|
|
2528
|
+
// warn — removal is a deploy-time ops step, not an init action.
|
|
2529
|
+
warnSystemdDropInShadow("hicortex-nightly", timeoutMin);
|
|
2530
|
+
}
|
|
2531
|
+
/** Read the install-time config (~/.hicortex/config.json), null-tolerant. */
|
|
2532
|
+
function readInstallConfig() {
|
|
2533
|
+
try {
|
|
2534
|
+
return JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(HICORTEX_HOME, "config.json"), "utf8"));
|
|
2535
|
+
}
|
|
2536
|
+
catch {
|
|
2537
|
+
return null;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
/**
|
|
2541
|
+
* Warn when `~/.config/systemd/user/<unit>.service.d/*.conf` drop-ins exist —
|
|
2542
|
+
* they can shadow the generated TimeoutStartSec (and anything else the unit
|
|
2543
|
+
* sets). Linux only; the macOS launchd path has no drop-in mechanism.
|
|
2544
|
+
*/
|
|
2545
|
+
function warnSystemdDropInShadow(unitBase, derivedTimeoutMin) {
|
|
2546
|
+
if ((0, node_os_1.platform)() !== "linux")
|
|
2547
|
+
return;
|
|
2548
|
+
const dropInDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".config", "systemd", "user", `${unitBase}.service.d`);
|
|
2549
|
+
let entries = [];
|
|
2550
|
+
try {
|
|
2551
|
+
entries = (0, node_fs_1.readdirSync)(dropInDir).filter((f) => f.endsWith(".conf"));
|
|
2552
|
+
}
|
|
2553
|
+
catch {
|
|
2554
|
+
return; // no drop-in dir — the common case
|
|
2555
|
+
}
|
|
2556
|
+
if (entries.length === 0)
|
|
2557
|
+
return;
|
|
2558
|
+
console.warn(`[hicortex] systemd drop-ins present for ${unitBase}.service (${entries.join(", ")}) — ` +
|
|
2559
|
+
`they can SHADOW the generated unit settings (TimeoutStartSec is now derived: ` +
|
|
2560
|
+
`${derivedTimeoutMin}min = nightlyTimeBudgetMinutes + 60 slack). ` +
|
|
2561
|
+
`Remove stale drop-ins so the derived values take effect.`);
|
|
2487
2562
|
}
|
|
2488
2563
|
/**
|
|
2489
2564
|
* Remove the consolidation timer + service (and the macOS plist). Used on
|