@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.
Files changed (44) hide show
  1. package/README.md +19 -15
  2. package/dist/agent-loop.js +75 -72
  3. package/dist/annotations.js +23 -14
  4. package/dist/convex.js +15 -18
  5. package/dist/index.js +14 -12
  6. package/dist/llm.js +12 -1
  7. package/dist/local-memory-file.js +14 -1
  8. package/dist/local-memory.js +13 -0
  9. package/dist/output-schemas.js +71 -17
  10. package/dist/project.js +36 -0
  11. package/dist/resources.js +6 -11
  12. package/dist/server.js +35 -13
  13. package/dist/token-gate.js +2 -2
  14. package/dist/tool-filter.js +14 -5
  15. package/dist/tools/agents.js +106 -394
  16. package/dist/tools/automation.js +42 -6
  17. package/dist/tools/base-mcp.js +3 -15
  18. package/dist/tools/base.js +34 -20
  19. package/dist/tools/coder.js +1 -1
  20. package/dist/tools/deep-research.js +1 -1
  21. package/dist/tools/defi.js +14 -34
  22. package/dist/tools/equity.js +10 -2
  23. package/dist/tools/events.js +1 -1
  24. package/dist/tools/github.js +51 -1
  25. package/dist/tools/insider.js +1 -1
  26. package/dist/tools/insight.js +4 -4
  27. package/dist/tools/market.js +5 -5
  28. package/dist/tools/memory.js +52 -88
  29. package/dist/tools/miroshark.js +8 -1
  30. package/dist/tools/monitor.js +8 -8
  31. package/dist/tools/os.js +9 -4
  32. package/dist/tools/packets.js +2 -2
  33. package/dist/tools/research-chain.js +1 -1
  34. package/dist/tools/research-compare.js +1 -1
  35. package/dist/tools/research.js +2 -2
  36. package/dist/tools/rh-bridge.js +1 -1
  37. package/dist/tools/rh-mcp.js +29 -4
  38. package/dist/tools/rh-orders.js +201 -123
  39. package/dist/tools/scanner.js +33 -3
  40. package/dist/tools/stake.js +369 -0
  41. package/dist/tools/vault.js +294 -40
  42. package/dist/wallet.js +130 -19
  43. package/package.json +4 -5
  44. package/dist/tools/framework.js +0 -150
@@ -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: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
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 data = await (0, convex_js_1.callConvex)("/vault/save", "POST", {
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
- }, "vault_save");
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: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
465
- const data = await (0, convex_js_1.callConvex)(`/vault/entry?key=agent/${parsed.data.name}`, "GET", undefined, "vault_read");
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: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
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
- const current = await (0, convex_js_1.callConvex)(`/vault/entry?key=agent/${agentName}`, "GET", undefined, "vault_read");
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 data = await (0, convex_js_1.callConvex)("/vault/save", "POST", {
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
- }, "vault_save");
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 data = await (0, convex_js_1.callConvex)(`/vault/history?key=agent/${encodeURIComponent(agentName)}&limit=${cap}`, "GET", undefined, "vault_history");
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
  }