@avantf/dsh-mem 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +89 -28
  2. package/cordis.patch.yml +17 -0
  3. package/lib/client.js +155 -14
  4. package/lib/engine/mem/config/config_files.d.ts +26 -0
  5. package/lib/engine/mem/config/domains.d.ts +20 -0
  6. package/lib/engine/mem/config/loader.d.ts +35 -0
  7. package/lib/engine/mem/config/paths.d.ts +53 -0
  8. package/lib/engine/mem/db/chunk.d.ts +26 -0
  9. package/lib/engine/mem/db/conn.d.ts +13 -0
  10. package/lib/engine/mem/db/dao/chunks.d.ts +150 -0
  11. package/lib/engine/mem/db/dao/contradictions.d.ts +98 -0
  12. package/lib/engine/mem/db/dao/documents.d.ts +51 -0
  13. package/lib/engine/mem/db/dao/entities.d.ts +67 -0
  14. package/lib/engine/mem/db/dao/facts.d.ts +472 -0
  15. package/lib/engine/mem/db/dao/stats.d.ts +14 -0
  16. package/lib/engine/mem/db/dao/triples.d.ts +80 -0
  17. package/lib/engine/mem/db/hash.d.ts +2 -0
  18. package/lib/engine/mem/db/knowledge.d.ts +17 -0
  19. package/lib/engine/mem/db/migrations.d.ts +71 -0
  20. package/lib/engine/mem/db/port.d.ts +57 -0
  21. package/lib/engine/mem/db/schema.d.ts +26 -0
  22. package/lib/engine/mem/db/sqlite.d.ts +89 -0
  23. package/lib/engine/mem/db/store.d.ts +35 -0
  24. package/lib/engine/mem/db/tokenizer.d.ts +61 -0
  25. package/lib/engine/mem/db/vectors.d.ts +50 -0
  26. package/lib/engine/mem/dispatch.d.ts +51 -0
  27. package/lib/engine/mem/entities/extract.d.ts +84 -0
  28. package/lib/engine/mem/eval/loader.d.ts +19 -0
  29. package/lib/engine/mem/eval/metrics.d.ts +25 -0
  30. package/lib/engine/mem/eval/runner.d.ts +10 -0
  31. package/lib/engine/mem/eval/write_metrics.d.ts +88 -0
  32. package/lib/engine/mem/git.d.ts +73 -0
  33. package/lib/engine/mem/hrr/algebra.d.ts +30 -0
  34. package/lib/engine/mem/hrr/atoms.d.ts +22 -0
  35. package/lib/engine/mem/hrr/encode.d.ts +32 -0
  36. package/lib/engine/mem/hrr/index.d.ts +3 -0
  37. package/lib/engine/mem/index.d.ts +27 -0
  38. package/lib/engine/mem/lifecycle/contradiction.d.ts +156 -0
  39. package/lib/engine/mem/lifecycle/index.d.ts +5 -0
  40. package/lib/engine/mem/lifecycle/maintenance.d.ts +48 -0
  41. package/lib/engine/mem/lifecycle/presence.d.ts +29 -0
  42. package/lib/engine/mem/lifecycle/tick.d.ts +43 -0
  43. package/lib/engine/mem/lifecycle/trust.d.ts +90 -0
  44. package/lib/engine/mem/modelBootstrap.d.ts +207 -0
  45. package/lib/engine/mem/router.d.ts +51 -0
  46. package/lib/engine/mem/runtime.d.ts +232 -0
  47. package/lib/engine/mem/store/common.d.ts +82 -0
  48. package/lib/engine/mem/store/doc_files.d.ts +125 -0
  49. package/lib/engine/mem/store/document_text.d.ts +47 -0
  50. package/lib/engine/mem/store/floors.d.ts +191 -0
  51. package/lib/engine/mem/store/hybrid.d.ts +232 -0
  52. package/lib/engine/mem/store/ingest_guard.d.ts +25 -0
  53. package/lib/engine/mem/store/knowledge.d.ts +483 -0
  54. package/lib/engine/mem/store/lexical.d.ts +67 -0
  55. package/lib/engine/mem/store/memory.d.ts +543 -0
  56. package/lib/engine/mem/store/source_picker.d.ts +43 -0
  57. package/lib/engine/mem-contract/config.d.ts +129 -0
  58. package/lib/engine/mem-contract/env.d.ts +31 -0
  59. package/lib/engine/mem-contract/family.d.ts +25 -0
  60. package/lib/engine/mem-contract/index.d.ts +8 -0
  61. package/lib/engine/mem-contract/log.d.ts +29 -0
  62. package/lib/engine/mem-contract/remote.d.ts +80 -0
  63. package/lib/engine/mem-contract/text.d.ts +92 -0
  64. package/lib/engine/mem-contract/tools.d.ts +284 -0
  65. package/lib/engine/mem-contract/types.d.ts +615 -0
  66. package/lib/engine/mem-retrieval/adapters/auto_vstore.d.ts +47 -0
  67. package/lib/engine/mem-retrieval/adapters/hnswlib.d.ts +174 -0
  68. package/lib/engine/mem-retrieval/adapters/local_bge.d.ts +121 -0
  69. package/lib/engine/mem-retrieval/adapters/local_numpy.d.ts +28 -0
  70. package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +52 -0
  71. package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +10 -0
  72. package/lib/engine/mem-retrieval/adapters/warm_gate.d.ts +46 -0
  73. package/lib/engine/mem-retrieval/budget.d.ts +30 -0
  74. package/lib/engine/mem-retrieval/fusion.d.ts +53 -0
  75. package/lib/engine/mem-retrieval/index.d.ts +14 -0
  76. package/lib/engine/mem-retrieval/interfaces.d.ts +82 -0
  77. package/lib/engine/mem-retrieval/log.d.ts +11 -0
  78. package/lib/engine/mem-retrieval/registry.d.ts +23 -0
  79. package/lib/engine/mem-retrieval/rerank.d.ts +13 -0
  80. package/lib/engine/mem-retrieval/stats.d.ts +64 -0
  81. package/lib/engine/mem-retrieval/text_budget.d.ts +52 -0
  82. package/lib/index.js +1773 -473
  83. package/lib/interface-version.json +2 -2
  84. package/lib/types/client/domains.d.ts +5 -4
  85. package/lib/types/client/domains.d.ts.map +1 -1
  86. package/lib/types/client/index.d.ts.map +1 -1
  87. package/lib/types/client/ingest.d.ts +1 -1
  88. package/lib/types/client/wire.d.ts +36 -0
  89. package/lib/types/client/wire.d.ts.map +1 -0
  90. package/lib/types/envinit-bootstrap.d.ts +3 -2
  91. package/lib/types/envinit.d.ts +2 -19
  92. package/lib/types/envinit.d.ts.map +1 -1
  93. package/lib/types/hints.d.ts +1 -1
  94. package/lib/types/index.d.ts +8 -3
  95. package/lib/types/index.d.ts.map +1 -1
  96. package/lib/types/open.d.ts +61 -11
  97. package/lib/types/open.d.ts.map +1 -1
  98. package/lib/types/prompt.d.ts +19 -1
  99. package/lib/types/prompt.d.ts.map +1 -1
  100. package/lib/types/provision.d.ts +1 -1
  101. package/lib/types/provision.d.ts.map +1 -1
  102. package/lib/types/reconcile.d.ts +64 -0
  103. package/lib/types/reconcile.d.ts.map +1 -0
  104. package/lib/types/remote.d.ts +53 -0
  105. package/lib/types/remote.d.ts.map +1 -1
  106. package/lib/types/render.d.ts +35 -0
  107. package/lib/types/render.d.ts.map +1 -0
  108. package/lib/types/tool_schema.d.ts +3 -13
  109. package/lib/types/tool_schema.d.ts.map +1 -1
  110. package/package.json +24 -15
  111. package/lib/client.js.map +0 -1
  112. package/lib/types/client/domains.js +0 -25
  113. package/lib/types/client/domains.js.map +0 -1
  114. package/lib/types/client/index.js +0 -1371
  115. package/lib/types/client/index.js.map +0 -1
  116. package/lib/types/client/ingest.js +0 -30
  117. package/lib/types/client/ingest.js.map +0 -1
  118. package/lib/types/client/paging.js +0 -36
  119. package/lib/types/client/paging.js.map +0 -1
  120. package/lib/types/client/summarize.js +0 -79
  121. package/lib/types/client/summarize.js.map +0 -1
  122. package/lib/types/data_home.js +0 -26
  123. package/lib/types/data_home.js.map +0 -1
  124. package/lib/types/envinit-bootstrap.js +0 -333
  125. package/lib/types/envinit.js +0 -402
  126. package/lib/types/envinit.js.map +0 -1
  127. package/lib/types/hints.js +0 -53
  128. package/lib/types/hints.js.map +0 -1
  129. package/lib/types/index.js +0 -755
  130. package/lib/types/index.js.map +0 -1
  131. package/lib/types/inject.js +0 -14
  132. package/lib/types/inject.js.map +0 -1
  133. package/lib/types/interface_gate.js +0 -84
  134. package/lib/types/interface_gate.js.map +0 -1
  135. package/lib/types/open.js +0 -133
  136. package/lib/types/open.js.map +0 -1
  137. package/lib/types/prompt.js +0 -148
  138. package/lib/types/prompt.js.map +0 -1
  139. package/lib/types/provision.js +0 -177
  140. package/lib/types/provision.js.map +0 -1
  141. package/lib/types/remote.js +0 -247
  142. package/lib/types/remote.js.map +0 -1
  143. package/lib/types/tool_schema.js +0 -149
  144. package/lib/types/tool_schema.js.map +0 -1
