@finchagentic/mcp 4.0.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +96 -249
- package/dist/agent-loop.js +139 -69
- package/dist/annotations.js +14 -13
- package/dist/cli.js +78 -21
- package/dist/config.js +16 -16
- package/dist/convex.js +15 -18
- package/dist/index.js +4 -5
- package/dist/llm.js +24 -49
- package/dist/local-memory-file.js +147 -0
- package/dist/local-memory.js +42 -9
- package/dist/output-schemas.js +71 -17
- package/dist/resources.js +8 -13
- package/dist/server.js +27 -13
- package/dist/token-gate.js +2 -2
- package/dist/tool-filter.js +13 -4
- package/dist/tools/agents.js +66 -394
- package/dist/tools/base-mcp.js +7 -19
- package/dist/tools/base.js +34 -20
- package/dist/tools/chronicle.js +4 -4
- package/dist/tools/deep-research.js +10 -5
- package/dist/tools/defi.js +36 -35
- package/dist/tools/equity.js +9 -1
- package/dist/tools/insight.js +25 -29
- package/dist/tools/memory.js +76 -108
- package/dist/tools/monitor.js +7 -7
- package/dist/tools/os.js +10 -6
- package/dist/tools/packets.js +5 -5
- package/dist/tools/research.js +2 -2
- package/dist/tools/rh-mcp.js +25 -2
- package/dist/tools/rh-orders.js +201 -123
- package/dist/tools/scanner.js +30 -0
- package/dist/tools/stake.js +329 -0
- package/dist/tools/vault.js +122 -42
- package/dist/wallet.js +212 -24
- package/package.json +9 -21
- package/dist/tools/framework.js +0 -150
package/dist/tools/memory.js
CHANGED
|
@@ -47,6 +47,7 @@ const zod_1 = require("zod");
|
|
|
47
47
|
const crypto = __importStar(require("crypto"));
|
|
48
48
|
const convex_js_1 = require("../convex.js");
|
|
49
49
|
const public_url_js_1 = require("../public-url.js");
|
|
50
|
+
const local_vault_js_1 = require("../local-vault.js");
|
|
50
51
|
const local_memory_js_1 = require("../local-memory.js");
|
|
51
52
|
// memory_extract and memory_consolidate used to run their own LLM calls here
|
|
52
53
|
// (and a matching pair of Convex routes did the same server-side). Both are now
|
|
@@ -89,6 +90,18 @@ function lookupRecentHash(hash) {
|
|
|
89
90
|
}
|
|
90
91
|
return hit;
|
|
91
92
|
}
|
|
93
|
+
// memory_delete must purge this too - otherwise a deleted memory's hash stays
|
|
94
|
+
// cached (up to an hour) and re-adding the exact same content within that
|
|
95
|
+
// window gets silently skipped as a "duplicate" of an id that no longer
|
|
96
|
+
// exists, when there is no longer any real duplicate to skip.
|
|
97
|
+
function forgetRecentHashById(id) {
|
|
98
|
+
for (const [hash, entry] of recentHashCache) {
|
|
99
|
+
if (entry.id === id) {
|
|
100
|
+
recentHashCache.delete(hash);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
92
105
|
// Two-tier dedup lookup: in-process cache first (catches same-session
|
|
93
106
|
// dupes during eventual-consistency window), then a list call (catches
|
|
94
107
|
// cross-session dupes once they've been indexed). Returns null on any
|
|
@@ -101,7 +114,7 @@ async function findDuplicateMemory(hash) {
|
|
|
101
114
|
const local = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
102
115
|
const results = local
|
|
103
116
|
? await (0, local_memory_js_1.localMemoryList)(local, 50)
|
|
104
|
-
: ((await (0, convex_js_1.callConvex)("/memory/list", "POST", { n: 50 }))?.results ?? []);
|
|
117
|
+
: ((await (0, convex_js_1.callConvex)("/memory/list", "POST", { n: 50 }, "memory_list"))?.results ?? []);
|
|
105
118
|
const match = results.find((r) => r.metadata?.contentHash === hash);
|
|
106
119
|
if (!match)
|
|
107
120
|
return null;
|
|
@@ -154,7 +167,7 @@ async function syncToSupermemory(content, metadata, sourceUrl) {
|
|
|
154
167
|
};
|
|
155
168
|
for (let attempt = 0; attempt <= SYNC_RETRY_DELAYS_MS.length; attempt++) {
|
|
156
169
|
try {
|
|
157
|
-
await (0, convex_js_1.callConvex)("/memory/add", "POST", payload);
|
|
170
|
+
await (0, convex_js_1.callConvex)("/memory/add", "POST", payload, "memory_add");
|
|
158
171
|
return;
|
|
159
172
|
}
|
|
160
173
|
catch (err) {
|
|
@@ -183,7 +196,7 @@ async function searchSupermemory(query, limit = 10) {
|
|
|
183
196
|
const local = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
184
197
|
if (local)
|
|
185
198
|
return await (0, local_memory_js_1.localMemorySearch)(local, query, limit);
|
|
186
|
-
const data = await (0, convex_js_1.callConvex)("/memory/search", "POST", { q: query, n: limit });
|
|
199
|
+
const data = await (0, convex_js_1.callConvex)("/memory/search", "POST", { q: query, n: limit }, "memory_search");
|
|
187
200
|
return data?.results ?? [];
|
|
188
201
|
}
|
|
189
202
|
catch {
|
|
@@ -195,7 +208,7 @@ async function searchSupermemory(query, limit = 10) {
|
|
|
195
208
|
// directly without re-normalizing raw scores.
|
|
196
209
|
async function lexicalSearch(query, limit = 30) {
|
|
197
210
|
try {
|
|
198
|
-
const data = await (0, convex_js_1.callConvex)("/memory/lexical", "POST", { q: query, n: limit });
|
|
211
|
+
const data = await (0, convex_js_1.callConvex)("/memory/lexical", "POST", { q: query, n: limit }, "memory_search");
|
|
199
212
|
const rows = (data?.results ?? []);
|
|
200
213
|
return rows.map((r, idx) => ({
|
|
201
214
|
id: r.id,
|
|
@@ -284,12 +297,13 @@ async function hybridMemorySearch(query, limit = 30) {
|
|
|
284
297
|
exports.MEMORY_TOOLS = [
|
|
285
298
|
{
|
|
286
299
|
name: "memory_add",
|
|
287
|
-
description: "Add content to your Finch
|
|
300
|
+
description: "Add content to your Finch memory - no setup needed, no extra API keys. " +
|
|
288
301
|
"Unlike vault_save, memory_add is instant: no versioning, no type required. " +
|
|
289
|
-
"Use for notes, decisions, preferences, or anything you want to find later
|
|
302
|
+
"Use for notes, decisions, preferences, or anything you want to find later. " +
|
|
290
303
|
"Pass sourceUrl to fetch and index any web page, GitHub repo, or Notion page automatically - " +
|
|
291
|
-
"searchable in ~30s.
|
|
292
|
-
"
|
|
304
|
+
"searchable in ~30s. Retrieval is full-text (keyword) search, not embeddings - " +
|
|
305
|
+
"'what did I say about ETH yield?' finds notes containing those words or close variants, " +
|
|
306
|
+
"not unrelated phrasing with the same meaning. " +
|
|
293
307
|
"Auto-deduplicates: identical content in your recent 50 memories is skipped (override with force:true).",
|
|
294
308
|
inputSchema: {
|
|
295
309
|
type: "object",
|
|
@@ -305,10 +319,11 @@ exports.MEMORY_TOOLS = [
|
|
|
305
319
|
},
|
|
306
320
|
{
|
|
307
321
|
name: "memory_search",
|
|
308
|
-
description: "
|
|
309
|
-
"
|
|
310
|
-
"
|
|
311
|
-
"
|
|
322
|
+
description: "Full-text (keyword) search over your stored memories, with 90-day time-decay weighting so " +
|
|
323
|
+
"recent notes outrank stale ones with similar wording. Good for exact-token lookups (env var " +
|
|
324
|
+
"names, contract addresses, IDs, specific phrases) - it does not understand meaning, so " +
|
|
325
|
+
"'low risk crypto yield' will not match a note phrased as 'conservative DeFi strategies' " +
|
|
326
|
+
"unless the words themselves overlap.",
|
|
312
327
|
inputSchema: {
|
|
313
328
|
type: "object",
|
|
314
329
|
properties: {
|
|
@@ -320,9 +335,10 @@ exports.MEMORY_TOOLS = [
|
|
|
320
335
|
},
|
|
321
336
|
{
|
|
322
337
|
name: "memory_context",
|
|
323
|
-
description: "Retrieve the most
|
|
338
|
+
description: "Retrieve the most relevant memories for a topic, formatted as AI-ready context. " +
|
|
324
339
|
"Use at the start of research tasks to prime with everything stored about a topic. " +
|
|
325
|
-
"Uses
|
|
340
|
+
"Uses full-text (keyword) search, not embeddings - phrase your topic with the words " +
|
|
341
|
+
"you expect were actually used when the memory was saved.",
|
|
326
342
|
inputSchema: {
|
|
327
343
|
type: "object",
|
|
328
344
|
properties: {
|
|
@@ -334,7 +350,7 @@ exports.MEMORY_TOOLS = [
|
|
|
334
350
|
},
|
|
335
351
|
{
|
|
336
352
|
name: "memory_profile",
|
|
337
|
-
description: "Show your
|
|
353
|
+
description: "Show your memory stats - total memories stored, your memory space, and connected sources. " +
|
|
338
354
|
"Useful for auditing what Finch knows about you.",
|
|
339
355
|
inputSchema: {
|
|
340
356
|
type: "object",
|
|
@@ -373,7 +389,7 @@ exports.MEMORY_TOOLS = [
|
|
|
373
389
|
},
|
|
374
390
|
{
|
|
375
391
|
name: "memory_insight",
|
|
376
|
-
description: "Get a full intelligence report on any topic - combines
|
|
392
|
+
description: "Get a full intelligence report on any topic - combines memory AND vault entries, " +
|
|
377
393
|
"then identifies knowledge gaps and suggests next actions. " +
|
|
378
394
|
"Use this before starting any research or trade decision to see everything Finch already knows. " +
|
|
379
395
|
"Returns: confidence level, what you know, coverage timeline, gaps, and recommended next steps.",
|
|
@@ -388,7 +404,7 @@ exports.MEMORY_TOOLS = [
|
|
|
388
404
|
},
|
|
389
405
|
{
|
|
390
406
|
name: "memory_extract",
|
|
391
|
-
description: "Save discrete facts, preferences and decisions to
|
|
407
|
+
description: "Save discrete facts, preferences and decisions to memory as individually searchable atoms " +
|
|
392
408
|
"instead of one wall of text. Two-pass, no API key needed. " +
|
|
393
409
|
"PASS 1 — call with `text`: returns the text with the extraction rubric. " +
|
|
394
410
|
"PASS 2 — call with `facts: [...]`: stores each fact separately, deduped. " +
|
|
@@ -407,26 +423,6 @@ exports.MEMORY_TOOLS = [
|
|
|
407
423
|
required: [],
|
|
408
424
|
},
|
|
409
425
|
},
|
|
410
|
-
{
|
|
411
|
-
name: "memory_publish",
|
|
412
|
-
description: "IRREVERSIBLE, PUBLIC. Publish a memory snippet to the Memory Marketplace — visible to " +
|
|
413
|
-
"ALL Finch users at /memory-marketplace. Reversible with vault_unpublish, but only for " +
|
|
414
|
-
"future discovery — anyone who already read it keeps what they saw. " +
|
|
415
|
-
"Requires confirm: true. Never call this on the user's behalf without them explicitly asking " +
|
|
416
|
-
"to publish; re-read the content for anything private (keys, addresses, personal details) first. " +
|
|
417
|
-
"Saved as a public vault entry (type=memory).",
|
|
418
|
-
inputSchema: {
|
|
419
|
-
type: "object",
|
|
420
|
-
properties: {
|
|
421
|
-
title: { type: "string", description: "Short title for the memory (shown publicly in the marketplace)" },
|
|
422
|
-
content: { type: "string", description: "The memory content to share — this becomes PUBLIC" },
|
|
423
|
-
tags: { type: "array", items: { type: "string" }, description: "Optional tags (e.g. ['DeFi', 'Base', 'research'])" },
|
|
424
|
-
authorName: { type: "string", description: "Public display name. Defaults to \"Anonymous\" — do NOT pass a wallet address unless the user asks to be identified." },
|
|
425
|
-
confirm: { type: "boolean", description: "Must be true to publish. Guards against accidental public disclosure." },
|
|
426
|
-
},
|
|
427
|
-
required: ["title", "content", "confirm"],
|
|
428
|
-
},
|
|
429
|
-
},
|
|
430
426
|
{
|
|
431
427
|
name: "memory_consolidate",
|
|
432
428
|
description: "Clean up fragmented knowledge after heavy research sessions. Two-pass, no API key needed. " +
|
|
@@ -560,7 +556,7 @@ async function handleMemoryTool(name, args) {
|
|
|
560
556
|
const localAdd = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
561
557
|
const data = localAdd
|
|
562
558
|
? await (0, local_memory_js_1.localMemoryAdd)(localAdd, content, addMetadata, sourceUrl).catch((err) => ({ error: err.message }))
|
|
563
|
-
: await (0, convex_js_1.callConvex)("/memory/add", "POST", { content, metadata: addMetadata, ...(sourceUrl ? { sourceUrl } : {}) }).catch((err) => ({ error: err.message }));
|
|
559
|
+
: await (0, convex_js_1.callConvex)("/memory/add", "POST", { content, metadata: addMetadata, ...(sourceUrl ? { sourceUrl } : {}) }, "memory_add").catch((err) => ({ error: err.message }));
|
|
564
560
|
if (data?.error)
|
|
565
561
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
566
562
|
// Cache the hash so an immediate second call with identical content
|
|
@@ -588,11 +584,12 @@ async function handleMemoryTool(name, args) {
|
|
|
588
584
|
const { query, limit = 10 } = parsed.data;
|
|
589
585
|
// Over-fetch so post-decay ranking still has enough material.
|
|
590
586
|
const overfetch = Math.min(50, Math.max(limit * 2, 20));
|
|
591
|
-
// ───
|
|
592
|
-
// Fan out
|
|
593
|
-
//
|
|
594
|
-
//
|
|
595
|
-
//
|
|
587
|
+
// ─── Retrieval ───────────────────────────────────────────────────
|
|
588
|
+
// Fan out two full-text queries (searchSupermemory and lexicalSearch -
|
|
589
|
+
// both call Convex full-text search under different names, there is no
|
|
590
|
+
// embedding step despite the "semantic"/"Supermemory" naming) in
|
|
591
|
+
// parallel, fuse via Reciprocal Rank Fusion so results present in both
|
|
592
|
+
// rank highest.
|
|
596
593
|
const fused = await hybridMemorySearch(query, overfetch);
|
|
597
594
|
const raw = fused;
|
|
598
595
|
if (!raw.length)
|
|
@@ -697,7 +694,7 @@ async function handleMemoryTool(name, args) {
|
|
|
697
694
|
const localProfileCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
698
695
|
const data = localProfileCfg
|
|
699
696
|
? await (0, local_memory_js_1.localMemoryProfile)(localProfileCfg).catch(() => null)
|
|
700
|
-
: await (0, convex_js_1.callConvex)("/memory/profile", "GET").catch(() => null);
|
|
697
|
+
: await (0, convex_js_1.callConvex)("/memory/profile", "GET", undefined, "memory_profile").catch(() => null);
|
|
701
698
|
const total = data?.total ?? 0;
|
|
702
699
|
const status = data?.status ?? "unknown";
|
|
703
700
|
const space = data?.space ?? "-";
|
|
@@ -705,7 +702,7 @@ async function handleMemoryTool(name, args) {
|
|
|
705
702
|
content: [{
|
|
706
703
|
type: "text",
|
|
707
704
|
text: [
|
|
708
|
-
`🧠 **Finch
|
|
705
|
+
`🧠 **Finch Memory**`,
|
|
709
706
|
``,
|
|
710
707
|
`Space: \`${space}\``,
|
|
711
708
|
`Total memories: **${total}**`,
|
|
@@ -716,7 +713,7 @@ async function handleMemoryTool(name, args) {
|
|
|
716
713
|
`• memory_add (URL indexing) - ✅`,
|
|
717
714
|
`• Google Drive / Gmail / Notion - connect at finchagentic.com`,
|
|
718
715
|
``,
|
|
719
|
-
`**Capabilities:**
|
|
716
|
+
`**Capabilities:** Full-text (keyword) search with 90-day time-decay ranking - not embeddings, no vector search.`,
|
|
720
717
|
].join("\n"),
|
|
721
718
|
}],
|
|
722
719
|
structuredContent: buildMemoryProfile(data),
|
|
@@ -738,7 +735,7 @@ async function handleMemoryTool(name, args) {
|
|
|
738
735
|
}
|
|
739
736
|
}
|
|
740
737
|
else {
|
|
741
|
-
const data = await (0, convex_js_1.callConvex)("/memory/list", "POST", { n: limit, tag }).catch((err) => ({ error: err.message }));
|
|
738
|
+
const data = await (0, convex_js_1.callConvex)("/memory/list", "POST", { n: limit, tag }, "memory_list").catch((err) => ({ error: err.message }));
|
|
742
739
|
if (data?.error)
|
|
743
740
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
744
741
|
results = data?.results ?? [];
|
|
@@ -777,10 +774,11 @@ async function handleMemoryTool(name, args) {
|
|
|
777
774
|
}
|
|
778
775
|
}
|
|
779
776
|
else {
|
|
780
|
-
const data = await (0, convex_js_1.callConvex)("/memory/delete", "POST", { id: parsed.data.id }).catch((err) => ({ error: err.message }));
|
|
777
|
+
const data = await (0, convex_js_1.callConvex)("/memory/delete", "POST", { id: parsed.data.id }, "memory_delete").catch((err) => ({ error: err.message }));
|
|
781
778
|
if (data?.error)
|
|
782
779
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
783
780
|
}
|
|
781
|
+
forgetRecentHashById(parsed.data.id);
|
|
784
782
|
return { content: [{ type: "text", text: `🗑️ Memory deleted: \`${parsed.data.id}\`` }] };
|
|
785
783
|
}
|
|
786
784
|
case "memory_insight": {
|
|
@@ -791,10 +789,15 @@ async function handleMemoryTool(name, args) {
|
|
|
791
789
|
const memLimit = depth === "deep" ? 15 : depth === "quick" ? 5 : 8;
|
|
792
790
|
// v3.25.1: hybrid retrieval for memory side (was semantic-only) so the
|
|
793
791
|
// intelligence report surfaces exact-token matches alongside meaning
|
|
794
|
-
// matches. Vault side runs in parallel as before
|
|
792
|
+
// matches. Vault side runs in parallel as before - but only against
|
|
793
|
+
// Convex when the vault isn't local; a local vault's topic string must
|
|
794
|
+
// never leave the machine, and this never checked that before.
|
|
795
|
+
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
795
796
|
const [memResults, vaultData] = await Promise.all([
|
|
796
797
|
hybridMemorySearch(topic, memLimit),
|
|
797
|
-
|
|
798
|
+
localVault
|
|
799
|
+
? Promise.resolve((0, local_vault_js_1.localVaultSearch)(localVault, topic, { limit: 6 }))
|
|
800
|
+
: (0, convex_js_1.callConvex)(`/vault/search?q=${encodeURIComponent(topic)}&limit=6`, "GET", undefined, "vault_search").catch(() => ({ results: [] })),
|
|
798
801
|
]);
|
|
799
802
|
const vaultResults = vaultData.results ?? [];
|
|
800
803
|
const total = memResults.length + vaultResults.length;
|
|
@@ -905,7 +908,7 @@ async function handleMemoryTool(name, args) {
|
|
|
905
908
|
const metaFor = () => ({ source, addedAt: Date.now() });
|
|
906
909
|
const results = await Promise.allSettled(facts.map((fact) => localExtractCfg
|
|
907
910
|
? (0, local_memory_js_1.localMemoryAdd)(localExtractCfg, fact, metaFor())
|
|
908
|
-
: (0, convex_js_1.callConvex)("/memory/add", "POST", { content: fact, metadata: metaFor() })));
|
|
911
|
+
: (0, convex_js_1.callConvex)("/memory/add", "POST", { content: fact, metadata: metaFor() }, "memory_add")));
|
|
909
912
|
const saved = results.filter((r) => r.status === "fulfilled").length;
|
|
910
913
|
const failed = results.length - saved;
|
|
911
914
|
return {
|
|
@@ -934,7 +937,7 @@ async function handleMemoryTool(name, args) {
|
|
|
934
937
|
const meta = { title: `Consolidated: ${topic}`, source: "memory_consolidate", addedAt: Date.now() };
|
|
935
938
|
const saved = localConsolidateCfg
|
|
936
939
|
? await (0, local_memory_js_1.localMemoryAdd)(localConsolidateCfg, summary, meta).catch((err) => ({ error: err.message }))
|
|
937
|
-
: await (0, convex_js_1.callConvex)("/memory/add", "POST", { content: summary, metadata: meta }).catch((err) => ({ error: err.message }));
|
|
940
|
+
: await (0, convex_js_1.callConvex)("/memory/add", "POST", { content: summary, metadata: meta }, "memory_add").catch((err) => ({ error: err.message }));
|
|
938
941
|
if (saved?.error) {
|
|
939
942
|
return { content: [{ type: "text", text: `Error saving consolidated memory: ${saved.error}` }], isError: true };
|
|
940
943
|
}
|
|
@@ -951,7 +954,11 @@ async function handleMemoryTool(name, args) {
|
|
|
951
954
|
};
|
|
952
955
|
}
|
|
953
956
|
// ── PASS 1: fetch every memory on the topic, numbered ───────────────
|
|
954
|
-
|
|
957
|
+
// Uses the same RRF-fused semantic+lexical retrieval as memory_search/
|
|
958
|
+
// memory_context - a plain single-source searchSupermemory() call here
|
|
959
|
+
// used to mean consolidation could miss memories only the lexical/BM25
|
|
960
|
+
// side would surface, while still claiming an exhaustive "N memories".
|
|
961
|
+
const rows = await hybridMemorySearch(topic, limit);
|
|
955
962
|
if (rows.length === 0) {
|
|
956
963
|
return { content: [{ type: "text", text: `No memories found for "${topic}" to consolidate.` }], isError: true };
|
|
957
964
|
}
|
|
@@ -985,61 +992,22 @@ async function handleMemoryTool(name, args) {
|
|
|
985
992
|
}],
|
|
986
993
|
};
|
|
987
994
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
return {
|
|
1005
|
-
content: [{
|
|
1006
|
-
type: "text",
|
|
1007
|
-
text: "Refusing to publish with a wallet address as the author name — that permanently " +
|
|
1008
|
-
"links your on-chain identity to this public entry. Use a handle, or omit authorName " +
|
|
1009
|
-
"to publish as \"Anonymous\".",
|
|
1010
|
-
}],
|
|
1011
|
-
isError: true,
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
const data = await (0, convex_js_1.callConvex)("/vault/save", "POST", {
|
|
1015
|
-
type: "memory",
|
|
1016
|
-
title,
|
|
1017
|
-
content,
|
|
1018
|
-
tags: tags ?? [],
|
|
1019
|
-
isPublic: true,
|
|
1020
|
-
authorName: authorName ?? "Anonymous",
|
|
1021
|
-
commitMsg: "published to marketplace",
|
|
1022
|
-
}, "vault_save");
|
|
1023
|
-
if (data.error)
|
|
1024
|
-
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
1025
|
-
return {
|
|
1026
|
-
content: [{
|
|
1027
|
-
type: "text",
|
|
1028
|
-
text: [
|
|
1029
|
-
`🧠 **Memory Published**`,
|
|
1030
|
-
``,
|
|
1031
|
-
`**Title:** ${title}`,
|
|
1032
|
-
`**Key:** \`${data.key}\``,
|
|
1033
|
-
`**Version:** ${data.version ?? 1}`,
|
|
1034
|
-
``,
|
|
1035
|
-
`Now visible at the Memory Marketplace in the Finch app.`,
|
|
1036
|
-
``,
|
|
1037
|
-
`Make it private again: \`vault_unpublish key: "${data.key}"\``,
|
|
1038
|
-
`That stops future discovery. Anyone who already read it keeps what they saw.`,
|
|
1039
|
-
].join("\n"),
|
|
1040
|
-
}],
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
995
|
+
// memory_publish was removed - it was broken two levels deep. The tool
|
|
996
|
+
// called POST /vault/save with isPublic/authorName fields that the
|
|
997
|
+
// handler silently dropped (only type/title/content/key/contentType/
|
|
998
|
+
// agentId/tags/commitMsg/metadata are forwarded to vault.saveEntry - see
|
|
999
|
+
// app/convex/http.ts), so `published` was never actually set true; the
|
|
1000
|
+
// real publishEntry mutation (POST /vault/publish) existed but this tool
|
|
1001
|
+
// never called it. And even a correctly-wired publish would have done
|
|
1002
|
+
// nothing observable: nothing anywhere reads the `published` field for
|
|
1003
|
+
// cross-user browsing - no /vault/community route (dangling comment
|
|
1004
|
+
// only, same pattern as the other removed routes), no backend query for
|
|
1005
|
+
// it, no "Memory Marketplace" page in app/src. The tool asked users to
|
|
1006
|
+
// accept an "IRREVERSIBLE, PUBLIC" risk for a marketplace that doesn't
|
|
1007
|
+
// exist at any layer. Re-add only alongside building the actual
|
|
1008
|
+
// discovery path: a community-browse query + route + UI that reads
|
|
1009
|
+
// `published: true` entries. vault_unpublish is left in place - it's a
|
|
1010
|
+
// correct, harmless no-op until then.
|
|
1043
1011
|
default:
|
|
1044
1012
|
return null;
|
|
1045
1013
|
}
|
package/dist/tools/monitor.js
CHANGED
|
@@ -22,8 +22,8 @@ const MONITOR_INPUT_SCHEMA = {
|
|
|
22
22
|
exports.MONITOR_TOOLS = [
|
|
23
23
|
{
|
|
24
24
|
name: "schedule_research",
|
|
25
|
-
description: "Schedule recurring autonomous research on any topic - runs on a cron schedule, saves findings to vault
|
|
26
|
-
"
|
|
25
|
+
description: "Schedule recurring autonomous research on any topic - runs on a cron schedule, saves findings to vault. " +
|
|
26
|
+
"The agent runs completely on its own with no prompting needed. " +
|
|
27
27
|
"Requires TRIGGER_SECRET_KEY env var (trigger.dev). " +
|
|
28
28
|
"Examples: daily morning briefing, weekly competitor analysis, hourly price alerts, monthly industry report.",
|
|
29
29
|
inputSchema: MONITOR_INPUT_SCHEMA,
|
|
@@ -103,7 +103,7 @@ function buildMonitorList(schedules, configs = {}) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
async function handleMonitorTool(name, args) {
|
|
106
|
-
if (name === "schedule_research"
|
|
106
|
+
if (name === "schedule_research") {
|
|
107
107
|
const parsed = CreateSchema.safeParse(args);
|
|
108
108
|
if (!parsed.success)
|
|
109
109
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
@@ -117,7 +117,7 @@ async function handleMonitorTool(name, args) {
|
|
|
117
117
|
// created 4× by an over-eager agent loop, which is what produced the
|
|
118
118
|
// duplicate Telegram briefings users complained about.
|
|
119
119
|
try {
|
|
120
|
-
const existingMonitors = await (0, convex_js_1.callConvex)("/vault/list", "POST", { type: "workflow", tags: ["monitor-config"] }, "
|
|
120
|
+
const existingMonitors = await (0, convex_js_1.callConvex)("/vault/list", "POST", { type: "workflow", tags: ["monitor-config"] }, "vault_list");
|
|
121
121
|
const duplicates = (existingMonitors?.entries ?? []).filter((e) => {
|
|
122
122
|
try {
|
|
123
123
|
const cfg = JSON.parse(e.content ?? "{}");
|
|
@@ -192,7 +192,7 @@ async function handleMonitorTool(name, args) {
|
|
|
192
192
|
key: `monitor-config/${externalId}`,
|
|
193
193
|
agentId: "os",
|
|
194
194
|
tags: ["monitor-config"],
|
|
195
|
-
commitMsg: "
|
|
195
|
+
commitMsg: "schedule_research config",
|
|
196
196
|
}, "vault_save");
|
|
197
197
|
}
|
|
198
198
|
catch {
|
|
@@ -210,7 +210,7 @@ async function handleMonitorTool(name, args) {
|
|
|
210
210
|
data.nextRun ? `⏭️ Next run: ${new Date(data.nextRun).toUTCString()}` : "",
|
|
211
211
|
``,
|
|
212
212
|
configSaved
|
|
213
|
-
? `The agent will research "${topic}" on schedule
|
|
213
|
+
? `The agent will research "${topic}" on schedule and save findings to vault.`
|
|
214
214
|
: `⚠️ Monitor schedule created but config save failed - the agent may use a default topic on first run. Try \`cancel_monitor\` and recreate.`,
|
|
215
215
|
`Use \`list_monitors\` to see all active monitors.`,
|
|
216
216
|
].filter(Boolean).join("\n"),
|
|
@@ -240,7 +240,7 @@ async function handleMonitorTool(name, args) {
|
|
|
240
240
|
return {
|
|
241
241
|
content: [{
|
|
242
242
|
type: "text",
|
|
243
|
-
text: `No active monitors.\n\nUse \`
|
|
243
|
+
text: `No active monitors.\n\nUse \`schedule_research\` to set up an autonomous agent that runs on a schedule.`,
|
|
244
244
|
}],
|
|
245
245
|
structuredContent: buildMonitorList([]),
|
|
246
246
|
};
|
package/dist/tools/os.js
CHANGED
|
@@ -45,14 +45,14 @@ async function handleOsTool(name, args) {
|
|
|
45
45
|
const localStatusCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
46
46
|
const memoryProfileCall = localStatusCfg
|
|
47
47
|
? (0, local_memory_js_1.localMemoryProfile)(localStatusCfg)
|
|
48
|
-
: (0, convex_js_1.callConvex)("/memory/profile", "GET");
|
|
48
|
+
: (0, convex_js_1.callConvex)("/memory/profile", "GET", undefined, "memory_profile");
|
|
49
49
|
const [tierResult, walletResult, memRes, autoRes, vaultRes, agentsRes] = await Promise.allSettled([
|
|
50
50
|
(0, token_gate_js_1.getTier)(),
|
|
51
51
|
(0, wallet_js_1.getOrCreateWallet)(),
|
|
52
52
|
memoryProfileCall,
|
|
53
53
|
(0, convex_js_1.callConvex)("/automations/list", "GET", undefined, "list_automations"),
|
|
54
|
-
(0, convex_js_1.callConvex)("/vault/list?type=research&limit=5", "GET", undefined, "
|
|
55
|
-
(0, convex_js_1.callConvex)("/vault/list?type=memory&limit=20", "GET", undefined, "
|
|
54
|
+
(0, convex_js_1.callConvex)("/vault/list?type=research&limit=5", "GET", undefined, "vault_list"),
|
|
55
|
+
(0, convex_js_1.callConvex)("/vault/list?type=memory&limit=20", "GET", undefined, "vault_list"),
|
|
56
56
|
]);
|
|
57
57
|
const tier = tierResult.status === "fulfilled" ? tierResult.value : "basic";
|
|
58
58
|
const wallet = walletResult.status === "fulfilled" ? walletResult.value : null;
|
|
@@ -138,7 +138,6 @@ async function handleOsTool(name, args) {
|
|
|
138
138
|
"FIRECRAWL_API_KEY": !!process.env.FIRECRAWL_API_KEY,
|
|
139
139
|
"FINCH_SESSION_TOKEN": !!process.env.FINCH_SESSION_TOKEN,
|
|
140
140
|
"FINCH_API_KEY": !!process.env.FINCH_API_KEY,
|
|
141
|
-
"TELEGRAM_BOT_TOKEN": !!process.env.TELEGRAM_BOT_TOKEN,
|
|
142
141
|
};
|
|
143
142
|
const localMemCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
144
143
|
const pingLocalMemory = async () => {
|
|
@@ -171,8 +170,13 @@ async function handleOsTool(name, args) {
|
|
|
171
170
|
hints.push(`→ No FIRECRAWL_API_KEY — deep_research falls back to Finch proxy (requires session token).`);
|
|
172
171
|
if (!localMemCfg)
|
|
173
172
|
hints.push(`→ Memory tools use the Finch-hosted proxy. Run \`finch setup\` for free, self-hosted local memory.`);
|
|
174
|
-
else if (localSmStatus !== "ok")
|
|
173
|
+
else if (localMemCfg.kind === "supermemory" && localSmStatus !== "ok")
|
|
175
174
|
hints.push(`→ Local memory configured but ${localMemCfg.url} isn't reachable — memory tools will fail. Run \`npx -y supermemory local\`.`);
|
|
175
|
+
const localMemLabel = !localMemCfg
|
|
176
|
+
? "not configured — run `finch setup`"
|
|
177
|
+
: localMemCfg.kind === "file"
|
|
178
|
+
? "file-based at ~/.finch/memory"
|
|
179
|
+
: localSmStatus === "ok" ? `reachable at ${localMemCfg.url}` : `configured but unreachable at ${localMemCfg.url}`;
|
|
176
180
|
const lines = [
|
|
177
181
|
`## 🩺 Finch Diagnostics`,
|
|
178
182
|
``,
|
|
@@ -180,7 +184,7 @@ async function handleOsTool(name, args) {
|
|
|
180
184
|
` ${statusIcon(convexStatus)} Convex backend ${convexStatus === "ok" ? "reachable" : "unreachable — check FINCH_CONVEX_URL"}`,
|
|
181
185
|
` ${statusIcon(fcStatus)} Firecrawl ${fcStatus === "ok" ? "reachable" : fcStatus === "unconfigured" ? "no FIRECRAWL_API_KEY — deep_research will use proxy" : "unreachable"}`,
|
|
182
186
|
` ${statusIcon(smStatus)} Supermemory (cloud) ${localMemCfg ? "not used — local memory active" : smStatus === "ok" ? "reachable" : "unreachable — memory tools may fail"}`,
|
|
183
|
-
` ${statusIcon(localSmStatus)}
|
|
187
|
+
` ${statusIcon(localSmStatus)} Local memory ${localMemLabel}`,
|
|
184
188
|
``,
|
|
185
189
|
`**API Keys configured:** _(none of the LLM keys are required — see below)_`,
|
|
186
190
|
...Object.entries(envKeys).map(([k, v]) => ` ${v ? "✅" : "⚪"} ${k}`),
|
package/dist/tools/packets.js
CHANGED
|
@@ -134,7 +134,7 @@ async function handlePacket(toolName, args) {
|
|
|
134
134
|
content,
|
|
135
135
|
contentType: "json",
|
|
136
136
|
tags: ["packet", ...(tags ?? [])],
|
|
137
|
-
}, "
|
|
137
|
+
}, "vault_save");
|
|
138
138
|
return {
|
|
139
139
|
content: [{
|
|
140
140
|
type: "text",
|
|
@@ -153,7 +153,7 @@ async function handlePacket(toolName, args) {
|
|
|
153
153
|
}
|
|
154
154
|
if (toolName === "packet_run") {
|
|
155
155
|
const { name } = args;
|
|
156
|
-
const data = await (0, convex_js_1.callConvex)(`/vault/entry?key=${encodeURIComponent(packetKey(name))}`, "GET", undefined, "
|
|
156
|
+
const data = await (0, convex_js_1.callConvex)(`/vault/entry?key=${encodeURIComponent(packetKey(name))}`, "GET", undefined, "vault_read");
|
|
157
157
|
if (!data?.content || data?.error) {
|
|
158
158
|
return {
|
|
159
159
|
content: [{ type: "text", text: `Packet \`${name}\` not found. Use \`packet_list\` to see available packets.` }],
|
|
@@ -245,8 +245,8 @@ async function handlePacket(toolName, args) {
|
|
|
245
245
|
}
|
|
246
246
|
if (toolName === "packet_share") {
|
|
247
247
|
const { name, authorName, confirm } = args;
|
|
248
|
-
// Same bar as
|
|
249
|
-
//
|
|
248
|
+
// Same bar as vault_delete: an action the user cannot fully take back
|
|
249
|
+
// needs an explicit second step, not a single call.
|
|
250
250
|
if (confirm !== true) {
|
|
251
251
|
return {
|
|
252
252
|
content: [{
|
|
@@ -271,7 +271,7 @@ async function handlePacket(toolName, args) {
|
|
|
271
271
|
const data = await (0, convex_js_1.callConvex)("/vault/publish", "POST", {
|
|
272
272
|
key: packetKey(name),
|
|
273
273
|
authorName: authorName ?? "anonymous",
|
|
274
|
-
}, "
|
|
274
|
+
}, "vault_publish");
|
|
275
275
|
if (data.error)
|
|
276
276
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
277
277
|
return {
|
package/dist/tools/research.js
CHANGED
|
@@ -68,7 +68,7 @@ async function firecrawlScrape(url) {
|
|
|
68
68
|
// FIRECRAWL_API_KEY set either, in which case web_scrape silently falls
|
|
69
69
|
// through to basicFetch below.
|
|
70
70
|
try {
|
|
71
|
-
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-scrape", "POST", { url }, "
|
|
71
|
+
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-scrape", "POST", { url }, "web_scrape", 25000);
|
|
72
72
|
if (data?.markdown)
|
|
73
73
|
return data.markdown;
|
|
74
74
|
}
|
|
@@ -149,7 +149,7 @@ async function handleResearchTool(name, args) {
|
|
|
149
149
|
}
|
|
150
150
|
if (!results) {
|
|
151
151
|
try {
|
|
152
|
-
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-search", "POST", { query, limit }, "
|
|
152
|
+
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-search", "POST", { query, limit }, "web_search", 30000);
|
|
153
153
|
results = data?.results ?? [];
|
|
154
154
|
}
|
|
155
155
|
catch (err) {
|
package/dist/tools/rh-mcp.js
CHANGED
|
@@ -14,6 +14,7 @@ exports.dexTokenBest = dexTokenBest;
|
|
|
14
14
|
exports.resolveTokenSmart = resolveTokenSmart;
|
|
15
15
|
exports.buildRhAnalysis = buildRhAnalysis;
|
|
16
16
|
exports.buildRhStocksList = buildRhStocksList;
|
|
17
|
+
exports.rhProviderAsync = rhProviderAsync;
|
|
17
18
|
exports.rhErc20Balance = rhErc20Balance;
|
|
18
19
|
exports.rhPriceUsd = rhPriceUsd;
|
|
19
20
|
exports.buildRhSafetyStructured = buildRhSafetyStructured;
|
|
@@ -33,6 +34,15 @@ exports.RH_EXPLORER = "https://robinhoodchain.blockscout.com";
|
|
|
33
34
|
const NATIVE_ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
34
35
|
const RH_PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
35
36
|
const RH_UNIVERSAL_ROUTER = "0x8876789976decbfcbbbe364623c63652db8c0904";
|
|
37
|
+
/**
|
|
38
|
+
* Canonical Global Dollar (USDG) — RH chain's settlement stablecoin (same
|
|
39
|
+
* address as app/convex/_settlement.ts). Hardcoded rather than resolved via
|
|
40
|
+
* DexScreener ticker search: "USDG" is the exact symbol an imposter contract
|
|
41
|
+
* would spoof, and dexSearchByTicker ranks by pool liquidity, not authenticity
|
|
42
|
+
* — a fake pool with inflated liquidity could otherwise outrank the real token.
|
|
43
|
+
*/
|
|
44
|
+
const USDG_ADDRESS = "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168";
|
|
45
|
+
const USDG_DECIMALS = 6;
|
|
36
46
|
/** ClawHood / Finch catalog (22 tokenized stocks — official "Robinhood Token" contracts). */
|
|
37
47
|
exports.RH_STOCKS = [
|
|
38
48
|
{ address: "0xaf3d76f1834a1d425780943c99ea8a608f8a93f9", symbol: "AAPL", name: "Apple" },
|
|
@@ -336,6 +346,9 @@ async function resolveTokenSmart(input) {
|
|
|
336
346
|
if (isEth(input))
|
|
337
347
|
return { kind: "eth", address: NATIVE_ETH, symbol: "ETH", decimals: 18 };
|
|
338
348
|
const t = input.trim();
|
|
349
|
+
if (t.toUpperCase() === "USDG") {
|
|
350
|
+
return { kind: "token", address: USDG_ADDRESS, symbol: "USDG", name: "Global Dollar", decimals: USDG_DECIMALS };
|
|
351
|
+
}
|
|
339
352
|
// 1) catalog symbol fast-path (22 tokenized stocks, all 18-decimals)
|
|
340
353
|
const cat = exports.RH_STOCKS.find((s) => s.symbol === t.toUpperCase());
|
|
341
354
|
if (cat)
|
|
@@ -978,7 +991,16 @@ async function quoteRh(args) {
|
|
|
978
991
|
throw new Error("RH swaps route ETH ↔ token. Buy a token with ETH, or sell a token for ETH.");
|
|
979
992
|
}
|
|
980
993
|
const sellAmount = parseHumanToWei(args.amount, from.decimals);
|
|
981
|
-
const
|
|
994
|
+
const slippagePct = args.maxSlippagePct ?? 2.0;
|
|
995
|
+
// Same bound defi.ts's SwapSchema already enforces for Base swaps
|
|
996
|
+
// (.positive().max(50)) - this file had no equivalent check anywhere, so a
|
|
997
|
+
// negative or absurd value flowed straight through to the backend as
|
|
998
|
+
// slippageBps, both for direct rh_mcp_swap calls and for every unattended
|
|
999
|
+
// rh_orders_tick execution of a DCA/bracket order created with a bad value.
|
|
1000
|
+
if (!(slippagePct > 0) || slippagePct > 50) {
|
|
1001
|
+
throw new Error(`maxSlippagePct must be greater than 0 and at most 50 (got ${args.maxSlippagePct}).`);
|
|
1002
|
+
}
|
|
1003
|
+
const slippageBps = Math.round(slippagePct * 100);
|
|
982
1004
|
const result = await (0, convex_js_1.callConvex)("/mcp/rh/quote", "POST", {
|
|
983
1005
|
sellToken: from.address,
|
|
984
1006
|
buyToken: to.address,
|
|
@@ -987,7 +1009,7 @@ async function quoteRh(args) {
|
|
|
987
1009
|
slippageBps,
|
|
988
1010
|
fromSymbol: from.symbol,
|
|
989
1011
|
toSymbol: to.symbol,
|
|
990
|
-
}, "rh_mcp_estimate");
|
|
1012
|
+
}, args.toolName ?? "rh_mcp_estimate");
|
|
991
1013
|
if (result.error)
|
|
992
1014
|
throw new Error(result.error);
|
|
993
1015
|
return { ...result, from, to, sellAmount, slippageBps };
|
|
@@ -1257,6 +1279,7 @@ async function handleRhMcpTool(name, args) {
|
|
|
1257
1279
|
amount: String(a.amount),
|
|
1258
1280
|
maxSlippagePct: a.maxSlippagePct,
|
|
1259
1281
|
taker: wallet.address,
|
|
1282
|
+
toolName: "rh_mcp_swap",
|
|
1260
1283
|
});
|
|
1261
1284
|
const quote = q.quote ?? q;
|
|
1262
1285
|
const tx = quote.transaction ?? {
|