@elvisw/dsh-magic-context 0.0.0-stage → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/NOTICE +93 -0
- package/README.md +452 -2
- package/README.zh-CN.md +368 -0
- package/cordis.patch.yml +20 -0
- package/lib/client.js +2270 -0
- package/lib/core/compartments.js +424 -0
- package/lib/core/corpus-store.js +176 -0
- package/lib/core/directive-detect.js +147 -0
- package/lib/core/domain-reconcile.js +394 -0
- package/lib/core/domains.js +127 -0
- package/lib/core/embedding-local.js +247 -0
- package/lib/core/global-autostage.js +223 -0
- package/lib/core/global-domains.js +302 -0
- package/lib/core/journal.js +248 -0
- package/lib/core/llm-deadline.js +53 -0
- package/lib/core/machine-domain.js +106 -0
- package/lib/core/maintenance.js +380 -0
- package/lib/core/memory-bridge.js +818 -0
- package/lib/core/memory-categories.js +84 -0
- package/lib/core/memory-scoring.js +65 -0
- package/lib/core/memory-search.js +414 -0
- package/lib/core/memory-write.js +1176 -0
- package/lib/core/policy.js +111 -0
- package/lib/core/profile-confirmation.js +55 -0
- package/lib/core/profile-review.js +375 -0
- package/lib/core/profile-write.js +283 -0
- package/lib/core/reference-seeds.js +63 -0
- package/lib/core/route-health.js +106 -0
- package/lib/core/stores.js +1021 -0
- package/lib/core/tagger.js +7 -0
- package/lib/core/types.js +15 -0
- package/lib/core/vector-backfill.js +140 -0
- package/lib/core/vector-lane.js +184 -0
- package/lib/dsh/command-surface.js +376 -0
- package/lib/dsh/compaction-engine.js +231 -0
- package/lib/dsh/config-switches.js +56 -0
- package/lib/dsh/config.js +13 -0
- package/lib/dsh/context-pressure.js +36 -0
- package/lib/dsh/drop-landing.js +42 -0
- package/lib/dsh/emergency.js +89 -0
- package/lib/dsh/flush-command.js +148 -0
- package/lib/dsh/global-review.js +412 -0
- package/lib/dsh/historian.js +907 -0
- package/lib/dsh/live-journal.js +81 -0
- package/lib/dsh/maintenance-wiring.js +229 -0
- package/lib/dsh/management-pass.js +564 -0
- package/lib/dsh/memory-lane.js +535 -0
- package/lib/dsh/pre-step.js +675 -0
- package/lib/dsh/project-drift.js +116 -0
- package/lib/dsh/recomp.js +192 -0
- package/lib/dsh/reduce-guidance.js +188 -0
- package/lib/dsh/seams.js +588 -0
- package/lib/dsh/search-hint.js +186 -0
- package/lib/dsh/session-notice.js +100 -0
- package/lib/dsh/status-command.js +75 -0
- package/lib/dsh/telemetry.js +349 -0
- package/lib/dsh/tool-bridge.js +1009 -0
- package/lib/dsh/volatile.js +89 -0
- package/lib/index.js +931 -0
- package/package.json +108 -4
- package/tools/migrate-profile-package-name.mjs +317 -0
- package/vendor/onnx/LICENSE +27 -0
- package/vendor/onnx/ort-wasm-simd-threaded.mjs +59 -0
- package/vendor/onnx/ort-wasm-simd-threaded.wasm +0 -0
- package/vendor/onnx/ort.node.min.mjs +8 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function tagPrefix(tagNumber) {
|
|
2
|
+
return `§${tagNumber}§ `;
|
|
3
|
+
}
|
|
4
|
+
/** Allocate (idempotently) a tag for one target in one session. */
|
|
5
|
+
export function ensureTag(stores, sessionId, target, now = Date.now()) {
|
|
6
|
+
return stores.allocateTag(sessionId, target, now);
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutral vocabulary shared by the DSH adapter's core logic.
|
|
3
|
+
*
|
|
4
|
+
* NOTHING in `src/core` may import from `@deepseek-ai/*`. The DSH contact
|
|
5
|
+
* surface lives entirely in `src/dsh/`; translation between the two
|
|
6
|
+
* vocabularies happens there. This is the upgrade-insulation contract:
|
|
7
|
+
* when DSH iterates, only `src/dsh/` changes; when Magic Context core is
|
|
8
|
+
* wired in (Phase 1+), it is consumed through its existing host seams and
|
|
9
|
+
* this package's stores, never through DSH types.
|
|
10
|
+
*/
|
|
11
|
+
/** The one error-to-text spelling: every catch that logs or journals a
|
|
12
|
+
* failure composes its message through this, never by re-testing the shape. */
|
|
13
|
+
export function errText(error) {
|
|
14
|
+
return error instanceof Error ? error.message : String(error);
|
|
15
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WP11 — filling the vector lane.
|
|
3
|
+
*
|
|
4
|
+
* Magic Context used to own `memory_embeddings` and refill it. That job left
|
|
5
|
+
* with it, and the consequence is measurable: on 2026-10-10 the live store had
|
|
6
|
+
* 1224 active memories, 820 embedded (67%), and the newest embedded id 210 rows
|
|
7
|
+
* behind the newest id — a lane degrading silently, since a missing vector only
|
|
8
|
+
* ever costs recall quality, never an error.
|
|
9
|
+
*
|
|
10
|
+
* This module writes the missing rows. Every rule below exists because the
|
|
11
|
+
* failure mode here is SILENT:
|
|
12
|
+
*
|
|
13
|
+
* - **Parity is a precondition, not a per-row check.** A vector written into a
|
|
14
|
+
* space this adapter cannot reproduce looks exactly like a good vector
|
|
15
|
+
* afterwards, and it degrades every read that touches it. So the shared-space
|
|
16
|
+
* gate runs first, and a closed gate aborts the WHOLE batch — never "skip the
|
|
17
|
+
* bad ones and write the rest".
|
|
18
|
+
* - **The planned space must be the proven space.** The plan re-reads the
|
|
19
|
+
* dominant id from the store; if that disagrees with the space parity was
|
|
20
|
+
* proven against, the batch is refused rather than written into whichever
|
|
21
|
+
* space happens to be dominant now.
|
|
22
|
+
* - **An empty plan does not call the embedder.** Not merely an optimisation:
|
|
23
|
+
* it is the negative control that proves "no candidates" means "no writes"
|
|
24
|
+
* rather than "the loop happened to have nothing to do".
|
|
25
|
+
* - **A zero or short vector is never stored.** Both would break cosine
|
|
26
|
+
* similarity for every query against that row, silently.
|
|
27
|
+
* - **One row never aborts the batch.** A row that fails to embed or store is
|
|
28
|
+
* counted and skipped; the rest still land, and the next tick retries the gap.
|
|
29
|
+
*/
|
|
30
|
+
import { errText } from "./types.js";
|
|
31
|
+
/**
|
|
32
|
+
* A vector of all zeros is not "no information": cosine similarity against it is
|
|
33
|
+
* undefined (0/0), which some readers turn into NaN and others into a perfect
|
|
34
|
+
* match. Either way it is worse than a missing row, so it is never stored.
|
|
35
|
+
*/
|
|
36
|
+
export function isUsableVector(vector, dims) {
|
|
37
|
+
if (vector === undefined || vector.length !== dims)
|
|
38
|
+
return false;
|
|
39
|
+
// The whole vector must be scanned: an early `return true` on the first
|
|
40
|
+
// non-zero entry would never reach a NaN or an Infinity further along, which
|
|
41
|
+
// is precisely the value that ruins every cosine comparison.
|
|
42
|
+
let anyNonZero = false;
|
|
43
|
+
for (const value of vector) {
|
|
44
|
+
if (!Number.isFinite(value))
|
|
45
|
+
return false;
|
|
46
|
+
if (value !== 0)
|
|
47
|
+
anyNonZero = true;
|
|
48
|
+
}
|
|
49
|
+
return anyNonZero;
|
|
50
|
+
}
|
|
51
|
+
export async function runVectorBackfill(deps) {
|
|
52
|
+
const empty = { created: 0, present: 0, skipped: 0, failed: 0, total: 0 };
|
|
53
|
+
// (1) Parity first, for the whole batch. The one exception is the first-writer
|
|
54
|
+
// bootstrap: an empty store has no space to prove parity against, so the
|
|
55
|
+
// embedder's space becomes the target only when the caller supplies it.
|
|
56
|
+
if (deps.parityModelId === null && deps.bootstrapModelId === undefined) {
|
|
57
|
+
deps.journal.info('vector-lane', 'backfill skipped: shared-space parity is unproven', {
|
|
58
|
+
outcome: 'backfill-parity-unproven',
|
|
59
|
+
...empty,
|
|
60
|
+
});
|
|
61
|
+
return { ...empty, reason: 'parity-unproven' };
|
|
62
|
+
}
|
|
63
|
+
const plan = deps.plan();
|
|
64
|
+
if (plan === undefined)
|
|
65
|
+
return { ...empty, reason: 'no-plan' };
|
|
66
|
+
if (plan.candidates.length === 0) {
|
|
67
|
+
return { ...empty, total: plan.total, reason: 'no-candidates' };
|
|
68
|
+
}
|
|
69
|
+
// (2) The plan's space is re-read from the store; it must be the one we proved,
|
|
70
|
+
// or the bootstrap space when there was nothing to prove yet.
|
|
71
|
+
const targetModelId = deps.parityModelId ?? deps.bootstrapModelId;
|
|
72
|
+
if (targetModelId === undefined || plan.modelId !== targetModelId) {
|
|
73
|
+
deps.journal.warn('vector-lane', 'backfill skipped: planned space is not the parity-proven space', {
|
|
74
|
+
outcome: 'backfill-parity-mismatch',
|
|
75
|
+
planned: plan.modelId,
|
|
76
|
+
...deps.parityModelId === null ? { bootstrap: targetModelId } : { proven: targetModelId },
|
|
77
|
+
});
|
|
78
|
+
return { ...empty, total: plan.total, reason: 'parity-mismatch' };
|
|
79
|
+
}
|
|
80
|
+
const bootstrap = deps.parityModelId === null;
|
|
81
|
+
if (bootstrap) {
|
|
82
|
+
deps.journal.info('vector-lane', 'backfill: creating the first embedding space', {
|
|
83
|
+
outcome: 'backfill-bootstrap',
|
|
84
|
+
modelId: targetModelId,
|
|
85
|
+
total: plan.total,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const batch = plan.candidates.slice(0, Math.max(1, deps.limit));
|
|
89
|
+
const rows = deps.contents(batch).filter(row => row.content.trim().length > 0);
|
|
90
|
+
if (rows.length === 0)
|
|
91
|
+
return { ...empty, total: plan.total, reason: 'nothing-producible' };
|
|
92
|
+
let vectors;
|
|
93
|
+
try {
|
|
94
|
+
vectors = await deps.embed(rows.map(row => row.content));
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
deps.journal.warn('vector-lane', `backfill embed failed: ${errText(error)}`, {
|
|
98
|
+
outcome: 'backfill-embed-failed',
|
|
99
|
+
attempted: rows.length,
|
|
100
|
+
});
|
|
101
|
+
return { ...empty, total: plan.total, reason: 'embed-failed' };
|
|
102
|
+
}
|
|
103
|
+
let created = 0;
|
|
104
|
+
let present = 0;
|
|
105
|
+
let skipped = 0;
|
|
106
|
+
let failed = 0;
|
|
107
|
+
for (const [index, row] of rows.entries()) {
|
|
108
|
+
const vector = vectors[index];
|
|
109
|
+
if (!isUsableVector(vector, deps.dims)) {
|
|
110
|
+
skipped += 1;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const stored = deps.store({ memoryId: row.id, modelId: plan.modelId, vector });
|
|
114
|
+
if (!stored.ok) {
|
|
115
|
+
failed += 1;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (stored.created === true)
|
|
119
|
+
created += 1;
|
|
120
|
+
else
|
|
121
|
+
present += 1;
|
|
122
|
+
}
|
|
123
|
+
const outcome = {
|
|
124
|
+
created,
|
|
125
|
+
present,
|
|
126
|
+
skipped,
|
|
127
|
+
failed,
|
|
128
|
+
total: plan.total,
|
|
129
|
+
};
|
|
130
|
+
deps.journal.info('vector-lane', `backfill: +${String(created)} vector(s) in the ${bootstrap ? 'new' : 'dominant'} space`
|
|
131
|
+
+ `${plan.total > batch.length ? ` (${String(plan.total - batch.length)} still missing)` : ''}`, {
|
|
132
|
+
outcome: 'backfill',
|
|
133
|
+
...outcome,
|
|
134
|
+
modelId: plan.modelId,
|
|
135
|
+
batch: batch.length,
|
|
136
|
+
...outcome.skipped > 0 ? { skipped: outcome.skipped } : {},
|
|
137
|
+
...outcome.failed > 0 ? { failed: outcome.failed } : {},
|
|
138
|
+
});
|
|
139
|
+
return outcome;
|
|
140
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vector lane: measuring the degradation the takeover caused, and planning
|
|
3
|
+
* (never performing) the backfill.
|
|
4
|
+
*
|
|
5
|
+
* Why measuring is the deliverable, not the fix
|
|
6
|
+
* ---------------------------------------------
|
|
7
|
+
* Magic Context used to own `memory_embeddings` and refill it. That job has no
|
|
8
|
+
* owner now, and nothing in the adapter writes embeddings, so the lane decays
|
|
9
|
+
* silently: recall still *works*, it just answers with fewer neighbours. On
|
|
10
|
+
* 2026-10-10 the damage was already measurable and unmeasured until now:
|
|
11
|
+
*
|
|
12
|
+
* active 1224 · embedded 904 (73.9%) · missing 404
|
|
13
|
+
* newest embedded memory id 1103, newest memory id 1326 — nothing written
|
|
14
|
+
* since id 1103 has an embedding, and 194 rows BELOW that frontier lost one
|
|
15
|
+
*
|
|
16
|
+
* Two embedding spaces coexist (≈744 rows in one model, ≈96 in another as of
|
|
17
|
+
* 2026-10-10 — re-measure rather than trusting these). Writing into the wrong
|
|
18
|
+
* space, or into a third, is the specific failure this module exists to prevent:
|
|
19
|
+
* `memory_embeddings` is keyed `(memory_id, model_id)`, so a wrong-space row is
|
|
20
|
+
* not an error — it is simply invisible to the readers that use the other space,
|
|
21
|
+
* forever.
|
|
22
|
+
*
|
|
23
|
+
* So this module only reads: it reports coverage and hands back the id list a
|
|
24
|
+
* backfill WOULD take. The write itself lives in `vector-backfill.ts` (WP11),
|
|
25
|
+
* which consumes exactly this plan. The switch (`memory.vectorBackfill`)
|
|
26
|
+
* defaulted to off until 2026-10-12 and is on by default since (the write path
|
|
27
|
+
* never embeds, so an off-by-default lane let Chinese semantic recall decay
|
|
28
|
+
* with every new row); the phase-D blind spot that motivated the original
|
|
29
|
+
* wording is closed, the parity gate is not optional.
|
|
30
|
+
*/
|
|
31
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
32
|
+
function tableExists(db, name) {
|
|
33
|
+
return db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(name) !== undefined;
|
|
34
|
+
}
|
|
35
|
+
function columnNames(db, table) {
|
|
36
|
+
return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map(row => row.name));
|
|
37
|
+
}
|
|
38
|
+
/** Read the lane's state from the shared store. `undefined` when it cannot be read. */
|
|
39
|
+
export function measureVectorLane(corpusDbPath) {
|
|
40
|
+
let db;
|
|
41
|
+
try {
|
|
42
|
+
db = new DatabaseSync(corpusDbPath, { readOnly: true });
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
if (!tableExists(db, 'memories') || !tableExists(db, 'memory_embeddings'))
|
|
49
|
+
return undefined;
|
|
50
|
+
const memoryColumns = columnNames(db, 'memories');
|
|
51
|
+
const activeFilter = memoryColumns.has('status') ? "WHERE status = 'active'" : '';
|
|
52
|
+
const active = Number(db.prepare(`SELECT COUNT(*) AS c FROM memories ${activeFilter}`).get().c);
|
|
53
|
+
const newestMemoryId = Number(db.prepare(`SELECT COALESCE(MAX(id), 0) AS m FROM memories ${activeFilter}`).get().m);
|
|
54
|
+
// Space shares are measured over the ACTIVE corpus: the question they answer
|
|
55
|
+
// is "which space do live memories actually live in", and a reader only ever
|
|
56
|
+
// looks at active rows.
|
|
57
|
+
const activeJoin = memoryColumns.has('status') ? "WHERE m.status = 'active'" : '';
|
|
58
|
+
const spaces = db.prepare(`SELECT e.model_id AS modelId, COUNT(*) AS rows, COUNT(DISTINCT e.memory_id) AS memories
|
|
59
|
+
FROM memory_embeddings e JOIN memories m ON m.id = e.memory_id ${activeJoin}
|
|
60
|
+
GROUP BY e.model_id ORDER BY rows DESC, modelId`).all();
|
|
61
|
+
const totalRows = spaces.reduce((sum, space) => sum + Number(space.rows), 0);
|
|
62
|
+
const embeddedIds = db.prepare(`SELECT DISTINCT e.memory_id AS id FROM memory_embeddings e JOIN memories m ON m.id = e.memory_id ${memoryColumns.has('status') ? "WHERE m.status = 'active'" : ''}`).all();
|
|
63
|
+
const embedded = embeddedIds.length;
|
|
64
|
+
const dominantModelId = spaces[0]?.modelId;
|
|
65
|
+
const dominantIds = dominantModelId === undefined
|
|
66
|
+
? new Set()
|
|
67
|
+
: new Set(db.prepare(`SELECT DISTINCT e.memory_id AS id FROM memory_embeddings e JOIN memories m ON m.id = e.memory_id
|
|
68
|
+
WHERE e.model_id = ? ${memoryColumns.has('status') ? "AND m.status = 'active'" : ''}`).all(dominantModelId).map(row => Number(row.id)));
|
|
69
|
+
const allEmbeddedIds = new Set(embeddedIds.map(row => Number(row.id)));
|
|
70
|
+
const activeIds = db.prepare(`SELECT id FROM memories ${activeFilter}`).all()
|
|
71
|
+
.map(row => Number(row.id));
|
|
72
|
+
// The frontier is the newest ACTIVE memory that has an embedding. Taking the
|
|
73
|
+
// raw MAX(memory_id) instead would let an archived row's leftover embedding
|
|
74
|
+
// look like progress and hide the backlog behind it.
|
|
75
|
+
const newestEmbeddedMemoryId = embeddedIds.reduce((max, row) => Math.max(max, Number(row.id)), 0);
|
|
76
|
+
let unembeddedAboveFrontier = 0;
|
|
77
|
+
let gapsBelowFrontier = 0;
|
|
78
|
+
let dominantCoverageCount = 0;
|
|
79
|
+
for (const id of activeIds) {
|
|
80
|
+
if (!allEmbeddedIds.has(id)) {
|
|
81
|
+
if (id > newestEmbeddedMemoryId)
|
|
82
|
+
unembeddedAboveFrontier += 1;
|
|
83
|
+
else
|
|
84
|
+
gapsBelowFrontier += 1;
|
|
85
|
+
}
|
|
86
|
+
if (dominantIds.has(id))
|
|
87
|
+
dominantCoverageCount += 1;
|
|
88
|
+
}
|
|
89
|
+
const multiSpaceMemories = Number(db.prepare('SELECT COUNT(*) AS c FROM (SELECT memory_id FROM memory_embeddings GROUP BY memory_id HAVING COUNT(DISTINCT model_id) > 1)').get().c);
|
|
90
|
+
return {
|
|
91
|
+
active,
|
|
92
|
+
embedded,
|
|
93
|
+
missing: active - embedded,
|
|
94
|
+
coverage: active === 0 ? 0 : embedded / active,
|
|
95
|
+
spaces: spaces.map(space => ({
|
|
96
|
+
modelId: space.modelId,
|
|
97
|
+
rows: Number(space.rows),
|
|
98
|
+
memories: Number(space.memories),
|
|
99
|
+
share: totalRows === 0 ? 0 : Number(space.rows) / totalRows,
|
|
100
|
+
})),
|
|
101
|
+
...dominantModelId === undefined ? {} : { dominantModelId },
|
|
102
|
+
frontier: { newestEmbeddedMemoryId, newestMemoryId, unembeddedAboveFrontier },
|
|
103
|
+
gapsBelowFrontier,
|
|
104
|
+
multiSpaceMemories,
|
|
105
|
+
dominantCoverage: active === 0 ? 0 : dominantCoverageCount / active,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
db.close();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Which memories a backfill would take, and which space it may write into.
|
|
117
|
+
*
|
|
118
|
+
* A candidate is an ACTIVE memory with no embedding row under the DOMINANT
|
|
119
|
+
* model. That covers both "never embedded" and "embedded only in the other
|
|
120
|
+
* space" — the second case is how a space migration happens without creating a
|
|
121
|
+
* third space.
|
|
122
|
+
*
|
|
123
|
+
* `bootstrapModelId` is used only when the store has no space at all: it is the
|
|
124
|
+
* first-writer path for an empty corpus. It never overrides an existing
|
|
125
|
+
* dominant space.
|
|
126
|
+
*
|
|
127
|
+
* Writes nothing. The contract write is still ahead.
|
|
128
|
+
*/
|
|
129
|
+
export function planBackfill(corpusDbPath, options = {}) {
|
|
130
|
+
const report = measureVectorLane(corpusDbPath);
|
|
131
|
+
if (report === undefined)
|
|
132
|
+
return undefined;
|
|
133
|
+
const limit = options.limit ?? 64;
|
|
134
|
+
const db = new DatabaseSync(corpusDbPath, { readOnly: true });
|
|
135
|
+
try {
|
|
136
|
+
const bootstrap = options.bootstrapModelId?.trim() ?? '';
|
|
137
|
+
// A bootstrap id may only be used when no space exists at all. Once any
|
|
138
|
+
// embedding row is present, the dominant space is the only legal target;
|
|
139
|
+
// accepting a bootstrap id here would be the third-space bug this module
|
|
140
|
+
// exists to prevent.
|
|
141
|
+
const dominant = report.dominantModelId ?? (bootstrap === '' ? undefined : bootstrap);
|
|
142
|
+
if (dominant === undefined) {
|
|
143
|
+
return { modelId: '', candidates: [], total: 0, reason: 'no embedding space exists yet — the first writer decides the space' };
|
|
144
|
+
}
|
|
145
|
+
const memoryColumns = columnNames(db, 'memories');
|
|
146
|
+
const activeFilter = memoryColumns.has('status') ? "AND m.status = 'active'" : '';
|
|
147
|
+
// Newest first (2026-10-10). The rows a session is most likely to search for
|
|
148
|
+
// are the ones written most recently, so the lane closes the frontier gap
|
|
149
|
+
// before it grinds through the older churn holes. The order is a policy:
|
|
150
|
+
// tests/vector-lane.spec.ts pins it, and flipping it back is the rollback.
|
|
151
|
+
const missing = db.prepare(`SELECT m.id AS id FROM memories m
|
|
152
|
+
WHERE NOT EXISTS (SELECT 1 FROM memory_embeddings e WHERE e.memory_id = m.id AND e.model_id = ?)
|
|
153
|
+
${activeFilter}
|
|
154
|
+
ORDER BY m.id DESC`).all(dominant);
|
|
155
|
+
const ids = missing.map(row => Number(row.id));
|
|
156
|
+
return {
|
|
157
|
+
modelId: dominant,
|
|
158
|
+
candidates: ids.slice(0, limit),
|
|
159
|
+
total: ids.length,
|
|
160
|
+
...ids.length === 0
|
|
161
|
+
? {
|
|
162
|
+
reason: report.active === 0
|
|
163
|
+
? 'no active memories to embed'
|
|
164
|
+
: 'every active memory already has a row in the dominant space',
|
|
165
|
+
}
|
|
166
|
+
: {},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
db.close();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** One line for a health report: the shape of the decay, not just a percentage. */
|
|
177
|
+
export function describeVectorLane(report) {
|
|
178
|
+
const spaces = report.spaces
|
|
179
|
+
.map(space => `${space.modelId.replace('embedding-provider:', '')} ${String(space.rows)} 行 (${(space.share * 100).toFixed(0)}%)`)
|
|
180
|
+
.join(' / ');
|
|
181
|
+
return `active ${String(report.active)} · 已嵌入 ${String(report.embedded)} (${(report.coverage * 100).toFixed(1)}%) · `
|
|
182
|
+
+ `缺 ${String(report.missing)}(前沿之上 ${String(report.frontier.unembeddedAboveFrontier)} / 前沿之下 ${String(report.gapsBelowFrontier)})· `
|
|
183
|
+
+ `空间:${spaces === '' ? '无' : spaces}`;
|
|
184
|
+
}
|