@@ -0,0 +1,615 @@
1
+ /** Shared domain types for avantf-mem. Single source of truth in @avantf/mem-contract. */
2
+ export declare const CATEGORY_VALUES: readonly ["user_pref", "user_env", "project", "tool", "general"];
3
+ export type FactCategory = (typeof CATEGORY_VALUES)[number] | string;
4
+ /**
5
+ * The fact statuses a caller may filter by, as the ONE list every surface reads: the tool schema's
6
+ * `z.enum`, the derived MCP inputSchema and DSH parameter spec, and the CLI's `--status` flag (which
7
+ * used to hand-copy the pair, so a new status reached every consumer except the CLI).
8
+ */
9
+ export declare const FACT_STATUSES: readonly ["active", "archived"];
10
+ export type FactStatus = (typeof FACT_STATUSES)[number];
11
+ export interface FactSummary {
12
+ fact_id: number;
13
+ content: string;
14
+ category: string;
15
+ status: FactStatus;
16
+ /**
17
+ * Trust as of now: the decayed value for an ACTIVE fact, the stored value for a
18
+ * non-active one. It never influences ranking (TRUST_MODEL.md D9).
19
+ */
20
+ trust_score: number;
21
+ /** Permanent memory: never decays, never auto-archives, never purged (D6). */
22
+ pinned: boolean;
23
+ /** Remaining ACTIVE days before `forgot`; null when pinned / non-active / trust disabled. */
24
+ remaining_days: number | null;
25
+ helpful_count: number;
26
+ mirror_source: string | null;
27
+ created_at: string;
28
+ archived_at: string | null;
29
+ archive_reason: string | null;
30
+ }
31
+ export interface FactTriple {
32
+ subj: string;
33
+ pred: string;
34
+ obj: string;
35
+ confidence: number;
36
+ }
37
+ /**
38
+ * The retention diagnostics on a fact view: the trust value, the active days left before the
39
+ * trust clock forgets it, and the reinforcement counter.
40
+ *
41
+ * They belong to the OPERATOR surfaces (the settings page, the CLI, `trust_diagnose`) and are
42
+ * stripped from model-facing tool results — see {@link withoutRetentionDiagnostics}. The model
43
+ * can neither observe these clocks nor act on them, and prompt/result text about them only
44
+ * invites "refresh a fact by rewriting it", which defeats the policy it was told about.
45
+ */
46
+ export declare const RETENTION_DIAGNOSTIC_FIELDS: readonly ["trust_score", "remaining_days", "helpful_count"];
47
+ /**
48
+ * Drop the retention diagnostics from a fact payload (a `FactSummary`/`FactDetail`, or the
49
+ * `{ facts: [...] }` page `mem_admin list` returns). Non-object values pass through, so a
50
+ * caller can apply it without knowing the exact shape.
51
+ */
52
+ export declare function withoutRetentionDiagnostics<T>(value: T): T;
53
+ export interface FactDetail extends FactSummary {
54
+ retrieval_count: number;
55
+ mirror_target: string | null;
56
+ supersedes_id: number | null;
57
+ entities: string[];
58
+ triples: FactTriple[];
59
+ /** Active-day clock value at the last settle. */
60
+ settle_clock: number;
61
+ pinned_at: string | null;
62
+ last_reinforced_at: string | null;
63
+ /**
64
+ * When this row was last CHANGED (edit/pin/unpin/reinforce/archive/restore). Retrieval and the
65
+ * daily settle do not write it, so it is not `last_retrieved_at`; `null` on a legacy row.
66
+ */
67
+ updated_at: string | null;
68
+ /** Effective reinforcement events in the current 24h window (recall + feedback). */
69
+ bonus_count: number;
70
+ bonus_window_at: string | null;
71
+ }
72
+ /** A single hit from hybrid retrieval over the merged candidate pool. */
73
+ export interface RecallHit {
74
+ kind: 'fact' | 'doc_chunk';
75
+ ref_id: number;
76
+ text: string;
77
+ score: number;
78
+ /**
79
+ * Knowledge-base taxonomy (`domain → source`) of a `doc_chunk`, taken from the
80
+ * `documents` row by the store that owns it. Both are null for memory facts.
81
+ *
82
+ * Carried as fields rather than re-parsed out of `source_ref`: either name may
83
+ * itself contain `:` (`domain:source:docId:idx`), so any split of `source_ref`
84
+ * is a guess. Filters must use these, and so must the UI.
85
+ */
86
+ domain: string | null;
87
+ source: string | null;
88
+ source_ref: string;
89
+ entities: string[];
90
+ /**
91
+ * When this hit was first recorded: a fact's revision chain keeps the ORIGINAL row's
92
+ * `created_at` across a supersede (`mem_remember update`), so this is when the memory was
93
+ * first asserted, not when its current text was written (see `updated_at`). A doc_chunk
94
+ * carries its owning document's `created_at`.
95
+ */
96
+ created_at: string;
97
+ /**
98
+ * When this row was last CHANGED — an edit, pin/unpin, reinforcement, archive or restore.
99
+ * Retrieval and the daily settle deliberately do not write it, so it never means "last
100
+ * retrieved"; a copy can carry `null` on a legacy row. A doc_chunk carries its owning
101
+ * document's `updated_at` (re-ingest bumps it).
102
+ */
103
+ updated_at: string | null;
104
+ /**
105
+ * Set when an output budget shortened `text` (see `retriever.max_output_tokens` and the
106
+ * `max_tokens` call parameter). An entry can be truncated to EMPTY: the hit still carries
107
+ * `source_ref`, so the caller knows it exists and can fetch it explicitly.
108
+ */
109
+ truncated?: boolean;
110
+ }
111
+ /**
112
+ * The relevance floors in force for ONE retrieval (`0` = that leg is not gated).
113
+ *
114
+ * These are values on the legs' OWN raw scales, applied before `fuse()`. They are reported because
115
+ * an empty result has two very different readings — "the floors removed every candidate" and "no
116
+ * leg had a candidate at all" — and only the effective values plus
117
+ * {@link RetrievalFloorDrops} tell them apart.
118
+ */
119
+ export interface RetrievalFloors {
120
+ /** Cosine floor for the semantic leg (`retriever.min_semantic_similarity`). */
121
+ semantic: number;
122
+ /** Distinct-query-term floor for the FTS leg (`retriever.min_fts_terms`; 1 when degraded). */
123
+ fts: number;
124
+ /** Entity-overlap ratio floor (`retriever.min_jaccard`). */
125
+ jaccard: number;
126
+ }
127
+ /**
128
+ * How many candidates each leg dropped because they fell below their floor.
129
+ *
130
+ * A leg absent from a query (semantic down, no query entities) reports 0 here — the counter is
131
+ * "dropped by a floor", not "was not consulted". `hrr` shares the Jaccard floor and is only
132
+ * separately named so a probe whose candidate set the floor narrowed is still attributable.
133
+ */
134
+ export interface RetrievalFloorDrops {
135
+ semantic: number;
136
+ fts: number;
137
+ jaccard: number;
138
+ hrr: number;
139
+ }
140
+ /**
141
+ * The floor profiles a retrieval CALL may ask for. There is deliberately no numeric per-call
142
+ * override: the two profiles are named policies, and their values live in ONE place
143
+ * (`@avantf/mem-retrieval`'s `store/floors.ts`), so a caller cannot invent a third calibration.
144
+ *
145
+ * - **omitted** — the default policy: the configured (strict) floors, and if they empty the result
146
+ * while having dropped candidates, ONE relaxed pass over the absolute bottom line before
147
+ * answering. `RecallResult.relaxed` marks that second pass.
148
+ * - **`strict`** — the configured floors with NO fallback. This is what the UI's 严格 mode sends, so
149
+ * the panel can show the honest strict outcome plus how many candidates the floors removed.
150
+ * - **`loose`** — the relaxed floors outright (never "no floors": it still answers "nothing
151
+ * relevant" for an unrelated question).
152
+ *
153
+ * Why a second pass exists at all. The floors are calibrated for paraphrase-style relevance, and
154
+ * there is a band where no threshold separates "answers the question" from "unrelated": measured on
155
+ * the live store, the fact answering 「我是谁」 scored cosine 0.444 while four unrelated queries
156
+ * topped out at 0.384 — a 0.06 margin, and the answering fact was the query's own top-1. A relaxed
157
+ * PASS is bounded (it runs only on an empty strict result and keeps its own floor); a lower default,
158
+ * or exempting the user's `pinned` archive from the floors, trades precision on every query.
159
+ */
160
+ export declare const FLOOR_PROFILES: readonly ["strict", "loose"];
161
+ export type FloorProfile = (typeof FLOOR_PROFILES)[number];
162
+ export interface RecallResult {
163
+ hits: RecallHit[];
164
+ degraded: boolean;
165
+ weights: {
166
+ semantic: number;
167
+ fts: number;
168
+ jaccard: number;
169
+ };
170
+ /**
171
+ * The effective relevance floors for this query, when it ran through the hybrid orchestration.
172
+ * Absent on graph-only answers (`probe`/`chain`/`reason`/`related`), which never scored a leg.
173
+ * When {@link RecallResult.relaxed} is true these are the RELAXED values that produced the hits.
174
+ */
175
+ floors?: RetrievalFloors;
176
+ /** Per-leg candidates removed by those floors (see {@link RetrievalFloorDrops}). */
177
+ dropped_by_floor?: RetrievalFloorDrops;
178
+ /**
179
+ * The strict floors emptied this query's result and ONE relaxed pass supplied these hits (`floors`
180
+ * are that pass's values). Absent on an ordinary answer, on an explicit `floors: 'loose'` request
181
+ * (the caller asked for it) and on a graph-only answer. Its purpose is honesty at the UI: relaxed
182
+ * hits sit below the configured relevance bar and must not look as trustworthy as strict ones.
183
+ */
184
+ relaxed?: boolean;
185
+ }
186
+ /**
187
+ * Weights reported when the semantic leg is unavailable (FTS+entity rebalance).
188
+ * Single source for both stores and the cross-retrieval router — previously the
189
+ * same literal lived in three files and could silently desync.
190
+ */
191
+ export declare const DEGRADED_WEIGHTS: RecallResult['weights'];
192
+ export interface ContradictionRecord {
193
+ contradiction_id: number;
194
+ fact_a: number;
195
+ fact_b: number;
196
+ score: number;
197
+ detected_at: string;
198
+ content_a: string;
199
+ content_b: string;
200
+ category_a: string | null;
201
+ category_b: string | null;
202
+ resolved: number;
203
+ }
204
+ /**
205
+ * One contradiction found while WRITING a fact, reported from that fact's point of view.
206
+ *
207
+ * Declared here (not in `core`) because it is a tool-result payload: the settings page and
208
+ * the model both consume it, and AGENTS.md makes this package the single source for tool
209
+ * and UI payload shapes. `fact_a`/`fact_b` are normalized in the log; the write path maps
210
+ * them to the OTHER side of the pair.
211
+ */
212
+ export interface DetectedContradiction {
213
+ /**
214
+ * Row id of the conflict pair — the handle `mem_admin contradict_resolve` takes. Reported here
215
+ * because the write path already holds it: without it, a writer that just created a conflict
216
+ * has to go back through `mem_recall contradict` to find the id it was already told about.
217
+ */
218
+ contradiction_id: number;
219
+ other_fact_id: number;
220
+ score: number;
221
+ }
222
+ /** Result of `mem_remember` `add`/`update` — the shape the tool returns and the UI reads. */
223
+ export interface RememberResult {
224
+ fact_id: number;
225
+ is_new: boolean;
226
+ revived: boolean;
227
+ entities: string[];
228
+ /**
229
+ * Contradictions detected against the existing corpus while writing this fact (DESIGN §11).
230
+ * Present only when at least one was found, so the writer learns immediately instead of
231
+ * discovering it from a later sweep.
232
+ */
233
+ contradictions?: DetectedContradiction[];
234
+ }
235
+ /** One context type's share of the retrieval counters (see `RetrievalHealthSummary.by_kind`). */
236
+ export interface KindHealth {
237
+ queries: number;
238
+ zero_results: number;
239
+ results: number;
240
+ }
241
+ /** The raw retrieval-health counters the engine keeps in memory and persists (DESIGN §20). */
242
+ export interface RetrievalHealth {
243
+ queries: number;
244
+ zero_results: number;
245
+ results: number;
246
+ latency_ms_total: number;
247
+ latency_ms_max: number;
248
+ /** Queries where the semantic leg was live / degraded (FTS+entity only). */
249
+ semantic_live: number;
250
+ semantic_degraded: number;
251
+ rerank_used: number;
252
+ rerank_fallback: number;
253
+ /** Model-facing text that had to be bounded by a budget (see `retrieval-core/text_budget`). */
254
+ embedding_truncated: number;
255
+ rerank_truncated: number;
256
+ output_truncated: number;
257
+ /**
258
+ * Retrieval legs that returned exactly their cap, i.e. whose tail was dropped (`retriever.leg_cap`).
259
+ * A leg that finished under the cap cannot have been cut, so `size === cap` is the observable
260
+ * signal — and the only one, since fusion scaling keeps a trimmed tail from moving the survivors.
261
+ */
262
+ legs_capped: number;
263
+ /**
264
+ * Candidates dropped because they fell below a leg's relevance floor (summed per leg, see
265
+ * {@link RetrievalFloorDrops}). Zero-result queries are the reason this counter exists: with it,
266
+ * "the floors cut everything" is distinguishable from "nothing matched" after the fact.
267
+ */
268
+ candidates_dropped_by_floor: number;
269
+ updated_at: string | null;
270
+ by_kind: Record<string, KindHealth>;
271
+ }
272
+ /** Derived, rounded rates — what the diagnostics surfaces actually print. */
273
+ export interface RetrievalHealthSummary {
274
+ queries: number;
275
+ zero_result_rate: number;
276
+ avg_results_per_query: number;
277
+ avg_latency_ms: number;
278
+ max_latency_ms: number;
279
+ semantic_live_rate: number;
280
+ rerank_used: number;
281
+ rerank_fallback: number;
282
+ embedding_truncated: number;
283
+ rerank_truncated: number;
284
+ output_truncated: number;
285
+ /** Retrieval legs that returned exactly their cap (see `RetrievalHealth`). */
286
+ legs_capped: number;
287
+ /** Candidates removed by a leg's relevance floor (see `RetrievalHealth`). */
288
+ candidates_dropped_by_floor: number;
289
+ updated_at: string | null;
290
+ by_kind: Record<string, KindHealth>;
291
+ }
292
+ /** `mem_admin stats`: store counts plus the retrieval-health counters. */
293
+ export interface StatsSummary {
294
+ active: number;
295
+ archived: number;
296
+ retrieval: RetrievalHealthSummary;
297
+ }
298
+ /** One page of facts (`mem_admin list`). */
299
+ export interface FactPage {
300
+ facts: FactSummary[];
301
+ count: number;
302
+ total: number;
303
+ /** `true` when more rows exist beyond this page (`offset + count < total`). */
304
+ truncated: boolean;
305
+ }
306
+ /** `mem_admin trust_diagnose` — the retention report (operator-facing, never model-facing). */
307
+ export interface TrustDiagnostic {
308
+ enabled: boolean;
309
+ clock: number;
310
+ active: number;
311
+ pinned: number;
312
+ /** ACTIVE, unpinned facts whose decayed trust reaches zero within the report's horizon. */
313
+ forgetting_soon: number;
314
+ reinforced_today: number;
315
+ bonus_granted_today: number;
316
+ idle_candidates: number;
317
+ archived_by_reason: Record<string, number>;
318
+ oldest_settle_clock: number | null;
319
+ /**
320
+ * ACTIVE facts whose embedding-leg conflict check has not run — they have no vector yet, so the
321
+ * leg could not run when they were written (the embedder was unavailable). Durable: the queue is
322
+ * a column, not an in-process Set, so it survives a restart and `contradict_check` drains it in
323
+ * bounded batches.
324
+ */
325
+ conflict_pending: number;
326
+ /** ACTIVE facts still carrying entity/triple rows from an older extraction rules version. */
327
+ entities_stale: number;
328
+ }
329
+ /** `mem_admin vectors_diagnose` (see DESIGN §20 for what `space_stale` separates from `stale`). */
330
+ export interface VectorsDiagnostic {
331
+ total: number;
332
+ with_semantic: number;
333
+ missing: number;
334
+ /** Persisted vectors whose dim no longer matches `semantic.dim`. */
335
+ stale: number;
336
+ /** Usable vectors written in a DIFFERENT vector space (model swap, or pre-space-id rows). */
337
+ space_stale: number;
338
+ indexed: number;
339
+ unindexed: number;
340
+ models: Record<string, number>;
341
+ /** Which backend is actually serving reads (`auto:local_numpy` → `auto:hnswlib`). */
342
+ store: string;
343
+ }
344
+ /**
345
+ * `mem_admin vectors_fix`: what a repair did, or — with `dry_run` — what it WOULD do.
346
+ *
347
+ * `semantic_available` is "the model is loaded RIGHT NOW" (a dry run must not load it), so the
348
+ * preview also carries `would_warm`: together they separate "unknown until you run it" from
349
+ * "the warmup was attempted and failed".
350
+ */
351
+ export interface VectorsFixReport {
352
+ missing: number;
353
+ stale: number;
354
+ space_stale: number;
355
+ unindexed: number;
356
+ reindexed: number;
357
+ dropped: number;
358
+ fixed: number;
359
+ semantic_available: boolean;
360
+ would_warm: boolean;
361
+ dry_run: boolean;
362
+ }
363
+ /** `kb_manage ingest`: what one document produced. */
364
+ export interface IngestResult {
365
+ doc_id: number;
366
+ chunks: number;
367
+ /**
368
+ * How many chunks did NOT get a semantic vector. Absent when every chunk did, so a healthy ingest
369
+ * stays as quiet as it was.
370
+ *
371
+ * `chunks` counts what was CHUNKED, not what became semantically searchable: an encode failure (no
372
+ * model, bad input) or a write failure (`SQLITE_BUSY` under multi-process contention) skips that
373
+ * chunk while the text/entity/FTS indexes stay complete. Reporting the shortfall here is what stops
374
+ * "入库成功 N 段" from implying "N 段都能被语义检索到" — before this, the only way to find out was to
375
+ * run `kb_reindex` and compare `vectors_stale` against `vectors_encoded`. A missing embedding model
376
+ * reports every chunk, which is the honest reading of "indexed, but not semantically".
377
+ */
378
+ vectors_failed?: number;
379
+ /**
380
+ * How the source bytes became text: `utf-8`, `utf-8-bom`, `utf-16le`, `utf-16be` or `gb18030`
381
+ * (absent for pasted text, for a document pulled back from its managed file, and for a PDF —
382
+ * whose text comes from an extracted layer rather than from a byte encoding). Reported because
383
+ * decoding a legacy-encoded file is a GUESS the caller should be able to see.
384
+ */
385
+ encoding?: string;
386
+ /**
387
+ * Which converter turned the source into Markdown — `docx`, `xlsx`, `html` or `csv` (absent for
388
+ * text, PDF, and a body re-read from its managed file). The body is derived rather than read in
389
+ * that case, so the caller is told what derived it.
390
+ */
391
+ converter?: string;
392
+ /** What that conversion could not carry over (dropped media, a truncated sheet, ragged rows). */
393
+ warnings?: string[];
394
+ /** The managed file this ingest wrote (`knowledge.docs.dir/<domain>/<source>/<title>.md`). */
395
+ file?: string;
396
+ /** Why the managed file could not be written; the document itself is still searchable. */
397
+ file_error?: string;
398
+ }
399
+ /**
400
+ * One existing document an ingest would land on — the machine-readable half of a collision.
401
+ *
402
+ * `path` is the managed `.md` copy (`knowledge.docs.dir/<domain>/<source>/<title>.md`), which is
403
+ * what the caller edits to change that document; `doc_id`/`title` are what it takes to name the
404
+ * collision. Reported by BOTH the add-only refusal (as text, for the agent) and the replace
405
+ * confirmation (as a list, for the UI's dialog and the CLI).
406
+ */
407
+ export interface KbIngestConflict {
408
+ doc_id: number;
409
+ title: string;
410
+ path: string;
411
+ }
412
+ /**
413
+ * What `kb_manage` answers when a write would REPLACE an existing document and the caller did not
414
+ * confirm it. `conflict: true` is the flag the UI's 入库 form and the CLI dispatch on; nothing was
415
+ * written when this comes back, so "取消" is a no-op rather than a rollback.
416
+ *
417
+ * `would_overwrite`/`would_add` are the summary the confirmation dialog shows ("将覆盖 N 篇 /
418
+ * 新增 M 篇") — computed at plan time, when the targets are known, rather than re-derived by the
419
+ * caller from `conflicts`.
420
+ */
421
+ export interface KbConflictReport {
422
+ conflict: true;
423
+ /** Chinese, actionable: names the colliding documents and says nothing was written. */
424
+ error: string;
425
+ conflicts: KbIngestConflict[];
426
+ would_overwrite: number;
427
+ would_add: number;
428
+ }
429
+ /**
430
+ * `kb_manage import`: what landed, what failed (with the reason), and what was skipped.
431
+ *
432
+ * `skipped` is the DIRECTORY walk's rejections (files whose name is not an ingestable format) — a
433
+ * silent list before this, which made "3 of 200 files imported" indistinguishable from "3 files".
434
+ * Only the first `SKIPPED_REPORTED` (a `core` constant) paths are listed; `skipped_total` is the real count.
435
+ */
436
+ export interface ImportResult {
437
+ imported: IngestResult[];
438
+ failed: {
439
+ path: string;
440
+ error: string;
441
+ }[];
442
+ skipped: string[];
443
+ skipped_total: number;
444
+ }
445
+ /**
446
+ * One document's managed file, as the file system sees it right now.
447
+ *
448
+ * `stale` means the file's body no longer matches what the KB indexed (someone edited it and
449
+ * the change has not been ingested); `missing` means there is no file for this document (never
450
+ * written, deleted by hand, or replaced by another document's file).
451
+ */
452
+ export interface KbDocFile {
453
+ doc_id: number;
454
+ title: string;
455
+ path: string;
456
+ stale: boolean;
457
+ missing: boolean;
458
+ }
459
+ /**
460
+ * `kb_manage sync`: the managed files vs what the KB indexed.
461
+ *
462
+ * `stale` is the plan and `reingested` what happened (equal unless `dry_run`); `orphans` are
463
+ * managed files with no document behind them — left by a hand-deleted row, and safe to delete.
464
+ */
465
+ export interface KbSyncReport {
466
+ checked: number;
467
+ stale: KbDocFile[];
468
+ missing: KbDocFile[];
469
+ orphans: string[];
470
+ /**
471
+ * Files that sit exactly where a document's path says its own copy belongs, yet do not claim that
472
+ * document — a whole-file overwrite that destroyed the frontmatter.
473
+ *
474
+ * These ARE recovered automatically, but only behind guards: no `doc_id` at all, a non-empty
475
+ * body, a stamp that holds still (see `ADOPT_SETTLE_MS`) and a path exactly one document maps
476
+ * to. A candidate that fails any of them stays here, unclaimed, and `sync {adopt: true}` is the
477
+ * explicit per-document way to take it back immediately.
478
+ */
479
+ unclaimed: KbUnclaimedFile[];
480
+ reingested: number;
481
+ /** How many documents this call adopted (`sync {adopt: true}`); 0 otherwise. */
482
+ adopted: number;
483
+ dry_run: boolean;
484
+ }
485
+ /** One adoptable file: its path names this document, but the file does not claim it. */
486
+ export interface KbUnclaimedFile {
487
+ doc_id: number;
488
+ title: string;
489
+ path: string;
490
+ }
491
+ /**
492
+ * `kb_manage reindex`: what it did, and what it deliberately did NOT redo (DESIGN §20).
493
+ *
494
+ * `vectors_stale` is the plan and `vectors_encoded` what happened; they differ when the embedder
495
+ * is unavailable, which is the case a caller needs to see instead of "nothing to do".
496
+ */
497
+ export interface ReindexReport {
498
+ chunks: number;
499
+ entities_rebuilt: number;
500
+ vectors_stale: number;
501
+ vectors_encoded: number;
502
+ semantic_available: boolean;
503
+ dry_run: boolean;
504
+ }
505
+ /** `kb_manage list`: the stored document columns (everything but `meta`/`status`). */
506
+ export interface DocumentSummary {
507
+ doc_id: number;
508
+ domain: string;
509
+ source: string;
510
+ title: string;
511
+ source_uri: string | null;
512
+ created_at: string;
513
+ updated_at: string;
514
+ }
515
+ /** The full stored document row (`kb detail` minus its chunks). */
516
+ export interface DocumentRecord extends DocumentSummary {
517
+ meta: string;
518
+ status: string;
519
+ }
520
+ /** One stored chunk as `kb detail` returns it. */
521
+ export interface DocumentChunk {
522
+ chunk_id: number;
523
+ idx: number;
524
+ text: string;
525
+ headings_path: string;
526
+ source_ref: string;
527
+ }
528
+ /** `kb_manage detail`: the document row plus its chunks, in document order. */
529
+ export interface DocumentDetail extends DocumentRecord {
530
+ chunks: DocumentChunk[];
531
+ }
532
+ /** What one `kb_manage` source string turned out to be. */
533
+ export type SourceKind = 'url' | 'file' | 'directory' | 'text' | 'missing';
534
+ /**
535
+ * The answer the UI needs to label the input and pick the right `kb_manage` action.
536
+ *
537
+ * It lives in the CONTRACT rather than next to the engine that computes it, because the browser half
538
+ * cannot import the engine (it must not pull node into the client bundle) and used to hand-mirror this
539
+ * shape — the same "mirror drifts, then a render body dereferences a field the host stopped sending"
540
+ * failure that has already cost this family once.
541
+ */
542
+ export interface SourceClassification {
543
+ readonly kind: SourceKind;
544
+ /** Resolved, boundary-checked absolute paths (when `kind` is `file`/`directory`). */
545
+ readonly paths: readonly string[];
546
+ /** Inputs that look like paths but do not resolve, or resolve outside the allowed roots. */
547
+ readonly missing: readonly string[];
548
+ /** Why the missing ones are missing (the boundary's own message; they differ per input). */
549
+ readonly reasons: readonly string[];
550
+ /** How many files a directory holds that ingestion would actually take. */
551
+ readonly files: number;
552
+ }
553
+ /** One row of a directory listing (the 选择 picker's `browseDir`). */
554
+ export interface BrowseEntry {
555
+ readonly name: string;
556
+ readonly path: string;
557
+ /** `dir` navigates; `ingestable` can be picked; `other` exists but ingestion would refuse it. */
558
+ readonly kind: 'dir' | 'ingestable' | 'other';
559
+ }
560
+ /**
561
+ * One directory as the browser picker sees it.
562
+ *
563
+ * Pinned to the engine's `browseDirectory` return BY the host Remote declaration, which annotates its
564
+ * payload with this interface: the two cannot drift without a type error. That check is the reason this
565
+ * shape is spelled here rather than mirrored in the browser half.
566
+ */
567
+ export interface BrowseListing {
568
+ readonly path: string;
569
+ /** The parent directory, or `null` when this is an allowed root (or the filesystem root). */
570
+ readonly parent: string | null;
571
+ /** The configured roots — what the picker may walk when `unrestricted` is false. */
572
+ readonly roots: readonly string[];
573
+ /**
574
+ * `knowledge.ingest.allow_outside_workspace`: the whole filesystem is fair game, so `roots` is
575
+ * informational only and the UI must say "不限制" rather than show a boundary it does not honour.
576
+ */
577
+ readonly unrestricted: boolean;
578
+ readonly entries: readonly BrowseEntry[];
579
+ }
580
+ /**
581
+ * The 知识页 domain picker's option list: the configured `knowledge.domains` allowlist ∪ the
582
+ * domains the library already holds, plus whether the allowlist is active.
583
+ *
584
+ * `restricted` mirrors "the allowlist is non-empty": then the control is a closed `<select>` and a
585
+ * new domain is a config change, not a second spelling of an existing one; an explicitly empty
586
+ * allowlist means no restriction, so the control stays a free input. The union half is not a
587
+ * compatibility layer — without it, narrowing the allowlist would make every existing document
588
+ * un-re-ingestable.
589
+ *
590
+ * It lives in the CONTRACT rather than being mirrored on both halves for the reason every UI payload
591
+ * here does: the host half declared it as an anonymous shape and the browser half hand-copied it,
592
+ * which is exactly how a field goes missing. `KnowledgeStore.domainCatalog()` / `addDomain()` (the
593
+ * real producers) and the picker (the real consumer) now name this one type.
594
+ */
595
+ export interface DomainCatalog {
596
+ readonly domains: readonly string[];
597
+ readonly restricted: boolean;
598
+ }
599
+ /** Which of the two things a document row can open (`openDoc`). */
600
+ export type OpenTarget = 'file' | 'dir';
601
+ /**
602
+ * What `openDoc` launched, for the UI's status line.
603
+ *
604
+ * In the contract, not beside `openDocumentPath` (the host-side producer), for the same reason as
605
+ * {@link DomainCatalog}: the browser half cannot import that module — it pulls `node:child_process` —
606
+ * and used to hand-copy `{ path, opener }` (dropping `target`). The host Remote declaration annotates
607
+ * its payload with this interface, so producer and wire cannot drift without a type error.
608
+ */
609
+ export interface OpenOutcome {
610
+ /** The managed path actually handed to the opener (the file, or the directory holding it). */
611
+ readonly path: string;
612
+ readonly target: OpenTarget;
613
+ /** The command that was actually used (`code`, `explorer.exe`, …). */
614
+ readonly opener: string;
615
+ }
@@ -0,0 +1,47 @@
1
+ import type { VectorStore } from '../interfaces.js';
2
+ /**
3
+ * `auto` — monotonic vector-store upgrade: start on brute-force `local_numpy`,
4
+ * and once the live vector count crosses `auto_thresholds.hnswlib`, migrate to
5
+ * a real `hnswlib` ANN index (only when the native binding actually loads).
6
+ * Never downgrades within a process; if hnswlib is unavailable, stays on numpy.
7
+ *
8
+ * The migration is **lazy**: `rebuild()` (the startup path) only records that an upgrade is due,
9
+ * and the native build happens on the first `topk` that needs it — or on an explicit
10
+ * `prepare()`. Building it in `rebuild()` made every process start pay a full ANN build
11
+ * (measured: 0.75 s at 2000 vectors, 6.3 s at 8000, 14.7 s at 16000) even for a command that
12
+ * never runs a vector search, which is why `avantf-mem list` took seconds.
13
+ */
14
+ export declare class AutoVectorStore implements VectorStore {
15
+ readonly dim: number;
16
+ private delegate;
17
+ private readonly threshold;
18
+ private readonly efSearch;
19
+ private upgradeAttempted;
20
+ /** An upgrade is owed but has not been built yet (see the class comment). */
21
+ private upgradeDue;
22
+ private pendingPersistence;
23
+ constructor(dim: number, threshold?: number, efSearch?: number);
24
+ /** Name of the backend currently serving reads/writes. */
25
+ get name(): string;
26
+ add(id: number, vec: Float32Array): void;
27
+ topk(vec: Float32Array, k: number): {
28
+ id: number;
29
+ score: number;
30
+ }[];
31
+ fetch(ids: number[]): Map<number, Float32Array>;
32
+ remove(id: number): void;
33
+ removeMany(ids: number[]): void;
34
+ count(): number;
35
+ rebuild(rows: Iterable<{
36
+ id: number;
37
+ vec: Float32Array;
38
+ }>): void;
39
+ /** Build the ANN index now if one is owed (see the class comment). */
40
+ prepare(): void;
41
+ attachPersistence(path: string): void;
42
+ flush(): void;
43
+ /** Reclaim dead entries in the migrated backend (the numpy fallback has none). */
44
+ compact(): void;
45
+ private noteCount;
46
+ private maybeUpgrade;
47
+ }