@finchagentic/mcp 4.1.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/README.md +14 -14
- package/dist/agent-loop.js +75 -72
- package/dist/annotations.js +14 -13
- package/dist/convex.js +15 -18
- package/dist/index.js +4 -5
- 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/resources.js +6 -11
- 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 +62 -390
- package/dist/tools/base-mcp.js +3 -15
- package/dist/tools/base.js +34 -20
- package/dist/tools/defi.js +10 -30
- package/dist/tools/equity.js +9 -1
- package/dist/tools/insight.js +2 -2
- package/dist/tools/memory.js +44 -80
- package/dist/tools/monitor.js +6 -6
- package/dist/tools/os.js +0 -1
- package/dist/tools/packets.js +2 -2
- package/dist/tools/rh-mcp.js +13 -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 +99 -21
- package/dist/wallet.js +130 -19
- package/package.json +2 -2
- package/dist/tools/framework.js +0 -150
package/dist/tools/agents.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AGENT_TOOLS = void 0;
|
|
4
|
-
exports.buildAgentList = buildAgentList;
|
|
5
4
|
exports.buildAgentLedger = buildAgentLedger;
|
|
6
|
-
exports.buildAgentRuns = buildAgentRuns;
|
|
7
5
|
exports.handleAgentTool = handleAgentTool;
|
|
8
6
|
const zod_1 = require("zod");
|
|
9
|
-
const ethers_1 = require("ethers");
|
|
10
7
|
const convex_js_1 = require("../convex.js");
|
|
11
8
|
const llm_js_1 = require("../llm.js");
|
|
9
|
+
const local_vault_js_1 = require("../local-vault.js");
|
|
12
10
|
// ─── Agent Learning Memory (v3.25) ──────────────────────────────────────────
|
|
13
11
|
// After every agent_update, an LLM reviews the new progress in context of the
|
|
14
12
|
// agent's goal + prior learnings to extract a single repeatable insight. The
|
|
@@ -42,7 +40,7 @@ async function extractLearning(goal, priorUpdates, priorLearnings, latest) {
|
|
|
42
40
|
// immediately rather than proxying through Finch (BYOK is required), and
|
|
43
41
|
// the catch below swallows that into a silent skip. This early return just
|
|
44
42
|
// avoids building the prompt for a call we already know will fail.
|
|
45
|
-
const hasLLM = !!(process.env.BANKR_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.GROK_API_KEY);
|
|
43
|
+
const hasLLM = !!(process.env.BANKR_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GROK_API_KEY);
|
|
46
44
|
if (!hasLLM && !process.env.FINCH_SESSION_TOKEN) {
|
|
47
45
|
return null;
|
|
48
46
|
}
|
|
@@ -104,42 +102,24 @@ function withAgentLock(agentName, fn) {
|
|
|
104
102
|
});
|
|
105
103
|
return next;
|
|
106
104
|
}
|
|
105
|
+
// list_agents/hire_agent were removed (not disabled - deleted) because their
|
|
106
|
+
// backend routes, /agents/list and /agents/hire, were never actually
|
|
107
|
+
// registered in app/convex/http.ts - only dangling section-header comments
|
|
108
|
+
// exist there, no matching http.route(...) call. Every call to either tool
|
|
109
|
+
// always 404'd. Re-add them (and the matching handler blocks below) only
|
|
110
|
+
// alongside building those two routes for real.
|
|
111
|
+
//
|
|
112
|
+
// Same story, same fix, applied again here: agent_identity, agent_schedule,
|
|
113
|
+
// agent_unschedule, agent_pause, agent_resume and agent_runs called
|
|
114
|
+
// /agents/identity, /agents/schedule, /agents/unschedule, /agents/pause and
|
|
115
|
+
// /agents/runs - none of which exist in http.ts, and none of which have any
|
|
116
|
+
// backing data model in schema.ts (no agent-identity table, no scheduler,
|
|
117
|
+
// no run-history table; there isn't even a cron for it in crons.ts). This
|
|
118
|
+
// isn't a missing route, it's an unbuilt subsystem, so the tools were
|
|
119
|
+
// removed rather than stubbed. Re-add only alongside building that
|
|
120
|
+
// autonomous-scheduling backend for real: identity/schedule storage, a cron
|
|
121
|
+
// that actually wakes agents up, and run-history writes.
|
|
107
122
|
exports.AGENT_TOOLS = [
|
|
108
|
-
{
|
|
109
|
-
name: "list_agents",
|
|
110
|
-
description: "List all available specialist agents you can hire - built-in experts (analyst, risk-manager, researcher, executor, scout) plus any community-published agents.",
|
|
111
|
-
inputSchema: { type: "object", properties: {}, required: [] },
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
name: "hire_agent",
|
|
115
|
-
description: "Load a specialist agent's expertise and apply it to a task YOURSELF — the tool returns the agent's " +
|
|
116
|
-
"full persona (its framework, thresholds and house rules) scoped to your task, and you answer in that " +
|
|
117
|
-
"voice. Built-in: analyst, risk-manager, researcher, executor, scout; use list_agents for the rest. " +
|
|
118
|
-
"No API key needed. Pass `execute: true` only when you need the agent's own model to answer instead " +
|
|
119
|
-
"(costs a server-side LLM call and you cannot steer the reasoning).",
|
|
120
|
-
inputSchema: {
|
|
121
|
-
type: "object",
|
|
122
|
-
properties: {
|
|
123
|
-
agentId: {
|
|
124
|
-
type: "string",
|
|
125
|
-
description: "Agent ID from list_agents. Built-in: analyst, risk-manager, researcher, executor, scout. Or a custom agent ID.",
|
|
126
|
-
},
|
|
127
|
-
task: {
|
|
128
|
-
type: "string",
|
|
129
|
-
description: "The task or question for the agent. Be specific - better input = better output.",
|
|
130
|
-
},
|
|
131
|
-
execute: {
|
|
132
|
-
type: "boolean",
|
|
133
|
-
description: "Run the agent server-side instead of returning its persona for you to apply. Default false. Requires a server LLM key.",
|
|
134
|
-
},
|
|
135
|
-
maxTokens: {
|
|
136
|
-
type: "number",
|
|
137
|
-
description: "execute:true only. Max response tokens (default 800, max 1200).",
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
required: ["agentId", "task"],
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
123
|
{
|
|
144
124
|
name: "agent_spawn",
|
|
145
125
|
description: "Create a persistent NAMED agent with a goal - survives across sessions, state saved to vault under `agent/<name>` key. " +
|
|
@@ -187,27 +167,6 @@ exports.AGENT_TOOLS = [
|
|
|
187
167
|
required: ["name", "progress"],
|
|
188
168
|
},
|
|
189
169
|
},
|
|
190
|
-
{
|
|
191
|
-
name: "agent_identity",
|
|
192
|
-
description: "Get or create a persistent on-chain identity (wallet address) for a named agent. " +
|
|
193
|
-
"Every agent gets a unique Base address that acts as its digital identity - visible in the app, " +
|
|
194
|
-
"usable for receiving payments, and permanently tied to that agent name. " +
|
|
195
|
-
"Call once per agent; calling again returns the same address.",
|
|
196
|
-
inputSchema: {
|
|
197
|
-
type: "object",
|
|
198
|
-
properties: {
|
|
199
|
-
agentId: {
|
|
200
|
-
type: "string",
|
|
201
|
-
description: "Agent ID or name (e.g. 'market-researcher', 'analyst')",
|
|
202
|
-
},
|
|
203
|
-
agentName: {
|
|
204
|
-
type: "string",
|
|
205
|
-
description: "Human-readable agent name for display (optional)",
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
required: ["agentId"],
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
170
|
{
|
|
212
171
|
name: "agent_ledger",
|
|
213
172
|
description: "View the full activity ledger for a persistent agent - every update, status change, and finding logged in order. " +
|
|
@@ -228,79 +187,7 @@ exports.AGENT_TOOLS = [
|
|
|
228
187
|
required: ["name"],
|
|
229
188
|
},
|
|
230
189
|
},
|
|
231
|
-
{
|
|
232
|
-
name: "agent_schedule",
|
|
233
|
-
description: "Attach an autonomous schedule to an existing agent. The agent wakes up on cron cadence, executes a workflow, " +
|
|
234
|
-
"saves the result to vault under `agent/<name>/runs/<date>`, and optionally pings you on Telegram. " +
|
|
235
|
-
"Workflows: " +
|
|
236
|
-
"deep_research (LLM brief on agent's goal) · " +
|
|
237
|
-
"packet (run a named packet) · " +
|
|
238
|
-
"llm (agentic loop with restricted tools) · " +
|
|
239
|
-
"reflection (walk recent vault activity → update profile/state for persistent context). " +
|
|
240
|
-
"Cron shorthand: hourly | daily | weekly | every-2-minutes | every-5-minutes (last two for testing).",
|
|
241
|
-
inputSchema: {
|
|
242
|
-
type: "object",
|
|
243
|
-
properties: {
|
|
244
|
-
name: { type: "string", description: "Agent name (must already exist via agent_spawn)" },
|
|
245
|
-
cron: { type: "string", description: "Schedule: hourly | daily | weekly | every-2-minutes | every-5-minutes" },
|
|
246
|
-
workflow: { type: "string", enum: ["deep_research", "packet", "llm", "reflection"], description: "Workflow type" },
|
|
247
|
-
topic: { type: "string", description: "[deep_research] Research topic (defaults to agent's goal)" },
|
|
248
|
-
packetName: { type: "string", description: "[packet] Name of packet to run (must exist via packet_create)" },
|
|
249
|
-
prompt: { type: "string", description: "[llm] Instructions for the autonomous agent loop" },
|
|
250
|
-
allowedTools: { type: "array", items: { type: "string" }, description: "[llm] Tool whitelist - subset of: vault_save, vault_search, get_market_data, web_search" },
|
|
251
|
-
windowDays: { type: "number", description: "[reflection] Days of vault activity to walk (default 7)" },
|
|
252
|
-
maxRunsPerDay: { type: "number", description: "Cost ceiling - default 4 (daily), 24 (hourly), 1 (weekly)" },
|
|
253
|
-
},
|
|
254
|
-
required: ["name", "cron", "workflow"],
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
name: "agent_unschedule",
|
|
259
|
-
description: "Remove the autonomous schedule from an agent. The agent itself remains in vault - only its scheduled execution is cleared.",
|
|
260
|
-
inputSchema: {
|
|
261
|
-
type: "object",
|
|
262
|
-
properties: { name: { type: "string", description: "Agent name" } },
|
|
263
|
-
required: ["name"],
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
name: "agent_pause",
|
|
268
|
-
description: "Pause an agent's autonomous schedule without deleting it. Use agent_resume to re-enable.",
|
|
269
|
-
inputSchema: {
|
|
270
|
-
type: "object",
|
|
271
|
-
properties: { name: { type: "string", description: "Agent name" } },
|
|
272
|
-
required: ["name"],
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
name: "agent_resume",
|
|
277
|
-
description: "Re-enable a paused agent's schedule. Resets the consecutive-failure counter.",
|
|
278
|
-
inputSchema: {
|
|
279
|
-
type: "object",
|
|
280
|
-
properties: { name: { type: "string", description: "Agent name" } },
|
|
281
|
-
required: ["name"],
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
name: "agent_runs",
|
|
286
|
-
description: "View recent autonomous run history for a scheduled agent - when it ran, success/failure status, " +
|
|
287
|
-
"vault key where the output was saved, and duration. Use this to verify the autonomous loop is healthy.",
|
|
288
|
-
inputSchema: {
|
|
289
|
-
type: "object",
|
|
290
|
-
properties: {
|
|
291
|
-
name: { type: "string", description: "Agent name" },
|
|
292
|
-
limit: { type: "number", description: "Max runs to return (default 10, max 50)" },
|
|
293
|
-
},
|
|
294
|
-
required: ["name"],
|
|
295
|
-
},
|
|
296
|
-
},
|
|
297
190
|
];
|
|
298
|
-
const HireAgentSchema = zod_1.z.object({
|
|
299
|
-
agentId: zod_1.z.string().min(1),
|
|
300
|
-
task: zod_1.z.string().min(1),
|
|
301
|
-
execute: zod_1.z.boolean().optional(),
|
|
302
|
-
maxTokens: zod_1.z.number().int().min(100).max(1200).optional(),
|
|
303
|
-
});
|
|
304
191
|
const SpawnAgentSchema = zod_1.z.object({
|
|
305
192
|
name: zod_1.z.string().min(1).max(60).regex(/^[a-z0-9-]+$/, "name must be lowercase alphanumeric with hyphens"),
|
|
306
193
|
goal: zod_1.z.string().min(1),
|
|
@@ -315,19 +202,6 @@ const UpdateAgentSchema = zod_1.z.object({
|
|
|
315
202
|
nextStep: zod_1.z.string().optional(),
|
|
316
203
|
});
|
|
317
204
|
// ── Structured output builders (schemas in output-schemas.ts) ───────────────
|
|
318
|
-
function buildAgentList(agents) {
|
|
319
|
-
return {
|
|
320
|
-
count: agents.length,
|
|
321
|
-
agents: agents.map((a) => ({
|
|
322
|
-
id: a.id,
|
|
323
|
-
name: a.name,
|
|
324
|
-
description: a.description ?? null,
|
|
325
|
-
category: a.category ?? null,
|
|
326
|
-
pricingType: a.pricingType ?? null,
|
|
327
|
-
runs: a.runs ?? null,
|
|
328
|
-
})),
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
205
|
function buildAgentLedger(name, versions) {
|
|
332
206
|
return {
|
|
333
207
|
name,
|
|
@@ -339,97 +213,7 @@ function buildAgentLedger(name, versions) {
|
|
|
339
213
|
})),
|
|
340
214
|
};
|
|
341
215
|
}
|
|
342
|
-
function buildAgentRuns(name, runs) {
|
|
343
|
-
return {
|
|
344
|
-
name,
|
|
345
|
-
count: runs.length,
|
|
346
|
-
runs: runs.map((r) => ({
|
|
347
|
-
startedAt: r.startedAt ?? null,
|
|
348
|
-
endedAt: r.endedAt ?? null,
|
|
349
|
-
status: r.status ?? null,
|
|
350
|
-
workflow: r.workflow ?? null,
|
|
351
|
-
durationMs: r.durationMs ?? null,
|
|
352
|
-
toolCallCount: r.toolCallCount ?? null,
|
|
353
|
-
resultSummary: r.resultSummary ?? null,
|
|
354
|
-
errorMsg: r.errorMsg ?? null,
|
|
355
|
-
})),
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
216
|
async function handleAgentTool(name, args) {
|
|
359
|
-
if (name === "list_agents") {
|
|
360
|
-
const data = await (0, convex_js_1.callConvex)("/agents/list", "GET", undefined, "list_agents");
|
|
361
|
-
const agents = data.agents ?? [];
|
|
362
|
-
const lines = agents.map((a) => {
|
|
363
|
-
const badge = a.pricingType === "free" ? "free" : "token-based";
|
|
364
|
-
const runs = a.runs != null ? ` · ${a.runs} runs` : "";
|
|
365
|
-
return `**${a.name}** (\`${a.id}\`) [${badge}${runs}]\n ${a.description}`;
|
|
366
|
-
});
|
|
367
|
-
return {
|
|
368
|
-
content: [{
|
|
369
|
-
type: "text",
|
|
370
|
-
text: `## Available Agents (${agents.length})\n\n${lines.join("\n\n")}`,
|
|
371
|
-
}],
|
|
372
|
-
structuredContent: buildAgentList(agents),
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
if (name === "hire_agent") {
|
|
376
|
-
const parsed = HireAgentSchema.safeParse(args);
|
|
377
|
-
if (!parsed.success) {
|
|
378
|
-
return {
|
|
379
|
-
content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }],
|
|
380
|
-
isError: true,
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
const { agentId, task, maxTokens, execute = false } = parsed.data;
|
|
384
|
-
const data = await (0, convex_js_1.callConvex)("/agents/hire", "POST", { agentId, task, maxTokens, mode: execute ? "run" : "brief" }, "hire_agent");
|
|
385
|
-
if (data.error) {
|
|
386
|
-
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
387
|
-
}
|
|
388
|
-
if (execute) {
|
|
389
|
-
const footer = data.tokensUsed ? `\n\n*Tokens used: ${data.tokensUsed}*` : "";
|
|
390
|
-
return {
|
|
391
|
-
content: [{
|
|
392
|
-
type: "text",
|
|
393
|
-
text: `## ${data.agent ?? agentId} - Response\n\n${data.result}${footer}`,
|
|
394
|
-
}],
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
// The registry lookup is the tool's real work - it holds each agent's
|
|
398
|
-
// persona, thresholds and hard rules. Applying them is model work, and the
|
|
399
|
-
// caller's model is the better one *and* the one the user can redirect.
|
|
400
|
-
if (!data.systemPrompt) {
|
|
401
|
-
return {
|
|
402
|
-
content: [{
|
|
403
|
-
type: "text",
|
|
404
|
-
text: `Agent \`${agentId}\` returned no persona. The backend may predate brief mode — retry with \`execute: true\`.`,
|
|
405
|
-
}],
|
|
406
|
-
isError: true,
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
return {
|
|
410
|
-
content: [{
|
|
411
|
-
type: "text",
|
|
412
|
-
text: [
|
|
413
|
-
`## Adopt: ${data.agent ?? agentId}`,
|
|
414
|
-
``,
|
|
415
|
-
`You are now this specialist. Answer the task below **in this persona**, applying its framework, ` +
|
|
416
|
-
`thresholds and hard rules exactly as written — including the ones that tell you to refuse, ` +
|
|
417
|
-
`skip or downgrade a setup. Those gates are the reason the persona exists; do not soften them ` +
|
|
418
|
-
`to give a more agreeable answer.`,
|
|
419
|
-
``,
|
|
420
|
-
`---`,
|
|
421
|
-
``,
|
|
422
|
-
data.systemPrompt,
|
|
423
|
-
``,
|
|
424
|
-
`---`,
|
|
425
|
-
``,
|
|
426
|
-
`### Task`,
|
|
427
|
-
``,
|
|
428
|
-
task,
|
|
429
|
-
].join("\n"),
|
|
430
|
-
}],
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
217
|
if (name === "agent_spawn") {
|
|
434
218
|
const parsed = SpawnAgentSchema.safeParse(args);
|
|
435
219
|
if (!parsed.success)
|
|
@@ -442,7 +226,7 @@ async function handleAgentTool(name, args) {
|
|
|
442
226
|
context: context ?? null,
|
|
443
227
|
updates: [],
|
|
444
228
|
}, null, 2);
|
|
445
|
-
const
|
|
229
|
+
const savePayload = {
|
|
446
230
|
type: "memory",
|
|
447
231
|
key: `agent/${agentName}`,
|
|
448
232
|
title: `Agent: ${agentName}`,
|
|
@@ -451,18 +235,31 @@ async function handleAgentTool(name, args) {
|
|
|
451
235
|
agentId: agentName,
|
|
452
236
|
tags: ["persistent-agent"],
|
|
453
237
|
commitMsg: "spawned",
|
|
454
|
-
}
|
|
238
|
+
};
|
|
239
|
+
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
240
|
+
const data = localVault
|
|
241
|
+
? (0, local_vault_js_1.localVaultSave)(localVault, savePayload)
|
|
242
|
+
: await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
|
|
455
243
|
if (data.error)
|
|
456
244
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
457
245
|
return {
|
|
458
|
-
content: [{ type: "text", text: `🤖 Agent **${agentName}** spawned.\n\n**Goal:** ${goal}\n\nRecall with \`agent_recall\` · Update progress with \`agent_update\`` }],
|
|
246
|
+
content: [{ type: "text", text: `🤖 Agent **${agentName}** spawned${localVault ? " locally" : ""}.\n\n**Goal:** ${goal}\n\nRecall with \`agent_recall\` · Update progress with \`agent_update\`` }],
|
|
459
247
|
};
|
|
460
248
|
}
|
|
461
249
|
if (name === "agent_recall") {
|
|
462
250
|
const parsed = RecallAgentSchema.safeParse(args);
|
|
463
251
|
if (!parsed.success)
|
|
464
252
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
465
|
-
const
|
|
253
|
+
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
254
|
+
let data;
|
|
255
|
+
try {
|
|
256
|
+
data = localVault
|
|
257
|
+
? (0, local_vault_js_1.localVaultRead)(localVault, `agent/${parsed.data.name}`)
|
|
258
|
+
: await (0, convex_js_1.callConvex)(`/vault/entry?key=agent/${parsed.data.name}`, "GET", undefined, "vault_read");
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
data = { error: "not found" };
|
|
262
|
+
}
|
|
466
263
|
if (data.error || !data.content) {
|
|
467
264
|
return { content: [{ type: "text", text: `Agent \`${parsed.data.name}\` not found. Spawn it first with \`agent_spawn\`.` }], isError: true };
|
|
468
265
|
}
|
|
@@ -502,9 +299,18 @@ async function handleAgentTool(name, args) {
|
|
|
502
299
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
503
300
|
const { name: agentName, progress, findings, status = "active", nextStep } = parsed.data;
|
|
504
301
|
return withAgentLock(agentName, async () => {
|
|
302
|
+
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
505
303
|
// Load current state - inside the lock so two parallel updates can't
|
|
506
304
|
// both read v=N and both write v=N+1 (the second silently loses).
|
|
507
|
-
|
|
305
|
+
let current;
|
|
306
|
+
try {
|
|
307
|
+
current = localVault
|
|
308
|
+
? (0, local_vault_js_1.localVaultRead)(localVault, `agent/${agentName}`)
|
|
309
|
+
: await (0, convex_js_1.callConvex)(`/vault/entry?key=agent/${agentName}`, "GET", undefined, "vault_read");
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
current = { error: "not found" };
|
|
313
|
+
}
|
|
508
314
|
if (current.error || !current.content) {
|
|
509
315
|
return { content: [{ type: "text", text: `Agent \`${agentName}\` not found. Spawn it first.` }], isError: true };
|
|
510
316
|
}
|
|
@@ -534,7 +340,7 @@ async function handleAgentTool(name, args) {
|
|
|
534
340
|
const entry = { learned: newLearning, ts: Date.now(), fromUpdate: state.updates.length - 1 };
|
|
535
341
|
state.learnings = [...(state.learnings ?? []), entry].slice(-MAX_LEARNINGS);
|
|
536
342
|
}
|
|
537
|
-
const
|
|
343
|
+
const savePayload = {
|
|
538
344
|
type: "memory",
|
|
539
345
|
key: `agent/${agentName}`,
|
|
540
346
|
title: `Agent: ${agentName}`,
|
|
@@ -542,7 +348,10 @@ async function handleAgentTool(name, args) {
|
|
|
542
348
|
contentType: "json",
|
|
543
349
|
agentId: agentName,
|
|
544
350
|
commitMsg: `[${status}] ${progress.slice(0, 60)}${newLearning ? " · +learning" : ""}`,
|
|
545
|
-
}
|
|
351
|
+
};
|
|
352
|
+
const data = localVault
|
|
353
|
+
? (0, local_vault_js_1.localVaultSave)(localVault, savePayload)
|
|
354
|
+
: await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
|
|
546
355
|
if (data.error)
|
|
547
356
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
548
357
|
const statusEmoji = status === "complete" ? "✅" : status === "blocked" ? "🚫" : "🔄";
|
|
@@ -559,10 +368,19 @@ async function handleAgentTool(name, args) {
|
|
|
559
368
|
if (!agentName)
|
|
560
369
|
return { content: [{ type: "text", text: "name is required" }], isError: true };
|
|
561
370
|
const cap = Math.min(Math.max(1, limit), 50);
|
|
562
|
-
const
|
|
371
|
+
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
372
|
+
let data;
|
|
373
|
+
try {
|
|
374
|
+
data = localVault
|
|
375
|
+
? (0, local_vault_js_1.localVaultHistory)(localVault, `agent/${agentName}`)
|
|
376
|
+
: await (0, convex_js_1.callConvex)(`/vault/history?key=agent/${encodeURIComponent(agentName)}&limit=${cap}`, "GET", undefined, "vault_history");
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
data = { history: [] };
|
|
380
|
+
}
|
|
563
381
|
if (data.error)
|
|
564
382
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
565
|
-
const versions = data.history ?? [];
|
|
383
|
+
const versions = (data.history ?? []).slice(0, cap);
|
|
566
384
|
if (!versions.length) {
|
|
567
385
|
return { content: [{ type: "text", text: `No ledger entries found for agent \`${agentName}\`. Spawn it first with \`agent_spawn\`.` }], structuredContent: buildAgentLedger(agentName, []) };
|
|
568
386
|
}
|
|
@@ -579,151 +397,5 @@ async function handleAgentTool(name, args) {
|
|
|
579
397
|
structuredContent: buildAgentLedger(agentName, versions),
|
|
580
398
|
};
|
|
581
399
|
}
|
|
582
|
-
if (name === "agent_identity") {
|
|
583
|
-
const { agentId, agentName } = args;
|
|
584
|
-
// Check if identity already exists
|
|
585
|
-
const existing = await (0, convex_js_1.callConvex)(`/agents/identity?agentId=${encodeURIComponent(agentId)}`, "GET", undefined, "agent_identity");
|
|
586
|
-
if (existing?.identity) {
|
|
587
|
-
const id = existing.identity;
|
|
588
|
-
return {
|
|
589
|
-
content: [{
|
|
590
|
-
type: "text",
|
|
591
|
-
text: [
|
|
592
|
-
`🤖 **Agent Identity: \`${agentId}\`**`,
|
|
593
|
-
``,
|
|
594
|
-
`**Address:** \`${id.walletAddress}\``,
|
|
595
|
-
`**Network:** Base mainnet`,
|
|
596
|
-
``,
|
|
597
|
-
`This address is permanent and tied to this agent.`,
|
|
598
|
-
`Share it to receive funds · Track spending · Verify agent actions`,
|
|
599
|
-
].join("\n"),
|
|
600
|
-
}],
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
// Generate new wallet address for the agent
|
|
604
|
-
const wallet = ethers_1.ethers.Wallet.createRandom();
|
|
605
|
-
await (0, convex_js_1.callConvex)("/agents/identity", "POST", {
|
|
606
|
-
agentId,
|
|
607
|
-
walletAddress: wallet.address,
|
|
608
|
-
agentName: agentName ?? agentId,
|
|
609
|
-
}, "agent_identity");
|
|
610
|
-
return {
|
|
611
|
-
content: [{
|
|
612
|
-
type: "text",
|
|
613
|
-
text: [
|
|
614
|
-
`🤖 **Agent Identity Created: \`${agentId}\`**`,
|
|
615
|
-
``,
|
|
616
|
-
`**Address:** \`${wallet.address}\``,
|
|
617
|
-
`**Network:** Base mainnet`,
|
|
618
|
-
``,
|
|
619
|
-
`This identity is now permanently linked to \`${agentId}\`.`,
|
|
620
|
-
`Visible in the Agents page of your Finch app.`,
|
|
621
|
-
].join("\n"),
|
|
622
|
-
}],
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
// ─── Autonomous schedule tools ──────────────────────────────────────────────
|
|
626
|
-
if (name === "agent_schedule") {
|
|
627
|
-
const a = args;
|
|
628
|
-
if (!a.name || !a.cron || !a.workflow) {
|
|
629
|
-
return { content: [{ type: "text", text: "name, cron, workflow required" }], isError: true };
|
|
630
|
-
}
|
|
631
|
-
const agentKey = `agent/${a.name}`;
|
|
632
|
-
const workflowConfig = {};
|
|
633
|
-
if (a.workflow === "deep_research" && a.topic)
|
|
634
|
-
workflowConfig.topic = a.topic;
|
|
635
|
-
if (a.workflow === "packet")
|
|
636
|
-
workflowConfig.packetName = a.packetName;
|
|
637
|
-
if (a.workflow === "llm") {
|
|
638
|
-
workflowConfig.prompt = a.prompt;
|
|
639
|
-
workflowConfig.allowedTools = a.allowedTools ?? ["vault_save", "vault_search"];
|
|
640
|
-
}
|
|
641
|
-
if (a.workflow === "reflection") {
|
|
642
|
-
workflowConfig.windowDays = typeof a.windowDays === "number" ? a.windowDays : 7;
|
|
643
|
-
}
|
|
644
|
-
const result = await (0, convex_js_1.callConvex)("/agents/schedule", "POST", {
|
|
645
|
-
agentKey, cron: a.cron, workflow: a.workflow, workflowConfig,
|
|
646
|
-
maxRunsPerDay: a.maxRunsPerDay,
|
|
647
|
-
}, "agent_schedule");
|
|
648
|
-
if (result.error)
|
|
649
|
-
return { content: [{ type: "text", text: `Schedule failed: ${result.error}` }], isError: true };
|
|
650
|
-
return {
|
|
651
|
-
content: [{
|
|
652
|
-
type: "text",
|
|
653
|
-
text: [
|
|
654
|
-
`🤖 **Schedule attached to \`${a.name}\`**`,
|
|
655
|
-
``,
|
|
656
|
-
`Workflow: ${a.workflow}${a.topic ? `\nTopic: ${a.topic}` : ""}${a.packetName ? `\nPacket: ${a.packetName}` : ""}`,
|
|
657
|
-
`Cron: ${a.cron}`,
|
|
658
|
-
`Next run: ${result.nextRunIso}`,
|
|
659
|
-
``,
|
|
660
|
-
`The agent will run autonomously. Check progress with \`agent_runs name=${a.name}\`.`,
|
|
661
|
-
].filter(Boolean).join("\n"),
|
|
662
|
-
}],
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
if (name === "agent_unschedule") {
|
|
666
|
-
const a = args;
|
|
667
|
-
if (!a.name)
|
|
668
|
-
return { content: [{ type: "text", text: "name required" }], isError: true };
|
|
669
|
-
const result = await (0, convex_js_1.callConvex)("/agents/unschedule", "POST", { agentKey: `agent/${a.name}` }, "agent_unschedule");
|
|
670
|
-
if (result.error)
|
|
671
|
-
return { content: [{ type: "text", text: `Unschedule failed: ${result.error}` }], isError: true };
|
|
672
|
-
return {
|
|
673
|
-
content: [{
|
|
674
|
-
type: "text",
|
|
675
|
-
text: result.deleted
|
|
676
|
-
? `🗑️ Schedule removed from \`${a.name}\`. Agent itself remains in vault.`
|
|
677
|
-
: `\`${a.name}\` had no active schedule.`,
|
|
678
|
-
}],
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
if (name === "agent_pause" || name === "agent_resume") {
|
|
682
|
-
const a = args;
|
|
683
|
-
if (!a.name)
|
|
684
|
-
return { content: [{ type: "text", text: "name required" }], isError: true };
|
|
685
|
-
const enabled = name === "agent_resume";
|
|
686
|
-
const result = await (0, convex_js_1.callConvex)("/agents/pause", "POST", { agentKey: `agent/${a.name}`, enabled }, name);
|
|
687
|
-
if (result.error)
|
|
688
|
-
return { content: [{ type: "text", text: `${name} failed: ${result.error}` }], isError: true };
|
|
689
|
-
if (!result.changed)
|
|
690
|
-
return { content: [{ type: "text", text: `\`${a.name}\` has no schedule to ${enabled ? "resume" : "pause"}.` }] };
|
|
691
|
-
return {
|
|
692
|
-
content: [{
|
|
693
|
-
type: "text",
|
|
694
|
-
text: enabled
|
|
695
|
-
? `▶️ Resumed schedule for \`${a.name}\`. Failure counter reset.`
|
|
696
|
-
: `⏸️ Paused schedule for \`${a.name}\`. Use \`agent_resume name=${a.name}\` to re-enable.`,
|
|
697
|
-
}],
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
if (name === "agent_runs") {
|
|
701
|
-
const a = args;
|
|
702
|
-
if (!a.name)
|
|
703
|
-
return { content: [{ type: "text", text: "name required" }], isError: true };
|
|
704
|
-
const limit = Math.min(Math.max(a.limit ?? 10, 1), 50);
|
|
705
|
-
const result = await (0, convex_js_1.callConvex)(`/agents/runs?agentKey=${encodeURIComponent(`agent/${a.name}`)}&limit=${limit}`, "GET", undefined, "agent_runs");
|
|
706
|
-
if (result.error)
|
|
707
|
-
return { content: [{ type: "text", text: `agent_runs failed: ${result.error}` }], isError: true };
|
|
708
|
-
const runs = (result.runs ?? []);
|
|
709
|
-
if (!runs.length) {
|
|
710
|
-
return { content: [{ type: "text", text: `No autonomous runs yet for \`${a.name}\`. The agent runs on its cron schedule - be patient.` }], structuredContent: buildAgentRuns(a.name, []) };
|
|
711
|
-
}
|
|
712
|
-
const lines = [`## Recent runs for \`${a.name}\` (${runs.length})`, ""];
|
|
713
|
-
for (const r of runs) {
|
|
714
|
-
const when = new Date(r.startedAt).toISOString().replace("T", " ").slice(0, 19);
|
|
715
|
-
const dur = r.durationMs ? `${Math.round(r.durationMs / 1000)}s` : "-";
|
|
716
|
-
const icon = r.status === "success" ? "✅" : r.status === "failed" ? "❌" : r.status === "timeout" ? "⏱️" : "⏳";
|
|
717
|
-
lines.push(`${icon} **${when}** · ${r.workflow} · ${dur}${r.toolCallCount != null ? ` · ${r.toolCallCount} tool calls` : ""}`);
|
|
718
|
-
if (r.vaultKeyResult)
|
|
719
|
-
lines.push(` → \`${r.vaultKeyResult}\``);
|
|
720
|
-
if (r.resultSummary)
|
|
721
|
-
lines.push(` ${r.resultSummary.slice(0, 200)}`);
|
|
722
|
-
if (r.errorMsg)
|
|
723
|
-
lines.push(` ⚠️ ${r.errorMsg.slice(0, 200)}`);
|
|
724
|
-
lines.push("");
|
|
725
|
-
}
|
|
726
|
-
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent: buildAgentRuns(a.name, runs) };
|
|
727
|
-
}
|
|
728
400
|
return null;
|
|
729
401
|
}
|
package/dist/tools/base-mcp.js
CHANGED
|
@@ -306,9 +306,9 @@ async function resolveBasename(input) {
|
|
|
306
306
|
*
|
|
307
307
|
* `base_mcp_send` and `base_mcp_swap` sign and broadcast from the user's wallet
|
|
308
308
|
* on the first call — an on-chain transfer that cannot be recalled. The chain's
|
|
309
|
-
* own swap tool (`rh_mcp_swap`) already required confirmation, as
|
|
310
|
-
* `vault_delete
|
|
311
|
-
* funds with less friction than
|
|
309
|
+
* own swap tool (`rh_mcp_swap`) already required confirmation, as does
|
|
310
|
+
* `vault_delete`; these two were the outliers, moving real
|
|
311
|
+
* funds with less friction than an irreversible delete. The guard also gives the
|
|
312
312
|
* model somewhere to stop when a destination address came from a scraped page
|
|
313
313
|
* or tool output rather than from the user.
|
|
314
314
|
*/
|
|
@@ -460,18 +460,6 @@ async function handleBaseMcpTool(name, args) {
|
|
|
460
460
|
structuredContent: buildBasenameResolution(a.name, result),
|
|
461
461
|
};
|
|
462
462
|
}
|
|
463
|
-
case "base_mcp_analyze": {
|
|
464
|
-
// Resolve basename if input is a name
|
|
465
|
-
let address = a.address;
|
|
466
|
-
if (typeof a.address === "string" && !/^0x[a-fA-F0-9]{40}$/.test(a.address)) {
|
|
467
|
-
const resolved = await resolveBasename(a.address);
|
|
468
|
-
if (resolved.error || !resolved.address) {
|
|
469
|
-
return { content: [{ type: "text", text: `Could not resolve "${a.address}": ${resolved.error ?? "unknown"}` }], isError: true };
|
|
470
|
-
}
|
|
471
|
-
address = resolved.address;
|
|
472
|
-
}
|
|
473
|
-
return (0, defi_js_1.handleDefiTool)("analyze_wallet", { address, label: a.label });
|
|
474
|
-
}
|
|
475
463
|
default:
|
|
476
464
|
return null;
|
|
477
465
|
}
|