context-vault 2.8.19 → 2.10.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/bin/cli.js +7 -0
- package/node_modules/@context-vault/core/package.json +2 -2
- package/node_modules/@context-vault/core/src/core/error-log.js +54 -0
- package/node_modules/@context-vault/core/src/core/status.js +13 -0
- package/node_modules/@context-vault/core/src/index/db.js +31 -53
- package/node_modules/@context-vault/core/src/server/helpers.js +15 -2
- package/node_modules/@context-vault/core/src/server/tools/context-status.js +37 -0
- package/node_modules/@context-vault/core/src/server/tools/get-context.js +16 -6
- package/node_modules/@context-vault/core/src/server/tools/list-context.js +30 -5
- package/node_modules/@context-vault/core/src/server/tools.js +38 -3
- package/package.json +3 -4
- package/scripts/postinstall.js +4 -43
- package/scripts/prepack.js +1 -1
- package/src/server/index.js +19 -0
package/bin/cli.js
CHANGED
|
@@ -699,6 +699,8 @@ async function configureClaude(tool, vaultDir) {
|
|
|
699
699
|
"add",
|
|
700
700
|
"-s",
|
|
701
701
|
"user",
|
|
702
|
+
"-e",
|
|
703
|
+
"NODE_OPTIONS=--no-warnings=ExperimentalWarning",
|
|
702
704
|
"context-vault",
|
|
703
705
|
"--",
|
|
704
706
|
"npx",
|
|
@@ -719,6 +721,8 @@ async function configureClaude(tool, vaultDir) {
|
|
|
719
721
|
"add",
|
|
720
722
|
"-s",
|
|
721
723
|
"user",
|
|
724
|
+
"-e",
|
|
725
|
+
"NODE_OPTIONS=--no-warnings=ExperimentalWarning",
|
|
722
726
|
"context-vault",
|
|
723
727
|
"--",
|
|
724
728
|
process.execPath,
|
|
@@ -801,6 +805,7 @@ function configureJsonTool(tool, vaultDir) {
|
|
|
801
805
|
config[tool.configKey]["context-vault"] = {
|
|
802
806
|
command: "npx",
|
|
803
807
|
args: ["-y", "context-vault", "serve", ...serverArgs],
|
|
808
|
+
env: { NODE_OPTIONS: "--no-warnings=ExperimentalWarning" },
|
|
804
809
|
};
|
|
805
810
|
} else if (isInstalledPackage()) {
|
|
806
811
|
const launcherPath = join(HOME, ".context-mcp", "server.mjs");
|
|
@@ -809,6 +814,7 @@ function configureJsonTool(tool, vaultDir) {
|
|
|
809
814
|
config[tool.configKey]["context-vault"] = {
|
|
810
815
|
command: process.execPath,
|
|
811
816
|
args: [launcherPath, ...serverArgs],
|
|
817
|
+
env: { NODE_OPTIONS: "--no-warnings=ExperimentalWarning" },
|
|
812
818
|
};
|
|
813
819
|
} else {
|
|
814
820
|
const serverArgs = [SERVER_PATH];
|
|
@@ -816,6 +822,7 @@ function configureJsonTool(tool, vaultDir) {
|
|
|
816
822
|
config[tool.configKey]["context-vault"] = {
|
|
817
823
|
command: process.execPath,
|
|
818
824
|
args: serverArgs,
|
|
825
|
+
env: { NODE_OPTIONS: "--no-warnings=ExperimentalWarning" },
|
|
819
826
|
};
|
|
820
827
|
}
|
|
821
828
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@context-vault/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared core: capture, index, retrieve, tools, and utilities for context-vault",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
],
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"engines": {
|
|
34
|
-
"node": ">=
|
|
34
|
+
"node": ">=24"
|
|
35
35
|
},
|
|
36
36
|
"author": "Felix Hellstrom",
|
|
37
37
|
"repository": {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
statSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
const MAX_LOG_SIZE = 1024 * 1024; // 1MB
|
|
12
|
+
|
|
13
|
+
export function errorLogPath(dataDir) {
|
|
14
|
+
return join(dataDir, "error.log");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Append a structured JSON entry to the startup error log.
|
|
19
|
+
* Rotates the file if it exceeds MAX_LOG_SIZE.
|
|
20
|
+
* Never throws — logging failures must not mask the original error.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} dataDir
|
|
23
|
+
* @param {object} entry
|
|
24
|
+
*/
|
|
25
|
+
export function appendErrorLog(dataDir, entry) {
|
|
26
|
+
try {
|
|
27
|
+
mkdirSync(dataDir, { recursive: true });
|
|
28
|
+
const logPath = errorLogPath(dataDir);
|
|
29
|
+
if (existsSync(logPath) && statSync(logPath).size >= MAX_LOG_SIZE) {
|
|
30
|
+
writeFileSync(logPath, "");
|
|
31
|
+
}
|
|
32
|
+
appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
33
|
+
} catch {
|
|
34
|
+
// intentionally swallowed
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Return number of log lines in the error log, or 0 if absent.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} dataDir
|
|
42
|
+
* @returns {number}
|
|
43
|
+
*/
|
|
44
|
+
export function errorLogCount(dataDir) {
|
|
45
|
+
try {
|
|
46
|
+
const logPath = errorLogPath(dataDir);
|
|
47
|
+
if (!existsSync(logPath)) return 0;
|
|
48
|
+
return readFileSync(logPath, "utf-8")
|
|
49
|
+
.split("\n")
|
|
50
|
+
.filter((l) => l.trim()).length;
|
|
51
|
+
} catch {
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -128,6 +128,18 @@ export function gatherVaultStatus(ctx, opts = {}) {
|
|
|
128
128
|
// Embedding model availability
|
|
129
129
|
const embedModelAvailable = isEmbedAvailable();
|
|
130
130
|
|
|
131
|
+
// Count auto-captured feedback entries (written by tracked() on unhandled errors)
|
|
132
|
+
let autoCapturedFeedbackCount = 0;
|
|
133
|
+
try {
|
|
134
|
+
autoCapturedFeedbackCount = db
|
|
135
|
+
.prepare(
|
|
136
|
+
`SELECT COUNT(*) as c FROM vault WHERE kind = 'feedback' AND tags LIKE '%"auto-captured"%' ${userAnd}`,
|
|
137
|
+
)
|
|
138
|
+
.get(...userParams).c;
|
|
139
|
+
} catch (e) {
|
|
140
|
+
errors.push(`Auto-captured feedback count failed: ${e.message}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
131
143
|
return {
|
|
132
144
|
fileCount,
|
|
133
145
|
subdirs,
|
|
@@ -140,6 +152,7 @@ export function gatherVaultStatus(ctx, opts = {}) {
|
|
|
140
152
|
expiredCount,
|
|
141
153
|
embeddingStatus,
|
|
142
154
|
embedModelAvailable,
|
|
155
|
+
autoCapturedFeedbackCount,
|
|
143
156
|
resolvedFrom: config.resolvedFrom,
|
|
144
157
|
errors,
|
|
145
158
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { unlinkSync, copyFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
3
|
|
|
3
4
|
export class NativeModuleError extends Error {
|
|
4
5
|
constructor(originalError) {
|
|
@@ -11,55 +12,34 @@ export class NativeModuleError extends Error {
|
|
|
11
12
|
|
|
12
13
|
function formatNativeModuleError(err) {
|
|
13
14
|
const msg = err.message || "";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
const lines = [
|
|
19
|
-
`Native module failed to load: ${msg}`,
|
|
15
|
+
return [
|
|
16
|
+
`sqlite-vec extension failed to load: ${msg}`,
|
|
20
17
|
"",
|
|
21
18
|
` Running Node.js: ${process.version} (${process.execPath})`,
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
if (versionMatch) {
|
|
25
|
-
lines.push(` Module compiled for: NODE_MODULE_VERSION ${versionMatch[1]}`);
|
|
26
|
-
lines.push(` Current runtime: NODE_MODULE_VERSION ${versionMatch[2]}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
lines.push(
|
|
30
|
-
"",
|
|
31
|
-
" Fix: Rebuild native modules for your current Node.js:",
|
|
32
|
-
" npm rebuild better-sqlite3 sqlite-vec",
|
|
33
19
|
"",
|
|
34
|
-
"
|
|
20
|
+
" Fix: Reinstall context-vault:",
|
|
35
21
|
" npx -y context-vault@latest setup",
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
return lines.join("\n");
|
|
22
|
+
].join("\n");
|
|
39
23
|
}
|
|
40
24
|
|
|
41
|
-
let _Database = null;
|
|
42
25
|
let _sqliteVec = null;
|
|
43
26
|
|
|
44
|
-
async function
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
_Database = dbMod.default;
|
|
51
|
-
} catch (e) {
|
|
52
|
-
throw new NativeModuleError(e);
|
|
53
|
-
}
|
|
27
|
+
async function loadSqliteVec() {
|
|
28
|
+
if (_sqliteVec) return _sqliteVec;
|
|
29
|
+
const vecMod = await import("sqlite-vec");
|
|
30
|
+
_sqliteVec = vecMod;
|
|
31
|
+
return _sqliteVec;
|
|
32
|
+
}
|
|
54
33
|
|
|
34
|
+
function runTransaction(db, fn) {
|
|
35
|
+
db.exec("BEGIN");
|
|
55
36
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
37
|
+
fn();
|
|
38
|
+
db.exec("COMMIT");
|
|
58
39
|
} catch (e) {
|
|
59
|
-
|
|
40
|
+
db.exec("ROLLBACK");
|
|
41
|
+
throw e;
|
|
60
42
|
}
|
|
61
|
-
|
|
62
|
-
return { Database: _Database, sqliteVec: _sqliteVec };
|
|
63
43
|
}
|
|
64
44
|
|
|
65
45
|
export const SCHEMA_DDL = `
|
|
@@ -118,12 +98,12 @@ export const SCHEMA_DDL = `
|
|
|
118
98
|
`;
|
|
119
99
|
|
|
120
100
|
export async function initDatabase(dbPath) {
|
|
121
|
-
const
|
|
101
|
+
const sqliteVec = await loadSqliteVec();
|
|
122
102
|
|
|
123
103
|
function createDb(path) {
|
|
124
|
-
const db = new
|
|
125
|
-
db.
|
|
126
|
-
db.
|
|
104
|
+
const db = new DatabaseSync(path, { allowExtension: true });
|
|
105
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
106
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
127
107
|
try {
|
|
128
108
|
sqliteVec.load(db);
|
|
129
109
|
} catch (e) {
|
|
@@ -133,7 +113,7 @@ export async function initDatabase(dbPath) {
|
|
|
133
113
|
}
|
|
134
114
|
|
|
135
115
|
const db = createDb(dbPath);
|
|
136
|
-
const version = db.
|
|
116
|
+
const version = db.prepare("PRAGMA user_version").get().user_version;
|
|
137
117
|
|
|
138
118
|
// Enforce fresh-DB-only — old schemas get a full rebuild (with backup)
|
|
139
119
|
if (version > 0 && version < 5) {
|
|
@@ -167,17 +147,17 @@ export async function initDatabase(dbPath) {
|
|
|
167
147
|
|
|
168
148
|
const freshDb = createDb(dbPath);
|
|
169
149
|
freshDb.exec(SCHEMA_DDL);
|
|
170
|
-
freshDb.
|
|
150
|
+
freshDb.exec("PRAGMA user_version = 7");
|
|
171
151
|
return freshDb;
|
|
172
152
|
}
|
|
173
153
|
|
|
174
154
|
if (version < 5) {
|
|
175
155
|
db.exec(SCHEMA_DDL);
|
|
176
|
-
db.
|
|
156
|
+
db.exec("PRAGMA user_version = 7");
|
|
177
157
|
} else if (version === 5) {
|
|
178
158
|
// v5 -> v6 migration: add multi-tenancy + encryption columns
|
|
179
159
|
// Wrapped in transaction with duplicate-column guards for idempotent retry
|
|
180
|
-
|
|
160
|
+
runTransaction(db, () => {
|
|
181
161
|
const addColumnSafe = (sql) => {
|
|
182
162
|
try {
|
|
183
163
|
db.exec(sql);
|
|
@@ -197,21 +177,19 @@ export async function initDatabase(dbPath) {
|
|
|
197
177
|
db.exec(
|
|
198
178
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_vault_identity ON vault(user_id, kind, identity_key) WHERE identity_key IS NOT NULL`,
|
|
199
179
|
);
|
|
200
|
-
db.
|
|
180
|
+
db.exec("PRAGMA user_version = 7");
|
|
201
181
|
});
|
|
202
|
-
migrate();
|
|
203
182
|
} else if (version === 6) {
|
|
204
183
|
// v6 -> v7 migration: add team_id column
|
|
205
|
-
|
|
184
|
+
runTransaction(db, () => {
|
|
206
185
|
try {
|
|
207
186
|
db.exec(`ALTER TABLE vault ADD COLUMN team_id TEXT`);
|
|
208
187
|
} catch (e) {
|
|
209
188
|
if (!e.message.includes("duplicate column")) throw e;
|
|
210
189
|
}
|
|
211
190
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_vault_team ON vault(team_id)`);
|
|
212
|
-
db.
|
|
191
|
+
db.exec("PRAGMA user_version = 7");
|
|
213
192
|
});
|
|
214
|
-
migrate();
|
|
215
193
|
}
|
|
216
194
|
|
|
217
195
|
return db;
|
|
@@ -247,15 +225,15 @@ export function prepareStatements(db) {
|
|
|
247
225
|
} catch (e) {
|
|
248
226
|
throw new Error(
|
|
249
227
|
`Failed to prepare database statements. The database may be corrupted.\n` +
|
|
250
|
-
`Try deleting and rebuilding:
|
|
228
|
+
`Try deleting and rebuilding: context-vault reindex\n` +
|
|
251
229
|
`Original error: ${e.message}`,
|
|
252
230
|
);
|
|
253
231
|
}
|
|
254
232
|
}
|
|
255
233
|
|
|
256
234
|
export function insertVec(stmts, rowid, embedding) {
|
|
257
|
-
// sqlite-vec requires BigInt for primary key —
|
|
258
|
-
//
|
|
235
|
+
// sqlite-vec requires BigInt for primary key — node:sqlite may bind Number as REAL
|
|
236
|
+
// for vec0 virtual tables which only accept INTEGER rowids
|
|
259
237
|
const safeRowid = BigInt(rowid);
|
|
260
238
|
if (safeRowid < 1n) throw new Error(`Invalid rowid: ${rowid}`);
|
|
261
239
|
stmts.insertVecStmt.run(safeRowid, embedding);
|
|
@@ -2,12 +2,25 @@
|
|
|
2
2
|
* helpers.js — Shared MCP response helpers and validation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import pkg from "../../package.json" with { type: "json" };
|
|
6
|
+
|
|
5
7
|
export function ok(text) {
|
|
6
8
|
return { content: [{ type: "text", text }] };
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
export function err(text, code = "UNKNOWN") {
|
|
10
|
-
return {
|
|
11
|
+
export function err(text, code = "UNKNOWN", meta = {}) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: "text", text }],
|
|
14
|
+
isError: true,
|
|
15
|
+
code,
|
|
16
|
+
_meta: {
|
|
17
|
+
cv_version: pkg.version,
|
|
18
|
+
node_version: process.version,
|
|
19
|
+
platform: process.platform,
|
|
20
|
+
arch: process.arch,
|
|
21
|
+
...meta,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
11
24
|
}
|
|
12
25
|
|
|
13
26
|
export function ensureVaultExists(config) {
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { gatherVaultStatus } from "../../core/status.js";
|
|
2
|
+
import { errorLogPath, errorLogCount } from "../../core/error-log.js";
|
|
2
3
|
import { ok } from "../helpers.js";
|
|
3
4
|
|
|
5
|
+
function relativeTime(ts) {
|
|
6
|
+
const secs = Math.floor((Date.now() - ts) / 1000);
|
|
7
|
+
if (secs < 60) return `${secs}s ago`;
|
|
8
|
+
const mins = Math.floor(secs / 60);
|
|
9
|
+
if (mins < 60) return `${mins} minute${mins === 1 ? "" : "s"} ago`;
|
|
10
|
+
const hrs = Math.floor(mins / 60);
|
|
11
|
+
return `${hrs} hour${hrs === 1 ? "" : "s"} ago`;
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
export const name = "context_status";
|
|
5
15
|
|
|
6
16
|
export const description =
|
|
@@ -83,6 +93,33 @@ export function handler(_args, ctx) {
|
|
|
83
93
|
lines.push(`Auto-reindex will fix this on next search or save.`);
|
|
84
94
|
}
|
|
85
95
|
|
|
96
|
+
// Error log
|
|
97
|
+
const logPath = errorLogPath(config.dataDir);
|
|
98
|
+
const logCount = errorLogCount(config.dataDir);
|
|
99
|
+
if (logCount > 0) {
|
|
100
|
+
lines.push(``, `### Startup Error Log`);
|
|
101
|
+
lines.push(`- Path: ${logPath}`);
|
|
102
|
+
lines.push(`- Entries: ${logCount} (share this file for support)`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Health: session-level tool call stats
|
|
106
|
+
const ts = ctx.toolStats;
|
|
107
|
+
if (ts) {
|
|
108
|
+
lines.push(``, `### Health`);
|
|
109
|
+
lines.push(`- Tool calls (session): ${ts.ok} ok, ${ts.errors} errors`);
|
|
110
|
+
if (ts.lastError) {
|
|
111
|
+
const { tool, code, timestamp } = ts.lastError;
|
|
112
|
+
lines.push(
|
|
113
|
+
`- Last error: ${tool ?? "unknown"} — ${code} (${relativeTime(timestamp)})`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (status.autoCapturedFeedbackCount > 0) {
|
|
117
|
+
lines.push(
|
|
118
|
+
`- Auto-captured feedback entries: ${status.autoCapturedFeedbackCount} (run get_context with kind:feedback tags:auto-captured)`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
86
123
|
// Suggested actions
|
|
87
124
|
const actions = [];
|
|
88
125
|
if (status.stalePaths)
|
|
@@ -182,12 +182,21 @@ export async function handler(
|
|
|
182
182
|
for (const r of filtered) r.score = 0;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
if (!filtered.length)
|
|
185
|
+
if (!filtered.length) {
|
|
186
|
+
if (autoWindowed) {
|
|
187
|
+
const days = config.eventDecayDays || 30;
|
|
188
|
+
return ok(
|
|
189
|
+
hasQuery
|
|
190
|
+
? `No results found for "${query}" in events (last ${days} days).\nTry with \`since: "YYYY-MM-DD"\` to search older events.`
|
|
191
|
+
: `No entries found matching the given filters in events (last ${days} days).\nTry with \`since: "YYYY-MM-DD"\` to search older events.`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
186
194
|
return ok(
|
|
187
195
|
hasQuery
|
|
188
196
|
? "No results found for: " + query
|
|
189
197
|
: "No entries found matching the given filters.",
|
|
190
198
|
);
|
|
199
|
+
}
|
|
191
200
|
|
|
192
201
|
// Decrypt encrypted entries if ctx.decrypt is available
|
|
193
202
|
if (ctx.decrypt) {
|
|
@@ -212,6 +221,12 @@ export async function handler(
|
|
|
212
221
|
);
|
|
213
222
|
const heading = hasQuery ? `Results for "${query}"` : "Filtered entries";
|
|
214
223
|
lines.push(`## ${heading} (${filtered.length} matches)\n`);
|
|
224
|
+
if (autoWindowed) {
|
|
225
|
+
const days = config.eventDecayDays || 30;
|
|
226
|
+
lines.push(
|
|
227
|
+
`> ℹ Event search limited to last ${days} days. Use \`since\` parameter for older results.\n`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
215
230
|
for (let i = 0; i < filtered.length; i++) {
|
|
216
231
|
const r = filtered[i];
|
|
217
232
|
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
@@ -229,10 +244,5 @@ export async function handler(
|
|
|
229
244
|
lines.push(r.body?.slice(0, 300) + (r.body?.length > 300 ? "..." : ""));
|
|
230
245
|
lines.push("");
|
|
231
246
|
}
|
|
232
|
-
if (autoWindowed) {
|
|
233
|
-
lines.push(
|
|
234
|
-
`_Showing events from last ${config.eventDecayDays || 30} days. Use since/until for custom range._`,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
247
|
return ok(lines.join("\n"));
|
|
238
248
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { normalizeKind } from "../../core/files.js";
|
|
3
|
+
import { categoryFor } from "../../core/categories.js";
|
|
3
4
|
import { ok } from "../helpers.js";
|
|
4
5
|
|
|
5
6
|
export const name = "list_context";
|
|
@@ -50,6 +51,17 @@ export async function handler(
|
|
|
50
51
|
|
|
51
52
|
await ensureIndexed();
|
|
52
53
|
|
|
54
|
+
const kindFilter = kind ? normalizeKind(kind) : null;
|
|
55
|
+
const effectiveCategory =
|
|
56
|
+
category || (kindFilter ? categoryFor(kindFilter) : null);
|
|
57
|
+
let effectiveSince = since || null;
|
|
58
|
+
let autoWindowed = false;
|
|
59
|
+
if (effectiveCategory === "event" && !since && !until) {
|
|
60
|
+
const decayMs = (config.eventDecayDays || 30) * 86400000;
|
|
61
|
+
effectiveSince = new Date(Date.now() - decayMs).toISOString();
|
|
62
|
+
autoWindowed = true;
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
const clauses = [];
|
|
54
66
|
const params = [];
|
|
55
67
|
|
|
@@ -57,17 +69,17 @@ export async function handler(
|
|
|
57
69
|
clauses.push("user_id = ?");
|
|
58
70
|
params.push(userId);
|
|
59
71
|
}
|
|
60
|
-
if (
|
|
72
|
+
if (kindFilter) {
|
|
61
73
|
clauses.push("kind = ?");
|
|
62
|
-
params.push(
|
|
74
|
+
params.push(kindFilter);
|
|
63
75
|
}
|
|
64
76
|
if (category) {
|
|
65
77
|
clauses.push("category = ?");
|
|
66
78
|
params.push(category);
|
|
67
79
|
}
|
|
68
|
-
if (
|
|
80
|
+
if (effectiveSince) {
|
|
69
81
|
clauses.push("created_at >= ?");
|
|
70
|
-
params.push(
|
|
82
|
+
params.push(effectiveSince);
|
|
71
83
|
}
|
|
72
84
|
if (until) {
|
|
73
85
|
clauses.push("created_at <= ?");
|
|
@@ -103,8 +115,15 @@ export async function handler(
|
|
|
103
115
|
.slice(0, effectiveLimit)
|
|
104
116
|
: rows;
|
|
105
117
|
|
|
106
|
-
if (!filtered.length)
|
|
118
|
+
if (!filtered.length) {
|
|
119
|
+
if (autoWindowed) {
|
|
120
|
+
const days = config.eventDecayDays || 30;
|
|
121
|
+
return ok(
|
|
122
|
+
`No entries found matching the given filters in events (last ${days} days).\nTry with \`since: "YYYY-MM-DD"\` to search older events.`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
107
125
|
return ok("No entries found matching the given filters.");
|
|
126
|
+
}
|
|
108
127
|
|
|
109
128
|
const lines = [];
|
|
110
129
|
if (reindexFailed)
|
|
@@ -112,6 +131,12 @@ export async function handler(
|
|
|
112
131
|
`> **Warning:** Auto-reindex failed. Results may be stale. Run \`context-vault reindex\` to fix.\n`,
|
|
113
132
|
);
|
|
114
133
|
lines.push(`## Vault Entries (${filtered.length} shown, ${total} total)\n`);
|
|
134
|
+
if (autoWindowed) {
|
|
135
|
+
const days = config.eventDecayDays || 30;
|
|
136
|
+
lines.push(
|
|
137
|
+
`> ℹ Event search limited to last ${days} days. Use \`since\` parameter for older results.\n`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
115
140
|
for (const r of filtered) {
|
|
116
141
|
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
117
142
|
const tagStr = entryTags.length ? entryTags.join(", ") : "none";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { reindex } from "../index/index.js";
|
|
2
|
+
import { captureAndIndex } from "../capture/index.js";
|
|
2
3
|
import { err } from "./helpers.js";
|
|
4
|
+
import pkg from "../../package.json" with { type: "json" };
|
|
3
5
|
|
|
4
6
|
import * as getContext from "./tools/get-context.js";
|
|
5
7
|
import * as saveContext from "./tools/save-context.js";
|
|
@@ -24,14 +26,14 @@ const TOOL_TIMEOUT_MS = 60_000;
|
|
|
24
26
|
export function registerTools(server, ctx) {
|
|
25
27
|
const userId = ctx.userId !== undefined ? ctx.userId : undefined;
|
|
26
28
|
|
|
27
|
-
function tracked(handler) {
|
|
29
|
+
function tracked(handler, toolName) {
|
|
28
30
|
return async (...args) => {
|
|
29
31
|
if (ctx.activeOps) ctx.activeOps.count++;
|
|
30
32
|
let timer;
|
|
31
33
|
let handlerPromise;
|
|
32
34
|
try {
|
|
33
35
|
handlerPromise = Promise.resolve(handler(...args));
|
|
34
|
-
|
|
36
|
+
const result = await Promise.race([
|
|
35
37
|
handlerPromise,
|
|
36
38
|
new Promise((_, reject) => {
|
|
37
39
|
timer = setTimeout(
|
|
@@ -40,16 +42,49 @@ export function registerTools(server, ctx) {
|
|
|
40
42
|
);
|
|
41
43
|
}),
|
|
42
44
|
]);
|
|
45
|
+
if (ctx.toolStats) ctx.toolStats.ok++;
|
|
46
|
+
return result;
|
|
43
47
|
} catch (e) {
|
|
44
48
|
if (e.message === "TOOL_TIMEOUT") {
|
|
45
49
|
// Suppress any late rejection from the still-running handler to
|
|
46
50
|
// prevent unhandled promise rejection warnings in the host process.
|
|
47
51
|
handlerPromise?.catch(() => {});
|
|
52
|
+
if (ctx.toolStats) {
|
|
53
|
+
ctx.toolStats.errors++;
|
|
54
|
+
ctx.toolStats.lastError = {
|
|
55
|
+
tool: toolName,
|
|
56
|
+
code: "TIMEOUT",
|
|
57
|
+
timestamp: Date.now(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
48
60
|
return err(
|
|
49
61
|
"Tool timed out after 60s. Try a simpler query or run `context-vault reindex` first.",
|
|
50
62
|
"TIMEOUT",
|
|
51
63
|
);
|
|
52
64
|
}
|
|
65
|
+
if (ctx.toolStats) {
|
|
66
|
+
ctx.toolStats.errors++;
|
|
67
|
+
ctx.toolStats.lastError = {
|
|
68
|
+
tool: toolName,
|
|
69
|
+
code: "UNKNOWN",
|
|
70
|
+
timestamp: Date.now(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await captureAndIndex(ctx, {
|
|
75
|
+
kind: "feedback",
|
|
76
|
+
title: `Unhandled error in ${toolName ?? "tool"} call`,
|
|
77
|
+
body: `${e.message}\n\n${e.stack ?? ""}`,
|
|
78
|
+
tags: ["bug", "auto-captured"],
|
|
79
|
+
source: "auto-capture",
|
|
80
|
+
meta: {
|
|
81
|
+
tool: toolName,
|
|
82
|
+
error_type: e.constructor?.name,
|
|
83
|
+
cv_version: pkg.version,
|
|
84
|
+
auto: true,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
} catch {} // never block on feedback capture
|
|
53
88
|
throw e;
|
|
54
89
|
} finally {
|
|
55
90
|
clearTimeout(timer);
|
|
@@ -110,7 +145,7 @@ export function registerTools(server, ctx) {
|
|
|
110
145
|
mod.name,
|
|
111
146
|
mod.description,
|
|
112
147
|
mod.inputSchema,
|
|
113
|
-
tracked((args) => mod.handler(args, ctx, shared)),
|
|
148
|
+
tracked((args) => mod.handler(args, ctx, shared), mod.name),
|
|
114
149
|
);
|
|
115
150
|
}
|
|
116
151
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-vault",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
],
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
28
|
+
"node": ">=24"
|
|
29
29
|
},
|
|
30
30
|
"author": "Felix Hellstrom",
|
|
31
31
|
"repository": {
|
|
@@ -55,9 +55,8 @@
|
|
|
55
55
|
"@context-vault/core"
|
|
56
56
|
],
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@context-vault/core": "^2.
|
|
58
|
+
"@context-vault/core": "^2.10.0",
|
|
59
59
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
60
|
-
"better-sqlite3": "^12.6.2",
|
|
61
60
|
"sqlite-vec": "^0.1.0"
|
|
62
61
|
}
|
|
63
62
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* postinstall.js — Post-install setup for context-vault
|
|
5
5
|
*
|
|
6
|
-
* 1.
|
|
7
|
-
* 2. Installs @huggingface/transformers with --ignore-scripts to avoid sharp's
|
|
6
|
+
* 1. Installs @huggingface/transformers with --ignore-scripts to avoid sharp's
|
|
8
7
|
* broken install lifecycle in global contexts. Semantic search degrades
|
|
9
8
|
* gracefully if this step fails.
|
|
9
|
+
* 2. Writes local server launcher (global installs only).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { execSync } from "node:child_process";
|
|
@@ -20,46 +20,7 @@ const PKG_ROOT = join(__dirname, "..");
|
|
|
20
20
|
const NODE_MODULES = join(PKG_ROOT, "node_modules");
|
|
21
21
|
|
|
22
22
|
async function main() {
|
|
23
|
-
// ── 1.
|
|
24
|
-
let needsRebuild = false;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
await import("better-sqlite3");
|
|
28
|
-
} catch (e) {
|
|
29
|
-
if (e.message?.includes("NODE_MODULE_VERSION")) {
|
|
30
|
-
needsRebuild = true;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
await import("sqlite-vec");
|
|
36
|
-
} catch (e) {
|
|
37
|
-
if (e.message?.includes("NODE_MODULE_VERSION")) {
|
|
38
|
-
needsRebuild = true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (needsRebuild) {
|
|
43
|
-
console.log(
|
|
44
|
-
"[context-vault] Rebuilding native modules for Node.js " +
|
|
45
|
-
process.version +
|
|
46
|
-
"...",
|
|
47
|
-
);
|
|
48
|
-
try {
|
|
49
|
-
execSync("npm rebuild better-sqlite3 sqlite-vec", {
|
|
50
|
-
stdio: "inherit",
|
|
51
|
-
timeout: 60000,
|
|
52
|
-
});
|
|
53
|
-
console.log("[context-vault] Native modules rebuilt successfully.");
|
|
54
|
-
} catch {
|
|
55
|
-
console.error("[context-vault] Warning: native module rebuild failed.");
|
|
56
|
-
console.error(
|
|
57
|
-
"[context-vault] Try manually: npm rebuild better-sqlite3 sqlite-vec",
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ── 2. Install @huggingface/transformers (optional) ───────────────────
|
|
23
|
+
// ── 1. Install @huggingface/transformers (optional) ───────────────────
|
|
63
24
|
// The transformers package depends on `sharp`, whose install script fails
|
|
64
25
|
// in global npm contexts. We install with --ignore-scripts to skip it —
|
|
65
26
|
// context-vault only uses text embeddings, not image processing.
|
|
@@ -90,7 +51,7 @@ async function main() {
|
|
|
90
51
|
}
|
|
91
52
|
}
|
|
92
53
|
|
|
93
|
-
// ──
|
|
54
|
+
// ── 2. Write local server launcher (global installs only) ────────────
|
|
94
55
|
// Under npx the path would be stale after cache eviction — configs use
|
|
95
56
|
// `npx context-vault serve` instead, so skip writing the launcher.
|
|
96
57
|
const isNpx = PKG_ROOT.includes("/_npx/") || PKG_ROOT.includes("\\_npx\\");
|
package/scripts/prepack.js
CHANGED
|
@@ -30,7 +30,7 @@ cpSync(CORE_SRC, CORE_DEST, { recursive: true, dereference: true });
|
|
|
30
30
|
rmSync(join(CORE_DEST, "node_modules"), { recursive: true, force: true });
|
|
31
31
|
|
|
32
32
|
// Strip all dependencies from the bundled core's package.json.
|
|
33
|
-
// Core's deps (
|
|
33
|
+
// Core's deps (sqlite-vec, MCP SDK) are hoisted to
|
|
34
34
|
// context-vault's own dependencies. @huggingface/transformers is
|
|
35
35
|
// dynamically imported and installed via postinstall. Leaving them
|
|
36
36
|
// in the bundled core causes duplicate resolution that breaks native
|
package/src/server/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
unlinkSync,
|
|
11
11
|
} from "node:fs";
|
|
12
12
|
import { join, dirname } from "node:path";
|
|
13
|
+
import { homedir } from "node:os";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
14
15
|
|
|
15
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -18,6 +19,7 @@ const pkg = JSON.parse(
|
|
|
18
19
|
);
|
|
19
20
|
|
|
20
21
|
import { resolveConfig } from "@context-vault/core/core/config";
|
|
22
|
+
import { appendErrorLog } from "@context-vault/core/core/error-log";
|
|
21
23
|
import { embed } from "@context-vault/core/index/embed";
|
|
22
24
|
import {
|
|
23
25
|
initDatabase,
|
|
@@ -106,6 +108,7 @@ async function main() {
|
|
|
106
108
|
insertVec: (rowid, embedding) => insertVec(stmts, rowid, embedding),
|
|
107
109
|
deleteVec: (rowid) => deleteVec(stmts, rowid),
|
|
108
110
|
activeOps: { count: 0 },
|
|
111
|
+
toolStats: { ok: 0, errors: 0, lastError: null },
|
|
109
112
|
};
|
|
110
113
|
|
|
111
114
|
// ── Phase: SERVER ────────────────────────────────────────────────────────
|
|
@@ -186,6 +189,20 @@ async function main() {
|
|
|
186
189
|
.catch(() => {});
|
|
187
190
|
}, 3000);
|
|
188
191
|
} catch (err) {
|
|
192
|
+
const dataDir = config?.dataDir || join(homedir(), ".context-mcp");
|
|
193
|
+
|
|
194
|
+
const logEntry = {
|
|
195
|
+
timestamp: new Date().toISOString(),
|
|
196
|
+
error_type: err.constructor?.name || "Error",
|
|
197
|
+
message: err.message,
|
|
198
|
+
node_version: process.version,
|
|
199
|
+
platform: process.platform,
|
|
200
|
+
arch: process.arch,
|
|
201
|
+
cv_version: pkg.version,
|
|
202
|
+
phase,
|
|
203
|
+
};
|
|
204
|
+
appendErrorLog(dataDir, logEntry);
|
|
205
|
+
|
|
189
206
|
if (err instanceof NativeModuleError) {
|
|
190
207
|
// Boxed diagnostic for native module mismatch
|
|
191
208
|
console.error("");
|
|
@@ -203,6 +220,7 @@ async function main() {
|
|
|
203
220
|
console.error("");
|
|
204
221
|
console.error(` Node.js path: ${process.execPath}`);
|
|
205
222
|
console.error(` Node.js version: ${process.version}`);
|
|
223
|
+
console.error(` Error log: ${join(dataDir, "error.log")}`);
|
|
206
224
|
console.error("");
|
|
207
225
|
process.exit(78); // EX_CONFIG
|
|
208
226
|
}
|
|
@@ -210,6 +228,7 @@ async function main() {
|
|
|
210
228
|
console.error(
|
|
211
229
|
`[context-vault] Fatal error during ${phase} phase: ${err.message}`,
|
|
212
230
|
);
|
|
231
|
+
console.error(`[context-vault] Error log: ${join(dataDir, "error.log")}`);
|
|
213
232
|
if (phase === "DB") {
|
|
214
233
|
console.error(
|
|
215
234
|
`[context-vault] Try deleting the DB file and restarting: rm "${config?.dbPath || "vault.db"}"`,
|