@gamaze/hicortex 0.20.9 → 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 +8 -0
- package/assets/dashboard.html +3989 -836
- package/dist/calibration.d.ts +119 -0
- package/dist/calibration.js +149 -1
- 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 +9 -0
- package/dist/capture.js +2 -1
- package/dist/cli.js +36 -0
- package/dist/consolidate.d.ts +35 -0
- package/dist/consolidate.js +85 -9
- package/dist/dashboard.d.ts +322 -3
- package/dist/dashboard.js +592 -7
- package/dist/db.js +105 -0
- package/dist/eval/importance-eval.d.ts +85 -0
- package/dist/eval/importance-eval.js +286 -0
- package/dist/eval/planted-fixtures.d.ts +1 -1
- 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/init.d.ts +14 -0
- package/dist/init.js +32 -0
- package/dist/mcp-server.d.ts +12 -0
- package/dist/mcp-server.js +184 -3
- package/dist/nightly.d.ts +9 -1
- package/dist/nightly.js +59 -7
- package/dist/prompts.d.ts +10 -0
- package/dist/prompts.js +28 -5
- package/dist/reconsolidation.d.ts +59 -30
- package/dist/reconsolidation.js +526 -296
- package/dist/rescore-importance.d.ts +80 -0
- package/dist/rescore-importance.js +236 -0
- package/dist/retrieval.d.ts +12 -0
- package/dist/retrieval.js +30 -1
- package/dist/stages.d.ts +37 -0
- package/dist/stages.js +51 -0
- package/dist/state.d.ts +32 -6
- package/dist/storage.d.ts +34 -2
- package/dist/storage.js +63 -6
- package/dist/types.d.ts +48 -0
- package/package.json +3 -1
package/dist/calibration.d.ts
CHANGED
|
@@ -65,6 +65,16 @@ export declare const NOVELTY_FLOOR_SLOTS = 2;
|
|
|
65
65
|
/** Turns an already-shown memory stays suppressed in the same session before
|
|
66
66
|
* it may reappear in the pushed index (#192 turn-based dedup). */
|
|
67
67
|
export declare const RECALL_RESHOW_TURNS = 30;
|
|
68
|
+
/** uses_per_showing below this reads Low on the console band.
|
|
69
|
+
* PROVISIONAL (owner anchor 2026-09-13, #426). */
|
|
70
|
+
export declare const RECALL_USES_LOW_MAX = 0.05;
|
|
71
|
+
/** uses_per_showing below this (and ≥ RECALL_USES_LOW_MAX) reads Normal.
|
|
72
|
+
* PROVISIONAL (owner anchor 2026-09-13, #426). */
|
|
73
|
+
export declare const RECALL_USES_NORMAL_MAX = 0.25;
|
|
74
|
+
/** Display-axis maximum for the console band (the marker clamps here).
|
|
75
|
+
* PROVISIONAL (owner anchor 2026-09-13, #426) — chosen so Overfetching
|
|
76
|
+
* keeps a visible span, not a measured bound. */
|
|
77
|
+
export declare const RECALL_USES_AXIS_MAX = 0.3;
|
|
68
78
|
/** Semantic-similarity share of the composite score. 0.50 (raised from 0.40
|
|
69
79
|
* in the 0.15.2 rebalance, #191 Phase B): on the production corpus effective
|
|
70
80
|
* strength (0.30) outweighed what similarity could recover — hardened old
|
|
@@ -106,9 +116,48 @@ export declare const RRF_FTS_WEIGHT = 0.5;
|
|
|
106
116
|
/** #205 per-list RRF weight for the vector list (vec stays at 1.0 — the
|
|
107
117
|
* conservative nudge is on the FTS side only). */
|
|
108
118
|
export declare const RRF_VECTOR_WEIGHT = 1;
|
|
119
|
+
/**
|
|
120
|
+
* Additive boost for candidates the TWO retrieval channels AGREE on
|
|
121
|
+
* (vector KNN AND BM25 FTS both matched — computeScore option
|
|
122
|
+
* `bothChannel`). #425 (owner decision D3, 2026-09-13): the two-channel
|
|
123
|
+
* signal is the genuine-match signature and is immune to FTS-only token
|
|
124
|
+
* collisions, which is why the boost lives in the composite score, not in
|
|
125
|
+
* the RRF FTS weight (raising that re-opens the #205 cross-scope collision
|
|
126
|
+
* it fixed). D3's dominance property: a both-channel match outranks a
|
|
127
|
+
* single-channel rival unless the rival is >0.10 more similar — at the
|
|
128
|
+
* shipped simWeight 0.50, 0.10 gives that property 2x headroom (a rival
|
|
129
|
+
* needs >0.20 more similarity).
|
|
130
|
+
*
|
|
131
|
+
* SIZING (the #425 sweep, 2026-09-13, production snapshot copy: case-1
|
|
132
|
+
* planted fixture + a 30-query real battery vs the boost-0 baseline): every
|
|
133
|
+
* value in {0.05, 0.10, 0.15} passed the hard gates (exact-match rank-1,
|
|
134
|
+
* no-match byte-stability, zero both-channel exact-match losses, the
|
|
135
|
+
* Sirnäs flip) with IDENTICAL battery stability (96.7% top-1 ex-promotions
|
|
136
|
+
* — every raw change was a both-channel exact match displacing a
|
|
137
|
+
* single-channel row, the property firing); they differed only in case-1
|
|
138
|
+
* margin (0.021 / 0.061 / 0.101) and per-id top-3 churn. 0.10 chosen:
|
|
139
|
+
* 0.05's margin is within one embedder revision of flipping, 0.15 churns
|
|
140
|
+
* more for no gate benefit. Weight rebalances measured WORSE (sim .55/str
|
|
141
|
+
* .15 → 93.3%; sim .60/str .10 → 86.7%, fails the 90% gate) — the
|
|
142
|
+
* SCORE_*_WEIGHT values stay as shipped. ZERO-boost neutral (never a
|
|
143
|
+
* penalty): graph-only and single-channel candidates add nothing.
|
|
144
|
+
*/
|
|
145
|
+
export declare const BOTH_CHANNEL_BOOST = 0.1;
|
|
109
146
|
export declare const BM25_WEIGHT_BODY = 1;
|
|
110
147
|
export declare const BM25_WEIGHT_PROJECT = 2;
|
|
111
148
|
export declare const BM25_WEIGHT_DOMAIN = 2;
|
|
149
|
+
/**
|
|
150
|
+
* Write cap on base_strength / importance (#425, owner decision D2
|
|
151
|
+
* 2026-09-13: cap 0.95). The decay model's rate is `1 − BASE_DECAY·(1 −
|
|
152
|
+
* importance)` — at importance exactly 1.0 the rate is exactly 1.0 and the
|
|
153
|
+
* row NEVER decays (measured on the production snapshot: ~14% of live rows
|
|
154
|
+
* pegged at ≥0.999, 1838/17399 at exactly 1.0). The cap kills the
|
|
155
|
+
* immortality cliff at every write site (stageImportance, enrich, hub
|
|
156
|
+
* boost) while keeping a multi-year half-life for the top band (0.95 →
|
|
157
|
+
* ~13 years). effectiveStrength ALSO clamps the read side, so legacy
|
|
158
|
+
* base-1.0 rows decay again.
|
|
159
|
+
*/
|
|
160
|
+
export declare const IMPORTANCE_CEILING = 0.95;
|
|
112
161
|
/** Deterministic merge ceiling of the unified resolution pass (#392): pairs
|
|
113
162
|
* at/above this cosine merge LLM-free; [CORRECTION_MIN_SIMILARITY, this)
|
|
114
163
|
* get the one verdict call. 0.92 — measured on the #191 mechanical audit
|
|
@@ -130,6 +179,76 @@ export declare const CORRECTION_REWRITE_MIN_CONFIDENCE = 0.8;
|
|
|
130
179
|
* memory to earn a WEAK primary instead of decaying (owner amendment
|
|
131
180
|
* 07.07). Starting point for bge-small-en-v1.5. */
|
|
132
181
|
export declare const WEAK_PRIMARY_FLOOR = 0.45;
|
|
182
|
+
/**
|
|
183
|
+
* Console stage bands (#409/#421) — TWO #408-contract calibrations:
|
|
184
|
+
*
|
|
185
|
+
* (1) 2026-09-13 (PR #424 fix round) against the INFLATED distribution:
|
|
186
|
+
* effectiveStrength p25 = 0.599, p50 = 0.788, p86 = 0.900, ~14%
|
|
187
|
+
* saturated at exactly 1.000 → edges 0.40/0.60/0.90 targeting a rendered
|
|
188
|
+
* split ≈ 10/15/61/14 (fading/forming/belief/truth).
|
|
189
|
+
*
|
|
190
|
+
* (2) This release (#425) against the POST-FIX distribution: the importance
|
|
191
|
+
* recalibration (re-anchored prompt + 0.95 cap) plus the
|
|
192
|
+
* rescore-importance backfill re-spread the same 17,399-row store to
|
|
193
|
+
* base_strength median 0.40 / p90 0.60 / max 0.90 (zero rows ≥ 0.999);
|
|
194
|
+
* measured effectiveStrength on the backfilled copy: p25 ≈ 0.296,
|
|
195
|
+
* p50 ≈ 0.397, p75 ≈ 0.497, p86 ≈ 0.585, max ≈ 0.900. The stale 2026-09-13
|
|
196
|
+
* edges rendered that store as 58.5/37.2/4.2/0 — no Truth left. These
|
|
197
|
+
* edges (0.20/0.30/0.58) re-derive the same ≈ 10/15/61/14 split intent
|
|
198
|
+
* against the honest distribution and render 11.8/25.6/47.7/14.9 (the
|
|
199
|
+
* scorer emits one-decimal scores, so the mass clusters at exact atoms —
|
|
200
|
+
* 0.20/0.30/0.60 — and the 0.60 atom alone holds ~12% of the store; the
|
|
201
|
+
* truth edge therefore sits at 0.58, just under the atom, rather than on
|
|
202
|
+
* the round number that would strand it). Evidence: the #425
|
|
203
|
+
* post-backfill band-stats photo (backfilled copy, deriveStage math via
|
|
204
|
+
* the production effectiveStrength).
|
|
205
|
+
*/
|
|
206
|
+
/**
|
|
207
|
+
* Days without access after which a memory's derived stage is Fading
|
|
208
|
+
* (regardless of strength — the recency gate runs FIRST in deriveStage).
|
|
209
|
+
* 120 ≈ a season: long enough that a working memory is never mislabeled,
|
|
210
|
+
* short enough that the rim of the field turns over within a year.
|
|
211
|
+
*/
|
|
212
|
+
export declare const STAGE_FADING_DAYS = 120;
|
|
213
|
+
/**
|
|
214
|
+
* Effective-strength ceiling of the living bands: below this the memory is
|
|
215
|
+
* Fading (the measured weak cluster + the recency-gated rim, together ~12% of
|
|
216
|
+
* the post-#425 store) even when recently touched.
|
|
217
|
+
*/
|
|
218
|
+
export declare const STAGE_FADING_STRENGTH = 0.2;
|
|
219
|
+
/** The Forming/Belief edge: [FADING, this) → forming, ≥ this → belief. */
|
|
220
|
+
export declare const STAGE_BELIEF_STRENGTH = 0.3;
|
|
221
|
+
/** The Belief/Truth edge: ≥ this → truth — the hardened core (~15% post-#425;
|
|
222
|
+
* just under the 0.60 score atom, see the block JSDoc above). */
|
|
223
|
+
export declare const STAGE_TRUTH_STRENGTH = 0.58;
|
|
224
|
+
/**
|
|
225
|
+
* One owner corroboration's bump to base_strength (#423 phase 3, POST
|
|
226
|
+
* /enrich — evidence about importance). Post-#425 the forming band
|
|
227
|
+
* [0.20,0.30) is 0.10 wide; +0.10 = a full band — one enrich visibly crosses
|
|
228
|
+
* an edge for a mid-band Forming memory. Repeated enriches cap at the
|
|
229
|
+
* IMPORTANCE_CEILING (0.95, the SQL MIN); for a young memory effStr ≈ base so
|
|
230
|
+
* the bump lands ~1:1 (the phase-3 verify "enrich a Forming memory → stage
|
|
231
|
+
* change" holds for a mid-band memory ≥0.25).
|
|
232
|
+
*/
|
|
233
|
+
export declare const ENRICH_STRENGTH_DELTA = 0.1;
|
|
234
|
+
/**
|
|
235
|
+
* Days of session history a FIRST nightly run discovers (the first-run
|
|
236
|
+
* watermark is now − this many days, not the epoch).
|
|
237
|
+
*
|
|
238
|
+
* Owner ruling (2026-09-14, #436): full-history first-run ingestion is not
|
|
239
|
+
* feasible — "if everybody signing up ingests their full history, we will
|
|
240
|
+
* pay millions in tokens during the trial period before customers pay us
|
|
241
|
+
* anything." Cloud needs a very short window; the local/installable version
|
|
242
|
+
* uses the SAME default.
|
|
243
|
+
*
|
|
244
|
+
* Unlike every other constant in this module, this one KEEPS a config
|
|
245
|
+
* override (`firstRunLookbackDays`, read at the capture call sites): it
|
|
246
|
+
* describes an install's cost posture — the same family as
|
|
247
|
+
* `llmTokensPerMonth` / `captureCooldownHours`, which stayed config through
|
|
248
|
+
* #408 — not brain tuning. More history is a deliberate act:
|
|
249
|
+
* `hicortex nightly --recapture-window <days>`.
|
|
250
|
+
*/
|
|
251
|
+
export declare const DEFAULT_FIRST_RUN_LOOKBACK_DAYS = 7;
|
|
133
252
|
/**
|
|
134
253
|
* Context window for ollama (one value, all phases; #220/#228). 8192 is
|
|
135
254
|
* where context stops being the binding constraint for a sub-8B model on
|
package/dist/calibration.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* the removal is never silent.
|
|
25
25
|
*/
|
|
26
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
-
exports.DIAGNOSTIC_ENV_TIER = exports.OLLAMA_FLUSH_WAIT_MS = exports.OLLAMA_FLUSH_EVERY = exports.NUM_CTX = exports.WEAK_PRIMARY_FLOOR = exports.CORRECTION_REWRITE_MIN_CONFIDENCE = exports.CORRECTION_MIN_SIMILARITY = exports.SUPERSESSION_MIN_SIMILARITY = exports.DEDUP_AUTO_MERGE_THRESHOLD = exports.BM25_WEIGHT_DOMAIN = exports.BM25_WEIGHT_PROJECT = exports.BM25_WEIGHT_BODY = exports.RRF_VECTOR_WEIGHT = exports.RRF_FTS_WEIGHT = exports.RRF_COMPOSITE_WEIGHT = exports.RRF_K = exports.DOMAIN_AFFINITY_WEIGHT = exports.PROJECT_AFFINITY_WEIGHT = exports.SUPERSEDED_DEMOTION = exports.FRESHNESS_BOOST_WEIGHT = exports.FRESHNESS_BOOST_DAYS = exports.SCORE_RECENCY_WEIGHT = exports.SCORE_CONNECTIONS_WEIGHT = exports.SCORE_STRENGTH_WEIGHT = exports.SCORE_SIMILARITY_WEIGHT = exports.RECALL_RESHOW_TURNS = exports.NOVELTY_FLOOR_SLOTS = exports.RECALL_TITLE_CHARS = exports.RECALL_MIN_PROMPT_CHARS = exports.RECALL_MAX_ITEMS = exports.RECALL_MIN_SIMILARITY = exports.SESSION_INTENT_WEIGHT = exports.COLD_EXPOSURE_SLOTS = exports.RECENT_WINDOW_DAYS = exports.RECENT_LIMIT = exports.SEARCH_LIMIT = exports.DECAY_HALF_LIFE_DAYS = void 0;
|
|
27
|
+
exports.DIAGNOSTIC_ENV_TIER = exports.OLLAMA_FLUSH_WAIT_MS = exports.OLLAMA_FLUSH_EVERY = exports.NUM_CTX = exports.DEFAULT_FIRST_RUN_LOOKBACK_DAYS = exports.ENRICH_STRENGTH_DELTA = exports.STAGE_TRUTH_STRENGTH = exports.STAGE_BELIEF_STRENGTH = exports.STAGE_FADING_STRENGTH = exports.STAGE_FADING_DAYS = exports.WEAK_PRIMARY_FLOOR = exports.CORRECTION_REWRITE_MIN_CONFIDENCE = exports.CORRECTION_MIN_SIMILARITY = exports.SUPERSESSION_MIN_SIMILARITY = exports.DEDUP_AUTO_MERGE_THRESHOLD = exports.IMPORTANCE_CEILING = exports.BM25_WEIGHT_DOMAIN = exports.BM25_WEIGHT_PROJECT = exports.BM25_WEIGHT_BODY = exports.BOTH_CHANNEL_BOOST = exports.RRF_VECTOR_WEIGHT = exports.RRF_FTS_WEIGHT = exports.RRF_COMPOSITE_WEIGHT = exports.RRF_K = exports.DOMAIN_AFFINITY_WEIGHT = exports.PROJECT_AFFINITY_WEIGHT = exports.SUPERSEDED_DEMOTION = exports.FRESHNESS_BOOST_WEIGHT = exports.FRESHNESS_BOOST_DAYS = exports.SCORE_RECENCY_WEIGHT = exports.SCORE_CONNECTIONS_WEIGHT = exports.SCORE_STRENGTH_WEIGHT = exports.SCORE_SIMILARITY_WEIGHT = exports.RECALL_USES_AXIS_MAX = exports.RECALL_USES_NORMAL_MAX = exports.RECALL_USES_LOW_MAX = exports.RECALL_RESHOW_TURNS = exports.NOVELTY_FLOOR_SLOTS = exports.RECALL_TITLE_CHARS = exports.RECALL_MIN_PROMPT_CHARS = exports.RECALL_MAX_ITEMS = exports.RECALL_MIN_SIMILARITY = exports.SESSION_INTENT_WEIGHT = exports.COLD_EXPOSURE_SLOTS = exports.RECENT_WINDOW_DAYS = exports.RECENT_LIMIT = exports.SEARCH_LIMIT = exports.DECAY_HALF_LIFE_DAYS = void 0;
|
|
28
28
|
exports.resolveNumCtx = resolveNumCtx;
|
|
29
29
|
exports.resolveOllamaFlushEvery = resolveOllamaFlushEvery;
|
|
30
30
|
exports.resolveOllamaFlushWaitMs = resolveOllamaFlushWaitMs;
|
|
@@ -76,6 +76,26 @@ exports.NOVELTY_FLOOR_SLOTS = 2;
|
|
|
76
76
|
/** Turns an already-shown memory stays suppressed in the same session before
|
|
77
77
|
* it may reappear in the pushed index (#192 turn-based dedup). */
|
|
78
78
|
exports.RECALL_RESHOW_TURNS = 30;
|
|
79
|
+
// Recall-uses band (console) — PROVISIONAL zone edges. Owner anchor
|
|
80
|
+
// 2026-09-13 (#426 final ruling): the console renders uses-per-showing on a
|
|
81
|
+
// red→green→red band with three zones (Low / Normal / Overfetching; owner
|
|
82
|
+
// confirmed "3 bands is fine"). The owner's calibration state is explicit —
|
|
83
|
+
// "we have no clue on how to calibrate yet" — so these edges are provisional
|
|
84
|
+
// anchors from #426's owner-anchor record, NOT measured boundaries; fleet
|
|
85
|
+
// telemetry is expected to move them (the console marks the band
|
|
86
|
+
// "provisional" and renders no edge numerics). They position zone boundaries
|
|
87
|
+
// on the console's gradient and classify the marker's zone word; they gate
|
|
88
|
+
// nothing server-side.
|
|
89
|
+
/** uses_per_showing below this reads Low on the console band.
|
|
90
|
+
* PROVISIONAL (owner anchor 2026-09-13, #426). */
|
|
91
|
+
exports.RECALL_USES_LOW_MAX = 0.05;
|
|
92
|
+
/** uses_per_showing below this (and ≥ RECALL_USES_LOW_MAX) reads Normal.
|
|
93
|
+
* PROVISIONAL (owner anchor 2026-09-13, #426). */
|
|
94
|
+
exports.RECALL_USES_NORMAL_MAX = 0.25;
|
|
95
|
+
/** Display-axis maximum for the console band (the marker clamps here).
|
|
96
|
+
* PROVISIONAL (owner anchor 2026-09-13, #426) — chosen so Overfetching
|
|
97
|
+
* keeps a visible span, not a measured bound. */
|
|
98
|
+
exports.RECALL_USES_AXIS_MAX = 0.30;
|
|
79
99
|
// ---------------------------------------------------------------------------
|
|
80
100
|
// Composite ranking weights (was: score*Weight, freshnessBoost*,
|
|
81
101
|
// supersededDemotion, *AffinityWeight, rrf*)
|
|
@@ -121,6 +141,33 @@ exports.RRF_FTS_WEIGHT = 0.5;
|
|
|
121
141
|
/** #205 per-list RRF weight for the vector list (vec stays at 1.0 — the
|
|
122
142
|
* conservative nudge is on the FTS side only). */
|
|
123
143
|
exports.RRF_VECTOR_WEIGHT = 1.0;
|
|
144
|
+
/**
|
|
145
|
+
* Additive boost for candidates the TWO retrieval channels AGREE on
|
|
146
|
+
* (vector KNN AND BM25 FTS both matched — computeScore option
|
|
147
|
+
* `bothChannel`). #425 (owner decision D3, 2026-09-13): the two-channel
|
|
148
|
+
* signal is the genuine-match signature and is immune to FTS-only token
|
|
149
|
+
* collisions, which is why the boost lives in the composite score, not in
|
|
150
|
+
* the RRF FTS weight (raising that re-opens the #205 cross-scope collision
|
|
151
|
+
* it fixed). D3's dominance property: a both-channel match outranks a
|
|
152
|
+
* single-channel rival unless the rival is >0.10 more similar — at the
|
|
153
|
+
* shipped simWeight 0.50, 0.10 gives that property 2x headroom (a rival
|
|
154
|
+
* needs >0.20 more similarity).
|
|
155
|
+
*
|
|
156
|
+
* SIZING (the #425 sweep, 2026-09-13, production snapshot copy: case-1
|
|
157
|
+
* planted fixture + a 30-query real battery vs the boost-0 baseline): every
|
|
158
|
+
* value in {0.05, 0.10, 0.15} passed the hard gates (exact-match rank-1,
|
|
159
|
+
* no-match byte-stability, zero both-channel exact-match losses, the
|
|
160
|
+
* Sirnäs flip) with IDENTICAL battery stability (96.7% top-1 ex-promotions
|
|
161
|
+
* — every raw change was a both-channel exact match displacing a
|
|
162
|
+
* single-channel row, the property firing); they differed only in case-1
|
|
163
|
+
* margin (0.021 / 0.061 / 0.101) and per-id top-3 churn. 0.10 chosen:
|
|
164
|
+
* 0.05's margin is within one embedder revision of flipping, 0.15 churns
|
|
165
|
+
* more for no gate benefit. Weight rebalances measured WORSE (sim .55/str
|
|
166
|
+
* .15 → 93.3%; sim .60/str .10 → 86.7%, fails the 90% gate) — the
|
|
167
|
+
* SCORE_*_WEIGHT values stay as shipped. ZERO-boost neutral (never a
|
|
168
|
+
* penalty): graph-only and single-channel candidates add nothing.
|
|
169
|
+
*/
|
|
170
|
+
exports.BOTH_CHANNEL_BOOST = 0.10;
|
|
124
171
|
// ---------------------------------------------------------------------------
|
|
125
172
|
// BM25F field weights (was: bm25WeightBody/Project/Domain) — #205. Body is
|
|
126
173
|
// down-weighted relative to the scope fields so a project/domain token match
|
|
@@ -134,6 +181,18 @@ exports.BM25_WEIGHT_DOMAIN = 2.0;
|
|
|
134
181
|
// dedupMergeThreshold], supersessionMinSimilarity, correctionMinSimilarity,
|
|
135
182
|
// correctionRewriteMinConfidence, weakPrimaryFloor)
|
|
136
183
|
// ---------------------------------------------------------------------------
|
|
184
|
+
/**
|
|
185
|
+
* Write cap on base_strength / importance (#425, owner decision D2
|
|
186
|
+
* 2026-09-13: cap 0.95). The decay model's rate is `1 − BASE_DECAY·(1 −
|
|
187
|
+
* importance)` — at importance exactly 1.0 the rate is exactly 1.0 and the
|
|
188
|
+
* row NEVER decays (measured on the production snapshot: ~14% of live rows
|
|
189
|
+
* pegged at ≥0.999, 1838/17399 at exactly 1.0). The cap kills the
|
|
190
|
+
* immortality cliff at every write site (stageImportance, enrich, hub
|
|
191
|
+
* boost) while keeping a multi-year half-life for the top band (0.95 →
|
|
192
|
+
* ~13 years). effectiveStrength ALSO clamps the read side, so legacy
|
|
193
|
+
* base-1.0 rows decay again.
|
|
194
|
+
*/
|
|
195
|
+
exports.IMPORTANCE_CEILING = 0.95;
|
|
137
196
|
/** Deterministic merge ceiling of the unified resolution pass (#392): pairs
|
|
138
197
|
* at/above this cosine merge LLM-free; [CORRECTION_MIN_SIMILARITY, this)
|
|
139
198
|
* get the one verdict call. 0.92 — measured on the #191 mechanical audit
|
|
@@ -156,6 +215,95 @@ exports.CORRECTION_REWRITE_MIN_CONFIDENCE = 0.80;
|
|
|
156
215
|
* 07.07). Starting point for bge-small-en-v1.5. */
|
|
157
216
|
exports.WEAK_PRIMARY_FLOOR = 0.45;
|
|
158
217
|
// ---------------------------------------------------------------------------
|
|
218
|
+
// Console presentation family (#409/#421) — stage thresholds for the
|
|
219
|
+
// /dashboard console. The STAGE BANDS are calibrated per release against the
|
|
220
|
+
// MEASURED distribution (2026-09-13 against the inflated store; this release
|
|
221
|
+
// re-derived against the post-#425 backfilled store — see the block JSDoc
|
|
222
|
+
// immediately below). The recall-grade zones that used to
|
|
223
|
+
// live here are REMOVED per the owner semantics ruling 2026-09-13 (#426):
|
|
224
|
+
// recall depends only on the conversation — "if the index is good enough for
|
|
225
|
+
// the context needed, it does not have to be fetched" — so a graded
|
|
226
|
+
// Low/Medium/Good/Excellent scale is the wrong instrument (higher is not a
|
|
227
|
+
// target that exists). The console shows the raw uses-per-showing ratio + a
|
|
228
|
+
// trend; the reference expectations for reading that number live on #426,
|
|
229
|
+
// not in shipped bands. The evolution contract still applies to the stage
|
|
230
|
+
// bands: they move only in releases, with the eval / band-stats evidence
|
|
231
|
+
// linked in the changelog line that moves them.
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
/**
|
|
234
|
+
* Console stage bands (#409/#421) — TWO #408-contract calibrations:
|
|
235
|
+
*
|
|
236
|
+
* (1) 2026-09-13 (PR #424 fix round) against the INFLATED distribution:
|
|
237
|
+
* effectiveStrength p25 = 0.599, p50 = 0.788, p86 = 0.900, ~14%
|
|
238
|
+
* saturated at exactly 1.000 → edges 0.40/0.60/0.90 targeting a rendered
|
|
239
|
+
* split ≈ 10/15/61/14 (fading/forming/belief/truth).
|
|
240
|
+
*
|
|
241
|
+
* (2) This release (#425) against the POST-FIX distribution: the importance
|
|
242
|
+
* recalibration (re-anchored prompt + 0.95 cap) plus the
|
|
243
|
+
* rescore-importance backfill re-spread the same 17,399-row store to
|
|
244
|
+
* base_strength median 0.40 / p90 0.60 / max 0.90 (zero rows ≥ 0.999);
|
|
245
|
+
* measured effectiveStrength on the backfilled copy: p25 ≈ 0.296,
|
|
246
|
+
* p50 ≈ 0.397, p75 ≈ 0.497, p86 ≈ 0.585, max ≈ 0.900. The stale 2026-09-13
|
|
247
|
+
* edges rendered that store as 58.5/37.2/4.2/0 — no Truth left. These
|
|
248
|
+
* edges (0.20/0.30/0.58) re-derive the same ≈ 10/15/61/14 split intent
|
|
249
|
+
* against the honest distribution and render 11.8/25.6/47.7/14.9 (the
|
|
250
|
+
* scorer emits one-decimal scores, so the mass clusters at exact atoms —
|
|
251
|
+
* 0.20/0.30/0.60 — and the 0.60 atom alone holds ~12% of the store; the
|
|
252
|
+
* truth edge therefore sits at 0.58, just under the atom, rather than on
|
|
253
|
+
* the round number that would strand it). Evidence: the #425
|
|
254
|
+
* post-backfill band-stats photo (backfilled copy, deriveStage math via
|
|
255
|
+
* the production effectiveStrength).
|
|
256
|
+
*/
|
|
257
|
+
/**
|
|
258
|
+
* Days without access after which a memory's derived stage is Fading
|
|
259
|
+
* (regardless of strength — the recency gate runs FIRST in deriveStage).
|
|
260
|
+
* 120 ≈ a season: long enough that a working memory is never mislabeled,
|
|
261
|
+
* short enough that the rim of the field turns over within a year.
|
|
262
|
+
*/
|
|
263
|
+
exports.STAGE_FADING_DAYS = 120;
|
|
264
|
+
/**
|
|
265
|
+
* Effective-strength ceiling of the living bands: below this the memory is
|
|
266
|
+
* Fading (the measured weak cluster + the recency-gated rim, together ~12% of
|
|
267
|
+
* the post-#425 store) even when recently touched.
|
|
268
|
+
*/
|
|
269
|
+
exports.STAGE_FADING_STRENGTH = 0.20;
|
|
270
|
+
/** The Forming/Belief edge: [FADING, this) → forming, ≥ this → belief. */
|
|
271
|
+
exports.STAGE_BELIEF_STRENGTH = 0.30;
|
|
272
|
+
/** The Belief/Truth edge: ≥ this → truth — the hardened core (~15% post-#425;
|
|
273
|
+
* just under the 0.60 score atom, see the block JSDoc above). */
|
|
274
|
+
exports.STAGE_TRUTH_STRENGTH = 0.58;
|
|
275
|
+
/**
|
|
276
|
+
* One owner corroboration's bump to base_strength (#423 phase 3, POST
|
|
277
|
+
* /enrich — evidence about importance). Post-#425 the forming band
|
|
278
|
+
* [0.20,0.30) is 0.10 wide; +0.10 = a full band — one enrich visibly crosses
|
|
279
|
+
* an edge for a mid-band Forming memory. Repeated enriches cap at the
|
|
280
|
+
* IMPORTANCE_CEILING (0.95, the SQL MIN); for a young memory effStr ≈ base so
|
|
281
|
+
* the bump lands ~1:1 (the phase-3 verify "enrich a Forming memory → stage
|
|
282
|
+
* change" holds for a mid-band memory ≥0.25).
|
|
283
|
+
*/
|
|
284
|
+
exports.ENRICH_STRENGTH_DELTA = 0.10;
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Capture / first-run cost family (#436)
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
/**
|
|
289
|
+
* Days of session history a FIRST nightly run discovers (the first-run
|
|
290
|
+
* watermark is now − this many days, not the epoch).
|
|
291
|
+
*
|
|
292
|
+
* Owner ruling (2026-09-14, #436): full-history first-run ingestion is not
|
|
293
|
+
* feasible — "if everybody signing up ingests their full history, we will
|
|
294
|
+
* pay millions in tokens during the trial period before customers pay us
|
|
295
|
+
* anything." Cloud needs a very short window; the local/installable version
|
|
296
|
+
* uses the SAME default.
|
|
297
|
+
*
|
|
298
|
+
* Unlike every other constant in this module, this one KEEPS a config
|
|
299
|
+
* override (`firstRunLookbackDays`, read at the capture call sites): it
|
|
300
|
+
* describes an install's cost posture — the same family as
|
|
301
|
+
* `llmTokensPerMonth` / `captureCooldownHours`, which stayed config through
|
|
302
|
+
* #408 — not brain tuning. More history is a deliberate act:
|
|
303
|
+
* `hicortex nightly --recapture-window <days>`.
|
|
304
|
+
*/
|
|
305
|
+
exports.DEFAULT_FIRST_RUN_LOOKBACK_DAYS = 7;
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
159
307
|
// Diagnostic tier (env-overridable — #408). The ollama-operational family is
|
|
160
308
|
// NOT user tuning: it exists so an operator of a constrained box can pin the
|
|
161
309
|
// three values into a service unit's environment without a config-file
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /distill capture-health accounting (#422 Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* BOTH halves of the console's capture-health card live here (the same
|
|
5
|
+
* layering as recall-index.ts / identity-store.ts: pure logic, unit-testable
|
|
6
|
+
* without booting express — mcp-server.ts only wires the record calls into
|
|
7
|
+
* the /distill exits):
|
|
8
|
+
*
|
|
9
|
+
* - recordDistillActivity(db, entry) — one row per /distill POST, whatever
|
|
10
|
+
* the outcome ('ok' | 'skipped' | 'held'). Held rows are the point: they
|
|
11
|
+
* are the failed posts the client will retry next run (its cursor is
|
|
12
|
+
* held), so the card can show "2 held" instead of a silently missing
|
|
13
|
+
* night. `retried` is computed at INSERT (an earlier row with the same
|
|
14
|
+
* session_id + segment_id = this POST is the retry) and never updated.
|
|
15
|
+
*
|
|
16
|
+
* - readCaptureHealth(db) — the aggregation /dashboard/data exposes as
|
|
17
|
+
* `capture_health`: the most recent day with rows, grouped by machine ×
|
|
18
|
+
* agent (posts / sessions / bytes / held / retried), bytes DESC.
|
|
19
|
+
*
|
|
20
|
+
* Retention: 7 days, pruned inside recordDistillActivity at most ONCE per
|
|
21
|
+
* process per UTC day (in-module memo) — the insert path must not pay a
|
|
22
|
+
* DELETE on every POST. Old rows exist to explain recent nights, nothing
|
|
23
|
+
* else; the durable record of WHAT was captured is the memories themselves.
|
|
24
|
+
*/
|
|
25
|
+
import type Database from "better-sqlite3";
|
|
26
|
+
/** A /distill POST outcome. 'held' = the client will retry (cursor held):
|
|
27
|
+
* no-LLM, dead-endpoint probe, budget 429, distill failure. 'skipped' = a
|
|
28
|
+
* duplicate the dedup prechecks rejected (nothing owed). 'ok' = stored.
|
|
29
|
+
* 'paused' (#423 phase 3, D3) = deliberately skipped by the operator's
|
|
30
|
+
* capture pause — the session is NOT captured and will not be backfilled
|
|
31
|
+
* (the 200 advances the client's cursor by design). */
|
|
32
|
+
export type DistillOutcome = "ok" | "skipped" | "held" | "paused";
|
|
33
|
+
/** One /distill POST to record. The wire fields arrive as-is from the request
|
|
34
|
+
* body (machine/agent/sessionId/segmentId may be absent or mistyped) —
|
|
35
|
+
* normalization happens HERE so every handler call site is a one-liner. */
|
|
36
|
+
export interface DistillActivityEntry {
|
|
37
|
+
/** ISO timestamp of the POST; defaults to now. */
|
|
38
|
+
ts?: string;
|
|
39
|
+
/** Raw source_machine wire value — sanitized via storage.sanitizeSourceMachine, '' when absent. */
|
|
40
|
+
machine: unknown;
|
|
41
|
+
/** Raw source_agent wire value — 'unknown' when absent/blank. */
|
|
42
|
+
agent: unknown;
|
|
43
|
+
/** Raw session_id wire value — stored when a non-empty string, else NULL. */
|
|
44
|
+
sessionId: unknown;
|
|
45
|
+
/** Raw segment_id wire value — stored when a non-empty string, else NULL. */
|
|
46
|
+
segmentId: unknown;
|
|
47
|
+
/** Resolved conversationText length (post-redaction), 0 when unresolved. */
|
|
48
|
+
bytes: number;
|
|
49
|
+
outcome: DistillOutcome;
|
|
50
|
+
}
|
|
51
|
+
/** One aggregated machine × agent row of readCaptureHealth(). */
|
|
52
|
+
export interface CaptureHealthRow {
|
|
53
|
+
machine: string;
|
|
54
|
+
agent: string;
|
|
55
|
+
posts: number;
|
|
56
|
+
sessions: number;
|
|
57
|
+
bytes: number;
|
|
58
|
+
held: number;
|
|
59
|
+
retried: number;
|
|
60
|
+
}
|
|
61
|
+
/** The /dashboard/data capture_health block. day=null + rows=[] when nothing
|
|
62
|
+
* is recorded (fresh install, or every row pruned). */
|
|
63
|
+
export interface CaptureHealth {
|
|
64
|
+
day: string | null;
|
|
65
|
+
rows: CaptureHealthRow[];
|
|
66
|
+
}
|
|
67
|
+
/** Test-only: re-arm the once-per-day prune memo. The memo is deliberately
|
|
68
|
+
* process-global (production must not DELETE per insert); the test suite
|
|
69
|
+
* needs it fresh per case. Exported for testability, same precedent as
|
|
70
|
+
* quarantineMalformedConfig. */
|
|
71
|
+
export declare function resetDistillPruneMemoForTests(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Record one /distill POST outcome. Computes `retried` at insert: 1 when an
|
|
74
|
+
* EARLIER row exists with the same session_id AND same segment_id (matched on
|
|
75
|
+
* COALESCE(segment_id, '') so legacy whole-session POSTs — no segment_id —
|
|
76
|
+
* retry-match on the empty key). A NULL session_id never matches anything:
|
|
77
|
+
* without a session id the POST has no identity to be a retry OF. Also prunes
|
|
78
|
+
* rows older than 7 days, at most once per process per UTC day.
|
|
79
|
+
*/
|
|
80
|
+
export declare function recordDistillActivity(db: Database.Database, entry: DistillActivityEntry): void;
|
|
81
|
+
/**
|
|
82
|
+
* Aggregate the most recent day with rows into per machine × agent bundles.
|
|
83
|
+
* day = today when today has rows, else the most recent day with rows, else
|
|
84
|
+
* null (empty shape). sessions = COUNT(DISTINCT session_id) — NULL session
|
|
85
|
+
* ids don't count (no identity). Sorted bytes DESC (the card's bar scale).
|
|
86
|
+
*/
|
|
87
|
+
export declare function readCaptureHealth(db: Database.Database): CaptureHealth;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* /distill capture-health accounting (#422 Phase 2).
|
|
4
|
+
*
|
|
5
|
+
* BOTH halves of the console's capture-health card live here (the same
|
|
6
|
+
* layering as recall-index.ts / identity-store.ts: pure logic, unit-testable
|
|
7
|
+
* without booting express — mcp-server.ts only wires the record calls into
|
|
8
|
+
* the /distill exits):
|
|
9
|
+
*
|
|
10
|
+
* - recordDistillActivity(db, entry) — one row per /distill POST, whatever
|
|
11
|
+
* the outcome ('ok' | 'skipped' | 'held'). Held rows are the point: they
|
|
12
|
+
* are the failed posts the client will retry next run (its cursor is
|
|
13
|
+
* held), so the card can show "2 held" instead of a silently missing
|
|
14
|
+
* night. `retried` is computed at INSERT (an earlier row with the same
|
|
15
|
+
* session_id + segment_id = this POST is the retry) and never updated.
|
|
16
|
+
*
|
|
17
|
+
* - readCaptureHealth(db) — the aggregation /dashboard/data exposes as
|
|
18
|
+
* `capture_health`: the most recent day with rows, grouped by machine ×
|
|
19
|
+
* agent (posts / sessions / bytes / held / retried), bytes DESC.
|
|
20
|
+
*
|
|
21
|
+
* Retention: 7 days, pruned inside recordDistillActivity at most ONCE per
|
|
22
|
+
* process per UTC day (in-module memo) — the insert path must not pay a
|
|
23
|
+
* DELETE on every POST. Old rows exist to explain recent nights, nothing
|
|
24
|
+
* else; the durable record of WHAT was captured is the memories themselves.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.resetDistillPruneMemoForTests = resetDistillPruneMemoForTests;
|
|
28
|
+
exports.recordDistillActivity = recordDistillActivity;
|
|
29
|
+
exports.readCaptureHealth = readCaptureHealth;
|
|
30
|
+
const storage_js_1 = require("./storage.js");
|
|
31
|
+
/** Once-per-process-per-UTC-day prune memo (see module doc). The UTC day of
|
|
32
|
+
* the last insert that ran the DELETE. Tests reset it because vitest runs
|
|
33
|
+
* every suite in ONE process — see resetDistillPruneMemoForTests. */
|
|
34
|
+
let lastPruneDay = null;
|
|
35
|
+
/** Test-only: re-arm the once-per-day prune memo. The memo is deliberately
|
|
36
|
+
* process-global (production must not DELETE per insert); the test suite
|
|
37
|
+
* needs it fresh per case. Exported for testability, same precedent as
|
|
38
|
+
* quarantineMalformedConfig. */
|
|
39
|
+
function resetDistillPruneMemoForTests() {
|
|
40
|
+
lastPruneDay = null;
|
|
41
|
+
}
|
|
42
|
+
/** Normalize an optional wire string: non-empty trimmed string, else null. */
|
|
43
|
+
function optString(v) {
|
|
44
|
+
if (typeof v !== "string")
|
|
45
|
+
return null;
|
|
46
|
+
const t = v.trim();
|
|
47
|
+
return t.length > 0 ? t : null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Record one /distill POST outcome. Computes `retried` at insert: 1 when an
|
|
51
|
+
* EARLIER row exists with the same session_id AND same segment_id (matched on
|
|
52
|
+
* COALESCE(segment_id, '') so legacy whole-session POSTs — no segment_id —
|
|
53
|
+
* retry-match on the empty key). A NULL session_id never matches anything:
|
|
54
|
+
* without a session id the POST has no identity to be a retry OF. Also prunes
|
|
55
|
+
* rows older than 7 days, at most once per process per UTC day.
|
|
56
|
+
*/
|
|
57
|
+
function recordDistillActivity(db, entry) {
|
|
58
|
+
const ts = entry.ts ?? new Date().toISOString();
|
|
59
|
+
// UTC YYYY-MM-DD of ts — day buckets are UTC everywhere (dashboard
|
|
60
|
+
// conventions; SQLite date('now') is UTC too).
|
|
61
|
+
const day = ts.slice(0, 10);
|
|
62
|
+
const sessionId = optString(entry.sessionId);
|
|
63
|
+
const segmentId = optString(entry.segmentId);
|
|
64
|
+
// Retry detection BEFORE the insert (the new row must not match itself).
|
|
65
|
+
// `IS ?` binds NULL correctly for the session half.
|
|
66
|
+
const retried = sessionId !== null &&
|
|
67
|
+
(db
|
|
68
|
+
.prepare("SELECT 1 FROM distill_activity WHERE session_id IS ? AND COALESCE(segment_id, '') = ? LIMIT 1")
|
|
69
|
+
.get(sessionId, segmentId ?? "") !== undefined)
|
|
70
|
+
? 1
|
|
71
|
+
: 0;
|
|
72
|
+
db.prepare(`INSERT INTO distill_activity (ts, day, machine, agent, session_id, segment_id, bytes, outcome, retried)
|
|
73
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(ts, day, (0, storage_js_1.sanitizeSourceMachine)(entry.machine) ?? "", optString(entry.agent) ?? "unknown", sessionId, segmentId, entry.bytes, entry.outcome, retried);
|
|
74
|
+
// Prune, at most once per process per UTC day. Runs AFTER the insert so the
|
|
75
|
+
// triggering row is subject to the same window as everything else.
|
|
76
|
+
if (lastPruneDay !== day) {
|
|
77
|
+
db.prepare("DELETE FROM distill_activity WHERE day < date('now', '-7 days')").run();
|
|
78
|
+
lastPruneDay = day;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Aggregate the most recent day with rows into per machine × agent bundles.
|
|
83
|
+
* day = today when today has rows, else the most recent day with rows, else
|
|
84
|
+
* null (empty shape). sessions = COUNT(DISTINCT session_id) — NULL session
|
|
85
|
+
* ids don't count (no identity). Sorted bytes DESC (the card's bar scale).
|
|
86
|
+
*/
|
|
87
|
+
function readCaptureHealth(db) {
|
|
88
|
+
const dayRow = db
|
|
89
|
+
.prepare("SELECT MAX(day) AS day FROM distill_activity")
|
|
90
|
+
.get();
|
|
91
|
+
if (!dayRow.day)
|
|
92
|
+
return { day: null, rows: [] };
|
|
93
|
+
const rows = db
|
|
94
|
+
.prepare(`SELECT machine, agent,
|
|
95
|
+
COUNT(*) AS posts,
|
|
96
|
+
COUNT(DISTINCT session_id) AS sessions,
|
|
97
|
+
COALESCE(SUM(bytes), 0) AS bytes,
|
|
98
|
+
COALESCE(SUM(outcome = 'held'), 0) AS held,
|
|
99
|
+
COALESCE(SUM(retried), 0) AS retried
|
|
100
|
+
FROM distill_activity
|
|
101
|
+
WHERE day = ?
|
|
102
|
+
GROUP BY machine, agent
|
|
103
|
+
ORDER BY bytes DESC`)
|
|
104
|
+
.all(dayRow.day);
|
|
105
|
+
return { day: dayRow.day, rows };
|
|
106
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator capture pause (#423 phase 3, D3) — the server-side 200-skip.
|
|
3
|
+
*
|
|
4
|
+
* A pause is a row in `capture_pauses` (migration v18): machine × harness →
|
|
5
|
+
* paused_at. A row EXISTS = paused for that bundle; the /distill handler
|
|
6
|
+
* reads the table per POST, so a pause takes effect on the very next post —
|
|
7
|
+
* no restart — and answers 200 {skipped: true, paused: true}. The 200 is the
|
|
8
|
+
* whole point: capture.ts treats every 200 as confirmed and advances its
|
|
9
|
+
* cursor, so sessions that arrive while paused are deliberately NOT captured
|
|
10
|
+
* and are never re-sent or backfilled. Zero client changes.
|
|
11
|
+
*
|
|
12
|
+
* THE BUNDLE KEY. The pause's (machine, harness) must derive EXACTLY like
|
|
13
|
+
* the traffic it gates: machine via storage.sanitizeSourceMachine ('' when
|
|
14
|
+
* absent) and harness via harnessOfAgent below — the same normalization
|
|
15
|
+
* recordDistillActivity applies when it writes distill_activity, and the same
|
|
16
|
+
* "harness/profile" → "harness" split the console groups its bundles on. A
|
|
17
|
+
* key derived any other way would never match and the pause would silently
|
|
18
|
+
* not fire.
|
|
19
|
+
*
|
|
20
|
+
* LAST-SEEN (presence dots) derives ONLY from /distill activity — the one
|
|
21
|
+
* per-agent-identified traffic the server sees. Recall traffic (/search,
|
|
22
|
+
* /recall-index, /memory) carries no agent/machine identity on the wire, so
|
|
23
|
+
* attributing it would need new client fields — the heartbeats the spec
|
|
24
|
+
* forbids. Thresholds (green ≤36h — a nightly poster reads online through
|
|
25
|
+
* the following day; amber ≤7d — the distill_activity retention window; none
|
|
26
|
+
* beyond or with no rows) are page-side presentation; this module just
|
|
27
|
+
* reports the newest ts per bundle.
|
|
28
|
+
*
|
|
29
|
+
* Pure, unit-testable without express (the capture-health.ts layering):
|
|
30
|
+
* mcp-server.ts and dashboard.ts wire these functions to the live db.
|
|
31
|
+
*/
|
|
32
|
+
import type Database from "better-sqlite3";
|
|
33
|
+
/** The bundle half of a pause key: "harness/profile" → "harness". */
|
|
34
|
+
export interface CapturePauseKey {
|
|
35
|
+
/** Sanitized machine ('' when the post carried none) — matches distill_activity. */
|
|
36
|
+
machine: string;
|
|
37
|
+
/** The harness name (pre-slash part, 128-char cap; 'unknown' when absent/blank). */
|
|
38
|
+
harness: string;
|
|
39
|
+
}
|
|
40
|
+
/** One paused bundle as the dashboard fleet block serves it. */
|
|
41
|
+
export interface CapturePause {
|
|
42
|
+
machine: string;
|
|
43
|
+
harness: string;
|
|
44
|
+
paused_at: string;
|
|
45
|
+
}
|
|
46
|
+
/** One bundle's newest /distill activity row (the presence signal). */
|
|
47
|
+
export interface FleetLastSeen {
|
|
48
|
+
machine: string;
|
|
49
|
+
harness: string;
|
|
50
|
+
last_seen: string;
|
|
51
|
+
/** The outcome of that newest row ('ok' | 'skipped' | 'held' | 'paused'). */
|
|
52
|
+
last_outcome: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Derive the harness from a source_agent wire value — the bundle split the
|
|
56
|
+
* console already uses ("claude-code/main" → "claude-code"): the part before
|
|
57
|
+
* the first '/' when a slash is present at index > 0, else the whole trimmed
|
|
58
|
+
* string, capped at 128. Non-string/blank → "unknown" (mirrors how
|
|
59
|
+
* recordDistillActivity stores the agent when absent).
|
|
60
|
+
*/
|
|
61
|
+
export declare function harnessOfAgent(sourceAgent: unknown): string;
|
|
62
|
+
/**
|
|
63
|
+
* Normalize the raw /distill wire fields into the pause key. MUST match the
|
|
64
|
+
* recordDistillActivity normalization (machine '' when absent, agent
|
|
65
|
+
* 'unknown' when absent) and the console's bundle grouping
|
|
66
|
+
* ((machine||'')+'|'+harness) — see the module doc.
|
|
67
|
+
*/
|
|
68
|
+
export declare function capturePauseKey(machine: unknown, sourceAgent: unknown): CapturePauseKey;
|
|
69
|
+
/** True when a pause row exists for the (machine, harness) bundle. */
|
|
70
|
+
export declare function isCapturePaused(db: Database.Database, machine: string, harness: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Pause (upsert the row, timestamp now) or resume (delete it). Returns the
|
|
73
|
+
* persisted paused_at when pausing, null when resuming. No pruning, ever —
|
|
74
|
+
* see migration v18's provenance comment.
|
|
75
|
+
*/
|
|
76
|
+
export declare function setCapturePause(db: Database.Database, machine: string, harness: string, paused: boolean): string | null;
|
|
77
|
+
/** Every paused bundle (newest first) — the dashboard fleet.pauses block. */
|
|
78
|
+
export declare function listCapturePauses(db: Database.Database): CapturePause[];
|
|
79
|
+
/**
|
|
80
|
+
* The newest /distill activity per bundle: for each (machine, agent) take
|
|
81
|
+
* MAX(ts) with that latest row's outcome, derive the harness per agent, then
|
|
82
|
+
* merge same-bundle agents keeping the newest ts (one dot per bundle, not
|
|
83
|
+
* per profile). Reads only distill_activity, which the recorder prunes to
|
|
84
|
+
* 7 days — older-than-window bundles simply have no rows and no dot.
|
|
85
|
+
*/
|
|
86
|
+
export declare function readFleetLastSeen(db: Database.Database): FleetLastSeen[];
|