context-vault 2.8.0 → 2.8.3
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/README.md +39 -375
- package/bin/cli.js +26 -67
- package/node_modules/@context-vault/core/package.json +7 -3
- package/node_modules/@context-vault/core/src/capture/file-ops.js +20 -2
- package/node_modules/@context-vault/core/src/capture/import-pipeline.js +0 -34
- package/node_modules/@context-vault/core/src/capture/importers.js +64 -37
- package/node_modules/@context-vault/core/src/capture/ingest-url.js +80 -44
- package/node_modules/@context-vault/core/src/constants.js +8 -0
- package/node_modules/@context-vault/core/src/core/config.js +65 -29
- package/node_modules/@context-vault/core/src/core/files.js +8 -15
- package/node_modules/@context-vault/core/src/core/frontmatter.js +22 -10
- package/node_modules/@context-vault/core/src/core/status.js +32 -15
- package/node_modules/@context-vault/core/src/index/db.js +47 -34
- package/node_modules/@context-vault/core/src/index/embed.js +15 -5
- package/node_modules/@context-vault/core/src/index.js +39 -6
- package/node_modules/@context-vault/core/src/retrieve/index.js +40 -8
- package/node_modules/@context-vault/core/src/server/helpers.js +8 -6
- package/node_modules/@context-vault/core/src/server/tools/context-status.js +24 -10
- package/node_modules/@context-vault/core/src/server/tools/delete-context.js +8 -3
- package/node_modules/@context-vault/core/src/server/tools/get-context.js +117 -35
- package/node_modules/@context-vault/core/src/server/tools/ingest-url.js +5 -4
- package/node_modules/@context-vault/core/src/server/tools/list-context.js +59 -18
- package/node_modules/@context-vault/core/src/server/tools/save-context.js +10 -10
- package/node_modules/@context-vault/core/src/server/tools.js +24 -28
- package/package.json +2 -2
- package/scripts/local-server.js +30 -30
- package/scripts/postinstall.js +25 -10
- package/scripts/prepack.js +18 -15
- package/src/server/index.js +78 -29
- package/app-dist/assets/index-DjXoWapE.css +0 -1
- package/app-dist/assets/index-R4n9Qz4U.js +0 -380
- package/app-dist/index.html +0 -16
- package/node_modules/@context-vault/core/src/server/types.js +0 -78
|
@@ -11,13 +11,36 @@ export const description =
|
|
|
11
11
|
"Search your knowledge vault. Returns entries ranked by relevance using hybrid full-text + semantic search. Use this to find insights, decisions, patterns, or any saved context. Each result includes an `id` you can use with save_context or delete_context.";
|
|
12
12
|
|
|
13
13
|
export const inputSchema = {
|
|
14
|
-
query: z
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
query: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe(
|
|
18
|
+
"Search query (natural language or keywords). Optional if filters (tags, kind, category) are provided.",
|
|
19
|
+
),
|
|
20
|
+
kind: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Filter by kind (e.g. 'insight', 'decision', 'pattern')"),
|
|
24
|
+
category: z
|
|
25
|
+
.enum(["knowledge", "entity", "event"])
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Filter by category"),
|
|
28
|
+
identity_key: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("For entity lookup: exact match on identity key. Requires kind."),
|
|
32
|
+
tags: z
|
|
33
|
+
.array(z.string())
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Filter by tags (entries must match at least one)"),
|
|
36
|
+
since: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("ISO date, return entries created after this"),
|
|
40
|
+
until: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("ISO date, return entries created before this"),
|
|
21
44
|
limit: z.number().optional().describe("Max results to return (default 10)"),
|
|
22
45
|
};
|
|
23
46
|
|
|
@@ -26,25 +49,42 @@ export const inputSchema = {
|
|
|
26
49
|
* @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
|
|
27
50
|
* @param {import('../types.js').ToolShared} shared
|
|
28
51
|
*/
|
|
29
|
-
export async function handler(
|
|
52
|
+
export async function handler(
|
|
53
|
+
{ query, kind, category, identity_key, tags, since, until, limit },
|
|
54
|
+
ctx,
|
|
55
|
+
{ ensureIndexed, reindexFailed },
|
|
56
|
+
) {
|
|
30
57
|
const { config } = ctx;
|
|
31
58
|
const userId = ctx.userId !== undefined ? ctx.userId : undefined;
|
|
32
59
|
|
|
33
60
|
const hasQuery = query?.trim();
|
|
34
|
-
const hasFilters =
|
|
35
|
-
|
|
61
|
+
const hasFilters =
|
|
62
|
+
kind || category || tags?.length || since || until || identity_key;
|
|
63
|
+
if (!hasQuery && !hasFilters)
|
|
64
|
+
return err(
|
|
65
|
+
"Required: query or at least one filter (kind, category, tags, since, until, identity_key)",
|
|
66
|
+
"INVALID_INPUT",
|
|
67
|
+
);
|
|
36
68
|
await ensureIndexed();
|
|
37
69
|
|
|
38
70
|
const kindFilter = kind ? normalizeKind(kind) : null;
|
|
39
71
|
|
|
40
72
|
// Gap 1: Entity exact-match by identity_key
|
|
41
73
|
if (identity_key) {
|
|
42
|
-
if (!kindFilter)
|
|
43
|
-
|
|
74
|
+
if (!kindFilter)
|
|
75
|
+
return err("identity_key requires kind to be specified", "INVALID_INPUT");
|
|
76
|
+
const match = ctx.stmts.getByIdentityKey.get(
|
|
77
|
+
kindFilter,
|
|
78
|
+
identity_key,
|
|
79
|
+
userId !== undefined ? userId : null,
|
|
80
|
+
);
|
|
44
81
|
if (match) {
|
|
45
82
|
const entryTags = match.tags ? JSON.parse(match.tags) : [];
|
|
46
83
|
const tagStr = entryTags.length ? entryTags.join(", ") : "none";
|
|
47
|
-
const relPath =
|
|
84
|
+
const relPath =
|
|
85
|
+
match.file_path && config.vaultDir
|
|
86
|
+
? match.file_path.replace(config.vaultDir + "/", "")
|
|
87
|
+
: match.file_path || "n/a";
|
|
48
88
|
const lines = [
|
|
49
89
|
`## Entity Match (exact)\n`,
|
|
50
90
|
`### ${match.title || "(untitled)"} [${match.kind}/${match.category}]`,
|
|
@@ -57,7 +97,8 @@ export async function handler({ query, kind, category, identity_key, tags, since
|
|
|
57
97
|
}
|
|
58
98
|
|
|
59
99
|
// Gap 2: Event default time-window
|
|
60
|
-
const effectiveCategory =
|
|
100
|
+
const effectiveCategory =
|
|
101
|
+
category || (kindFilter ? categoryFor(kindFilter) : null);
|
|
61
102
|
let effectiveSince = since || null;
|
|
62
103
|
let effectiveUntil = until || null;
|
|
63
104
|
let autoWindowed = false;
|
|
@@ -86,38 +127,64 @@ export async function handler({ query, kind, category, identity_key, tags, since
|
|
|
86
127
|
|
|
87
128
|
// Post-filter by tags if provided, then apply requested limit
|
|
88
129
|
filtered = tags?.length
|
|
89
|
-
? sorted
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
130
|
+
? sorted
|
|
131
|
+
.filter((r) => {
|
|
132
|
+
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
133
|
+
return tags.some((t) => entryTags.includes(t));
|
|
134
|
+
})
|
|
135
|
+
.slice(0, effectiveLimit)
|
|
93
136
|
: sorted;
|
|
94
137
|
} else {
|
|
95
138
|
// Filter-only mode (no query, use SQL directly)
|
|
96
139
|
const clauses = [];
|
|
97
140
|
const params = [];
|
|
98
|
-
if (userId !== undefined) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
141
|
+
if (userId !== undefined) {
|
|
142
|
+
clauses.push("user_id = ?");
|
|
143
|
+
params.push(userId);
|
|
144
|
+
}
|
|
145
|
+
if (kindFilter) {
|
|
146
|
+
clauses.push("kind = ?");
|
|
147
|
+
params.push(kindFilter);
|
|
148
|
+
}
|
|
149
|
+
if (category) {
|
|
150
|
+
clauses.push("category = ?");
|
|
151
|
+
params.push(category);
|
|
152
|
+
}
|
|
153
|
+
if (effectiveSince) {
|
|
154
|
+
clauses.push("created_at >= ?");
|
|
155
|
+
params.push(effectiveSince);
|
|
156
|
+
}
|
|
157
|
+
if (effectiveUntil) {
|
|
158
|
+
clauses.push("created_at <= ?");
|
|
159
|
+
params.push(effectiveUntil);
|
|
160
|
+
}
|
|
103
161
|
clauses.push("(expires_at IS NULL OR expires_at > datetime('now'))");
|
|
104
162
|
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
105
163
|
params.push(fetchLimit);
|
|
106
|
-
const rows = ctx.db
|
|
164
|
+
const rows = ctx.db
|
|
165
|
+
.prepare(`SELECT * FROM vault ${where} ORDER BY created_at DESC LIMIT ?`)
|
|
166
|
+
.all(...params);
|
|
107
167
|
|
|
108
168
|
// Post-filter by tags if provided, then apply requested limit
|
|
109
169
|
filtered = tags?.length
|
|
110
|
-
? rows
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
170
|
+
? rows
|
|
171
|
+
.filter((r) => {
|
|
172
|
+
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
173
|
+
return tags.some((t) => entryTags.includes(t));
|
|
174
|
+
})
|
|
175
|
+
.slice(0, effectiveLimit)
|
|
114
176
|
: rows;
|
|
115
177
|
|
|
116
178
|
// Add score field for consistent output
|
|
117
179
|
for (const r of filtered) r.score = 0;
|
|
118
180
|
}
|
|
119
181
|
|
|
120
|
-
if (!filtered.length)
|
|
182
|
+
if (!filtered.length)
|
|
183
|
+
return ok(
|
|
184
|
+
hasQuery
|
|
185
|
+
? "No results found for: " + query
|
|
186
|
+
: "No entries found matching the given filters.",
|
|
187
|
+
);
|
|
121
188
|
|
|
122
189
|
// Decrypt encrypted entries if ctx.decrypt is available
|
|
123
190
|
if (ctx.decrypt) {
|
|
@@ -132,22 +199,37 @@ export async function handler({ query, kind, category, identity_key, tags, since
|
|
|
132
199
|
}
|
|
133
200
|
|
|
134
201
|
const lines = [];
|
|
135
|
-
if (reindexFailed)
|
|
136
|
-
|
|
202
|
+
if (reindexFailed)
|
|
203
|
+
lines.push(
|
|
204
|
+
`> **Warning:** Auto-reindex failed. Results may be stale. Run \`context-vault reindex\` to fix.\n`,
|
|
205
|
+
);
|
|
206
|
+
if (hasQuery && isEmbedAvailable() === false)
|
|
207
|
+
lines.push(
|
|
208
|
+
`> **Note:** Semantic search unavailable — results ranked by keyword match only. Run \`context-vault setup\` to download the embedding model.\n`,
|
|
209
|
+
);
|
|
137
210
|
const heading = hasQuery ? `Results for "${query}"` : "Filtered entries";
|
|
138
211
|
lines.push(`## ${heading} (${filtered.length} matches)\n`);
|
|
139
212
|
for (let i = 0; i < filtered.length; i++) {
|
|
140
213
|
const r = filtered[i];
|
|
141
214
|
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
142
215
|
const tagStr = entryTags.length ? entryTags.join(", ") : "none";
|
|
143
|
-
const relPath =
|
|
144
|
-
|
|
145
|
-
|
|
216
|
+
const relPath =
|
|
217
|
+
r.file_path && config.vaultDir
|
|
218
|
+
? r.file_path.replace(config.vaultDir + "/", "")
|
|
219
|
+
: r.file_path || "n/a";
|
|
220
|
+
lines.push(
|
|
221
|
+
`### [${i + 1}/${filtered.length}] ${r.title || "(untitled)"} [${r.kind}/${r.category}]`,
|
|
222
|
+
);
|
|
223
|
+
lines.push(
|
|
224
|
+
`${r.score.toFixed(3)} · ${tagStr} · ${relPath} · id: \`${r.id}\``,
|
|
225
|
+
);
|
|
146
226
|
lines.push(r.body?.slice(0, 300) + (r.body?.length > 300 ? "..." : ""));
|
|
147
227
|
lines.push("");
|
|
148
228
|
}
|
|
149
229
|
if (autoWindowed) {
|
|
150
|
-
lines.push(
|
|
230
|
+
lines.push(
|
|
231
|
+
`_Showing events from last ${config.eventDecayDays || 30} days. Use since/until for custom range._`,
|
|
232
|
+
);
|
|
151
233
|
}
|
|
152
234
|
return ok(lines.join("\n"));
|
|
153
235
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { captureAndIndex } from "../../capture/index.js";
|
|
3
3
|
import { ok, err, ensureVaultExists } from "../helpers.js";
|
|
4
|
+
import {
|
|
5
|
+
MAX_KIND_LENGTH,
|
|
6
|
+
MAX_TAG_LENGTH,
|
|
7
|
+
MAX_TAGS_COUNT,
|
|
8
|
+
} from "../../constants.js";
|
|
4
9
|
|
|
5
|
-
// ─── Input size limits (mirrors hosted validation) ────────────────────────────
|
|
6
10
|
const MAX_URL_LENGTH = 2048;
|
|
7
|
-
const MAX_KIND_LENGTH = 64;
|
|
8
|
-
const MAX_TAG_LENGTH = 100;
|
|
9
|
-
const MAX_TAGS_COUNT = 20;
|
|
10
11
|
|
|
11
12
|
export const name = "ingest_url";
|
|
12
13
|
|
|
@@ -8,12 +8,30 @@ export const description =
|
|
|
8
8
|
"Browse vault entries without a search query. Returns id, title, kind, category, tags, created_at. Use get_context with a query for semantic search. Use this to browse by tags or find recent entries.";
|
|
9
9
|
|
|
10
10
|
export const inputSchema = {
|
|
11
|
-
kind: z
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
kind: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Filter by kind (e.g. 'insight', 'decision', 'pattern')"),
|
|
15
|
+
category: z
|
|
16
|
+
.enum(["knowledge", "entity", "event"])
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Filter by category"),
|
|
19
|
+
tags: z
|
|
20
|
+
.array(z.string())
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Filter by tags (entries must match at least one)"),
|
|
23
|
+
since: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("ISO date, return entries created after this"),
|
|
27
|
+
until: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("ISO date, return entries created before this"),
|
|
31
|
+
limit: z
|
|
32
|
+
.number()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Max results to return (default 20, max 100)"),
|
|
17
35
|
offset: z.number().optional().describe("Skip first N results for pagination"),
|
|
18
36
|
};
|
|
19
37
|
|
|
@@ -22,7 +40,11 @@ export const inputSchema = {
|
|
|
22
40
|
* @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
|
|
23
41
|
* @param {import('../types.js').ToolShared} shared
|
|
24
42
|
*/
|
|
25
|
-
export async function handler(
|
|
43
|
+
export async function handler(
|
|
44
|
+
{ kind, category, tags, since, until, limit, offset },
|
|
45
|
+
ctx,
|
|
46
|
+
{ ensureIndexed, reindexFailed },
|
|
47
|
+
) {
|
|
26
48
|
const { config } = ctx;
|
|
27
49
|
const userId = ctx.userId !== undefined ? ctx.userId : undefined;
|
|
28
50
|
|
|
@@ -60,33 +82,52 @@ export async function handler({ kind, category, tags, since, until, limit, offse
|
|
|
60
82
|
const fetchLimit = tags?.length ? effectiveLimit * 10 : effectiveLimit;
|
|
61
83
|
|
|
62
84
|
const countParams = [...params];
|
|
63
|
-
const total = ctx.db
|
|
85
|
+
const total = ctx.db
|
|
86
|
+
.prepare(`SELECT COUNT(*) as c FROM vault ${where}`)
|
|
87
|
+
.get(...countParams).c;
|
|
64
88
|
|
|
65
89
|
params.push(fetchLimit, effectiveOffset);
|
|
66
|
-
const rows = ctx.db
|
|
90
|
+
const rows = ctx.db
|
|
91
|
+
.prepare(
|
|
92
|
+
`SELECT id, title, kind, category, tags, created_at, SUBSTR(body, 1, 120) as preview FROM vault ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
|
|
93
|
+
)
|
|
94
|
+
.all(...params);
|
|
67
95
|
|
|
68
96
|
// Post-filter by tags if provided, then apply requested limit
|
|
69
97
|
const filtered = tags?.length
|
|
70
|
-
? rows
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
98
|
+
? rows
|
|
99
|
+
.filter((r) => {
|
|
100
|
+
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
101
|
+
return tags.some((t) => entryTags.includes(t));
|
|
102
|
+
})
|
|
103
|
+
.slice(0, effectiveLimit)
|
|
74
104
|
: rows;
|
|
75
105
|
|
|
76
|
-
if (!filtered.length)
|
|
106
|
+
if (!filtered.length)
|
|
107
|
+
return ok("No entries found matching the given filters.");
|
|
77
108
|
|
|
78
109
|
const lines = [];
|
|
79
|
-
if (reindexFailed)
|
|
110
|
+
if (reindexFailed)
|
|
111
|
+
lines.push(
|
|
112
|
+
`> **Warning:** Auto-reindex failed. Results may be stale. Run \`context-vault reindex\` to fix.\n`,
|
|
113
|
+
);
|
|
80
114
|
lines.push(`## Vault Entries (${filtered.length} shown, ${total} total)\n`);
|
|
81
115
|
for (const r of filtered) {
|
|
82
116
|
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
83
117
|
const tagStr = entryTags.length ? entryTags.join(", ") : "none";
|
|
84
|
-
lines.push(
|
|
85
|
-
|
|
118
|
+
lines.push(
|
|
119
|
+
`- **${r.title || "(untitled)"}** [${r.kind}/${r.category}] — ${tagStr} — ${r.created_at} — \`${r.id}\``,
|
|
120
|
+
);
|
|
121
|
+
if (r.preview)
|
|
122
|
+
lines.push(
|
|
123
|
+
` ${r.preview.replace(/\n+/g, " ").trim()}${r.preview.length >= 120 ? "…" : ""}`,
|
|
124
|
+
);
|
|
86
125
|
}
|
|
87
126
|
|
|
88
127
|
if (effectiveOffset + effectiveLimit < total) {
|
|
89
|
-
lines.push(
|
|
128
|
+
lines.push(
|
|
129
|
+
`\n_Page ${Math.floor(effectiveOffset / effectiveLimit) + 1}. Use offset: ${effectiveOffset + effectiveLimit} for next page._`,
|
|
130
|
+
);
|
|
90
131
|
}
|
|
91
132
|
|
|
92
133
|
return ok(lines.join("\n"));
|
|
@@ -4,16 +4,16 @@ import { indexEntry } from "../../index/index.js";
|
|
|
4
4
|
import { categoryFor } from "../../core/categories.js";
|
|
5
5
|
import { normalizeKind } from "../../core/files.js";
|
|
6
6
|
import { ok, err, ensureVaultExists, ensureValidKind } from "../helpers.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
import {
|
|
8
|
+
MAX_BODY_LENGTH,
|
|
9
|
+
MAX_TITLE_LENGTH,
|
|
10
|
+
MAX_KIND_LENGTH,
|
|
11
|
+
MAX_TAG_LENGTH,
|
|
12
|
+
MAX_TAGS_COUNT,
|
|
13
|
+
MAX_META_LENGTH,
|
|
14
|
+
MAX_SOURCE_LENGTH,
|
|
15
|
+
MAX_IDENTITY_KEY_LENGTH,
|
|
16
|
+
} from "../../constants.js";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Validate input fields for save_context. Returns an error response or null.
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tools.js — MCP tool registrations (orchestrator)
|
|
3
|
-
*
|
|
4
|
-
* Seven tools: get_context (search), save_context (write/update), list_context (browse),
|
|
5
|
-
* delete_context (remove), submit_feedback (bug/feature reports), ingest_url (fetch+save),
|
|
6
|
-
* context_status (diag).
|
|
7
|
-
* Auto-reindex runs transparently on first tool call per session.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
import { reindex } from "../index/index.js";
|
|
11
2
|
import { err } from "./helpers.js";
|
|
12
3
|
|
|
@@ -30,17 +21,9 @@ const toolModules = [
|
|
|
30
21
|
|
|
31
22
|
const TOOL_TIMEOUT_MS = 60_000;
|
|
32
23
|
|
|
33
|
-
/**
|
|
34
|
-
* Register all MCP tools on the server.
|
|
35
|
-
*
|
|
36
|
-
* @param {import("@modelcontextprotocol/sdk/server/mcp.js").McpServer} server
|
|
37
|
-
* @param {import('./types.js').BaseCtx & Partial<import('./types.js').HostedCtxExtensions>} ctx
|
|
38
|
-
*/
|
|
39
24
|
export function registerTools(server, ctx) {
|
|
40
25
|
const userId = ctx.userId !== undefined ? ctx.userId : undefined;
|
|
41
26
|
|
|
42
|
-
// ─── Tool wrapper: tracks in-flight ops for graceful shutdown + timeout ────
|
|
43
|
-
|
|
44
27
|
function tracked(handler) {
|
|
45
28
|
return async (...args) => {
|
|
46
29
|
if (ctx.activeOps) ctx.activeOps.count++;
|
|
@@ -49,12 +32,18 @@ export function registerTools(server, ctx) {
|
|
|
49
32
|
return await Promise.race([
|
|
50
33
|
Promise.resolve(handler(...args)),
|
|
51
34
|
new Promise((_, reject) => {
|
|
52
|
-
timer = setTimeout(
|
|
35
|
+
timer = setTimeout(
|
|
36
|
+
() => reject(new Error("TOOL_TIMEOUT")),
|
|
37
|
+
TOOL_TIMEOUT_MS,
|
|
38
|
+
);
|
|
53
39
|
}),
|
|
54
40
|
]);
|
|
55
41
|
} catch (e) {
|
|
56
42
|
if (e.message === "TOOL_TIMEOUT") {
|
|
57
|
-
return err(
|
|
43
|
+
return err(
|
|
44
|
+
"Tool timed out after 60s. Try a simpler query or run `context-vault reindex` first.",
|
|
45
|
+
"TIMEOUT",
|
|
46
|
+
);
|
|
58
47
|
}
|
|
59
48
|
throw e;
|
|
60
49
|
} finally {
|
|
@@ -64,8 +53,6 @@ export function registerTools(server, ctx) {
|
|
|
64
53
|
};
|
|
65
54
|
}
|
|
66
55
|
|
|
67
|
-
// ─── Auto-Reindex (runs once per session, on first tool call) ──────────────
|
|
68
|
-
|
|
69
56
|
// In hosted mode, skip reindex — DB is always in sync via writeEntry→indexEntry
|
|
70
57
|
let reindexDone = userId !== undefined ? true : false;
|
|
71
58
|
let reindexPromise = null;
|
|
@@ -82,14 +69,20 @@ export function registerTools(server, ctx) {
|
|
|
82
69
|
reindexDone = true;
|
|
83
70
|
const total = stats.added + stats.updated + stats.removed;
|
|
84
71
|
if (total > 0) {
|
|
85
|
-
console.error(
|
|
72
|
+
console.error(
|
|
73
|
+
`[context-vault] Auto-reindex: +${stats.added} ~${stats.updated} -${stats.removed} (${stats.unchanged} unchanged)`,
|
|
74
|
+
);
|
|
86
75
|
}
|
|
87
76
|
})
|
|
88
77
|
.catch((e) => {
|
|
89
78
|
reindexAttempts++;
|
|
90
|
-
console.error(
|
|
79
|
+
console.error(
|
|
80
|
+
`[context-vault] Auto-reindex failed (attempt ${reindexAttempts}/${MAX_REINDEX_ATTEMPTS}): ${e.message}`,
|
|
81
|
+
);
|
|
91
82
|
if (reindexAttempts >= MAX_REINDEX_ATTEMPTS) {
|
|
92
|
-
console.error(
|
|
83
|
+
console.error(
|
|
84
|
+
`[context-vault] Giving up on auto-reindex. Run \`context-vault reindex\` manually to diagnose.`,
|
|
85
|
+
);
|
|
93
86
|
reindexDone = true;
|
|
94
87
|
reindexFailed = true;
|
|
95
88
|
} else {
|
|
@@ -100,16 +93,19 @@ export function registerTools(server, ctx) {
|
|
|
100
93
|
return reindexPromise;
|
|
101
94
|
}
|
|
102
95
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
const shared = {
|
|
97
|
+
ensureIndexed,
|
|
98
|
+
get reindexFailed() {
|
|
99
|
+
return reindexFailed;
|
|
100
|
+
},
|
|
101
|
+
};
|
|
106
102
|
|
|
107
103
|
for (const mod of toolModules) {
|
|
108
104
|
server.tool(
|
|
109
105
|
mod.name,
|
|
110
106
|
mod.description,
|
|
111
107
|
mod.inputSchema,
|
|
112
|
-
tracked((args) => mod.handler(args, ctx, shared))
|
|
108
|
+
tracked((args) => mod.handler(args, ctx, shared)),
|
|
113
109
|
);
|
|
114
110
|
}
|
|
115
111
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-vault",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
|
|
6
6
|
"bin": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@context-vault/core"
|
|
57
57
|
],
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@context-vault/core": "^2.8.
|
|
59
|
+
"@context-vault/core": "^2.8.3",
|
|
60
60
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
61
61
|
"better-sqlite3": "^12.6.2",
|
|
62
62
|
"sqlite-vec": "^0.1.0"
|