@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/agents.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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");
|
|
10
|
+
const memory_js_1 = require("./memory.js");
|
|
11
|
+
const project_js_1 = require("../project.js");
|
|
12
12
|
// ─── Agent Learning Memory (v3.25) ──────────────────────────────────────────
|
|
13
13
|
// After every agent_update, an LLM reviews the new progress in context of the
|
|
14
14
|
// agent's goal + prior learnings to extract a single repeatable insight. The
|
|
@@ -42,7 +42,7 @@ async function extractLearning(goal, priorUpdates, priorLearnings, latest) {
|
|
|
42
42
|
// immediately rather than proxying through Finch (BYOK is required), and
|
|
43
43
|
// the catch below swallows that into a silent skip. This early return just
|
|
44
44
|
// 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);
|
|
45
|
+
const hasLLM = !!(process.env.BANKR_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GROK_API_KEY);
|
|
46
46
|
if (!hasLLM && !process.env.FINCH_SESSION_TOKEN) {
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
@@ -104,42 +104,24 @@ function withAgentLock(agentName, fn) {
|
|
|
104
104
|
});
|
|
105
105
|
return next;
|
|
106
106
|
}
|
|
107
|
+
// list_agents/hire_agent were removed (not disabled - deleted) because their
|
|
108
|
+
// backend routes, /agents/list and /agents/hire, were never actually
|
|
109
|
+
// registered in app/convex/http.ts - only dangling section-header comments
|
|
110
|
+
// exist there, no matching http.route(...) call. Every call to either tool
|
|
111
|
+
// always 404'd. Re-add them (and the matching handler blocks below) only
|
|
112
|
+
// alongside building those two routes for real.
|
|
113
|
+
//
|
|
114
|
+
// Same story, same fix, applied again here: agent_identity, agent_schedule,
|
|
115
|
+
// agent_unschedule, agent_pause, agent_resume and agent_runs called
|
|
116
|
+
// /agents/identity, /agents/schedule, /agents/unschedule, /agents/pause and
|
|
117
|
+
// /agents/runs - none of which exist in http.ts, and none of which have any
|
|
118
|
+
// backing data model in schema.ts (no agent-identity table, no scheduler,
|
|
119
|
+
// no run-history table; there isn't even a cron for it in crons.ts). This
|
|
120
|
+
// isn't a missing route, it's an unbuilt subsystem, so the tools were
|
|
121
|
+
// removed rather than stubbed. Re-add only alongside building that
|
|
122
|
+
// autonomous-scheduling backend for real: identity/schedule storage, a cron
|
|
123
|
+
// that actually wakes agents up, and run-history writes.
|
|
107
124
|
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
125
|
{
|
|
144
126
|
name: "agent_spawn",
|
|
145
127
|
description: "Create a persistent NAMED agent with a goal - survives across sessions, state saved to vault under `agent/<name>` key. " +
|
|
@@ -153,6 +135,12 @@ exports.AGENT_TOOLS = [
|
|
|
153
135
|
name: { type: "string", description: "Unique agent name (e.g. 'market-researcher', 'onboarding-helper')" },
|
|
154
136
|
goal: { type: "string", description: "What this agent is trying to accomplish" },
|
|
155
137
|
context: { type: "string", description: "Optional starting context, data, or notes for the agent" },
|
|
138
|
+
workspaceProject: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "Optional: file this agent into a named Finch workspace project (visible on the Agents page's " +
|
|
141
|
+
"project switcher). Matched case-insensitively by name; created automatically if it doesn't exist " +
|
|
142
|
+
"yet. Hosted vault only (no effect in local-vault mode).",
|
|
143
|
+
},
|
|
156
144
|
},
|
|
157
145
|
required: ["name", "goal"],
|
|
158
146
|
},
|
|
@@ -160,6 +148,8 @@ exports.AGENT_TOOLS = [
|
|
|
160
148
|
{
|
|
161
149
|
name: "agent_recall",
|
|
162
150
|
description: "Recall a persistent agent by name - loads its goal, current progress, findings, full history, and accumulated learnings (patterns the agent extracted from past runs). " +
|
|
151
|
+
"Also pulls related context from memory/vault (code_session_save entries, deep_research reports, notes) matching the agent's goal, " +
|
|
152
|
+
"so recall reflects everything relevant to the goal - not just what agent_update explicitly logged. " +
|
|
163
153
|
"Use this to resume a long-running task, check what an agent last did, or hand context to a fresh LLM session. " +
|
|
164
154
|
"Learnings compound over time - the more an agent runs, the smarter recall becomes.",
|
|
165
155
|
inputSchema: {
|
|
@@ -187,27 +177,6 @@ exports.AGENT_TOOLS = [
|
|
|
187
177
|
required: ["name", "progress"],
|
|
188
178
|
},
|
|
189
179
|
},
|
|
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
180
|
{
|
|
212
181
|
name: "agent_ledger",
|
|
213
182
|
description: "View the full activity ledger for a persistent agent - every update, status change, and finding logged in order. " +
|
|
@@ -228,83 +197,12 @@ exports.AGENT_TOOLS = [
|
|
|
228
197
|
required: ["name"],
|
|
229
198
|
},
|
|
230
199
|
},
|
|
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
200
|
];
|
|
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
201
|
const SpawnAgentSchema = zod_1.z.object({
|
|
305
202
|
name: zod_1.z.string().min(1).max(60).regex(/^[a-z0-9-]+$/, "name must be lowercase alphanumeric with hyphens"),
|
|
306
203
|
goal: zod_1.z.string().min(1),
|
|
307
204
|
context: zod_1.z.string().optional(),
|
|
205
|
+
workspaceProject: zod_1.z.string().optional(),
|
|
308
206
|
});
|
|
309
207
|
const RecallAgentSchema = zod_1.z.object({ name: zod_1.z.string().min(1) });
|
|
310
208
|
const UpdateAgentSchema = zod_1.z.object({
|
|
@@ -315,19 +213,6 @@ const UpdateAgentSchema = zod_1.z.object({
|
|
|
315
213
|
nextStep: zod_1.z.string().optional(),
|
|
316
214
|
});
|
|
317
215
|
// ── 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
216
|
function buildAgentLedger(name, versions) {
|
|
332
217
|
return {
|
|
333
218
|
name,
|
|
@@ -339,102 +224,12 @@ function buildAgentLedger(name, versions) {
|
|
|
339
224
|
})),
|
|
340
225
|
};
|
|
341
226
|
}
|
|
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
227
|
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
228
|
if (name === "agent_spawn") {
|
|
434
229
|
const parsed = SpawnAgentSchema.safeParse(args);
|
|
435
230
|
if (!parsed.success)
|
|
436
|
-
return { content: [{ type: "text", text:
|
|
437
|
-
const { name: agentName, goal, context } = parsed.data;
|
|
231
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
232
|
+
const { name: agentName, goal, context, workspaceProject } = parsed.data;
|
|
438
233
|
const content = JSON.stringify({
|
|
439
234
|
goal,
|
|
440
235
|
status: "active",
|
|
@@ -442,7 +237,7 @@ async function handleAgentTool(name, args) {
|
|
|
442
237
|
context: context ?? null,
|
|
443
238
|
updates: [],
|
|
444
239
|
}, null, 2);
|
|
445
|
-
const
|
|
240
|
+
const savePayload = {
|
|
446
241
|
type: "memory",
|
|
447
242
|
key: `agent/${agentName}`,
|
|
448
243
|
title: `Agent: ${agentName}`,
|
|
@@ -451,18 +246,42 @@ async function handleAgentTool(name, args) {
|
|
|
451
246
|
agentId: agentName,
|
|
452
247
|
tags: ["persistent-agent"],
|
|
453
248
|
commitMsg: "spawned",
|
|
454
|
-
}
|
|
249
|
+
};
|
|
250
|
+
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
251
|
+
// Resolve the project name -> id server-side (auto-creates on first use).
|
|
252
|
+
// No-op in local-vault mode - there is no project concept there.
|
|
253
|
+
let resolvedProjectName = null;
|
|
254
|
+
if (workspaceProject && !localVault) {
|
|
255
|
+
const resolved = await (0, project_js_1.resolveProjectId)(workspaceProject);
|
|
256
|
+
if (resolved) {
|
|
257
|
+
savePayload.projectId = resolved.projectId;
|
|
258
|
+
resolvedProjectName = resolved.name;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const data = localVault
|
|
262
|
+
? (0, local_vault_js_1.localVaultSave)(localVault, savePayload)
|
|
263
|
+
: await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
|
|
455
264
|
if (data.error)
|
|
456
265
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
266
|
+
const projectLine = resolvedProjectName ? `\n**Project:** ${resolvedProjectName}` : "";
|
|
457
267
|
return {
|
|
458
|
-
content: [{ type: "text", text: `🤖 Agent **${agentName}** spawned.\n\n**Goal:** ${goal}\n\nRecall with \`agent_recall\` · Update progress with \`agent_update\`` }],
|
|
268
|
+
content: [{ type: "text", text: `🤖 Agent **${agentName}** spawned${localVault ? " locally" : ""}.\n\n**Goal:** ${goal}${projectLine}\n\nRecall with \`agent_recall\` · Update progress with \`agent_update\`` }],
|
|
459
269
|
};
|
|
460
270
|
}
|
|
461
271
|
if (name === "agent_recall") {
|
|
462
272
|
const parsed = RecallAgentSchema.safeParse(args);
|
|
463
273
|
if (!parsed.success)
|
|
464
|
-
return { content: [{ type: "text", text:
|
|
465
|
-
const
|
|
274
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
275
|
+
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
276
|
+
let data;
|
|
277
|
+
try {
|
|
278
|
+
data = localVault
|
|
279
|
+
? (0, local_vault_js_1.localVaultRead)(localVault, `agent/${parsed.data.name}`)
|
|
280
|
+
: await (0, convex_js_1.callConvex)(`/vault/entry?key=agent/${parsed.data.name}`, "GET", undefined, "vault_read");
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
data = { error: "not found" };
|
|
284
|
+
}
|
|
466
285
|
if (data.error || !data.content) {
|
|
467
286
|
return { content: [{ type: "text", text: `Agent \`${parsed.data.name}\` not found. Spawn it first with \`agent_spawn\`.` }], isError: true };
|
|
468
287
|
}
|
|
@@ -494,17 +313,44 @@ async function handleAgentTool(name, args) {
|
|
|
494
313
|
lines.push(`\n**Recent updates:**\n${updates.join("\n")}`);
|
|
495
314
|
if (state.nextStep)
|
|
496
315
|
lines.push(`\n**Next step:** ${state.nextStep}`);
|
|
316
|
+
// Related context - best-effort pull of relevant memory/vault knowledge
|
|
317
|
+
// (code_session_save entries, deep_research reports, manual notes) so the
|
|
318
|
+
// agent isn't blind to work done on its goal outside its own update log.
|
|
319
|
+
// This is what makes recall "continuously have context" rather than only
|
|
320
|
+
// ever knowing what agent_update explicitly logged.
|
|
321
|
+
if (state.goal) {
|
|
322
|
+
try {
|
|
323
|
+
const related = await (0, memory_js_1.hybridMemorySearch)(state.goal, 4);
|
|
324
|
+
if (related.length) {
|
|
325
|
+
lines.push(`\n**📎 Related context (${related.length}):**`);
|
|
326
|
+
related.forEach((r) => {
|
|
327
|
+
const title = r.metadata?.title ?? r.content.slice(0, 70).replace(/\n/g, " ");
|
|
328
|
+
lines.push(` • ${title}`);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch { /* best-effort - recall must never fail because of this */ }
|
|
333
|
+
}
|
|
497
334
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
498
335
|
}
|
|
499
336
|
if (name === "agent_update") {
|
|
500
337
|
const parsed = UpdateAgentSchema.safeParse(args);
|
|
501
338
|
if (!parsed.success)
|
|
502
|
-
return { content: [{ type: "text", text:
|
|
339
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
503
340
|
const { name: agentName, progress, findings, status = "active", nextStep } = parsed.data;
|
|
504
341
|
return withAgentLock(agentName, async () => {
|
|
342
|
+
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
505
343
|
// Load current state - inside the lock so two parallel updates can't
|
|
506
344
|
// both read v=N and both write v=N+1 (the second silently loses).
|
|
507
|
-
|
|
345
|
+
let current;
|
|
346
|
+
try {
|
|
347
|
+
current = localVault
|
|
348
|
+
? (0, local_vault_js_1.localVaultRead)(localVault, `agent/${agentName}`)
|
|
349
|
+
: await (0, convex_js_1.callConvex)(`/vault/entry?key=agent/${agentName}`, "GET", undefined, "vault_read");
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
current = { error: "not found" };
|
|
353
|
+
}
|
|
508
354
|
if (current.error || !current.content) {
|
|
509
355
|
return { content: [{ type: "text", text: `Agent \`${agentName}\` not found. Spawn it first.` }], isError: true };
|
|
510
356
|
}
|
|
@@ -534,7 +380,7 @@ async function handleAgentTool(name, args) {
|
|
|
534
380
|
const entry = { learned: newLearning, ts: Date.now(), fromUpdate: state.updates.length - 1 };
|
|
535
381
|
state.learnings = [...(state.learnings ?? []), entry].slice(-MAX_LEARNINGS);
|
|
536
382
|
}
|
|
537
|
-
const
|
|
383
|
+
const savePayload = {
|
|
538
384
|
type: "memory",
|
|
539
385
|
key: `agent/${agentName}`,
|
|
540
386
|
title: `Agent: ${agentName}`,
|
|
@@ -542,7 +388,10 @@ async function handleAgentTool(name, args) {
|
|
|
542
388
|
contentType: "json",
|
|
543
389
|
agentId: agentName,
|
|
544
390
|
commitMsg: `[${status}] ${progress.slice(0, 60)}${newLearning ? " · +learning" : ""}`,
|
|
545
|
-
}
|
|
391
|
+
};
|
|
392
|
+
const data = localVault
|
|
393
|
+
? (0, local_vault_js_1.localVaultSave)(localVault, savePayload)
|
|
394
|
+
: await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
|
|
546
395
|
if (data.error)
|
|
547
396
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
548
397
|
const statusEmoji = status === "complete" ? "✅" : status === "blocked" ? "🚫" : "🔄";
|
|
@@ -559,10 +408,19 @@ async function handleAgentTool(name, args) {
|
|
|
559
408
|
if (!agentName)
|
|
560
409
|
return { content: [{ type: "text", text: "name is required" }], isError: true };
|
|
561
410
|
const cap = Math.min(Math.max(1, limit), 50);
|
|
562
|
-
const
|
|
411
|
+
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
412
|
+
let data;
|
|
413
|
+
try {
|
|
414
|
+
data = localVault
|
|
415
|
+
? (0, local_vault_js_1.localVaultHistory)(localVault, `agent/${agentName}`)
|
|
416
|
+
: await (0, convex_js_1.callConvex)(`/vault/history?key=agent/${encodeURIComponent(agentName)}&limit=${cap}`, "GET", undefined, "vault_history");
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
data = { history: [] };
|
|
420
|
+
}
|
|
563
421
|
if (data.error)
|
|
564
422
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
565
|
-
const versions = data.history ?? [];
|
|
423
|
+
const versions = (data.history ?? []).slice(0, cap);
|
|
566
424
|
if (!versions.length) {
|
|
567
425
|
return { content: [{ type: "text", text: `No ledger entries found for agent \`${agentName}\`. Spawn it first with \`agent_spawn\`.` }], structuredContent: buildAgentLedger(agentName, []) };
|
|
568
426
|
}
|
|
@@ -579,151 +437,5 @@ async function handleAgentTool(name, args) {
|
|
|
579
437
|
structuredContent: buildAgentLedger(agentName, versions),
|
|
580
438
|
};
|
|
581
439
|
}
|
|
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
440
|
return null;
|
|
729
441
|
}
|