@finchagentic/mcp 4.6.1 → 4.6.2

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.
Files changed (58) hide show
  1. package/package.json +5 -6
  2. package/dist/_http-cache.js +0 -96
  3. package/dist/_text-search.js +0 -39
  4. package/dist/agent-loop.js +0 -301
  5. package/dist/annotations.js +0 -122
  6. package/dist/cli.js +0 -1391
  7. package/dist/clink-input.js +0 -15
  8. package/dist/config.js +0 -132
  9. package/dist/convex.js +0 -175
  10. package/dist/dex-pair.js +0 -54
  11. package/dist/enrichment-router.js +0 -315
  12. package/dist/index.js +0 -258
  13. package/dist/llm.js +0 -298
  14. package/dist/local-memory-file.js +0 -150
  15. package/dist/local-memory.js +0 -135
  16. package/dist/local-vault.js +0 -456
  17. package/dist/output-schemas.js +0 -605
  18. package/dist/project.js +0 -36
  19. package/dist/prompts.js +0 -111
  20. package/dist/public-url.js +0 -107
  21. package/dist/resources.js +0 -111
  22. package/dist/server.js +0 -322
  23. package/dist/signal-gate.js +0 -57
  24. package/dist/token-decimals.js +0 -26
  25. package/dist/token-gate.js +0 -88
  26. package/dist/tool-filter.js +0 -53
  27. package/dist/tools/_solidity-scan.js +0 -313
  28. package/dist/tools/agents.js +0 -441
  29. package/dist/tools/automation.js +0 -354
  30. package/dist/tools/base-mcp.js +0 -466
  31. package/dist/tools/base.js +0 -283
  32. package/dist/tools/chronicle.js +0 -268
  33. package/dist/tools/coder.js +0 -94
  34. package/dist/tools/deep-research.js +0 -1421
  35. package/dist/tools/defi.js +0 -292
  36. package/dist/tools/equity.js +0 -372
  37. package/dist/tools/events.js +0 -182
  38. package/dist/tools/github.js +0 -564
  39. package/dist/tools/insider.js +0 -264
  40. package/dist/tools/insight.js +0 -630
  41. package/dist/tools/market.js +0 -555
  42. package/dist/tools/memory.js +0 -1059
  43. package/dist/tools/miroshark.js +0 -350
  44. package/dist/tools/monitor.js +0 -319
  45. package/dist/tools/os.js +0 -236
  46. package/dist/tools/packets.js +0 -296
  47. package/dist/tools/research-chain.js +0 -226
  48. package/dist/tools/research-compare.js +0 -280
  49. package/dist/tools/research.js +0 -188
  50. package/dist/tools/rh-bridge.js +0 -148
  51. package/dist/tools/rh-mcp.js +0 -1448
  52. package/dist/tools/rh-orders.js +0 -556
  53. package/dist/tools/scanner.js +0 -564
  54. package/dist/tools/stake.js +0 -369
  55. package/dist/tools/vault.js +0 -1020
  56. package/dist/tools/wallet.js +0 -200
  57. package/dist/types.js +0 -2
  58. package/dist/wallet.js +0 -372
