context-vault 2.7.1 → 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 +373 -230
- 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 +11 -50
- package/node_modules/@context-vault/core/src/capture/importers.js +64 -37
- package/node_modules/@context-vault/core/src/capture/index.js +57 -15
- 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/index.js +206 -52
- 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 +34 -15
- 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 +164 -40
- package/node_modules/@context-vault/core/src/server/tools/submit-feedback.js +24 -18
- package/node_modules/@context-vault/core/src/server/tools.js +24 -28
- package/node_modules/@context-vault/core/src/sync/sync.js +24 -19
- package/package.json +2 -2
- package/scripts/local-server.js +334 -122
- 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
|
@@ -4,59 +4,93 @@ 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.
|
|
20
20
|
*/
|
|
21
|
-
function validateSaveInput({
|
|
21
|
+
function validateSaveInput({
|
|
22
|
+
kind,
|
|
23
|
+
title,
|
|
24
|
+
body,
|
|
25
|
+
tags,
|
|
26
|
+
meta,
|
|
27
|
+
source,
|
|
28
|
+
identity_key,
|
|
29
|
+
}) {
|
|
22
30
|
if (kind !== undefined && kind !== null) {
|
|
23
31
|
if (typeof kind !== "string" || kind.length > MAX_KIND_LENGTH) {
|
|
24
|
-
return err(
|
|
32
|
+
return err(
|
|
33
|
+
`kind must be a string, max ${MAX_KIND_LENGTH} chars`,
|
|
34
|
+
"INVALID_INPUT",
|
|
35
|
+
);
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
38
|
if (body !== undefined && body !== null) {
|
|
28
39
|
if (typeof body !== "string" || body.length > MAX_BODY_LENGTH) {
|
|
29
|
-
return err(
|
|
40
|
+
return err(
|
|
41
|
+
`body must be a string, max ${MAX_BODY_LENGTH / 1024}KB`,
|
|
42
|
+
"INVALID_INPUT",
|
|
43
|
+
);
|
|
30
44
|
}
|
|
31
45
|
}
|
|
32
46
|
if (title !== undefined && title !== null) {
|
|
33
47
|
if (typeof title !== "string" || title.length > MAX_TITLE_LENGTH) {
|
|
34
|
-
return err(
|
|
48
|
+
return err(
|
|
49
|
+
`title must be a string, max ${MAX_TITLE_LENGTH} chars`,
|
|
50
|
+
"INVALID_INPUT",
|
|
51
|
+
);
|
|
35
52
|
}
|
|
36
53
|
}
|
|
37
54
|
if (tags !== undefined && tags !== null) {
|
|
38
|
-
if (!Array.isArray(tags))
|
|
39
|
-
|
|
55
|
+
if (!Array.isArray(tags))
|
|
56
|
+
return err("tags must be an array of strings", "INVALID_INPUT");
|
|
57
|
+
if (tags.length > MAX_TAGS_COUNT)
|
|
58
|
+
return err(`tags: max ${MAX_TAGS_COUNT} tags allowed`, "INVALID_INPUT");
|
|
40
59
|
for (const tag of tags) {
|
|
41
60
|
if (typeof tag !== "string" || tag.length > MAX_TAG_LENGTH) {
|
|
42
|
-
return err(
|
|
61
|
+
return err(
|
|
62
|
+
`each tag must be a string, max ${MAX_TAG_LENGTH} chars`,
|
|
63
|
+
"INVALID_INPUT",
|
|
64
|
+
);
|
|
43
65
|
}
|
|
44
66
|
}
|
|
45
67
|
}
|
|
46
68
|
if (meta !== undefined && meta !== null) {
|
|
47
69
|
const metaStr = JSON.stringify(meta);
|
|
48
70
|
if (metaStr.length > MAX_META_LENGTH) {
|
|
49
|
-
return err(
|
|
71
|
+
return err(
|
|
72
|
+
`meta must be under ${MAX_META_LENGTH / 1024}KB when serialized`,
|
|
73
|
+
"INVALID_INPUT",
|
|
74
|
+
);
|
|
50
75
|
}
|
|
51
76
|
}
|
|
52
77
|
if (source !== undefined && source !== null) {
|
|
53
78
|
if (typeof source !== "string" || source.length > MAX_SOURCE_LENGTH) {
|
|
54
|
-
return err(
|
|
79
|
+
return err(
|
|
80
|
+
`source must be a string, max ${MAX_SOURCE_LENGTH} chars`,
|
|
81
|
+
"INVALID_INPUT",
|
|
82
|
+
);
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
85
|
if (identity_key !== undefined && identity_key !== null) {
|
|
58
|
-
if (
|
|
59
|
-
|
|
86
|
+
if (
|
|
87
|
+
typeof identity_key !== "string" ||
|
|
88
|
+
identity_key.length > MAX_IDENTITY_KEY_LENGTH
|
|
89
|
+
) {
|
|
90
|
+
return err(
|
|
91
|
+
`identity_key must be a string, max ${MAX_IDENTITY_KEY_LENGTH} chars`,
|
|
92
|
+
"INVALID_INPUT",
|
|
93
|
+
);
|
|
60
94
|
}
|
|
61
95
|
}
|
|
62
96
|
return null;
|
|
@@ -68,15 +102,44 @@ export const description =
|
|
|
68
102
|
"Save knowledge to your vault. Creates a .md file and indexes it for search. Use for any kind of context: insights, decisions, patterns, references, or any custom kind. To update an existing entry, pass its `id` — omitted fields are preserved.";
|
|
69
103
|
|
|
70
104
|
export const inputSchema = {
|
|
71
|
-
id: z
|
|
72
|
-
|
|
105
|
+
id: z
|
|
106
|
+
.string()
|
|
107
|
+
.optional()
|
|
108
|
+
.describe(
|
|
109
|
+
"Entry ULID to update. When provided, updates the existing entry instead of creating new. Omitted fields are preserved.",
|
|
110
|
+
),
|
|
111
|
+
kind: z
|
|
112
|
+
.string()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe(
|
|
115
|
+
"Entry kind — determines folder (e.g. 'insight', 'decision', 'pattern', 'reference', or any custom kind). Required for new entries.",
|
|
116
|
+
),
|
|
73
117
|
title: z.string().optional().describe("Entry title (optional for insights)"),
|
|
74
|
-
body: z
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
118
|
+
body: z
|
|
119
|
+
.string()
|
|
120
|
+
.optional()
|
|
121
|
+
.describe("Main content. Required for new entries."),
|
|
122
|
+
tags: z
|
|
123
|
+
.array(z.string())
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Tags for categorization and search"),
|
|
126
|
+
meta: z
|
|
127
|
+
.any()
|
|
128
|
+
.optional()
|
|
129
|
+
.describe(
|
|
130
|
+
"Additional structured metadata (JSON object, e.g. { language: 'js', status: 'accepted' })",
|
|
131
|
+
),
|
|
132
|
+
folder: z
|
|
133
|
+
.string()
|
|
134
|
+
.optional()
|
|
135
|
+
.describe("Subfolder within the kind directory (e.g. 'react/hooks')"),
|
|
78
136
|
source: z.string().optional().describe("Where this knowledge came from"),
|
|
79
|
-
identity_key: z
|
|
137
|
+
identity_key: z
|
|
138
|
+
.string()
|
|
139
|
+
.optional()
|
|
140
|
+
.describe(
|
|
141
|
+
"Required for entity kinds (contact, project, tool, source). The unique identifier for this entity.",
|
|
142
|
+
),
|
|
80
143
|
expires_at: z.string().optional().describe("ISO date for TTL expiry"),
|
|
81
144
|
};
|
|
82
145
|
|
|
@@ -85,14 +148,37 @@ export const inputSchema = {
|
|
|
85
148
|
* @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
|
|
86
149
|
* @param {import('../types.js').ToolShared} shared
|
|
87
150
|
*/
|
|
88
|
-
export async function handler(
|
|
151
|
+
export async function handler(
|
|
152
|
+
{
|
|
153
|
+
id,
|
|
154
|
+
kind,
|
|
155
|
+
title,
|
|
156
|
+
body,
|
|
157
|
+
tags,
|
|
158
|
+
meta,
|
|
159
|
+
folder,
|
|
160
|
+
source,
|
|
161
|
+
identity_key,
|
|
162
|
+
expires_at,
|
|
163
|
+
},
|
|
164
|
+
ctx,
|
|
165
|
+
{ ensureIndexed },
|
|
166
|
+
) {
|
|
89
167
|
const { config } = ctx;
|
|
90
168
|
const userId = ctx.userId !== undefined ? ctx.userId : undefined;
|
|
91
169
|
|
|
92
170
|
const vaultErr = ensureVaultExists(config);
|
|
93
171
|
if (vaultErr) return vaultErr;
|
|
94
172
|
|
|
95
|
-
const inputErr = validateSaveInput({
|
|
173
|
+
const inputErr = validateSaveInput({
|
|
174
|
+
kind,
|
|
175
|
+
title,
|
|
176
|
+
body,
|
|
177
|
+
tags,
|
|
178
|
+
meta,
|
|
179
|
+
source,
|
|
180
|
+
identity_key,
|
|
181
|
+
});
|
|
96
182
|
if (inputErr) return inputErr;
|
|
97
183
|
|
|
98
184
|
// ── Update mode ──
|
|
@@ -108,10 +194,16 @@ export async function handler({ id, kind, title, body, tags, meta, folder, sourc
|
|
|
108
194
|
}
|
|
109
195
|
|
|
110
196
|
if (kind && normalizeKind(kind) !== existing.kind) {
|
|
111
|
-
return err(
|
|
197
|
+
return err(
|
|
198
|
+
`Cannot change kind (current: "${existing.kind}"). Delete and re-create instead.`,
|
|
199
|
+
"INVALID_UPDATE",
|
|
200
|
+
);
|
|
112
201
|
}
|
|
113
202
|
if (identity_key && identity_key !== existing.identity_key) {
|
|
114
|
-
return err(
|
|
203
|
+
return err(
|
|
204
|
+
`Cannot change identity_key (current: "${existing.identity_key}"). Delete and re-create instead.`,
|
|
205
|
+
"INVALID_UPDATE",
|
|
206
|
+
);
|
|
115
207
|
}
|
|
116
208
|
|
|
117
209
|
// Decrypt existing entry before merge if encrypted
|
|
@@ -122,9 +214,18 @@ export async function handler({ id, kind, title, body, tags, meta, folder, sourc
|
|
|
122
214
|
if (decrypted.meta) existing.meta = JSON.stringify(decrypted.meta);
|
|
123
215
|
}
|
|
124
216
|
|
|
125
|
-
const entry = updateEntryFile(ctx, existing, {
|
|
217
|
+
const entry = updateEntryFile(ctx, existing, {
|
|
218
|
+
title,
|
|
219
|
+
body,
|
|
220
|
+
tags,
|
|
221
|
+
meta,
|
|
222
|
+
source,
|
|
223
|
+
expires_at,
|
|
224
|
+
});
|
|
126
225
|
await indexEntry(ctx, entry);
|
|
127
|
-
const relPath = entry.filePath
|
|
226
|
+
const relPath = entry.filePath
|
|
227
|
+
? entry.filePath.replace(config.vaultDir + "/", "")
|
|
228
|
+
: entry.filePath;
|
|
128
229
|
const parts = [`✓ Updated ${entry.kind} → ${relPath}`, ` id: ${entry.id}`];
|
|
129
230
|
if (entry.title) parts.push(` title: ${entry.title}`);
|
|
130
231
|
const entryTags = entry.tags || [];
|
|
@@ -137,23 +238,33 @@ export async function handler({ id, kind, title, body, tags, meta, folder, sourc
|
|
|
137
238
|
if (!kind) return err("Required: kind (for new entries)", "INVALID_INPUT");
|
|
138
239
|
const kindErr = ensureValidKind(kind);
|
|
139
240
|
if (kindErr) return kindErr;
|
|
140
|
-
if (!body?.trim())
|
|
241
|
+
if (!body?.trim())
|
|
242
|
+
return err("Required: body (for new entries)", "INVALID_INPUT");
|
|
141
243
|
|
|
142
244
|
// Normalize kind to canonical singular form (e.g. "insights" → "insight")
|
|
143
245
|
const normalizedKind = normalizeKind(kind);
|
|
144
246
|
|
|
145
247
|
if (categoryFor(normalizedKind) === "entity" && !identity_key) {
|
|
146
|
-
return err(
|
|
248
|
+
return err(
|
|
249
|
+
`Entity kind "${normalizedKind}" requires identity_key`,
|
|
250
|
+
"MISSING_IDENTITY_KEY",
|
|
251
|
+
);
|
|
147
252
|
}
|
|
148
253
|
|
|
149
254
|
// Hosted tier limit enforcement (skipped in local mode — no checkLimits on ctx)
|
|
150
255
|
if (ctx.checkLimits) {
|
|
151
256
|
const usage = ctx.checkLimits();
|
|
152
257
|
if (usage.entryCount >= usage.maxEntries) {
|
|
153
|
-
return err(
|
|
258
|
+
return err(
|
|
259
|
+
`Entry limit reached (${usage.maxEntries}). Upgrade to Pro for unlimited entries.`,
|
|
260
|
+
"LIMIT_EXCEEDED",
|
|
261
|
+
);
|
|
154
262
|
}
|
|
155
263
|
if (usage.storageMb >= usage.maxStorageMb) {
|
|
156
|
-
return err(
|
|
264
|
+
return err(
|
|
265
|
+
`Storage limit reached (${usage.maxStorageMb} MB). Upgrade to Pro for more storage.`,
|
|
266
|
+
"LIMIT_EXCEEDED",
|
|
267
|
+
);
|
|
157
268
|
}
|
|
158
269
|
}
|
|
159
270
|
|
|
@@ -163,8 +274,21 @@ export async function handler({ id, kind, title, body, tags, meta, folder, sourc
|
|
|
163
274
|
if (folder) mergedMeta.folder = folder;
|
|
164
275
|
const finalMeta = Object.keys(mergedMeta).length ? mergedMeta : undefined;
|
|
165
276
|
|
|
166
|
-
const entry = await captureAndIndex(ctx, {
|
|
167
|
-
|
|
277
|
+
const entry = await captureAndIndex(ctx, {
|
|
278
|
+
kind: normalizedKind,
|
|
279
|
+
title,
|
|
280
|
+
body,
|
|
281
|
+
meta: finalMeta,
|
|
282
|
+
tags,
|
|
283
|
+
source,
|
|
284
|
+
folder,
|
|
285
|
+
identity_key,
|
|
286
|
+
expires_at,
|
|
287
|
+
userId,
|
|
288
|
+
});
|
|
289
|
+
const relPath = entry.filePath
|
|
290
|
+
? entry.filePath.replace(config.vaultDir + "/", "")
|
|
291
|
+
: entry.filePath;
|
|
168
292
|
const parts = [`✓ Saved ${normalizedKind} → ${relPath}`, ` id: ${entry.id}`];
|
|
169
293
|
if (title) parts.push(` title: ${title}`);
|
|
170
294
|
if (tags?.length) parts.push(` tags: ${tags.join(", ")}`);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { captureAndIndex } from "../../capture/index.js";
|
|
3
|
-
import { indexEntry } from "../../index/index.js";
|
|
4
3
|
import { ok, ensureVaultExists } from "../helpers.js";
|
|
5
4
|
|
|
6
5
|
export const name = "submit_feedback";
|
|
@@ -12,7 +11,10 @@ export const inputSchema = {
|
|
|
12
11
|
type: z.enum(["bug", "feature", "improvement"]).describe("Type of feedback"),
|
|
13
12
|
title: z.string().describe("Short summary of the feedback"),
|
|
14
13
|
body: z.string().describe("Detailed description"),
|
|
15
|
-
severity: z
|
|
14
|
+
severity: z
|
|
15
|
+
.enum(["low", "medium", "high"])
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Severity level (default: medium)"),
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -20,7 +22,11 @@ export const inputSchema = {
|
|
|
20
22
|
* @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
|
|
21
23
|
* @param {import('../types.js').ToolShared} shared
|
|
22
24
|
*/
|
|
23
|
-
export async function handler(
|
|
25
|
+
export async function handler(
|
|
26
|
+
{ type, title, body, severity },
|
|
27
|
+
ctx,
|
|
28
|
+
{ ensureIndexed },
|
|
29
|
+
) {
|
|
24
30
|
const { config } = ctx;
|
|
25
31
|
const userId = ctx.userId !== undefined ? ctx.userId : undefined;
|
|
26
32
|
|
|
@@ -30,20 +36,20 @@ export async function handler({ type, title, body, severity }, ctx, { ensureInde
|
|
|
30
36
|
await ensureIndexed();
|
|
31
37
|
|
|
32
38
|
const effectiveSeverity = severity || "medium";
|
|
33
|
-
const entry = await captureAndIndex(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
const entry = await captureAndIndex(ctx, {
|
|
40
|
+
kind: "feedback",
|
|
41
|
+
title,
|
|
42
|
+
body,
|
|
43
|
+
tags: [type, effectiveSeverity],
|
|
44
|
+
source: "submit_feedback",
|
|
45
|
+
meta: { feedback_type: type, severity: effectiveSeverity, status: "new" },
|
|
46
|
+
userId,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const relPath = entry.filePath
|
|
50
|
+
? entry.filePath.replace(config.vaultDir + "/", "")
|
|
51
|
+
: entry.filePath;
|
|
52
|
+
return ok(
|
|
53
|
+
`Feedback submitted: ${type} [${effectiveSeverity}] → ${relPath}\n id: ${entry.id}\n title: ${title}`,
|
|
45
54
|
);
|
|
46
|
-
|
|
47
|
-
const relPath = entry.filePath ? entry.filePath.replace(config.vaultDir + "/", "") : entry.filePath;
|
|
48
|
-
return ok(`Feedback submitted: ${type} [${effectiveSeverity}] → ${relPath}\n id: ${entry.id}\n title: ${title}`);
|
|
49
55
|
}
|
|
@@ -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
|
}
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { captureAndIndex } from "../capture/index.js";
|
|
12
|
-
import { indexEntry } from "../index/index.js";
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Build a manifest of local vault entries (id → { id, created_at, kind, title }).
|
|
@@ -19,7 +18,9 @@ import { indexEntry } from "../index/index.js";
|
|
|
19
18
|
*/
|
|
20
19
|
export function buildLocalManifest(ctx) {
|
|
21
20
|
const rows = ctx.db
|
|
22
|
-
.prepare(
|
|
21
|
+
.prepare(
|
|
22
|
+
"SELECT id, created_at, kind, title FROM vault WHERE (expires_at IS NULL OR expires_at > datetime('now'))",
|
|
23
|
+
)
|
|
23
24
|
.all();
|
|
24
25
|
|
|
25
26
|
const manifest = new Map();
|
|
@@ -111,7 +112,10 @@ export function computeSyncPlan(local, remote) {
|
|
|
111
112
|
* @param {{ hostedUrl: string, apiKey: string, plan: SyncPlan, onProgress?: (phase: string, current: number, total: number) => void }} opts
|
|
112
113
|
* @returns {Promise<{ pushed: number, pulled: number, failed: number, errors: string[] }>}
|
|
113
114
|
*/
|
|
114
|
-
export async function executeSync(
|
|
115
|
+
export async function executeSync(
|
|
116
|
+
ctx,
|
|
117
|
+
{ hostedUrl, apiKey, plan, onProgress },
|
|
118
|
+
) {
|
|
115
119
|
let pushed = 0;
|
|
116
120
|
let pulled = 0;
|
|
117
121
|
let failed = 0;
|
|
@@ -157,7 +161,9 @@ export async function executeSync(ctx, { hostedUrl, apiKey, plan, onProgress })
|
|
|
157
161
|
if (!response.ok) {
|
|
158
162
|
const errData = await response.json().catch(() => ({}));
|
|
159
163
|
failed += batch.length;
|
|
160
|
-
errors.push(
|
|
164
|
+
errors.push(
|
|
165
|
+
`Push batch failed: HTTP ${response.status} — ${errData.error || "unknown"}`,
|
|
166
|
+
);
|
|
161
167
|
continue;
|
|
162
168
|
}
|
|
163
169
|
|
|
@@ -199,21 +205,20 @@ export async function executeSync(ctx, { hostedUrl, apiKey, plan, onProgress })
|
|
|
199
205
|
if (onProgress) onProgress("pull", i + 1, entriesToPull.length);
|
|
200
206
|
|
|
201
207
|
try {
|
|
202
|
-
await captureAndIndex(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
);
|
|
208
|
+
await captureAndIndex(ctx, {
|
|
209
|
+
kind: entry.kind,
|
|
210
|
+
title: entry.title,
|
|
211
|
+
body: entry.body,
|
|
212
|
+
meta:
|
|
213
|
+
entry.meta && typeof entry.meta === "object"
|
|
214
|
+
? entry.meta
|
|
215
|
+
: undefined,
|
|
216
|
+
tags: Array.isArray(entry.tags) ? entry.tags : undefined,
|
|
217
|
+
source: entry.source || "sync-pull",
|
|
218
|
+
identity_key: entry.identity_key,
|
|
219
|
+
expires_at: entry.expires_at,
|
|
220
|
+
userId: ctx.userId || null,
|
|
221
|
+
});
|
|
217
222
|
pulled++;
|
|
218
223
|
} catch (err) {
|
|
219
224
|
failed++;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-vault",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
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"
|