@finchagentic/mcp 4.1.0 → 4.4.1
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 +19 -15
- package/dist/agent-loop.js +75 -72
- package/dist/annotations.js +23 -14
- package/dist/convex.js +15 -18
- package/dist/index.js +14 -12
- package/dist/llm.js +12 -1
- package/dist/local-memory-file.js +14 -1
- package/dist/local-memory.js +13 -0
- package/dist/output-schemas.js +71 -17
- package/dist/project.js +36 -0
- package/dist/resources.js +6 -11
- package/dist/server.js +35 -13
- package/dist/token-gate.js +2 -2
- package/dist/tool-filter.js +14 -5
- package/dist/tools/agents.js +106 -394
- package/dist/tools/automation.js +42 -6
- package/dist/tools/base-mcp.js +3 -15
- package/dist/tools/base.js +34 -20
- package/dist/tools/coder.js +1 -1
- package/dist/tools/deep-research.js +1 -1
- package/dist/tools/defi.js +14 -34
- package/dist/tools/equity.js +10 -2
- package/dist/tools/events.js +1 -1
- package/dist/tools/github.js +51 -1
- package/dist/tools/insider.js +1 -1
- package/dist/tools/insight.js +4 -4
- package/dist/tools/market.js +5 -5
- package/dist/tools/memory.js +52 -88
- package/dist/tools/miroshark.js +8 -1
- package/dist/tools/monitor.js +8 -8
- package/dist/tools/os.js +9 -4
- package/dist/tools/packets.js +2 -2
- package/dist/tools/research-chain.js +1 -1
- package/dist/tools/research-compare.js +1 -1
- package/dist/tools/research.js +2 -2
- package/dist/tools/rh-bridge.js +1 -1
- package/dist/tools/rh-mcp.js +29 -4
- package/dist/tools/rh-orders.js +201 -123
- package/dist/tools/scanner.js +33 -3
- package/dist/tools/stake.js +369 -0
- package/dist/tools/vault.js +294 -40
- package/dist/wallet.js +130 -19
- package/package.json +4 -5
- package/dist/tools/framework.js +0 -150
package/dist/tools/vault.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.VAULT_TOOLS = void 0;
|
|
4
|
+
exports.buildVaultList = buildVaultList;
|
|
5
|
+
exports.buildVaultSearch = buildVaultSearch;
|
|
4
6
|
exports.handleVaultTool = handleVaultTool;
|
|
5
7
|
const zod_1 = require("zod");
|
|
6
8
|
const convex_js_1 = require("../convex.js");
|
|
7
9
|
const memory_js_1 = require("./memory.js");
|
|
8
10
|
const local_vault_js_1 = require("../local-vault.js");
|
|
9
11
|
const local_memory_js_1 = require("../local-memory.js");
|
|
10
|
-
const
|
|
12
|
+
const project_js_1 = require("../project.js");
|
|
13
|
+
const VAULT_TYPES = ["research", "execution", "workflow", "prompt", "file", "memory", "code", "credential"];
|
|
11
14
|
exports.VAULT_TOOLS = [
|
|
12
15
|
{
|
|
13
16
|
name: "vault_save",
|
|
14
17
|
description: "Save or update a versioned artifact in Finch Vault. Same key = update (git-style: prior version snapshotted, patched to v+1). " +
|
|
15
|
-
"Types: research | execution | workflow | prompt | file | memory. " +
|
|
18
|
+
"Types: research | execution | workflow | prompt | file | memory | code. " +
|
|
16
19
|
"Entries up to 10MB - content over 600KB auto-offloads to blob storage. " +
|
|
17
|
-
"For quick unstructured notes, use memory_add instead."
|
|
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.",
|
|
18
22
|
inputSchema: {
|
|
19
23
|
type: "object",
|
|
20
24
|
properties: {
|
|
@@ -27,10 +31,37 @@ exports.VAULT_TOOLS = [
|
|
|
27
31
|
tags: { type: "array", items: { type: "string" }, description: "Tags for filtering and search" },
|
|
28
32
|
commitMsg: { type: "string", description: "Commit message for this version, e.g. 'initial research', 'refined with on-chain data'" },
|
|
29
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
|
+
},
|
|
30
41
|
},
|
|
31
42
|
required: ["type", "content"],
|
|
32
43
|
},
|
|
33
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
|
+
},
|
|
34
65
|
{
|
|
35
66
|
name: "vault_read",
|
|
36
67
|
description: "Read a Finch Vault entry by its key. Returns full content, version, tags, and any linked entries.",
|
|
@@ -230,6 +261,14 @@ exports.VAULT_TOOLS = [
|
|
|
230
261
|
required: ["key"],
|
|
231
262
|
},
|
|
232
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
|
+
},
|
|
233
272
|
];
|
|
234
273
|
// ─── Zod schemas ─────────────────────────────────────────────────────────────
|
|
235
274
|
const SaveSchema = zod_1.z.object({
|
|
@@ -242,6 +281,15 @@ const SaveSchema = zod_1.z.object({
|
|
|
242
281
|
tags: zod_1.z.array(zod_1.z.string()).optional(),
|
|
243
282
|
commitMsg: zod_1.z.string().optional(),
|
|
244
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(),
|
|
245
293
|
});
|
|
246
294
|
const ReadSchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
|
|
247
295
|
const ListSchema = zod_1.z.object({
|
|
@@ -280,6 +328,35 @@ function formatBytes(n) {
|
|
|
280
328
|
function formatDate(ts) {
|
|
281
329
|
return new Date(ts).toUTCString();
|
|
282
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
|
+
}
|
|
283
360
|
// ─── Handler ─────────────────────────────────────────────────────────────────
|
|
284
361
|
async function handleVaultTool(name, args) {
|
|
285
362
|
// When the user has opted into a fully-local, user-owned vault
|
|
@@ -291,11 +368,37 @@ async function handleVaultTool(name, args) {
|
|
|
291
368
|
case "vault_save": {
|
|
292
369
|
const parsed = SaveSchema.safeParse(args);
|
|
293
370
|
if (!parsed.success)
|
|
294
|
-
return { content: [{ type: "text", text:
|
|
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
|
+
}
|
|
295
386
|
// Auto-generate title from content if not provided
|
|
296
387
|
const firstLine = parsed.data.content.split("\n")[0].replace(/^#+\s*/, "").slice(0, 80);
|
|
297
388
|
const autoTitle = parsed.data.title ?? (firstLine || `${parsed.data.type} - ${new Date().toISOString().slice(0, 10)}`);
|
|
298
|
-
const
|
|
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
|
+
}
|
|
299
402
|
const data = localVault
|
|
300
403
|
? (0, local_vault_js_1.localVaultSave)(localVault, savePayload)
|
|
301
404
|
: await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
|
|
@@ -335,6 +438,7 @@ async function handleVaultTool(name, args) {
|
|
|
335
438
|
`Key: \`${key}\``,
|
|
336
439
|
`Version: v${version}`,
|
|
337
440
|
changed && version > 1 ? `Previous version auto-snapshotted.` : "",
|
|
441
|
+
resolvedProjectName ? `📁 Project: ${resolvedProjectName}` : "",
|
|
338
442
|
mirrorToMemory ? `🧠 Synced to searchable memory` : (localVault ? `💾 Stored locally at ~/.finch/vault` : ""),
|
|
339
443
|
...linkSummary,
|
|
340
444
|
``,
|
|
@@ -342,10 +446,99 @@ async function handleVaultTool(name, args) {
|
|
|
342
446
|
].filter(Boolean);
|
|
343
447
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
344
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
|
+
}
|
|
345
538
|
case "vault_read": {
|
|
346
539
|
const parsed = ReadSchema.safeParse(args);
|
|
347
540
|
if (!parsed.success)
|
|
348
|
-
return { content: [{ type: "text", text:
|
|
541
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
349
542
|
const vaultReadKey = parsed.data.key;
|
|
350
543
|
let data;
|
|
351
544
|
try {
|
|
@@ -383,27 +576,38 @@ async function handleVaultTool(name, args) {
|
|
|
383
576
|
}
|
|
384
577
|
return { content: [{ type: "text", text: `vault_read error: ${data.error}` }], isError: true };
|
|
385
578
|
}
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
fullContent = (data.content ?? "") + `\n\n_(could not load full blob: ${err.message})_`;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
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 ?? "";
|
|
397
587
|
const sizeLabel = data.originalSize ? formatBytes(data.originalSize) : formatBytes(data.size);
|
|
398
588
|
const backlinksBlock = Array.isArray(data.backlinks) && data.backlinks.length > 0
|
|
399
589
|
? `\n🔙 Linked from (${data.backlinks.length}):\n${data.backlinks.map((b) => ` ← \`${b.key}\`${b.title ? ` - ${b.title}` : ""}`).join("\n")}`
|
|
400
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
|
+
}
|
|
401
604
|
const lines = [
|
|
402
605
|
`📂 **${data.title}**`,
|
|
403
606
|
`Key: \`${data.key}\` · Type: ${data.type} · v${data.version} · ${sizeLabel}${data.contentFileId ? " · blob" : ""}`,
|
|
404
607
|
data.tags?.length ? `Tags: ${data.tags.join(", ")}` : "",
|
|
405
608
|
data.isPinned ? "📌 Pinned" : "",
|
|
406
609
|
data.agentId ? `Agent: ${data.agentId}` : "",
|
|
610
|
+
projectLabel,
|
|
407
611
|
`Updated: ${formatDate(data.updatedAt)}`,
|
|
408
612
|
data.linkedKeys?.length ? `\nLinks out:\n${data.linkedKeys.map((l) => ` → ${l}`).join("\n")}` : "",
|
|
409
613
|
backlinksBlock,
|
|
@@ -417,7 +621,7 @@ async function handleVaultTool(name, args) {
|
|
|
417
621
|
case "vault_list": {
|
|
418
622
|
const parsed = ListSchema.safeParse(args ?? {});
|
|
419
623
|
if (!parsed.success)
|
|
420
|
-
return { content: [{ type: "text", text:
|
|
624
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
421
625
|
const params = new URLSearchParams();
|
|
422
626
|
if (parsed.data.type)
|
|
423
627
|
params.set("type", parsed.data.type);
|
|
@@ -433,23 +637,35 @@ async function handleVaultTool(name, args) {
|
|
|
433
637
|
if (data.error)
|
|
434
638
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
435
639
|
const entries = data.entries ?? [];
|
|
436
|
-
if (!entries.length)
|
|
437
|
-
return {
|
|
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
|
+
}
|
|
438
646
|
const header = `📚 **Finch Vault** (${entries.length} entries)`;
|
|
439
647
|
const rows = entries.map((e) => `${e.isPinned ? "📌 " : ""}[\`${e.key}\`] ${e.title} - v${e.version} · ${e.type} · ${formatBytes(e.size)} · ${formatDate(e.updatedAt)}`);
|
|
440
|
-
return {
|
|
648
|
+
return {
|
|
649
|
+
content: [{ type: "text", text: [header, "", ...rows].join("\n") }],
|
|
650
|
+
structuredContent: buildVaultList(entries, parsed.data.type),
|
|
651
|
+
};
|
|
441
652
|
}
|
|
442
653
|
case "vault_search": {
|
|
443
654
|
const parsed = SearchSchema.safeParse(args);
|
|
444
655
|
if (!parsed.success)
|
|
445
|
-
return { content: [{ type: "text", text:
|
|
656
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
446
657
|
// Full-text search, proxied through Convex (searchSupermemory is a
|
|
447
658
|
// legacy name - it calls Finch's own /memory/search endpoint, not a
|
|
448
659
|
// third-party semantic service; there is no embedding step here). Large vault
|
|
449
660
|
// entries are indexed as multiple chunks tagged with isVaultChunk +
|
|
450
661
|
// vaultKey - group chunks back to their parent entry so the result
|
|
451
662
|
// list shows one row per entry, not one row per chunk.
|
|
452
|
-
|
|
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) {
|
|
453
669
|
const limit = parsed.data.limit ?? 20;
|
|
454
670
|
// Over-fetch so that after chunk dedup we still have ~limit rows.
|
|
455
671
|
const smResults = await (0, memory_js_1.searchSupermemory)(parsed.data.query, Math.min(50, limit * 3));
|
|
@@ -489,7 +705,7 @@ async function handleVaultTool(name, args) {
|
|
|
489
705
|
const grouped = Array.from(groups.values())
|
|
490
706
|
.sort((a, b) => b.bestScore - a.bestScore)
|
|
491
707
|
.slice(0, limit);
|
|
492
|
-
const header = `🔍 **Vault Search
|
|
708
|
+
const header = `🔍 **Vault Search**: "${parsed.data.query}" - ${grouped.length} entry/entries`;
|
|
493
709
|
const rows = grouped.map((g, i) => {
|
|
494
710
|
const score = g.bestScore ? ` ${(g.bestScore * 100).toFixed(0)}%` : "";
|
|
495
711
|
const chunkBadge = g.isVaultChunk && g.chunkHits > 1
|
|
@@ -500,7 +716,10 @@ async function handleVaultTool(name, args) {
|
|
|
500
716
|
` ${g.bestPreview}${g.bestPreview.length >= 200 ? "…" : ""}`,
|
|
501
717
|
].join("\n");
|
|
502
718
|
});
|
|
503
|
-
return {
|
|
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
|
+
};
|
|
504
723
|
}
|
|
505
724
|
}
|
|
506
725
|
}
|
|
@@ -516,19 +735,26 @@ async function handleVaultTool(name, args) {
|
|
|
516
735
|
if (data.error)
|
|
517
736
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
518
737
|
const results = data.results ?? [];
|
|
519
|
-
if (!results.length)
|
|
520
|
-
return {
|
|
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
|
+
}
|
|
521
744
|
const header = `🔍 **Vault Search**: "${parsed.data.query}" - ${results.length} result(s)`;
|
|
522
745
|
const rows = results.map((r, i) => [
|
|
523
746
|
`${i + 1}. [\`${r.key}\`] **${r.title}** (${r.type} · v${r.version})`,
|
|
524
747
|
` ${r.preview}`,
|
|
525
748
|
].join("\n"));
|
|
526
|
-
return {
|
|
749
|
+
return {
|
|
750
|
+
content: [{ type: "text", text: [header, "", ...rows].join("\n") }],
|
|
751
|
+
structuredContent: buildVaultSearch(parsed.data.query, results),
|
|
752
|
+
};
|
|
527
753
|
}
|
|
528
754
|
case "vault_history": {
|
|
529
755
|
const parsed = HistorySchema.safeParse(args);
|
|
530
756
|
if (!parsed.success)
|
|
531
|
-
return { content: [{ type: "text", text:
|
|
757
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
532
758
|
const histKey = parsed.data.key;
|
|
533
759
|
let data;
|
|
534
760
|
try {
|
|
@@ -568,7 +794,7 @@ async function handleVaultTool(name, args) {
|
|
|
568
794
|
case "vault_diff": {
|
|
569
795
|
const parsed = DiffSchema.safeParse(args);
|
|
570
796
|
if (!parsed.success)
|
|
571
|
-
return { content: [{ type: "text", text:
|
|
797
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
572
798
|
const { key, fromVersion, toVersion } = parsed.data;
|
|
573
799
|
let data;
|
|
574
800
|
try {
|
|
@@ -612,7 +838,7 @@ async function handleVaultTool(name, args) {
|
|
|
612
838
|
case "vault_export": {
|
|
613
839
|
const parsed = ExportSchema.safeParse(args ?? {});
|
|
614
840
|
if (!parsed.success)
|
|
615
|
-
return { content: [{ type: "text", text:
|
|
841
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
616
842
|
const params = parsed.data.type ? `?type=${parsed.data.type}` : "";
|
|
617
843
|
const data = localVault
|
|
618
844
|
? (0, local_vault_js_1.localVaultExport)(localVault, parsed.data.type)
|
|
@@ -631,7 +857,7 @@ async function handleVaultTool(name, args) {
|
|
|
631
857
|
case "vault_store_credential": {
|
|
632
858
|
const parsed = StoreCredentialSchema.safeParse(args);
|
|
633
859
|
if (!parsed.success)
|
|
634
|
-
return { content: [{ type: "text", text:
|
|
860
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
635
861
|
const data = localVault
|
|
636
862
|
? (0, local_vault_js_1.localVaultStoreCredential)(localVault, parsed.data.name, parsed.data.value, parsed.data.description)
|
|
637
863
|
: await (0, convex_js_1.callConvex)("/vault/credential/store", "POST", parsed.data, "vault_store_credential");
|
|
@@ -642,7 +868,7 @@ async function handleVaultTool(name, args) {
|
|
|
642
868
|
case "vault_get_credential": {
|
|
643
869
|
const parsed = GetCredentialSchema.safeParse(args);
|
|
644
870
|
if (!parsed.success)
|
|
645
|
-
return { content: [{ type: "text", text:
|
|
871
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
646
872
|
const params = new URLSearchParams({ name: parsed.data.name });
|
|
647
873
|
let data;
|
|
648
874
|
try {
|
|
@@ -665,7 +891,7 @@ async function handleVaultTool(name, args) {
|
|
|
665
891
|
case "vault_pin": {
|
|
666
892
|
const parsed = PinSchema.safeParse(args);
|
|
667
893
|
if (!parsed.success)
|
|
668
|
-
return { content: [{ type: "text", text:
|
|
894
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
669
895
|
const { key, pinned = true } = parsed.data;
|
|
670
896
|
const data = localVault
|
|
671
897
|
? (0, local_vault_js_1.localVaultPin)(localVault, key, pinned)
|
|
@@ -677,7 +903,7 @@ async function handleVaultTool(name, args) {
|
|
|
677
903
|
case "vault_unpublish": {
|
|
678
904
|
const parsed = UnpublishSchema.safeParse(args);
|
|
679
905
|
if (!parsed.success)
|
|
680
|
-
return { content: [{ type: "text", text:
|
|
906
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
681
907
|
// Publishing is a hosted/marketplace concept - a local vault is private
|
|
682
908
|
// by construction, so there is nothing to retract.
|
|
683
909
|
if (localVault) {
|
|
@@ -698,7 +924,7 @@ async function handleVaultTool(name, args) {
|
|
|
698
924
|
case "vault_delete": {
|
|
699
925
|
const parsed = DeleteSchema.safeParse(args);
|
|
700
926
|
if (!parsed.success)
|
|
701
|
-
return { content: [{ type: "text", text:
|
|
927
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
702
928
|
if (args?.confirm !== true) {
|
|
703
929
|
return {
|
|
704
930
|
content: [{
|
|
@@ -714,12 +940,26 @@ async function handleVaultTool(name, args) {
|
|
|
714
940
|
: await (0, convex_js_1.callConvex)("/vault/delete", "POST", { key: parsed.data.key }, "vault_delete");
|
|
715
941
|
if (data.error)
|
|
716
942
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
717
|
-
|
|
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})` }] };
|
|
718
958
|
}
|
|
719
959
|
case "vault_tag": {
|
|
720
960
|
const parsed = TagSchema.safeParse(args);
|
|
721
961
|
if (!parsed.success)
|
|
722
|
-
return { content: [{ type: "text", text:
|
|
962
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
723
963
|
const { key, tags, replace = false } = parsed.data;
|
|
724
964
|
const data = localVault
|
|
725
965
|
? (0, local_vault_js_1.localVaultTag)(localVault, key, tags, replace)
|
|
@@ -731,7 +971,7 @@ async function handleVaultTool(name, args) {
|
|
|
731
971
|
case "vault_link": {
|
|
732
972
|
const parsed = LinkSchema.safeParse(args);
|
|
733
973
|
if (!parsed.success)
|
|
734
|
-
return { content: [{ type: "text", text:
|
|
974
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
735
975
|
const { fromKey, toKey, relation } = parsed.data;
|
|
736
976
|
const data = localVault
|
|
737
977
|
? (0, local_vault_js_1.localVaultLink)(localVault, fromKey, toKey, relation)
|
|
@@ -744,7 +984,7 @@ async function handleVaultTool(name, args) {
|
|
|
744
984
|
case "vault_related": {
|
|
745
985
|
const parsed = RelatedSchema.safeParse(args);
|
|
746
986
|
if (!parsed.success)
|
|
747
|
-
return { content: [{ type: "text", text:
|
|
987
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
748
988
|
const { key, relation } = parsed.data;
|
|
749
989
|
const params = new URLSearchParams({ key });
|
|
750
990
|
if (relation)
|
|
@@ -760,6 +1000,20 @@ async function handleVaultTool(name, args) {
|
|
|
760
1000
|
const lines = items.map(r => `- **${r.title}** (\`${r.key}\`) [${r.type}] - ${r.direction} \`${r.relation}\``);
|
|
761
1001
|
return { content: [{ type: "text", text: `## Related entries for \`${key}\` (${items.length})\n\n${lines.join("\n")}` }] };
|
|
762
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
|
+
}
|
|
763
1017
|
default:
|
|
764
1018
|
return null;
|
|
765
1019
|
}
|