context-vault 3.4.2 → 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 +127 -27
- 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 +133 -27
- package/src/tools/get-context.ts +25 -1
- package/src/tools/save-context.ts +62 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,EAAE,YAAY,CAAC;IACrB,aAAa,EAAE,mBAAmB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,cAAc,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,aAAa,CAAC;IAC3B,WAAW,EAAE,aAAa,CAAC;IAC3B,WAAW,EAAE,aAAa,CAAC;IAC3B,QAAQ,EAAE,aAAa,CAAC;IACxB,cAAc,EAAE,aAAa,CAAC;IAC9B,YAAY,EAAE,aAAa,CAAC;IAC5B,gBAAgB,EAAE,aAAa,CAAC;IAChC,mBAAmB,EAAE,aAAa,CAAC;IACnC,iBAAiB,EAAE,aAAa,CAAC;IACjC,eAAe,EAAE,aAAa,CAAC;IAC/B,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,EAAE,aAAa,CAAC;IAC7B,kBAAkB,EAAE,aAAa,CAAC;IAClC,oBAAoB,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@context-vault/core",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pure local engine: capture, index, search, and utilities for context-vault",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -61,6 +61,10 @@
|
|
|
61
61
|
"./types": {
|
|
62
62
|
"types": "./dist/types.d.ts",
|
|
63
63
|
"import": "./dist/types.js"
|
|
64
|
+
},
|
|
65
|
+
"./context": {
|
|
66
|
+
"types": "./dist/context.d.ts",
|
|
67
|
+
"import": "./dist/context.js"
|
|
64
68
|
}
|
|
65
69
|
},
|
|
66
70
|
"files": [
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contextual reinstatement: encoding context capture and serialization.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by hippocampal contextual reinstatement in neuroscience:
|
|
5
|
+
* the brain stores the situation (place, task, goal) alongside each memory,
|
|
6
|
+
* and re-entering a similar situation boosts recall. This module provides
|
|
7
|
+
* the same mechanism for vault entries.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface EncodingContext {
|
|
11
|
+
project?: string;
|
|
12
|
+
arc?: string;
|
|
13
|
+
task?: string;
|
|
14
|
+
cwd?: string;
|
|
15
|
+
session_id?: string;
|
|
16
|
+
[key: string]: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Serialize an EncodingContext into a natural-language sentence suitable
|
|
21
|
+
* for embedding with MiniLM. The output is a simple key-value string
|
|
22
|
+
* that produces meaningful sentence embeddings for similarity matching.
|
|
23
|
+
*
|
|
24
|
+
* Example: "project: leadfront, arc: auth-rewrite, task: implementing JWT rotation"
|
|
25
|
+
*/
|
|
26
|
+
export function serializeContext(ctx: EncodingContext): string {
|
|
27
|
+
const parts: string[] = [];
|
|
28
|
+
for (const [key, value] of Object.entries(ctx)) {
|
|
29
|
+
if (value != null && typeof value === 'string' && value.trim()) {
|
|
30
|
+
parts.push(`${key}: ${value.trim()}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return parts.join(', ');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse and validate a context parameter from tool input.
|
|
38
|
+
* Accepts either a string (used as-is for embedding) or a structured object.
|
|
39
|
+
* Returns null if the input is empty or invalid.
|
|
40
|
+
*/
|
|
41
|
+
export function parseContextParam(input: unknown): { text: string; structured: EncodingContext | null } | null {
|
|
42
|
+
if (input == null) return null;
|
|
43
|
+
|
|
44
|
+
if (typeof input === 'string') {
|
|
45
|
+
const trimmed = input.trim();
|
|
46
|
+
if (!trimmed) return null;
|
|
47
|
+
return { text: trimmed, structured: null };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof input === 'object' && !Array.isArray(input)) {
|
|
51
|
+
const obj = input as Record<string, unknown>;
|
|
52
|
+
const ctx: EncodingContext = {};
|
|
53
|
+
let hasValue = false;
|
|
54
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
55
|
+
if (typeof value === 'string' && value.trim()) {
|
|
56
|
+
ctx[key] = value.trim();
|
|
57
|
+
hasValue = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!hasValue) return null;
|
|
61
|
+
return { text: serializeContext(ctx), structured: ctx };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
@@ -175,9 +175,11 @@ export const SCHEMA_DDL = `
|
|
|
175
175
|
END;
|
|
176
176
|
|
|
177
177
|
CREATE VIRTUAL TABLE IF NOT EXISTS vault_vec USING vec0(embedding float[384]);
|
|
178
|
+
|
|
179
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vault_ctx_vec USING vec0(embedding float[384]);
|
|
178
180
|
`;
|
|
179
181
|
|
|
180
|
-
const CURRENT_VERSION =
|
|
182
|
+
const CURRENT_VERSION = 16;
|
|
181
183
|
|
|
182
184
|
export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
183
185
|
const sqliteVec = await loadSqliteVec();
|
|
@@ -202,6 +204,17 @@ export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
|
202
204
|
const version = (db.prepare('PRAGMA user_version').get() as { user_version: number })
|
|
203
205
|
.user_version;
|
|
204
206
|
|
|
207
|
+
// v15 -> v16: add vault_ctx_vec table for contextual reinstatement
|
|
208
|
+
if (version === 15) {
|
|
209
|
+
try {
|
|
210
|
+
db.exec('CREATE VIRTUAL TABLE IF NOT EXISTS vault_ctx_vec USING vec0(embedding float[384])');
|
|
211
|
+
db.exec(`PRAGMA user_version = ${CURRENT_VERSION}`);
|
|
212
|
+
} catch (e) {
|
|
213
|
+
console.error(`[context-vault] v15->v16 migration failed: ${(e as Error).message}`);
|
|
214
|
+
}
|
|
215
|
+
return db;
|
|
216
|
+
}
|
|
217
|
+
|
|
205
218
|
if (version > 0 && version < 15) {
|
|
206
219
|
console.error(`[context-vault] Schema v${version} is outdated. Rebuilding database...`);
|
|
207
220
|
|
|
@@ -243,7 +256,7 @@ export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
|
243
256
|
return freshDb;
|
|
244
257
|
}
|
|
245
258
|
|
|
246
|
-
if (version <
|
|
259
|
+
if (version < 16) {
|
|
247
260
|
db.exec(SCHEMA_DDL);
|
|
248
261
|
db.exec(`PRAGMA user_version = ${CURRENT_VERSION}`);
|
|
249
262
|
}
|
|
@@ -279,6 +292,8 @@ export function prepareStatements(db: DatabaseSync): PreparedStatements {
|
|
|
279
292
|
clearSupersededByRef: db.prepare(
|
|
280
293
|
`UPDATE vault SET superseded_by = NULL WHERE superseded_by = ?`
|
|
281
294
|
),
|
|
295
|
+
insertCtxVecStmt: db.prepare(`INSERT INTO vault_ctx_vec (rowid, embedding) VALUES (?, ?)`),
|
|
296
|
+
deleteCtxVecStmt: db.prepare(`DELETE FROM vault_ctx_vec WHERE rowid = ?`),
|
|
282
297
|
};
|
|
283
298
|
} catch (e) {
|
|
284
299
|
throw new Error(
|
|
@@ -301,6 +316,18 @@ export function deleteVec(stmts: PreparedStatements, rowid: number): void {
|
|
|
301
316
|
stmts.deleteVecStmt.run(safeRowid);
|
|
302
317
|
}
|
|
303
318
|
|
|
319
|
+
export function insertCtxVec(stmts: PreparedStatements, rowid: number, embedding: Float32Array): void {
|
|
320
|
+
const safeRowid = BigInt(rowid);
|
|
321
|
+
if (safeRowid < 1n) throw new Error(`Invalid rowid: ${rowid}`);
|
|
322
|
+
stmts.insertCtxVecStmt.run(safeRowid, embedding);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function deleteCtxVec(stmts: PreparedStatements, rowid: number): void {
|
|
326
|
+
const safeRowid = BigInt(rowid);
|
|
327
|
+
if (safeRowid < 1n) throw new Error(`Invalid rowid: ${rowid}`);
|
|
328
|
+
stmts.deleteCtxVecStmt.run(safeRowid);
|
|
329
|
+
}
|
|
330
|
+
|
|
304
331
|
export function testConnection(db: DatabaseSync): boolean {
|
|
305
332
|
try {
|
|
306
333
|
db.prepare('SELECT 1').get();
|
|
@@ -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';
|
|
@@ -176,6 +178,92 @@ ${progArgs.map(a => ` <string>${a}</string>`).join('\n')}
|
|
|
176
178
|
}
|
|
177
179
|
}
|
|
178
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Auto-update: check npm for a newer version. In daemon (HTTP) mode,
|
|
183
|
+
* install the update and gracefully restart. In stdio mode, just log.
|
|
184
|
+
*/
|
|
185
|
+
async function autoUpdate(isDaemon: boolean): Promise<string | null> {
|
|
186
|
+
const { execSync, spawn: spawnProc } = await import('node:child_process');
|
|
187
|
+
// Use a non-blocking npm check to avoid event loop stalls during reindex
|
|
188
|
+
const latest = await new Promise<string>((resolve, reject) => {
|
|
189
|
+
const child = spawnProc('npm', ['view', 'context-vault', 'version'], {
|
|
190
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
191
|
+
timeout: 10000,
|
|
192
|
+
});
|
|
193
|
+
let out = '';
|
|
194
|
+
child.stdout?.on('data', (d: Buffer) => { out += d.toString(); });
|
|
195
|
+
child.on('close', (code: number | null) => {
|
|
196
|
+
if (code === 0 && out.trim()) resolve(out.trim());
|
|
197
|
+
else reject(new Error(`npm view failed (code ${code})`));
|
|
198
|
+
});
|
|
199
|
+
child.on('error', reject);
|
|
200
|
+
}).catch(() => null as string | null) as string;
|
|
201
|
+
|
|
202
|
+
if (!latest) return null; // offline or registry unreachable
|
|
203
|
+
if (latest === pkg.version) return latest;
|
|
204
|
+
|
|
205
|
+
console.error(`[context-vault] Update available: v${pkg.version} -> v${latest}`);
|
|
206
|
+
|
|
207
|
+
if (!isDaemon) {
|
|
208
|
+
console.error('[context-vault] Run: context-vault update');
|
|
209
|
+
return latest;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Daemon mode: auto-install and restart
|
|
213
|
+
console.error(`[context-vault] Auto-updating to v${latest}...`);
|
|
214
|
+
try {
|
|
215
|
+
execSync('npm install -g context-vault@latest', {
|
|
216
|
+
encoding: 'utf-8',
|
|
217
|
+
timeout: 120000,
|
|
218
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
219
|
+
});
|
|
220
|
+
console.error(`[context-vault] Installed v${latest}. Restarting daemon...`);
|
|
221
|
+
|
|
222
|
+
// Find our own server.js path in the updated install
|
|
223
|
+
const newBin = execSync('which context-vault', {
|
|
224
|
+
encoding: 'utf-8',
|
|
225
|
+
timeout: 5000,
|
|
226
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
227
|
+
}).trim();
|
|
228
|
+
|
|
229
|
+
// Resolve the actual package root from the binary
|
|
230
|
+
const { readlinkSync } = await import('node:fs');
|
|
231
|
+
const { resolve: resolvePath } = await import('node:path');
|
|
232
|
+
let binTarget = newBin;
|
|
233
|
+
try { binTarget = readlinkSync(newBin); } catch {}
|
|
234
|
+
const newPkgRoot = resolvePath(dirname(binTarget), '..');
|
|
235
|
+
const newServerJs = join(newPkgRoot, 'dist', 'server.js');
|
|
236
|
+
|
|
237
|
+
// Spawn the new version as a replacement daemon
|
|
238
|
+
const portIdx = process.argv.indexOf('--port');
|
|
239
|
+
const port = portIdx !== -1 ? process.argv[portIdx + 1] : '3377';
|
|
240
|
+
const args = [newServerJs, '--http', '--port', port];
|
|
241
|
+
|
|
242
|
+
// Pass through vault-dir if specified
|
|
243
|
+
const vaultIdx = process.argv.indexOf('--vault-dir');
|
|
244
|
+
if (vaultIdx !== -1 && process.argv[vaultIdx + 1]) {
|
|
245
|
+
args.push('--vault-dir', process.argv[vaultIdx + 1]);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const { spawn } = await import('node:child_process');
|
|
249
|
+
const child = spawn(process.execPath, args, {
|
|
250
|
+
detached: true,
|
|
251
|
+
stdio: 'ignore',
|
|
252
|
+
env: { ...process.env, NODE_OPTIONS: '--no-warnings=ExperimentalWarning', CONTEXT_VAULT_NO_DAEMON: '1' },
|
|
253
|
+
});
|
|
254
|
+
child.unref();
|
|
255
|
+
|
|
256
|
+
// Give the new process a moment to bind the port, then exit
|
|
257
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
258
|
+
console.error(`[context-vault] New daemon spawned (PID ${child.pid}). Old daemon exiting.`);
|
|
259
|
+
process.exit(0);
|
|
260
|
+
} catch (e) {
|
|
261
|
+
console.error(`[context-vault] Auto-update failed: ${(e as Error).message}`);
|
|
262
|
+
console.error('[context-vault] Run manually: context-vault update');
|
|
263
|
+
}
|
|
264
|
+
return latest;
|
|
265
|
+
}
|
|
266
|
+
|
|
179
267
|
async function main(): Promise<void> {
|
|
180
268
|
let phase = 'CONFIG';
|
|
181
269
|
let db: import('node:sqlite').DatabaseSync | undefined;
|
|
@@ -242,6 +330,8 @@ async function main(): Promise<void> {
|
|
|
242
330
|
embed,
|
|
243
331
|
insertVec: (rowid: number, embedding: Float32Array) => insertVec(stmts, rowid, embedding),
|
|
244
332
|
deleteVec: (rowid: number) => deleteVec(stmts, rowid),
|
|
333
|
+
insertCtxVec: (rowid: number, embedding: Float32Array) => insertCtxVec(stmts, rowid, embedding),
|
|
334
|
+
deleteCtxVec: (rowid: number) => deleteCtxVec(stmts, rowid),
|
|
245
335
|
activeOps: { count: 0 },
|
|
246
336
|
toolStats: { ok: 0, errors: 0, lastError: null },
|
|
247
337
|
};
|
|
@@ -337,6 +427,7 @@ async function main(): Promise<void> {
|
|
|
337
427
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
338
428
|
|
|
339
429
|
phase = 'CONNECTED';
|
|
430
|
+
let latestKnownVersion: string | null = null;
|
|
340
431
|
|
|
341
432
|
const useHttp = process.argv.includes('--http');
|
|
342
433
|
|
|
@@ -354,6 +445,8 @@ async function main(): Promise<void> {
|
|
|
354
445
|
res.end(JSON.stringify({
|
|
355
446
|
ok: true,
|
|
356
447
|
version: pkg.version,
|
|
448
|
+
latestVersion: latestKnownVersion,
|
|
449
|
+
updateAvailable: latestKnownVersion ? latestKnownVersion !== pkg.version : null,
|
|
357
450
|
pid: process.pid,
|
|
358
451
|
uptime: process.uptime(),
|
|
359
452
|
sessions: Object.keys(transports).length,
|
|
@@ -391,15 +484,34 @@ async function main(): Promise<void> {
|
|
|
391
484
|
await transport.handleRequest(req, res, (req as any).body);
|
|
392
485
|
return;
|
|
393
486
|
} else if (sessionId) {
|
|
394
|
-
// Stale session (e.g., daemon restarted).
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
|
403
515
|
} else {
|
|
404
516
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
405
517
|
res.end(JSON.stringify({
|
|
@@ -486,24 +598,18 @@ async function main(): Promise<void> {
|
|
|
486
598
|
}
|
|
487
599
|
}
|
|
488
600
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
} catch {}
|
|
504
|
-
})
|
|
505
|
-
.catch(() => {});
|
|
506
|
-
}, 3000);
|
|
601
|
+
// Auto-update check (and apply for daemon mode)
|
|
602
|
+
const updateCheck = async () => {
|
|
603
|
+
const result = await autoUpdate(useHttp);
|
|
604
|
+
if (result) latestKnownVersion = result;
|
|
605
|
+
};
|
|
606
|
+
setTimeout(() => updateCheck().catch((e) => {
|
|
607
|
+
console.error(`[context-vault] Update check failed: ${(e as Error).message}`);
|
|
608
|
+
}), 5000);
|
|
609
|
+
// Re-check daily for long-running daemons
|
|
610
|
+
if (useHttp) {
|
|
611
|
+
setInterval(() => updateCheck().catch(() => {}), 24 * 60 * 60 * 1000);
|
|
612
|
+
}
|
|
507
613
|
} catch (rawErr) {
|
|
508
614
|
const err = rawErr as Error;
|
|
509
615
|
const dataDir = config?.dataDir || join(homedir(), '.context-mcp');
|
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
|