@finchagentic/mcp 4.6.0 → 4.6.2
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 +7 -7
- package/package.json +5 -6
- package/dist/_http-cache.js +0 -96
- package/dist/agent-loop.js +0 -301
- package/dist/annotations.js +0 -122
- package/dist/cli.js +0 -1391
- package/dist/clink-input.js +0 -15
- package/dist/config.js +0 -132
- package/dist/convex.js +0 -175
- package/dist/dex-pair.js +0 -54
- package/dist/enrichment-router.js +0 -315
- package/dist/index.js +0 -258
- package/dist/llm.js +0 -298
- package/dist/local-memory-file.js +0 -147
- package/dist/local-memory.js +0 -135
- package/dist/local-vault.js +0 -454
- package/dist/output-schemas.js +0 -605
- package/dist/project.js +0 -36
- package/dist/prompts.js +0 -111
- package/dist/public-url.js +0 -107
- package/dist/resources.js +0 -111
- package/dist/server.js +0 -322
- package/dist/signal-gate.js +0 -57
- package/dist/token-decimals.js +0 -26
- package/dist/token-gate.js +0 -88
- package/dist/tool-filter.js +0 -53
- package/dist/tools/_solidity-scan.js +0 -313
- package/dist/tools/agents.js +0 -441
- package/dist/tools/automation.js +0 -354
- package/dist/tools/base-mcp.js +0 -466
- package/dist/tools/base.js +0 -283
- package/dist/tools/chronicle.js +0 -268
- package/dist/tools/coder.js +0 -94
- package/dist/tools/deep-research.js +0 -1421
- package/dist/tools/defi.js +0 -292
- package/dist/tools/equity.js +0 -372
- package/dist/tools/events.js +0 -182
- package/dist/tools/github.js +0 -564
- package/dist/tools/insider.js +0 -264
- package/dist/tools/insight.js +0 -630
- package/dist/tools/market.js +0 -555
- package/dist/tools/memory.js +0 -1044
- package/dist/tools/miroshark.js +0 -350
- package/dist/tools/monitor.js +0 -319
- package/dist/tools/os.js +0 -236
- package/dist/tools/packets.js +0 -296
- package/dist/tools/research-chain.js +0 -226
- package/dist/tools/research-compare.js +0 -280
- package/dist/tools/research.js +0 -188
- package/dist/tools/rh-bridge.js +0 -148
- package/dist/tools/rh-mcp.js +0 -1448
- package/dist/tools/rh-orders.js +0 -556
- package/dist/tools/scanner.js +0 -564
- package/dist/tools/stake.js +0 -369
- package/dist/tools/vault.js +0 -1020
- package/dist/tools/wallet.js +0 -200
- package/dist/types.js +0 -2
- package/dist/wallet.js +0 -372
package/dist/tools/agents.js
DELETED
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AGENT_TOOLS = void 0;
|
|
4
|
-
exports.buildAgentLedger = buildAgentLedger;
|
|
5
|
-
exports.handleAgentTool = handleAgentTool;
|
|
6
|
-
const zod_1 = require("zod");
|
|
7
|
-
const convex_js_1 = require("../convex.js");
|
|
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
|
-
// ─── Agent Learning Memory (v3.25) ──────────────────────────────────────────
|
|
13
|
-
// After every agent_update, an LLM reviews the new progress in context of the
|
|
14
|
-
// agent's goal + prior learnings to extract a single repeatable insight. The
|
|
15
|
-
// insight is appended to the agent's `learnings[]` array (capped to last 30
|
|
16
|
-
// to keep the state file from bloating). On agent_recall and at the start of
|
|
17
|
-
// any agent run, learnings are surfaced so subsequent work compounds.
|
|
18
|
-
//
|
|
19
|
-
// Failure mode: extraction is best-effort. If the LLM is unavailable, slow,
|
|
20
|
-
// or returns NONE, the update still succeeds - we just don't append. Learning
|
|
21
|
-
// is a quality bonus, not a hard requirement of the update path.
|
|
22
|
-
const MAX_LEARNINGS = 30;
|
|
23
|
-
const LEARNING_SYSTEM_PROMPT = [
|
|
24
|
-
"You are an expert at extracting one reusable insight from an agent's progress update.",
|
|
25
|
-
"",
|
|
26
|
-
"You will see: the agent's goal, the agent's recent updates (chronological), and the latest update.",
|
|
27
|
-
"",
|
|
28
|
-
"TASK: Decide if the latest update reveals a NEW repeatable pattern, heuristic, or framework that would",
|
|
29
|
-
"improve this agent's future runs. Be strict - most updates are routine and do NOT contain a new insight.",
|
|
30
|
-
"",
|
|
31
|
-
"Output rules:",
|
|
32
|
-
"- If a genuine new insight emerged, output ONE sentence under 180 chars starting with an imperative verb.",
|
|
33
|
-
" Examples: \"Prefer Morpho vaults over Aave when USDC supply exceeds $10M.\"",
|
|
34
|
-
" \"Always check on-chain holder count before claiming a token has organic demand.\"",
|
|
35
|
-
" \"Skip schedule_research for time-sensitive queries - use deep_research with freshMode.\"",
|
|
36
|
-
"- If the update is routine (status report, progress without insight, repeat of prior learning),",
|
|
37
|
-
" output exactly: NONE",
|
|
38
|
-
"- Do not output explanations, prefixes, or quotation marks. Just the sentence or NONE.",
|
|
39
|
-
].join("\n");
|
|
40
|
-
async function extractLearning(goal, priorUpdates, priorLearnings, latest) {
|
|
41
|
-
// Fast skip when no LLM key is configured - callLLM() now throws
|
|
42
|
-
// immediately rather than proxying through Finch (BYOK is required), and
|
|
43
|
-
// the catch below swallows that into a silent skip. This early return just
|
|
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.OPENAI_API_KEY || process.env.GROK_API_KEY);
|
|
46
|
-
if (!hasLLM) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
// Build the user prompt with all the context the model needs.
|
|
50
|
-
const lines = [];
|
|
51
|
-
lines.push(`Agent goal: ${goal}`);
|
|
52
|
-
if (priorLearnings.length) {
|
|
53
|
-
lines.push(``);
|
|
54
|
-
lines.push(`Existing learnings (do not repeat any of these):`);
|
|
55
|
-
priorLearnings.slice(-10).forEach((l, i) => lines.push(` ${i + 1}. ${l}`));
|
|
56
|
-
}
|
|
57
|
-
if (priorUpdates.length) {
|
|
58
|
-
lines.push(``);
|
|
59
|
-
lines.push(`Recent updates (oldest first):`);
|
|
60
|
-
priorUpdates.slice(-5).forEach((u, i) => {
|
|
61
|
-
const finding = u.findings ? ` | findings: ${u.findings}` : "";
|
|
62
|
-
lines.push(` ${i + 1}. [${u.status ?? "active"}] ${u.progress}${finding}`);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
lines.push(``);
|
|
66
|
-
lines.push(`Latest update:`);
|
|
67
|
-
lines.push(` [${latest.status ?? "active"}] ${latest.progress}${latest.findings ? ` | findings: ${latest.findings}` : ""}`);
|
|
68
|
-
lines.push(``);
|
|
69
|
-
lines.push(`What new repeatable insight (if any) emerged from this latest update?`);
|
|
70
|
-
try {
|
|
71
|
-
// Short max_tokens - the answer should be one sentence. 8s timeout keeps
|
|
72
|
-
// agent_update responsive even when the LLM is slow; mutex still serializes
|
|
73
|
-
// updates so we don't pile up.
|
|
74
|
-
const out = await (0, llm_js_1.callLLM)(LEARNING_SYSTEM_PROMPT, lines.join("\n"), 120, [], 8000);
|
|
75
|
-
const trimmed = out.trim().replace(/^["']|["']$/g, "");
|
|
76
|
-
if (!trimmed || trimmed.toUpperCase() === "NONE")
|
|
77
|
-
return null;
|
|
78
|
-
// Guard against the model accidentally repeating a recent learning.
|
|
79
|
-
const lowered = trimmed.toLowerCase();
|
|
80
|
-
if (priorLearnings.some((p) => p.toLowerCase() === lowered))
|
|
81
|
-
return null;
|
|
82
|
-
// Hard cap on length to keep state file bounded.
|
|
83
|
-
return trimmed.length > 200 ? trimmed.slice(0, 200) : trimmed;
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Per-agent mutex for the read-modify-write cycle in agent_update / agent_spawn.
|
|
90
|
-
// Without this, two parallel calls on the same agent would both read the same
|
|
91
|
-
// state, both append their own update, and the second save would silently lose
|
|
92
|
-
// the first update. The mutex chains all writes for a given agent name through
|
|
93
|
-
// a single Promise so they execute serially.
|
|
94
|
-
const agentLocks = new Map();
|
|
95
|
-
function withAgentLock(agentName, fn) {
|
|
96
|
-
const prev = agentLocks.get(agentName) ?? Promise.resolve();
|
|
97
|
-
const next = prev.then(() => fn(), () => fn());
|
|
98
|
-
agentLocks.set(agentName, next);
|
|
99
|
-
// Clean up the map entry once this op completes - otherwise long-lived
|
|
100
|
-
// server processes accumulate dead entries.
|
|
101
|
-
next.finally(() => {
|
|
102
|
-
if (agentLocks.get(agentName) === next)
|
|
103
|
-
agentLocks.delete(agentName);
|
|
104
|
-
});
|
|
105
|
-
return next;
|
|
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.
|
|
124
|
-
exports.AGENT_TOOLS = [
|
|
125
|
-
{
|
|
126
|
-
name: "agent_spawn",
|
|
127
|
-
description: "Create a persistent NAMED agent with a goal - survives across sessions, state saved to vault under `agent/<name>` key. " +
|
|
128
|
-
"Use this when a task is ONGOING and will span multiple sessions: research you'll return to, a project you're tracking, " +
|
|
129
|
-
"a workflow you're iterating on. Track progress with agent_update, resume with agent_recall, audit with agent_ledger. " +
|
|
130
|
-
"Do NOT spawn an agent for one-shot tasks (single research query, single trade) - just call the relevant tool directly. " +
|
|
131
|
-
"Do NOT use this for ephemeral background data - use memory_add for that instead.",
|
|
132
|
-
inputSchema: {
|
|
133
|
-
type: "object",
|
|
134
|
-
properties: {
|
|
135
|
-
name: { type: "string", description: "Unique agent name (e.g. 'market-researcher', 'onboarding-helper')" },
|
|
136
|
-
goal: { type: "string", description: "What this agent is trying to accomplish" },
|
|
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
|
-
},
|
|
144
|
-
},
|
|
145
|
-
required: ["name", "goal"],
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
name: "agent_recall",
|
|
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. " +
|
|
153
|
-
"Use this to resume a long-running task, check what an agent last did, or hand context to a fresh LLM session. " +
|
|
154
|
-
"Learnings compound over time - the more an agent runs, the smarter recall becomes.",
|
|
155
|
-
inputSchema: {
|
|
156
|
-
type: "object",
|
|
157
|
-
properties: {
|
|
158
|
-
name: { type: "string", description: "Agent name as used in agent_spawn" },
|
|
159
|
-
},
|
|
160
|
-
required: ["name"],
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: "agent_update",
|
|
165
|
-
description: "Update a persistent agent's progress and findings. Creates a new vault version automatically - full history preserved. " +
|
|
166
|
-
"After each update an LLM extracts a single repeatable insight (if any) and appends it to the agent's accumulated learnings - the agent gets smarter every run. " +
|
|
167
|
-
"Status options: active | blocked | complete.",
|
|
168
|
-
inputSchema: {
|
|
169
|
-
type: "object",
|
|
170
|
-
properties: {
|
|
171
|
-
name: { type: "string", description: "Agent name" },
|
|
172
|
-
progress: { type: "string", description: "What was accomplished in this update" },
|
|
173
|
-
findings: { type: "string", description: "Key findings, data, or outputs from this step" },
|
|
174
|
-
status: { type: "string", enum: ["active", "blocked", "complete"], description: "Current agent status (default: active)" },
|
|
175
|
-
nextStep: { type: "string", description: "What should happen next (optional - helps on recall)" },
|
|
176
|
-
},
|
|
177
|
-
required: ["name", "progress"],
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: "agent_ledger",
|
|
182
|
-
description: "View the full activity ledger for a persistent agent - every update, status change, and finding logged in order. " +
|
|
183
|
-
"Each entry is a vault version created by agent_update. Use this to audit what an agent has done, " +
|
|
184
|
-
"trace its reasoning, or review progress since spawn.",
|
|
185
|
-
inputSchema: {
|
|
186
|
-
type: "object",
|
|
187
|
-
properties: {
|
|
188
|
-
name: {
|
|
189
|
-
type: "string",
|
|
190
|
-
description: "Agent name as used in agent_spawn",
|
|
191
|
-
},
|
|
192
|
-
limit: {
|
|
193
|
-
type: "number",
|
|
194
|
-
description: "Max entries to return (default 20, max 50)",
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
required: ["name"],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
];
|
|
201
|
-
const SpawnAgentSchema = zod_1.z.object({
|
|
202
|
-
name: zod_1.z.string().min(1).max(60).regex(/^[a-z0-9-]+$/, "name must be lowercase alphanumeric with hyphens"),
|
|
203
|
-
goal: zod_1.z.string().min(1),
|
|
204
|
-
context: zod_1.z.string().optional(),
|
|
205
|
-
workspaceProject: zod_1.z.string().optional(),
|
|
206
|
-
});
|
|
207
|
-
const RecallAgentSchema = zod_1.z.object({ name: zod_1.z.string().min(1) });
|
|
208
|
-
const UpdateAgentSchema = zod_1.z.object({
|
|
209
|
-
name: zod_1.z.string().min(1),
|
|
210
|
-
progress: zod_1.z.string().min(1),
|
|
211
|
-
findings: zod_1.z.string().optional(),
|
|
212
|
-
status: zod_1.z.enum(["active", "blocked", "complete"]).optional(),
|
|
213
|
-
nextStep: zod_1.z.string().optional(),
|
|
214
|
-
});
|
|
215
|
-
// ── Structured output builders (schemas in output-schemas.ts) ───────────────
|
|
216
|
-
function buildAgentLedger(name, versions) {
|
|
217
|
-
return {
|
|
218
|
-
name,
|
|
219
|
-
count: versions.length,
|
|
220
|
-
versions: versions.map((v) => ({
|
|
221
|
-
version: v.version,
|
|
222
|
-
commitMsg: v.commitMsg ?? null,
|
|
223
|
-
createdAt: v.createdAt ?? null,
|
|
224
|
-
})),
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
async function handleAgentTool(name, args) {
|
|
228
|
-
if (name === "agent_spawn") {
|
|
229
|
-
const parsed = SpawnAgentSchema.safeParse(args);
|
|
230
|
-
if (!parsed.success)
|
|
231
|
-
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
232
|
-
const { name: agentName, goal, context, workspaceProject } = parsed.data;
|
|
233
|
-
const content = JSON.stringify({
|
|
234
|
-
goal,
|
|
235
|
-
status: "active",
|
|
236
|
-
spawnedAt: new Date().toISOString(),
|
|
237
|
-
context: context ?? null,
|
|
238
|
-
updates: [],
|
|
239
|
-
}, null, 2);
|
|
240
|
-
const savePayload = {
|
|
241
|
-
type: "memory",
|
|
242
|
-
key: `agent/${agentName}`,
|
|
243
|
-
title: `Agent: ${agentName}`,
|
|
244
|
-
content,
|
|
245
|
-
contentType: "json",
|
|
246
|
-
agentId: agentName,
|
|
247
|
-
tags: ["persistent-agent"],
|
|
248
|
-
commitMsg: "spawned",
|
|
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");
|
|
264
|
-
if (data.error)
|
|
265
|
-
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
266
|
-
const projectLine = resolvedProjectName ? `\n**Project:** ${resolvedProjectName}` : "";
|
|
267
|
-
return {
|
|
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\`` }],
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
if (name === "agent_recall") {
|
|
272
|
-
const parsed = RecallAgentSchema.safeParse(args);
|
|
273
|
-
if (!parsed.success)
|
|
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
|
-
}
|
|
285
|
-
if (data.error || !data.content) {
|
|
286
|
-
return { content: [{ type: "text", text: `Agent \`${parsed.data.name}\` not found. Spawn it first with \`agent_spawn\`.` }], isError: true };
|
|
287
|
-
}
|
|
288
|
-
let state = {};
|
|
289
|
-
try {
|
|
290
|
-
state = JSON.parse(data.content);
|
|
291
|
-
}
|
|
292
|
-
catch { /* non-JSON content */ }
|
|
293
|
-
const updates = (state.updates ?? []).slice(-3).map((u, i) => ` ${i + 1}. [${u.status ?? "active"}] ${u.progress}${u.nextStep ? ` → next: ${u.nextStep}` : ""}`);
|
|
294
|
-
// Learnings - the agent's accumulated expertise across all prior updates.
|
|
295
|
-
// Surfaced prominently so the caller (or downstream LLM) can apply them.
|
|
296
|
-
const learningEntries = Array.isArray(state.learnings)
|
|
297
|
-
? state.learnings.map((l) => (typeof l === "string" ? l : l?.learned)).filter(Boolean)
|
|
298
|
-
: [];
|
|
299
|
-
const lines = [
|
|
300
|
-
`## Agent: ${parsed.data.name}`,
|
|
301
|
-
`**Goal:** ${state.goal ?? "-"}`,
|
|
302
|
-
`**Status:** ${state.status ?? "active"}`,
|
|
303
|
-
`**Version:** ${data.version ?? 1} · Updated: ${data.updatedAt ? new Date(data.updatedAt).toUTCString() : "-"}`,
|
|
304
|
-
];
|
|
305
|
-
if (state.context)
|
|
306
|
-
lines.push(`**Context:** ${state.context}`);
|
|
307
|
-
if (learningEntries.length) {
|
|
308
|
-
lines.push(`\n**🧠 Learned patterns (${learningEntries.length}):**`);
|
|
309
|
-
// Show the most recent 8 - these are the most refined and relevant.
|
|
310
|
-
learningEntries.slice(-8).forEach((l, i) => lines.push(` ${i + 1}. ${l}`));
|
|
311
|
-
}
|
|
312
|
-
if (updates.length)
|
|
313
|
-
lines.push(`\n**Recent updates:**\n${updates.join("\n")}`);
|
|
314
|
-
if (state.nextStep)
|
|
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
|
-
}
|
|
334
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
335
|
-
}
|
|
336
|
-
if (name === "agent_update") {
|
|
337
|
-
const parsed = UpdateAgentSchema.safeParse(args);
|
|
338
|
-
if (!parsed.success)
|
|
339
|
-
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
340
|
-
const { name: agentName, progress, findings, status = "active", nextStep } = parsed.data;
|
|
341
|
-
return withAgentLock(agentName, async () => {
|
|
342
|
-
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
343
|
-
// Load current state - inside the lock so two parallel updates can't
|
|
344
|
-
// both read v=N and both write v=N+1 (the second silently loses).
|
|
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
|
-
}
|
|
354
|
-
if (current.error || !current.content) {
|
|
355
|
-
return { content: [{ type: "text", text: `Agent \`${agentName}\` not found. Spawn it first.` }], isError: true };
|
|
356
|
-
}
|
|
357
|
-
let state = {};
|
|
358
|
-
try {
|
|
359
|
-
state = JSON.parse(current.content);
|
|
360
|
-
}
|
|
361
|
-
catch { /* start fresh */ }
|
|
362
|
-
const update = { progress, status, timestamp: new Date().toISOString() };
|
|
363
|
-
if (findings)
|
|
364
|
-
update.findings = findings;
|
|
365
|
-
if (nextStep)
|
|
366
|
-
update.nextStep = nextStep;
|
|
367
|
-
state.status = status;
|
|
368
|
-
state.nextStep = nextStep ?? state.nextStep;
|
|
369
|
-
state.updates = [...(state.updates ?? []), update].slice(-20);
|
|
370
|
-
// ─── Learning extraction (v3.25) ────────────────────────────────────
|
|
371
|
-
// Ask an LLM whether this update revealed a new repeatable insight.
|
|
372
|
-
// Best-effort: if it returns NONE or fails, we save without appending.
|
|
373
|
-
// The call sits inside the mutex so concurrent updates don't race on
|
|
374
|
-
// the learnings array - same protection that already covers updates[].
|
|
375
|
-
const priorLearnings = Array.isArray(state.learnings)
|
|
376
|
-
? state.learnings.map((l) => (typeof l === "string" ? l : l?.learned)).filter(Boolean)
|
|
377
|
-
: [];
|
|
378
|
-
const newLearning = await extractLearning(state.goal ?? "", (state.updates ?? []).slice(0, -1), priorLearnings, update);
|
|
379
|
-
if (newLearning) {
|
|
380
|
-
const entry = { learned: newLearning, ts: Date.now(), fromUpdate: state.updates.length - 1 };
|
|
381
|
-
state.learnings = [...(state.learnings ?? []), entry].slice(-MAX_LEARNINGS);
|
|
382
|
-
}
|
|
383
|
-
const savePayload = {
|
|
384
|
-
type: "memory",
|
|
385
|
-
key: `agent/${agentName}`,
|
|
386
|
-
title: `Agent: ${agentName}`,
|
|
387
|
-
content: JSON.stringify(state, null, 2),
|
|
388
|
-
contentType: "json",
|
|
389
|
-
agentId: agentName,
|
|
390
|
-
commitMsg: `[${status}] ${progress.slice(0, 60)}${newLearning ? " · +learning" : ""}`,
|
|
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");
|
|
395
|
-
if (data.error)
|
|
396
|
-
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
397
|
-
const statusEmoji = status === "complete" ? "✅" : status === "blocked" ? "🚫" : "🔄";
|
|
398
|
-
const learningLine = newLearning
|
|
399
|
-
? `\n\n🧠 **Learned:** ${newLearning}`
|
|
400
|
-
: "";
|
|
401
|
-
return {
|
|
402
|
-
content: [{ type: "text", text: `${statusEmoji} Agent **${agentName}** updated (v${data.version}).\n\n**Progress:** ${progress}${findings ? `\n**Findings:** ${findings}` : ""}${nextStep ? `\n**Next:** ${nextStep}` : ""}${learningLine}` }],
|
|
403
|
-
};
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
if (name === "agent_ledger") {
|
|
407
|
-
const { name: agentName, limit = 20 } = args;
|
|
408
|
-
if (!agentName)
|
|
409
|
-
return { content: [{ type: "text", text: "name is required" }], isError: true };
|
|
410
|
-
const cap = Math.min(Math.max(1, limit), 50);
|
|
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
|
-
}
|
|
421
|
-
if (data.error)
|
|
422
|
-
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
423
|
-
const versions = (data.history ?? []).slice(0, cap);
|
|
424
|
-
if (!versions.length) {
|
|
425
|
-
return { content: [{ type: "text", text: `No ledger entries found for agent \`${agentName}\`. Spawn it first with \`agent_spawn\`.` }], structuredContent: buildAgentLedger(agentName, []) };
|
|
426
|
-
}
|
|
427
|
-
const rows = versions.map((v) => {
|
|
428
|
-
const ts = v.createdAt ? new Date(v.createdAt).toUTCString() : "-";
|
|
429
|
-
const msg = v.commitMsg ?? "(no message)";
|
|
430
|
-
return ` v${v.version} ${ts}\n ${msg}`;
|
|
431
|
-
});
|
|
432
|
-
return {
|
|
433
|
-
content: [{
|
|
434
|
-
type: "text",
|
|
435
|
-
text: `## Agent Ledger: ${agentName} (${versions.length} entries)\n\n${rows.join("\n\n")}`,
|
|
436
|
-
}],
|
|
437
|
-
structuredContent: buildAgentLedger(agentName, versions),
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
return null;
|
|
441
|
-
}
|