context-vault 3.4.3 → 3.4.4
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/dist/server.js +28 -10
- package/dist/server.js.map +1 -1
- package/dist/tools/get-context.d.ts +2 -1
- package/dist/tools/get-context.d.ts.map +1 -1
- package/dist/tools/get-context.js +22 -2
- package/dist/tools/get-context.js.map +1 -1
- package/dist/tools/save-context.d.ts +2 -1
- package/dist/tools/save-context.d.ts.map +1 -1
- package/dist/tools/save-context.js +63 -2
- package/dist/tools/save-context.js.map +1 -1
- package/node_modules/@context-vault/core/dist/context.d.ts +34 -0
- package/node_modules/@context-vault/core/dist/context.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/context.js +55 -0
- package/node_modules/@context-vault/core/dist/context.js.map +1 -0
- package/node_modules/@context-vault/core/dist/db.d.ts +3 -1
- package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/db.js +29 -2
- package/node_modules/@context-vault/core/dist/db.js.map +1 -1
- package/node_modules/@context-vault/core/dist/search.d.ts +1 -0
- package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/search.js +57 -3
- package/node_modules/@context-vault/core/dist/search.js.map +1 -1
- package/node_modules/@context-vault/core/dist/types.d.ts +6 -0
- package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -1
- package/node_modules/@context-vault/core/package.json +5 -1
- package/node_modules/@context-vault/core/src/context.ts +65 -0
- package/node_modules/@context-vault/core/src/db.ts +29 -2
- package/node_modules/@context-vault/core/src/search.ts +54 -2
- package/node_modules/@context-vault/core/src/types.ts +6 -0
- package/package.json +2 -2
- package/src/server.ts +32 -9
- package/src/tools/get-context.ts +25 -1
- package/src/tools/save-context.ts +62 -1
|
@@ -106,6 +106,7 @@ export async function hybridSearch(
|
|
|
106
106
|
decayDays = 30,
|
|
107
107
|
includeSuperseeded = false,
|
|
108
108
|
includeEphemeral = false,
|
|
109
|
+
contextEmbedding = null,
|
|
109
110
|
} = opts;
|
|
110
111
|
|
|
111
112
|
const rowMap = new Map<string, VaultEntry>();
|
|
@@ -209,9 +210,60 @@ export async function hybridSearch(
|
|
|
209
210
|
}
|
|
210
211
|
}
|
|
211
212
|
|
|
213
|
+
// Context vector pass: KNN against vault_ctx_vec for contextual reinstatement
|
|
214
|
+
const ctxRankedIds: string[] = [];
|
|
215
|
+
if (contextEmbedding) {
|
|
216
|
+
try {
|
|
217
|
+
const ctxVecCount = (
|
|
218
|
+
ctx.db.prepare('SELECT COUNT(*) as c FROM vault_ctx_vec').get() as { c: number }
|
|
219
|
+
).c;
|
|
220
|
+
if (ctxVecCount > 0) {
|
|
221
|
+
const ctxRows = ctx.db
|
|
222
|
+
.prepare(
|
|
223
|
+
`SELECT v.rowid, v.distance FROM vault_ctx_vec v WHERE embedding MATCH ? ORDER BY distance LIMIT 15`
|
|
224
|
+
)
|
|
225
|
+
.all(contextEmbedding, 15) as { rowid: number; distance: number }[];
|
|
226
|
+
|
|
227
|
+
if (ctxRows.length) {
|
|
228
|
+
const ctxRowids = ctxRows.map((cr) => cr.rowid);
|
|
229
|
+
const placeholders = ctxRowids.map(() => '?').join(',');
|
|
230
|
+
const ctxHydrated = ctx.db
|
|
231
|
+
.prepare(`SELECT rowid, * FROM vault WHERE rowid IN (${placeholders})`)
|
|
232
|
+
.all(...ctxRowids) as unknown as (VaultEntry & { rowid: number })[];
|
|
233
|
+
|
|
234
|
+
const ctxByRowid = new Map<number, VaultEntry & { rowid: number }>();
|
|
235
|
+
for (const row of ctxHydrated) ctxByRowid.set(row.rowid, row);
|
|
236
|
+
|
|
237
|
+
for (const cr of ctxRows) {
|
|
238
|
+
const row = ctxByRowid.get(cr.rowid);
|
|
239
|
+
if (!row) continue;
|
|
240
|
+
if (kindFilter && row.kind !== kindFilter) continue;
|
|
241
|
+
if (categoryFilter && row.category !== categoryFilter) continue;
|
|
242
|
+
if (excludeEvents && row.category === 'event') continue;
|
|
243
|
+
if (since && row.created_at < since) continue;
|
|
244
|
+
if (until && row.created_at > until) continue;
|
|
245
|
+
if (row.expires_at && new Date(row.expires_at) <= new Date()) continue;
|
|
246
|
+
|
|
247
|
+
const { rowid: _rowid, ...cleanRow } = row;
|
|
248
|
+
ctxRankedIds.push(cleanRow.id);
|
|
249
|
+
if (!rowMap.has(cleanRow.id)) rowMap.set(cleanRow.id, cleanRow);
|
|
250
|
+
if (!idToRowid.has(cleanRow.id)) idToRowid.set(cleanRow.id, Number(row.rowid));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
if (!(err as Error).message?.includes('no such table')) {
|
|
256
|
+
console.error(`[retrieve] Context vector search error: ${(err as Error).message}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
212
261
|
if (rowMap.size === 0) return [];
|
|
213
262
|
|
|
214
|
-
|
|
263
|
+
// Build ranked lists for RRF: content FTS + content vec + optional context vec
|
|
264
|
+
const rankedLists = [ftsRankedIds, vecRankedIds];
|
|
265
|
+
if (ctxRankedIds.length > 0) rankedLists.push(ctxRankedIds);
|
|
266
|
+
const rrfScores = reciprocalRankFusion(rankedLists);
|
|
215
267
|
|
|
216
268
|
for (const [id, entry] of rowMap) {
|
|
217
269
|
const boost = recencyBoost(entry.created_at, entry.category, decayDays);
|
|
@@ -274,7 +326,7 @@ export async function hybridSearch(
|
|
|
274
326
|
return finalPage;
|
|
275
327
|
}
|
|
276
328
|
|
|
277
|
-
function trackAccess(ctx: BaseCtx, entries: SearchResult[]): void {
|
|
329
|
+
export function trackAccess(ctx: BaseCtx, entries: SearchResult[]): void {
|
|
278
330
|
if (!entries.length) return;
|
|
279
331
|
try {
|
|
280
332
|
const placeholders = entries.map(() => '?').join(',');
|
|
@@ -53,6 +53,8 @@ export interface PreparedStatements {
|
|
|
53
53
|
deleteVecStmt: StatementSync;
|
|
54
54
|
updateSupersededBy: StatementSync;
|
|
55
55
|
clearSupersededByRef: StatementSync;
|
|
56
|
+
insertCtxVecStmt: StatementSync;
|
|
57
|
+
deleteCtxVecStmt: StatementSync;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
export interface VaultEntry {
|
|
@@ -151,6 +153,8 @@ export interface BaseCtx {
|
|
|
151
153
|
embed: (text: string) => Promise<Float32Array | null>;
|
|
152
154
|
insertVec: (rowid: number, embedding: Float32Array) => void;
|
|
153
155
|
deleteVec: (rowid: number) => void;
|
|
156
|
+
insertCtxVec: (rowid: number, embedding: Float32Array) => void;
|
|
157
|
+
deleteCtxVec: (rowid: number) => void;
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
export interface SearchOptions {
|
|
@@ -164,4 +168,6 @@ export interface SearchOptions {
|
|
|
164
168
|
decayDays?: number;
|
|
165
169
|
includeSuperseeded?: boolean;
|
|
166
170
|
includeEphemeral?: boolean;
|
|
171
|
+
/** Pre-computed context embedding for contextual reinstatement boosting. */
|
|
172
|
+
contextEmbedding?: Float32Array | null;
|
|
167
173
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-vault",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
|
|
6
6
|
"bin": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@context-vault/core"
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@context-vault/core": "^3.4.
|
|
66
|
+
"@context-vault/core": "^3.4.4",
|
|
67
67
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
68
68
|
"adm-zip": "^0.5.16",
|
|
69
69
|
"sqlite-vec": "^0.1.0"
|
package/src/server.ts
CHANGED
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
prepareStatements,
|
|
27
27
|
insertVec,
|
|
28
28
|
deleteVec,
|
|
29
|
+
insertCtxVec,
|
|
30
|
+
deleteCtxVec,
|
|
29
31
|
} from '@context-vault/core/db';
|
|
30
32
|
import { registerTools } from './register-tools.js';
|
|
31
33
|
import { pruneExpired } from '@context-vault/core/index';
|
|
@@ -328,6 +330,8 @@ async function main(): Promise<void> {
|
|
|
328
330
|
embed,
|
|
329
331
|
insertVec: (rowid: number, embedding: Float32Array) => insertVec(stmts, rowid, embedding),
|
|
330
332
|
deleteVec: (rowid: number) => deleteVec(stmts, rowid),
|
|
333
|
+
insertCtxVec: (rowid: number, embedding: Float32Array) => insertCtxVec(stmts, rowid, embedding),
|
|
334
|
+
deleteCtxVec: (rowid: number) => deleteCtxVec(stmts, rowid),
|
|
331
335
|
activeOps: { count: 0 },
|
|
332
336
|
toolStats: { ok: 0, errors: 0, lastError: null },
|
|
333
337
|
};
|
|
@@ -480,15 +484,34 @@ async function main(): Promise<void> {
|
|
|
480
484
|
await transport.handleRequest(req, res, (req as any).body);
|
|
481
485
|
return;
|
|
482
486
|
} else if (sessionId) {
|
|
483
|
-
// Stale session (e.g., daemon restarted).
|
|
484
|
-
//
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
487
|
+
// Stale session (e.g., daemon restarted). Claude Code's MCP client
|
|
488
|
+
// does not auto-reinitialize on 404, so we recover transparently:
|
|
489
|
+
// create a new transport reusing the stale session ID, force it into
|
|
490
|
+
// initialized state, and handle the request as if nothing happened.
|
|
491
|
+
console.error(`[context-vault] Recovering stale session ${sessionId.slice(0, 8)}...`);
|
|
492
|
+
|
|
493
|
+
transport = new StreamableHTTPServerTransport({
|
|
494
|
+
sessionIdGenerator: () => sessionId,
|
|
495
|
+
onsessioninitialized: (sid: string) => {
|
|
496
|
+
transports[sid] = transport;
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
transport.onclose = () => {
|
|
500
|
+
if (transports[sessionId]) delete transports[sessionId];
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const sessionServer = createServer();
|
|
504
|
+
await sessionServer.connect(transport);
|
|
505
|
+
|
|
506
|
+
// Force transport into initialized state, bypassing the initialize
|
|
507
|
+
// handshake. The inner WebStandardStreamableHTTPServerTransport holds
|
|
508
|
+
// the _initialized flag and sessionId.
|
|
509
|
+
const inner = (transport as any)._webStandardTransport;
|
|
510
|
+
inner._initialized = true;
|
|
511
|
+
inner.sessionId = sessionId;
|
|
512
|
+
transports[sessionId] = transport;
|
|
513
|
+
|
|
514
|
+
// Fall through to handleRequest below
|
|
492
515
|
} else {
|
|
493
516
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
494
517
|
res.end(JSON.stringify({
|
package/src/tools/get-context.ts
CHANGED
|
@@ -2,9 +2,10 @@ import { z } from 'zod';
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { readFileSync, existsSync } from 'node:fs';
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
|
-
import { hybridSearch } from '@context-vault/core/search';
|
|
5
|
+
import { hybridSearch, trackAccess } from '@context-vault/core/search';
|
|
6
6
|
import { categoryFor } from '@context-vault/core/categories';
|
|
7
7
|
import { normalizeKind } from '@context-vault/core/files';
|
|
8
|
+
import { parseContextParam } from '@context-vault/core/context';
|
|
8
9
|
import { resolveTemporalParams } from '../temporal.js';
|
|
9
10
|
import { collectLinkedEntries } from '../linking.js';
|
|
10
11
|
import { ok, err, errWithHint } from '../helpers.js';
|
|
@@ -346,6 +347,12 @@ export const inputSchema = {
|
|
|
346
347
|
.describe(
|
|
347
348
|
'When true with identity_key, return a clear "not found" instead of falling through to semantic search on miss. Default: false.'
|
|
348
349
|
),
|
|
350
|
+
context: z
|
|
351
|
+
.any()
|
|
352
|
+
.optional()
|
|
353
|
+
.describe(
|
|
354
|
+
'Current context for contextual reinstatement. Boosts entries that were saved in a similar context. Pass a structured object (e.g. { project: "myapp", arc: "auth-rewrite", task: "debugging token expiry" }) or a free-text string. Entries saved with matching encoding_context will rank higher.'
|
|
355
|
+
),
|
|
349
356
|
};
|
|
350
357
|
|
|
351
358
|
export async function handler(
|
|
@@ -369,6 +376,7 @@ export async function handler(
|
|
|
369
376
|
follow_links,
|
|
370
377
|
body_limit,
|
|
371
378
|
strict,
|
|
379
|
+
context,
|
|
372
380
|
}: Record<string, any>,
|
|
373
381
|
ctx: LocalCtx,
|
|
374
382
|
{ ensureIndexed, reindexFailed }: SharedCtx
|
|
@@ -413,6 +421,7 @@ export async function handler(
|
|
|
413
421
|
if (!kindFilter) return err('identity_key requires kind to be specified', 'INVALID_INPUT');
|
|
414
422
|
const match = ctx.stmts.getByIdentityKey.get(kindFilter, identity_key) as any;
|
|
415
423
|
if (match) {
|
|
424
|
+
trackAccess(ctx, [{ ...match, score: 1 }]);
|
|
416
425
|
const entryTags = match.tags ? JSON.parse(match.tags) : [];
|
|
417
426
|
const tagStr = entryTags.length ? entryTags.join(', ') : 'none';
|
|
418
427
|
const relPath =
|
|
@@ -452,6 +461,17 @@ export async function handler(
|
|
|
452
461
|
? Math.min(effectiveLimit * 10, MAX_FETCH_LIMIT)
|
|
453
462
|
: effectiveLimit;
|
|
454
463
|
|
|
464
|
+
// Generate context embedding for contextual reinstatement boosting
|
|
465
|
+
let contextEmbedding: Float32Array | null = null;
|
|
466
|
+
const parsedCtx = parseContextParam(context);
|
|
467
|
+
if (parsedCtx?.text) {
|
|
468
|
+
try {
|
|
469
|
+
contextEmbedding = await ctx.embed(parsedCtx.text);
|
|
470
|
+
} catch {
|
|
471
|
+
// Non-fatal: proceed without context boosting
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
455
475
|
let filtered: any[];
|
|
456
476
|
if (hasQuery) {
|
|
457
477
|
// Hybrid search mode
|
|
@@ -465,6 +485,7 @@ export async function handler(
|
|
|
465
485
|
decayDays: config.eventDecayDays || 30,
|
|
466
486
|
includeSuperseeded: include_superseded ?? false,
|
|
467
487
|
includeEphemeral: include_ephemeral ?? false,
|
|
488
|
+
contextEmbedding,
|
|
468
489
|
});
|
|
469
490
|
|
|
470
491
|
// Post-filter by tags if provided, then apply requested limit
|
|
@@ -532,6 +553,9 @@ export async function handler(
|
|
|
532
553
|
|
|
533
554
|
// Add score field for consistent output
|
|
534
555
|
for (const r of filtered) r.score = 0;
|
|
556
|
+
|
|
557
|
+
// Track access for filter-only results (hybrid search tracks its own)
|
|
558
|
+
trackAccess(ctx, filtered);
|
|
535
559
|
}
|
|
536
560
|
|
|
537
561
|
// Brief score boost: briefs rank slightly higher so consolidated snapshots
|
|
@@ -3,6 +3,7 @@ import { captureAndIndex, updateEntryFile } from '@context-vault/core/capture';
|
|
|
3
3
|
import { indexEntry } from '@context-vault/core/index';
|
|
4
4
|
import { categoryFor, defaultTierFor } from '@context-vault/core/categories';
|
|
5
5
|
import { normalizeKind } from '@context-vault/core/files';
|
|
6
|
+
import { parseContextParam } from '@context-vault/core/context';
|
|
6
7
|
import { ok, err, errWithHint, ensureVaultExists, ensureValidKind } from '../helpers.js';
|
|
7
8
|
import { maybeShowFeedbackPrompt } from '../telemetry.js';
|
|
8
9
|
import { validateRelatedTo } from '../linking.js';
|
|
@@ -310,6 +311,12 @@ export const inputSchema = {
|
|
|
310
311
|
.describe(
|
|
311
312
|
'Conflict resolution mode. "suggest" (default): when similar entries are found, return structured conflict_candidates with suggested_action (ADD/UPDATE/SKIP) and reasoning_context for the calling agent to decide. Thresholds: score > 0.95 → SKIP (near-duplicate), score > 0.85 → UPDATE (very similar), score < 0.85 → ADD (distinct enough). "off": flag similar entries only (legacy behavior).'
|
|
312
313
|
),
|
|
314
|
+
encoding_context: z
|
|
315
|
+
.any()
|
|
316
|
+
.optional()
|
|
317
|
+
.describe(
|
|
318
|
+
'Encoding context for contextual reinstatement. Captures the situation when this entry was created, enabling context-aware retrieval boosting. Pass a structured object (e.g. { project: "myapp", arc: "auth-rewrite", task: "implementing JWT" }) or a free-text string describing the current context.'
|
|
319
|
+
),
|
|
313
320
|
};
|
|
314
321
|
|
|
315
322
|
export async function handler(
|
|
@@ -331,6 +338,7 @@ export async function handler(
|
|
|
331
338
|
similarity_threshold,
|
|
332
339
|
tier,
|
|
333
340
|
conflict_resolution,
|
|
341
|
+
encoding_context,
|
|
334
342
|
}: Record<string, any>,
|
|
335
343
|
ctx: LocalCtx,
|
|
336
344
|
{ ensureIndexed }: SharedCtx
|
|
@@ -388,13 +396,21 @@ export async function handler(
|
|
|
388
396
|
);
|
|
389
397
|
}
|
|
390
398
|
|
|
399
|
+
// Merge encoding context into meta for update path
|
|
400
|
+
const updateParsedCtx = parseContextParam(encoding_context);
|
|
401
|
+
let updateMeta = meta;
|
|
402
|
+
if (updateParsedCtx) {
|
|
403
|
+
updateMeta = { ...(meta || {}) };
|
|
404
|
+
updateMeta.encoding_context = updateParsedCtx.structured || updateParsedCtx.text;
|
|
405
|
+
}
|
|
406
|
+
|
|
391
407
|
let entry;
|
|
392
408
|
try {
|
|
393
409
|
entry = updateEntryFile(ctx, existing, {
|
|
394
410
|
title,
|
|
395
411
|
body,
|
|
396
412
|
tags,
|
|
397
|
-
meta,
|
|
413
|
+
meta: updateMeta,
|
|
398
414
|
source,
|
|
399
415
|
expires_at,
|
|
400
416
|
supersedes,
|
|
@@ -409,6 +425,24 @@ export async function handler(
|
|
|
409
425
|
'context-vault save_context update is failing. Check `cat ~/.context-mcp/error.log | tail -5` and help me debug.'
|
|
410
426
|
);
|
|
411
427
|
}
|
|
428
|
+
|
|
429
|
+
// Store context embedding for updated entry
|
|
430
|
+
if (updateParsedCtx?.text) {
|
|
431
|
+
try {
|
|
432
|
+
const ctxEmbed = await ctx.embed(updateParsedCtx.text);
|
|
433
|
+
if (ctxEmbed) {
|
|
434
|
+
const rowidResult = ctx.stmts.getRowid.get(entry.id) as { rowid: number } | undefined;
|
|
435
|
+
if (rowidResult?.rowid) {
|
|
436
|
+
const rowid = Number(rowidResult.rowid);
|
|
437
|
+
try { ctx.deleteCtxVec(rowid); } catch {}
|
|
438
|
+
ctx.insertCtxVec(rowid, ctxEmbed);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} catch (e) {
|
|
442
|
+
console.warn(`[context-vault] Context embedding update failed: ${(e as Error).message}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
412
446
|
if (entry.related_to?.length && ctx.stmts.updateRelatedTo) {
|
|
413
447
|
ctx.stmts.updateRelatedTo.run(JSON.stringify(entry.related_to), entry.id);
|
|
414
448
|
} else if (entry.related_to === null && ctx.stmts.updateRelatedTo) {
|
|
@@ -502,6 +536,15 @@ export async function handler(
|
|
|
502
536
|
|
|
503
537
|
const mergedMeta = { ...(meta || {}) };
|
|
504
538
|
if (folder) mergedMeta.folder = folder;
|
|
539
|
+
|
|
540
|
+
// Merge encoding context into meta for persistence
|
|
541
|
+
const parsedCtx = parseContextParam(encoding_context);
|
|
542
|
+
if (parsedCtx?.structured) {
|
|
543
|
+
mergedMeta.encoding_context = parsedCtx.structured;
|
|
544
|
+
} else if (parsedCtx?.text) {
|
|
545
|
+
mergedMeta.encoding_context = parsedCtx.text;
|
|
546
|
+
}
|
|
547
|
+
|
|
505
548
|
const finalMeta = Object.keys(mergedMeta).length ? mergedMeta : undefined;
|
|
506
549
|
|
|
507
550
|
const effectiveTier = tier ?? defaultTierFor(normalizedKind);
|
|
@@ -538,6 +581,24 @@ export async function handler(
|
|
|
538
581
|
);
|
|
539
582
|
}
|
|
540
583
|
|
|
584
|
+
// Store context embedding in vault_ctx_vec for contextual reinstatement
|
|
585
|
+
if (parsedCtx?.text && entry) {
|
|
586
|
+
try {
|
|
587
|
+
const ctxEmbedding = await ctx.embed(parsedCtx.text);
|
|
588
|
+
if (ctxEmbedding) {
|
|
589
|
+
const rowidResult = ctx.stmts.getRowid.get(entry.id) as { rowid: number } | undefined;
|
|
590
|
+
if (rowidResult?.rowid) {
|
|
591
|
+
const rowid = Number(rowidResult.rowid);
|
|
592
|
+
try { ctx.deleteCtxVec(rowid); } catch {}
|
|
593
|
+
ctx.insertCtxVec(rowid, ctxEmbedding);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
} catch (e) {
|
|
597
|
+
// Non-fatal: context embedding failure should not block the save
|
|
598
|
+
console.warn(`[context-vault] Context embedding failed: ${(e as Error).message}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
541
602
|
if (ctx.config?.dataDir) {
|
|
542
603
|
maybeShowFeedbackPrompt(ctx.config.dataDir);
|
|
543
604
|
}
|