@@ -1,1020 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VAULT_TOOLS = void 0;
4
- exports.buildVaultList = buildVaultList;
5
- exports.buildVaultSearch = buildVaultSearch;
6
- exports.handleVaultTool = handleVaultTool;
7
- const zod_1 = require("zod");
8
- const convex_js_1 = require("../convex.js");
9
- const memory_js_1 = require("./memory.js");
10
- const local_vault_js_1 = require("../local-vault.js");
11
- const local_memory_js_1 = require("../local-memory.js");
12
- const project_js_1 = require("../project.js");
13
- const VAULT_TYPES = ["research", "execution", "workflow", "prompt", "file", "memory", "code", "credential"];
14
- exports.VAULT_TOOLS = [
15
- {
16
- name: "vault_save",
17
- description: "Save or update a versioned artifact in Finch Vault. Same key = update (git-style: prior version snapshotted, patched to v+1). " +
18
- "Types: research | execution | workflow | prompt | file | memory | code. " +
19
- "Entries up to 10MB - content over 600KB auto-offloads to blob storage. " +
20
- "For quick unstructured notes, use memory_add instead. For coding sessions specifically, " +
21
- "prefer code_session_save - same versioning, but a structured template and auto-linking built in.",
22
- inputSchema: {
23
- type: "object",
24
- properties: {
25
- type: { type: "string", enum: [...VAULT_TYPES], description: "Entry type" },
26
- title: { type: "string", description: "Human-readable title (auto-generated from content if omitted)" },
27
- content: { type: "string", description: "Main content - markdown, JSON, code, or plain text" },
28
- key: { type: "string", description: "Optional slug key e.g. 'research/btc-dominance-analysis'. Auto-generated if omitted." },
29
- contentType: { type: "string", enum: ["markdown", "json", "text", "code"], description: "Content format hint" },
30
- agentId: { type: "string", description: "Agent ID writing this entry" },
31
- tags: { type: "array", items: { type: "string" }, description: "Tags for filtering and search" },
32
- commitMsg: { type: "string", description: "Commit message for this version, e.g. 'initial research', 'refined with on-chain data'" },
33
- metadata: { type: "string", description: "Optional JSON string for extra structured fields" },
34
- workspaceProject: {
35
- type: "string",
36
- description: "Optional: file this entry into a named Finch workspace project (the same Projects a user " +
37
- "organizes their Agents/vault into on the Agents page). Matched case-insensitively by name; " +
38
- "created automatically if it doesn't exist yet. Not the same thing as a `key` path segment - " +
39
- "this tags the entry in Finch's own project system. Hosted vault only (no effect in local-vault mode).",
40
- },
41
- },
42
- required: ["type", "content"],
43
- },
44
- },
45
- {
46
- name: "code_session_save",
47
- description: "Persist a coding/debugging session as a versioned Markdown snapshot in Finch Vault, keyed by " +
48
- "project (`code/<project>`) - so the next session (yours, or another agent's) has real context " +
49
- "instead of starting cold. Same project = new version, full history kept (git-style, like vault_save). " +
50
- "Auto-links to related past code and research entries. " +
51
- "Call this at the end of a substantive coding task - not for every single file read or trivial edit.",
52
- inputSchema: {
53
- type: "object",
54
- properties: {
55
- project: { type: "string", description: "Project or repo slug, e.g. 'finch-webapp', 'mcp-server'. Becomes the vault key: code/<project>." },
56
- summary: { type: "string", description: "What was done this session - the task, the approach, the outcome." },
57
- filesChanged: { type: "array", items: { type: "string" }, description: "Files touched, e.g. ['app/convex/vault.ts', 'app/src/App.tsx']" },
58
- decisions: { type: "string", description: "Notable decisions or tradeoffs made and why - the part a future session can't re-derive from a diff alone." },
59
- nextSteps: { type: "string", description: "What's left, or what to pick up next session." },
60
- tags: { type: "array", items: { type: "string" }, description: "Extra tags for search, e.g. ['bugfix', 'refactor']" },
61
- },
62
- required: ["project", "summary"],
63
- },
64
- },
65
- {
66
- name: "vault_read",
67
- description: "Read a Finch Vault entry by its key. Returns full content, version, tags, and any linked entries.",
68
- inputSchema: {
69
- type: "object",
70
- properties: {
71
- key: { type: "string", description: "Entry key e.g. 'research/btc-dominance-analysis'" },
72
- },
73
- required: ["key"],
74
- },
75
- },
76
- {
77
- name: "vault_list",
78
- description: "List Finch Vault entries. Filter by type, agent, or pinned status. Returns previews, not full content.",
79
- inputSchema: {
80
- type: "object",
81
- properties: {
82
- type: { type: "string", enum: [...VAULT_TYPES], description: "Filter by type" },
83
- agentId: { type: "string", description: "Filter by agent that wrote the entries" },
84
- pinned: { type: "boolean", description: "Show only pinned entries" },
85
- limit: { type: "number", description: "Max entries to return (default 50)" },
86
- },
87
- required: [],
88
- },
89
- },
90
- {
91
- name: "vault_search",
92
- description: "Search Finch Vault using full-text (keyword) search over titles, content, and tags. " +
93
- "This is lexical matching, not embeddings - exact and near words in your query rank " +
94
- "highest, so short specific phrases work better than long abstract descriptions. " +
95
- "Optionally filter by type. Returns ranked results with previews.",
96
- inputSchema: {
97
- type: "object",
98
- properties: {
99
- query: { type: "string", description: "Search query - specific keywords work better than abstract phrasing (this is full-text search, not semantic)" },
100
- type: { type: "string", enum: [...VAULT_TYPES], description: "Narrow search to a specific type" },
101
- limit: { type: "number", description: "Max results (default 20)" },
102
- },
103
- required: ["query"],
104
- },
105
- },
106
- {
107
- name: "vault_history",
108
- description: "Get the full version history of a Finch Vault entry - like git log. " +
109
- "Shows each version with its commit message, author agent, size, and timestamp.",
110
- inputSchema: {
111
- type: "object",
112
- properties: {
113
- key: { type: "string", description: "Entry key" },
114
- },
115
- required: ["key"],
116
- },
117
- },
118
- {
119
- name: "vault_diff",
120
- description: "Compare two versions of a Finch Vault entry - like git diff. " +
121
- "Shows lines added (+) and removed (-) between fromVersion and toVersion.",
122
- inputSchema: {
123
- type: "object",
124
- properties: {
125
- key: { type: "string", description: "Entry key" },
126
- fromVersion: { type: "number", description: "Older version number" },
127
- toVersion: { type: "number", description: "Newer version number" },
128
- },
129
- required: ["key", "fromVersion", "toVersion"],
130
- },
131
- },
132
- {
133
- name: "vault_export",
134
- description: "Export your entire Finch Vault or a specific type as a structured bundle. " +
135
- "Useful for archiving, syncing to GitHub, or passing context to another agent.",
136
- inputSchema: {
137
- type: "object",
138
- properties: {
139
- type: { type: "string", enum: [...VAULT_TYPES], description: "Export only this type (omit for full export)" },
140
- },
141
- required: [],
142
- },
143
- },
144
- {
145
- name: "vault_store_credential",
146
- description: "Securely store an API key, token, or secret in your vault. " +
147
- "Credentials are stored under type=credential and are excluded from normal search and export. " +
148
- "Use this to keep API keys organized and accessible across agent sessions.",
149
- inputSchema: {
150
- type: "object",
151
- properties: {
152
- name: { type: "string", description: "Credential name, e.g. 'ALCHEMY_API_KEY', 'TELEGRAM_BOT_TOKEN'" },
153
- value: { type: "string", description: "The secret value to store" },
154
- description: { type: "string", description: "Optional note about this credential - what it's for, expiry, etc." },
155
- },
156
- required: ["name", "value"],
157
- },
158
- },
159
- {
160
- name: "vault_get_credential",
161
- description: "Retrieve a stored credential from the vault by name. " +
162
- "Only returns credentials owned by the authenticated user.",
163
- inputSchema: {
164
- type: "object",
165
- properties: {
166
- name: { type: "string", description: "Credential name as used in vault_store_credential" },
167
- },
168
- required: ["name"],
169
- },
170
- },
171
- {
172
- name: "vault_pin",
173
- description: "Pin or unpin a Finch Vault entry. Pinned entries always appear first in vault_list and are " +
174
- "prioritized in memory_context and search results. Use for your most important research, key prompts, or canonical references.",
175
- inputSchema: {
176
- type: "object",
177
- properties: {
178
- key: { type: "string", description: "Entry key to pin or unpin" },
179
- pinned: { type: "boolean", description: "true to pin, false to unpin (default: true)" },
180
- },
181
- required: ["key"],
182
- },
183
- },
184
- {
185
- name: "vault_unpublish",
186
- description: "Make a previously shared Finch Vault entry private again, removing it from the public community " +
187
- "listing. Use this to reverse vault publishing or packet_share. Note that anyone who already " +
188
- "copied the content while it was public still has it — unpublishing stops future discovery, " +
189
- "it does not retract what was taken.",
190
- inputSchema: {
191
- type: "object",
192
- properties: {
193
- key: { type: "string", description: "Entry key to make private again, e.g. 'packets/daily-research'" },
194
- },
195
- required: ["key"],
196
- },
197
- },
198
- {
199
- name: "vault_delete",
200
- description: "PERMANENT. Delete a Finch Vault entry and ALL of its version history — this cannot be undone. " +
201
- "Requires confirm: true. Use vault_list to browse first, and show the user the exact entry " +
202
- "(key + title) you are about to destroy before confirming.",
203
- inputSchema: {
204
- type: "object",
205
- properties: {
206
- key: { type: "string", description: "Entry key to delete permanently" },
207
- confirm: { type: "boolean", description: "Must be true to delete. Guards against irreversible loss." },
208
- },
209
- required: ["key", "confirm"],
210
- },
211
- },
212
- {
213
- name: "vault_tag",
214
- description: "Add or replace tags on an existing Finch Vault entry without modifying its content. " +
215
- "Useful for organizing entries retroactively. Set replace=true to overwrite all existing tags.",
216
- inputSchema: {
217
- type: "object",
218
- properties: {
219
- key: { type: "string", description: "Entry key to update tags on" },
220
- tags: { type: "array", items: { type: "string" }, description: "Tags to add (or replace if replace=true)" },
221
- replace: { type: "boolean", description: "If true, replaces all existing tags. If false (default), merges with existing." },
222
- },
223
- required: ["key", "tags"],
224
- },
225
- },
226
- {
227
- name: "vault_link",
228
- description: "Create a semantic relationship between two Finch Vault entries - building a knowledge graph. " +
229
- "Relations: references | derived_from | supersedes | related | continues. " +
230
- "Example: link a synthesis entry as 'derived_from' several research entries, or mark a newer analysis as 'supersedes' an older one. " +
231
- "Duplicate links are updated in-place.",
232
- inputSchema: {
233
- type: "object",
234
- properties: {
235
- fromKey: { type: "string", description: "Source entry key" },
236
- toKey: { type: "string", description: "Target entry key" },
237
- relation: {
238
- type: "string",
239
- enum: ["references", "derived_from", "supersedes", "related", "continues"],
240
- description: "How fromKey relates to toKey",
241
- },
242
- },
243
- required: ["fromKey", "toKey", "relation"],
244
- },
245
- },
246
- {
247
- name: "vault_related",
248
- description: "Traverse the Finch Vault knowledge graph - get all entries linked to a given entry. " +
249
- "Returns both outbound links (entries this entry references) and inbound links (entries that reference this one). " +
250
- "Filter by relation type to find only derived entries, superseded versions, continuations, etc.",
251
- inputSchema: {
252
- type: "object",
253
- properties: {
254
- key: { type: "string", description: "Entry key to find related entries for" },
255
- relation: {
256
- type: "string",
257
- enum: ["references", "derived_from", "supersedes", "related", "continues"],
258
- description: "Filter to only this relation type (omit for all relations)",
259
- },
260
- },
261
- required: ["key"],
262
- },
263
- },
264
- {
265
- name: "list_projects",
266
- description: "List your Finch workspace projects - the same Projects used to organize Agents and vault content on the " +
267
- "webapp Agents page. Read-only. Check this before passing `workspaceProject` to vault_save/agent_spawn if " +
268
- "you want to reuse an existing project rather than relying on the automatic case-insensitive name match. " +
269
- "No effect / nothing to list in local-vault mode.",
270
- inputSchema: { type: "object", properties: {}, required: [] },
271
- },
272
- ];
273
- // ─── Zod schemas ─────────────────────────────────────────────────────────────
274
- const SaveSchema = zod_1.z.object({
275
- type: zod_1.z.enum(VAULT_TYPES),
276
- title: zod_1.z.string().optional(),
277
- content: zod_1.z.string().min(1),
278
- key: zod_1.z.string().optional(),
279
- contentType: zod_1.z.enum(["markdown", "json", "text", "code"]).optional(),
280
- agentId: zod_1.z.string().optional(),
281
- tags: zod_1.z.array(zod_1.z.string()).optional(),
282
- commitMsg: zod_1.z.string().optional(),
283
- metadata: zod_1.z.string().optional(),
284
- workspaceProject: zod_1.z.string().optional(),
285
- });
286
- const CodeSessionSchema = zod_1.z.object({
287
- project: zod_1.z.string().min(1).max(80),
288
- summary: zod_1.z.string().min(1),
289
- filesChanged: zod_1.z.array(zod_1.z.string()).max(100).optional(),
290
- decisions: zod_1.z.string().optional(),
291
- nextSteps: zod_1.z.string().optional(),
292
- tags: zod_1.z.array(zod_1.z.string()).optional(),
293
- });
294
- const ReadSchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
295
- const ListSchema = zod_1.z.object({
296
- type: zod_1.z.enum(VAULT_TYPES).optional(),
297
- agentId: zod_1.z.string().optional(),
298
- pinned: zod_1.z.boolean().optional(),
299
- limit: zod_1.z.number().optional(),
300
- });
301
- const SearchSchema = zod_1.z.object({
302
- query: zod_1.z.string().min(1),
303
- type: zod_1.z.enum(VAULT_TYPES).optional(),
304
- limit: zod_1.z.number().optional(),
305
- });
306
- const HistorySchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
307
- const DiffSchema = zod_1.z.object({ key: zod_1.z.string().min(1), fromVersion: zod_1.z.number(), toVersion: zod_1.z.number() });
308
- const ExportSchema = zod_1.z.object({ type: zod_1.z.enum(VAULT_TYPES).optional() });
309
- const StoreCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1), value: zod_1.z.string().min(1), description: zod_1.z.string().optional() });
310
- const GetCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1) });
311
- const PinSchema = zod_1.z.object({ key: zod_1.z.string().min(1), pinned: zod_1.z.boolean().optional() });
312
- const DeleteSchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
313
- const UnpublishSchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
314
- const TagSchema = zod_1.z.object({ key: zod_1.z.string().min(1), tags: zod_1.z.array(zod_1.z.string()).min(1), replace: zod_1.z.boolean().optional() });
315
- const VAULT_RELATIONS = ["references", "derived_from", "supersedes", "related", "continues"];
316
- const LinkSchema = zod_1.z.object({ fromKey: zod_1.z.string().min(1), toKey: zod_1.z.string().min(1), relation: zod_1.z.enum(VAULT_RELATIONS) });
317
- const RelatedSchema = zod_1.z.object({ key: zod_1.z.string().min(1), relation: zod_1.z.enum(VAULT_RELATIONS).optional() });
318
- // ─── Helpers ─────────────────────────────────────────────────────────────────
319
- function formatBytes(n) {
320
- if (!n)
321
- return "-";
322
- if (n < 1024)
323
- return `${n}B`;
324
- if (n < 1024 * 1024)
325
- return `${(n / 1024).toFixed(1)}KB`;
326
- return `${(n / 1024 / 1024).toFixed(2)}MB`;
327
- }
328
- function formatDate(ts) {
329
- return new Date(ts).toUTCString();
330
- }
331
- // ── Structured output builders (schemas in output-schemas.ts) ───────────────
332
- function buildVaultList(entries, type) {
333
- return {
334
- type: type ?? null,
335
- count: entries.length,
336
- entries: entries.map((e) => ({
337
- key: e.key,
338
- title: e.title ?? null,
339
- type: e.type ?? null,
340
- version: e.version ?? null,
341
- size: e.size ?? null,
342
- updatedAt: e.updatedAt ?? null,
343
- isPinned: !!e.isPinned,
344
- })),
345
- };
346
- }
347
- function buildVaultSearch(query, results) {
348
- return {
349
- query,
350
- count: results.length,
351
- results: results.map((r) => ({
352
- key: r.key,
353
- title: r.title ?? null,
354
- type: r.type ?? null,
355
- score: r.score ?? null,
356
- preview: r.preview ?? null,
357
- })),
358
- };
359
- }
360
- // ─── Handler ─────────────────────────────────────────────────────────────────
361
- async function handleVaultTool(name, args) {
362
- // When the user has opted into a fully-local, user-owned vault
363
- // (`vaultBackend: "local"`), every operation runs against ~/.finch/vault/
364
- // on their own disk - no Convex, no account. Falls through to the hosted
365
- // path when local isn't enabled. Same two-tier shape as local memory.
366
- const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
367
- switch (name) {
368
- case "vault_save": {
369
- const parsed = SaveSchema.safeParse(args);
370
- if (!parsed.success)
371
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
372
- if (parsed.data.type === "credential") {
373
- // vault_save writes plaintext to disk/DB - "credential" is only a
374
- // valid FILTER value for vault_list/search/export (which correctly
375
- // exclude it), never a valid type to actually SAVE through here.
376
- // vault_store_credential is the only path that encrypts at rest.
377
- return {
378
- content: [{
379
- type: "text",
380
- text: "Use `vault_store_credential` to save a secret - it encrypts at rest (AES-256-GCM). " +
381
- "`vault_save` writes plaintext, so `type: \"credential\"` is refused here.",
382
- }],
383
- isError: true,
384
- };
385
- }
386
- // Auto-generate title from content if not provided
387
- const firstLine = parsed.data.content.split("\n")[0].replace(/^#+\s*/, "").slice(0, 80);
388
- const autoTitle = parsed.data.title ?? (firstLine || `${parsed.data.type} - ${new Date().toISOString().slice(0, 10)}`);
389
- const { workspaceProject, ...rest } = parsed.data;
390
- const savePayload = { ...rest, title: autoTitle };
391
- // Resolve the project name -> id server-side (auto-creates on first use).
392
- // Local vault has no project concept at all - workspaceProject is
393
- // silently a no-op there rather than a confusing network error.
394
- let resolvedProjectName = null;
395
- if (workspaceProject && !localVault) {
396
- const resolved = await (0, project_js_1.resolveProjectId)(workspaceProject);
397
- if (resolved) {
398
- savePayload.projectId = resolved.projectId;
399
- resolvedProjectName = resolved.name;
400
- }
401
- }
402
- const data = localVault
403
- ? (0, local_vault_js_1.localVaultSave)(localVault, savePayload)
404
- : await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
405
- if (data.error)
406
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
407
- const { key, version, changed } = data;
408
- // Mirror to searchable memory (fire-and-forget). Skip when running a local
409
- // vault WITHOUT local memory - otherwise the mirror would phone home to
410
- // the hosted proxy (needs auth the local-only user doesn't have, and
411
- // ships their data off-machine, defeating the whole point). vault_search
412
- // already full-text-searches the local vault directly in that mode.
413
- const mirrorToMemory = savePayload.type !== "credential" && (!localVault || !!(0, local_memory_js_1.getLocalMemoryConfig)());
414
- if (mirrorToMemory) {
415
- (0, memory_js_1.syncToSupermemory)(savePayload.content, {
416
- vaultKey: key, title: autoTitle, type: savePayload.type,
417
- tags: savePayload.tags, version, source: "vault_save",
418
- });
419
- }
420
- // Surface inline-syntax results so the user sees [[wikilinks]] and
421
- // #tags doing their work. Backend reports inlineLinksDetected,
422
- // linksCreated, linksMissing[], inlineTagsExtracted.
423
- const linkSummary = [];
424
- if (typeof data.linksCreated === "number" && data.linksCreated > 0) {
425
- linkSummary.push(`🔗 ${data.linksCreated} wikilink edge(s) created from \`[[...]]\``);
426
- }
427
- if (Array.isArray(data.linksMissing) && data.linksMissing.length > 0) {
428
- linkSummary.push(`⚠️ Missing target(s): ${data.linksMissing.map((k) => `\`${k}\``).join(", ")} - save them later to backlink`);
429
- }
430
- if (typeof data.inlineTagsExtracted === "number" && data.inlineTagsExtracted > 0) {
431
- linkSummary.push(`🏷️ ${data.inlineTagsExtracted} \`#tag\`(s) auto-extracted`);
432
- }
433
- if (data.blobStored) {
434
- linkSummary.push(`📁 Large content (${Math.round((data.originalSize ?? 0) / 1024)}KB) stored as blob; chunked indexing in progress`);
435
- }
436
- const lines = [
437
- `📦 **Vault ${changed ? (version === 1 ? "Created" : "Updated") : "Unchanged"}**`,
438
- `Key: \`${key}\``,
439
- `Version: v${version}`,
440
- changed && version > 1 ? `Previous version auto-snapshotted.` : "",
441
- resolvedProjectName ? `📁 Project: ${resolvedProjectName}` : "",
442
- mirrorToMemory ? `🧠 Synced to searchable memory` : (localVault ? `💾 Stored locally at ~/.finch/vault` : ""),
443
- ...linkSummary,
444
- ``,
445
- `Use \`vault_read\` to retrieve, \`vault_history\` to see all versions.`,
446
- ].filter(Boolean);
447
- return { content: [{ type: "text", text: lines.join("\n") }] };
448
- }
449
- case "code_session_save": {
450
- const parsed = CodeSessionSchema.safeParse(args);
451
- if (!parsed.success)
452
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
453
- const { project, summary, filesChanged, decisions, nextSteps, tags } = parsed.data;
454
- const projectSlug = project.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "session";
455
- const key = `code/${projectSlug}`;
456
- const content = [
457
- `# Code session: ${project}`,
458
- ``,
459
- `_${new Date().toISOString()}_`,
460
- ``,
461
- `## Summary`,
462
- summary,
463
- filesChanged?.length ? `\n## Files changed\n${filesChanged.map((f) => `- \`${f}\``).join("\n")}` : "",
464
- decisions ? `\n## Decisions\n${decisions}` : "",
465
- nextSteps ? `\n## Next steps\n${nextSteps}` : "",
466
- ].filter(Boolean).join("\n");
467
- const savePayload = {
468
- type: "code",
469
- key,
470
- title: `Code: ${project}`,
471
- content,
472
- contentType: "markdown",
473
- agentId: "code-session",
474
- tags: ["code-session", ...(tags ?? [])],
475
- commitMsg: summary.slice(0, 80),
476
- };
477
- const data = localVault
478
- ? (0, local_vault_js_1.localVaultSave)(localVault, savePayload)
479
- : await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
480
- if (data.error)
481
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
482
- const { version, changed } = data;
483
- // Same mirror-to-memory rule as vault_save: skip only when local vault
484
- // is on WITHOUT local memory, so query terms never phone home for a
485
- // fully offline setup.
486
- const mirrorToMemory = !localVault || !!(0, local_memory_js_1.getLocalMemoryConfig)();
487
- if (mirrorToMemory) {
488
- (0, memory_js_1.syncToSupermemory)(content, {
489
- vaultKey: key, title: savePayload.title, type: "code",
490
- tags: savePayload.tags, version, source: "code_session_save",
491
- });
492
- }
493
- // Auto-link to related past code/research entries - same idea as
494
- // deep_research's auto-linking, so a project's session history and any
495
- // research that informed it stay connected instead of sitting as
496
- // disconnected entries. Purely additive - never blocks the save.
497
- const linked = [];
498
- try {
499
- const searchQuery = `${project} ${summary}`.slice(0, 200);
500
- let hits = [];
501
- if (localVault) {
502
- const searchResult = (0, local_vault_js_1.localVaultSearch)(localVault, searchQuery, { limit: 6 });
503
- hits = (searchResult.results ?? [])
504
- .filter((r) => !!r.key && r.key !== key)
505
- .map((r) => ({ key: r.key, title: r.title ?? "(untitled)" }));
506
- }
507
- else {
508
- const searchResult = (await (0, convex_js_1.callConvex)("/vault/search", "POST", { q: searchQuery, n: 8 }, "vault_search"));
509
- hits = (searchResult?.results ?? [])
510
- .map((r) => {
511
- const hitKey = r.metadata?.vaultKey ?? r.metadata?.key ?? null;
512
- return hitKey ? { key: hitKey, title: r.metadata?.title ?? "(untitled)" } : null;
513
- })
514
- .filter((h) => !!h && h.key !== key);
515
- }
516
- for (const hit of hits.slice(0, 3)) {
517
- try {
518
- if (localVault)
519
- (0, local_vault_js_1.localVaultLink)(localVault, key, hit.key, "related");
520
- else
521
- await (0, convex_js_1.callConvex)("/vault/link", "POST", { fromKey: key, toKey: hit.key, relation: "related" }, "vault_link");
522
- linked.push(hit.key);
523
- }
524
- catch { /* skip individual link failures */ }
525
- }
526
- }
527
- catch { /* auto-link is purely additive */ }
528
- const lines = [
529
- `📦 **Code session ${changed ? (version === 1 ? "saved" : "updated") : "unchanged"}** - \`${key}\` (v${version})`,
530
- filesChanged?.length ? `Files: ${filesChanged.length}` : "",
531
- linked.length ? `🔗 Linked to ${linked.length} related entr${linked.length === 1 ? "y" : "ies"}: ${linked.map((k) => `\`${k}\``).join(", ")}` : "",
532
- mirrorToMemory ? `🧠 Synced to searchable memory` : "",
533
- ``,
534
- `Next session: \`vault_read key="${key}"\` for the latest state, or \`vault_history key="${key}"\` for the full timeline.`,
535
- ].filter(Boolean);
536
- return { content: [{ type: "text", text: lines.join("\n") }] };
537
- }
538
- case "vault_read": {
539
- const parsed = ReadSchema.safeParse(args);
540
- if (!parsed.success)
541
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
542
- const vaultReadKey = parsed.data.key;
543
- let data;
544
- try {
545
- data = localVault
546
- ? (0, local_vault_js_1.localVaultRead)(localVault, vaultReadKey)
547
- : await (0, convex_js_1.callConvex)(`/vault/entry?key=${encodeURIComponent(vaultReadKey)}`, "GET", undefined, "vault_read");
548
- }
549
- catch (e) {
550
- const msg = String(e?.message ?? e).toLowerCase();
551
- const searchTerms = vaultReadKey.split("/").pop()?.replace(/-/g, " ") ?? vaultReadKey;
552
- if (msg.includes("404") || msg.includes("not found")) {
553
- return { content: [{ type: "text", text: [
554
- `vault_read: entry \`${vaultReadKey}\` not found.`,
555
- ``,
556
- `Try searching for it:`,
557
- `- \`vault_search query="${searchTerms}"\` — full-text search across all entries`,
558
- `- \`vault_list\` — browse all entries`,
559
- `- \`vault_search query="${vaultReadKey.split("/")[0]}"\` — search by type prefix`,
560
- ].join("\n") }], isError: true };
561
- }
562
- if (msg.includes("401") || msg.includes("auth") || msg.includes("unauthorized")) {
563
- return { content: [{ type: "text", text: `vault_read: not authenticated. Run \`finch login\` to sign in.` }], isError: true };
564
- }
565
- return { content: [{ type: "text", text: `vault_read failed: ${e?.message ?? e}` }], isError: true };
566
- }
567
- if (data?.error) {
568
- const err = String(data.error).toLowerCase();
569
- if (err.includes("not found") || err.includes("404") || err.includes("no entry")) {
570
- const searchTerms = vaultReadKey.split("/").pop()?.replace(/-/g, " ") ?? vaultReadKey;
571
- return { content: [{ type: "text", text: [
572
- `vault_read: entry \`${vaultReadKey}\` not found.`,
573
- `Try: vault_search query="${searchTerms}"`,
574
- `Or: vault_list`,
575
- ].join("\n") }], isError: true };
576
- }
577
- return { content: [{ type: "text", text: `vault_read error: ${data.error}` }], isError: true };
578
- }
579
- // NOTE: there is no blob-storage tier on the backend (see
580
- // app/convex/vault.ts MAX_CONTENT_BYTES comment) - oversized content is
581
- // rejected at save time, not offloaded to file storage, so `contentFileId`
582
- // never comes back on a vault entry. A `/vault/blob` fallback used to live
583
- // here but the route was never registered in http.ts either, so it was
584
- // dead in both directions - removed rather than fixed against a storage
585
- // tier that doesn't exist. Re-add only alongside building that tier.
586
- const fullContent = data.content ?? "";
587
- const sizeLabel = data.originalSize ? formatBytes(data.originalSize) : formatBytes(data.size);
588
- const backlinksBlock = Array.isArray(data.backlinks) && data.backlinks.length > 0
589
- ? `\n🔙 Linked from (${data.backlinks.length}):\n${data.backlinks.map((b) => ` ← \`${b.key}\`${b.title ? ` - ${b.title}` : ""}`).join("\n")}`
590
- : "";
591
- // A raw projectId means nothing to a reader - resolve it to a name.
592
- // One extra call only when the entry is actually tagged; skipped
593
- // entirely for the common case (unassigned) and in local-vault mode.
594
- let projectLabel = "";
595
- if (data.projectId && !localVault) {
596
- try {
597
- const projData = await (0, convex_js_1.callConvex)("/projects/list", "GET", undefined, "list_projects");
598
- const match = projData?.projects?.find((p) => p.id === data.projectId);
599
- if (match)
600
- projectLabel = `📁 Project: ${match.name}`;
601
- }
602
- catch { /* best-effort - never block the read over this */ }
603
- }
604
- const lines = [
605
- `📂 **${data.title}**`,
606
- `Key: \`${data.key}\` · Type: ${data.type} · v${data.version} · ${sizeLabel}${data.contentFileId ? " · blob" : ""}`,
607
- data.tags?.length ? `Tags: ${data.tags.join(", ")}` : "",
608
- data.isPinned ? "📌 Pinned" : "",
609
- data.agentId ? `Agent: ${data.agentId}` : "",
610
- projectLabel,
611
- `Updated: ${formatDate(data.updatedAt)}`,
612
- data.linkedKeys?.length ? `\nLinks out:\n${data.linkedKeys.map((l) => ` → ${l}`).join("\n")}` : "",
613
- backlinksBlock,
614
- ``,
615
- `---`,
616
- ``,
617
- fullContent,
618
- ].filter((l) => l !== "");
619
- return { content: [{ type: "text", text: lines.join("\n") }] };
620
- }
621
- case "vault_list": {
622
- const parsed = ListSchema.safeParse(args ?? {});
623
- if (!parsed.success)
624
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
625
- const params = new URLSearchParams();
626
- if (parsed.data.type)
627
- params.set("type", parsed.data.type);
628
- if (parsed.data.agentId)
629
- params.set("agentId", parsed.data.agentId);
630
- if (parsed.data.pinned !== undefined)
631
- params.set("pinned", String(parsed.data.pinned));
632
- if (parsed.data.limit)
633
- params.set("limit", String(parsed.data.limit));
634
- const data = localVault
635
- ? (0, local_vault_js_1.localVaultList)(localVault, parsed.data)
636
- : await (0, convex_js_1.callConvex)(`/vault/list?${params}`, "GET", undefined, "vault_list");
637
- if (data.error)
638
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
639
- const entries = data.entries ?? [];
640
- if (!entries.length) {
641
- return {
642
- content: [{ type: "text", text: `No vault entries found${parsed.data.type ? ` of type '${parsed.data.type}'` : ""}.` }],
643
- structuredContent: buildVaultList([], parsed.data.type),
644
- };
645
- }
646
- const header = `📚 **Finch Vault** (${entries.length} entries)`;
647
- const rows = entries.map((e) => `${e.isPinned ? "📌 " : ""}[\`${e.key}\`] ${e.title} - v${e.version} · ${e.type} · ${formatBytes(e.size)} · ${formatDate(e.updatedAt)}`);
648
- return {
649
- content: [{ type: "text", text: [header, "", ...rows].join("\n") }],
650
- structuredContent: buildVaultList(entries, parsed.data.type),
651
- };
652
- }
653
- case "vault_search": {
654
- const parsed = SearchSchema.safeParse(args);
655
- if (!parsed.success)
656
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
657
- // Full-text search, proxied through Convex (searchSupermemory is a
658
- // legacy name - it calls Finch's own /memory/search endpoint, not a
659
- // third-party semantic service; there is no embedding step here). Large vault
660
- // entries are indexed as multiple chunks tagged with isVaultChunk +
661
- // vaultKey - group chunks back to their parent entry so the result
662
- // list shows one row per entry, not one row per chunk.
663
- //
664
- // Skipped entirely when vaultBackend is local - a local vault's whole
665
- // point is "no network," so the query string must never leave the
666
- // machine, not even to check for results before falling back to the
667
- // (always-local) full-text branch below.
668
- if (!localVault) {
669
- const limit = parsed.data.limit ?? 20;
670
- // Over-fetch so that after chunk dedup we still have ~limit rows.
671
- const smResults = await (0, memory_js_1.searchSupermemory)(parsed.data.query, Math.min(50, limit * 3));
672
- if (smResults.length > 0) {
673
- const filtered = parsed.data.type
674
- ? smResults.filter(r => r.metadata?.type === parsed.data.type)
675
- : smResults;
676
- if (filtered.length > 0) {
677
- const groups = new Map();
678
- for (const r of filtered) {
679
- const isChunk = r.metadata?.isVaultChunk === true;
680
- const groupKey = isChunk ? (r.metadata?.vaultKey ?? r.id) : r.id;
681
- const existing = groups.get(groupKey);
682
- const score = r.score ?? 0;
683
- const preview = (r.content ?? "").slice(0, 200).replace(/\n/g, " ");
684
- const title = r.metadata?.title ?? r.content.slice(0, 60);
685
- const type = r.metadata?.type ?? (isChunk ? "vault" : "memory");
686
- if (!existing) {
687
- groups.set(groupKey, {
688
- key: groupKey,
689
- title,
690
- type,
691
- bestScore: score,
692
- bestPreview: preview,
693
- chunkHits: 1,
694
- isVaultChunk: isChunk,
695
- });
696
- }
697
- else {
698
- existing.chunkHits += 1;
699
- if (score > existing.bestScore) {
700
- existing.bestScore = score;
701
- existing.bestPreview = preview;
702
- }
703
- }
704
- }
705
- const grouped = Array.from(groups.values())
706
- .sort((a, b) => b.bestScore - a.bestScore)
707
- .slice(0, limit);
708
- const header = `🔍 **Vault Search**: "${parsed.data.query}" - ${grouped.length} entry/entries`;
709
- const rows = grouped.map((g, i) => {
710
- const score = g.bestScore ? ` ${(g.bestScore * 100).toFixed(0)}%` : "";
711
- const chunkBadge = g.isVaultChunk && g.chunkHits > 1
712
- ? ` · ${g.chunkHits} chunk hits`
713
- : "";
714
- return [
715
- `${i + 1}.${score} [\`${g.key}\`] **${g.title}** (${g.type}${chunkBadge})`,
716
- ` ${g.bestPreview}${g.bestPreview.length >= 200 ? "…" : ""}`,
717
- ].join("\n");
718
- });
719
- return {
720
- content: [{ type: "text", text: [header, "", ...rows].join("\n") }],
721
- structuredContent: buildVaultSearch(parsed.data.query, grouped.map((g) => ({ key: g.key, title: g.title, type: g.type, score: g.bestScore, preview: g.bestPreview }))),
722
- };
723
- }
724
- }
725
- }
726
- // Fallback: local full-text (user-owned vault) or Convex full-text.
727
- const params = new URLSearchParams({ q: parsed.data.query });
728
- if (parsed.data.type)
729
- params.set("type", parsed.data.type);
730
- if (parsed.data.limit)
731
- params.set("limit", String(parsed.data.limit));
732
- const data = localVault
733
- ? (0, local_vault_js_1.localVaultSearch)(localVault, parsed.data.query, { type: parsed.data.type, limit: parsed.data.limit })
734
- : await (0, convex_js_1.callConvex)(`/vault/search?${params}`, "GET", undefined, "vault_search");
735
- if (data.error)
736
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
737
- const results = data.results ?? [];
738
- if (!results.length) {
739
- return {
740
- content: [{ type: "text", text: `No vault entries found for: "${parsed.data.query}"` }],
741
- structuredContent: buildVaultSearch(parsed.data.query, []),
742
- };
743
- }
744
- const header = `🔍 **Vault Search**: "${parsed.data.query}" - ${results.length} result(s)`;
745
- const rows = results.map((r, i) => [
746
- `${i + 1}. [\`${r.key}\`] **${r.title}** (${r.type} · v${r.version})`,
747
- ` ${r.preview}`,
748
- ].join("\n"));
749
- return {
750
- content: [{ type: "text", text: [header, "", ...rows].join("\n") }],
751
- structuredContent: buildVaultSearch(parsed.data.query, results),
752
- };
753
- }
754
- case "vault_history": {
755
- const parsed = HistorySchema.safeParse(args);
756
- if (!parsed.success)
757
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
758
- const histKey = parsed.data.key;
759
- let data;
760
- try {
761
- data = localVault
762
- ? (0, local_vault_js_1.localVaultHistory)(localVault, histKey)
763
- : await (0, convex_js_1.callConvex)(`/vault/history?key=${encodeURIComponent(histKey)}`, "GET", undefined, "vault_history");
764
- }
765
- catch (e) {
766
- const msg = String(e?.message ?? e).toLowerCase();
767
- const searchTerms = histKey.split("/").pop()?.replace(/-/g, " ") ?? histKey;
768
- if (msg.includes("404") || msg.includes("not found")) {
769
- return { content: [{ type: "text", text: [
770
- `vault_history: entry \`${histKey}\` not found.`,
771
- `Try: vault_search query="${searchTerms}"`,
772
- `Or: vault_list`,
773
- ].join("\n") }], isError: true };
774
- }
775
- if (msg.includes("401") || msg.includes("unauthorized")) {
776
- return { content: [{ type: "text", text: `vault_history: not authenticated. Run \`finch login\`.` }], isError: true };
777
- }
778
- return { content: [{ type: "text", text: `vault_history failed: ${e?.message ?? e}` }], isError: true };
779
- }
780
- if (data?.error) {
781
- return { content: [{ type: "text", text: `vault_history failed: ${data.error}` }], isError: true };
782
- }
783
- const { key, title, currentVersion, history } = data;
784
- const header = [
785
- `📜 **History**: ${title}`,
786
- `Key: \`${key}\` · Current: v${currentVersion}`,
787
- ``,
788
- `| Version | Commit | Agent | Size | Date |`,
789
- `|---------|--------|-------|------|------|`,
790
- ];
791
- const rows = history.map((v) => `| v${v.version} | ${v.commitMsg ?? "-"} | ${v.agentId ?? "-"} | ${formatBytes(v.size)} | ${formatDate(v.createdAt)} |`);
792
- return { content: [{ type: "text", text: [...header, ...rows].join("\n") }] };
793
- }
794
- case "vault_diff": {
795
- const parsed = DiffSchema.safeParse(args);
796
- if (!parsed.success)
797
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
798
- const { key, fromVersion, toVersion } = parsed.data;
799
- let data;
800
- try {
801
- data = localVault
802
- ? (0, local_vault_js_1.localVaultDiff)(localVault, key, fromVersion, toVersion)
803
- : await (0, convex_js_1.callConvex)(`/vault/diff?key=${encodeURIComponent(key)}&from=${fromVersion}&to=${toVersion}`, "GET", undefined, "vault_diff");
804
- }
805
- catch (e) {
806
- const msg = String(e?.message ?? e).toLowerCase();
807
- const searchTerms = key.split("/").pop()?.replace(/-/g, " ") ?? key;
808
- if (msg.includes("404") || msg.includes("not found")) {
809
- return { content: [{ type: "text", text: [
810
- `vault_diff: entry \`${key}\` not found.`,
811
- `Try: vault_search query="${searchTerms}"`,
812
- `Or: vault_history key="${key}" to see available versions`,
813
- ].join("\n") }], isError: true };
814
- }
815
- if (msg.includes("version") || msg.includes("range")) {
816
- return { content: [{ type: "text", text: [
817
- `vault_diff: version range v${fromVersion}→v${toVersion} invalid for \`${key}\`.`,
818
- `Check available versions: vault_history key="${key}"`,
819
- ].join("\n") }], isError: true };
820
- }
821
- if (msg.includes("401") || msg.includes("unauthorized")) {
822
- return { content: [{ type: "text", text: `vault_diff: not authenticated. Run \`finch login\`.` }], isError: true };
823
- }
824
- return { content: [{ type: "text", text: `vault_diff failed: ${e?.message ?? e}` }], isError: true };
825
- }
826
- if (data?.error) {
827
- return { content: [{ type: "text", text: `vault_diff failed: ${data.error}` }], isError: true };
828
- }
829
- const lines = [
830
- `📝 **Diff**: \`${data.key}\` - v${fromVersion} → v${toVersion}`,
831
- ``,
832
- "```diff",
833
- data.diff,
834
- "```",
835
- ];
836
- return { content: [{ type: "text", text: lines.join("\n") }] };
837
- }
838
- case "vault_export": {
839
- const parsed = ExportSchema.safeParse(args ?? {});
840
- if (!parsed.success)
841
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
842
- const params = parsed.data.type ? `?type=${parsed.data.type}` : "";
843
- const data = localVault
844
- ? (0, local_vault_js_1.localVaultExport)(localVault, parsed.data.type)
845
- : await (0, convex_js_1.callConvex)(`/vault/export${params}`, "GET", undefined, "vault_export");
846
- if (data.error)
847
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
848
- const { exportedAt, totalEntries, entries } = data;
849
- const header = [
850
- `📤 **Vault Export**`,
851
- `Exported: ${formatDate(exportedAt)} · ${totalEntries} entries${parsed.data.type ? ` (type: ${parsed.data.type})` : ""}`,
852
- ``,
853
- ];
854
- const rows = entries.map((e) => `**[\`${e.key}\`]** ${e.title} (${e.type} · v${e.version})\n${e.content.slice(0, 500)}${e.content.length > 500 ? "\n…" : ""}`);
855
- return { content: [{ type: "text", text: [...header, ...rows.join("\n\n---\n\n").split("\n")].join("\n") }] };
856
- }
857
- case "vault_store_credential": {
858
- const parsed = StoreCredentialSchema.safeParse(args);
859
- if (!parsed.success)
860
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
861
- const data = localVault
862
- ? (0, local_vault_js_1.localVaultStoreCredential)(localVault, parsed.data.name, parsed.data.value, parsed.data.description)
863
- : await (0, convex_js_1.callConvex)("/vault/credential/store", "POST", parsed.data, "vault_store_credential");
864
- if (data.error)
865
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
866
- return { content: [{ type: "text", text: `🔐 Credential stored: \`${data.name}\`\nKey: \`${data.key}\`\nRetrieve with: \`vault_get_credential name: "${data.name}"\`` }] };
867
- }
868
- case "vault_get_credential": {
869
- const parsed = GetCredentialSchema.safeParse(args);
870
- if (!parsed.success)
871
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
872
- const params = new URLSearchParams({ name: parsed.data.name });
873
- let data;
874
- try {
875
- data = localVault
876
- ? (0, local_vault_js_1.localVaultGetCredential)(localVault, parsed.data.name)
877
- : await (0, convex_js_1.callConvex)(`/vault/credential?${params}`, "GET", undefined, "vault_get_credential");
878
- }
879
- catch {
880
- return { content: [{ type: "text", text: `vault_get_credential: no credential named \`${parsed.data.name}\`.` }], isError: true };
881
- }
882
- if (data.error)
883
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
884
- const lines = [`🔐 **${data.name}**`, `Value: \`${data.value}\``];
885
- if (data.description)
886
- lines.push(`Note: ${data.description}`);
887
- if (data.storedAt)
888
- lines.push(`Stored: ${data.storedAt}`);
889
- return { content: [{ type: "text", text: lines.join("\n") }] };
890
- }
891
- case "vault_pin": {
892
- const parsed = PinSchema.safeParse(args);
893
- if (!parsed.success)
894
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
895
- const { key, pinned = true } = parsed.data;
896
- const data = localVault
897
- ? (0, local_vault_js_1.localVaultPin)(localVault, key, pinned)
898
- : await (0, convex_js_1.callConvex)("/vault/pin", "POST", { key, pinned }, "vault_pin");
899
- if (data.error)
900
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
901
- return { content: [{ type: "text", text: pinned ? `📌 Pinned: \`${key}\`` : `📌 Unpinned: \`${key}\`` }] };
902
- }
903
- case "vault_unpublish": {
904
- const parsed = UnpublishSchema.safeParse(args);
905
- if (!parsed.success)
906
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
907
- // Publishing is a hosted/marketplace concept - a local vault is private
908
- // by construction, so there is nothing to retract.
909
- if (localVault) {
910
- return { content: [{ type: "text", text: `🔒 \`${parsed.data.key}\` is in your local vault — already private, nothing to unpublish. (Publishing only applies to the hosted vault.)` }] };
911
- }
912
- const data = await (0, convex_js_1.callConvex)("/vault/unpublish", "POST", { key: parsed.data.key }, "vault_unpublish");
913
- if (data.error)
914
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
915
- return {
916
- content: [{
917
- type: "text",
918
- text: `🔒 **Private again:** \`${parsed.data.key}\`\n\n` +
919
- `Removed from the public community listing. Anyone who copied it while it was public ` +
920
- `still has that copy — this stops discovery, not distribution.`,
921
- }],
922
- };
923
- }
924
- case "vault_delete": {
925
- const parsed = DeleteSchema.safeParse(args);
926
- if (!parsed.success)
927
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
928
- if (args?.confirm !== true) {
929
- return {
930
- content: [{
931
- type: "text",
932
- text: "Refusing to delete: this permanently removes the entry **and its entire version " +
933
- "history**, and cannot be undone. Show the user the exact entry, then pass `confirm: true`.",
934
- }],
935
- isError: true,
936
- };
937
- }
938
- const data = localVault
939
- ? (0, local_vault_js_1.localVaultDelete)(localVault, parsed.data.key)
940
- : await (0, convex_js_1.callConvex)("/vault/delete", "POST", { key: parsed.data.key }, "vault_delete");
941
- if (data.error)
942
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
943
- // vault_save mirrors non-credential entries into memory for search - a
944
- // "PERMANENT... cannot be undone" delete that leaves that mirror intact
945
- // is not actually permanent. Only relevant for the local memory-file
946
- // backend (the hosted Convex path cleans its own memories table inside
947
- // the /vault/delete mutation itself, same request, no separate call).
948
- let memoriesRemoved = 0;
949
- const localMem = (0, local_memory_js_1.getLocalMemoryConfig)();
950
- if (localMem) {
951
- memoriesRemoved = (0, local_memory_js_1.localMemoryDeleteByVaultKey)(localMem, parsed.data.key);
952
- }
953
- else if (typeof data.memoriesRemoved === "number") {
954
- memoriesRemoved = data.memoriesRemoved;
955
- }
956
- const memoryNote = memoriesRemoved > 0 ? ` + ${memoriesRemoved} memory mirror${memoriesRemoved === 1 ? "" : "s"} removed` : "";
957
- return { content: [{ type: "text", text: `🗑️ Deleted: \`${parsed.data.key}\` (${data.versionsRemoved ?? 0} versions removed${memoryNote})` }] };
958
- }
959
- case "vault_tag": {
960
- const parsed = TagSchema.safeParse(args);
961
- if (!parsed.success)
962
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
963
- const { key, tags, replace = false } = parsed.data;
964
- const data = localVault
965
- ? (0, local_vault_js_1.localVaultTag)(localVault, key, tags, replace)
966
- : await (0, convex_js_1.callConvex)("/vault/tag", "POST", { key, tags, replace }, "vault_tag");
967
- if (data.error)
968
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
969
- return { content: [{ type: "text", text: `🏷️ Tags ${replace ? "set" : "updated"} on \`${key}\`: ${(data.tags ?? tags).join(", ")}` }] };
970
- }
971
- case "vault_link": {
972
- const parsed = LinkSchema.safeParse(args);
973
- if (!parsed.success)
974
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
975
- const { fromKey, toKey, relation } = parsed.data;
976
- const data = localVault
977
- ? (0, local_vault_js_1.localVaultLink)(localVault, fromKey, toKey, relation)
978
- : await (0, convex_js_1.callConvex)("/vault/link", "POST", { fromKey, toKey, relation }, "vault_link");
979
- if (data.error)
980
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
981
- const action = data.updated ? "Updated link" : "Linked";
982
- return { content: [{ type: "text", text: `🔗 ${action}: \`${fromKey}\` -[${relation}]→ \`${toKey}\`` }] };
983
- }
984
- case "vault_related": {
985
- const parsed = RelatedSchema.safeParse(args);
986
- if (!parsed.success)
987
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
988
- const { key, relation } = parsed.data;
989
- const params = new URLSearchParams({ key });
990
- if (relation)
991
- params.set("relation", relation);
992
- const data = (localVault
993
- ? (0, local_vault_js_1.localVaultRelated)(localVault, key, relation)
994
- : await (0, convex_js_1.callConvex)(`/vault/related?${params}`, "GET", undefined, "vault_related"));
995
- if (data.error)
996
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
997
- const items = data.related ?? [];
998
- if (!items.length)
999
- return { content: [{ type: "text", text: `No related entries found for \`${key}\`${relation ? ` (relation: ${relation})` : ""}.` }] };
1000
- const lines = items.map(r => `- **${r.title}** (\`${r.key}\`) [${r.type}] - ${r.direction} \`${r.relation}\``);
1001
- return { content: [{ type: "text", text: `## Related entries for \`${key}\` (${items.length})\n\n${lines.join("\n")}` }] };
1002
- }
1003
- case "list_projects": {
1004
- if (localVault) {
1005
- return { content: [{ type: "text", text: "Local vault mode has no project concept - `workspaceProject` has no effect, and there's nothing to list here." }] };
1006
- }
1007
- const data = await (0, convex_js_1.callConvex)("/projects/list", "GET", undefined, "list_projects");
1008
- if (data.error)
1009
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
1010
- const projects = data.projects ?? [];
1011
- if (!projects.length) {
1012
- return { content: [{ type: "text", text: "No projects yet. Pass `workspaceProject: \"name\"` to `vault_save` or `agent_spawn` and one is created automatically." }] };
1013
- }
1014
- const lines = projects.map((p) => `- **${p.name}** (\`${p.slug}\`)${p.description ? ` - ${p.description}` : ""}`);
1015
- return { content: [{ type: "text", text: `📁 **Your projects** (${projects.length})\n\n${lines.join("\n")}` }] };
1016
- }
1017
- default:
1018
- return null;
1019
- }
1020
- }