@noelclaw/mcp 2.3.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -116
- package/dist/index.js +10 -9
- package/dist/server.js +39 -29
- package/dist/tools/automation.js +38 -0
- package/dist/tools/coder.js +62 -105
- package/dist/tools/defi.js +113 -24
- package/dist/tools/framework.js +0 -108
- package/dist/tools/humanizer.js +143 -2
- package/dist/tools/insight.js +197 -19
- package/dist/tools/market.js +182 -7
- package/dist/tools/memory.js +159 -43
- package/dist/tools/miroshark.js +15 -4
- package/dist/tools/os.js +223 -0
- package/dist/tools/scanner.js +183 -52
- package/dist/tools/swarm.js +37 -79
- package/dist/tools/vault.js +39 -200
- package/package.json +5 -2
- package/dist/tools/news.js +0 -6
- package/dist/tools/research.js +0 -8
- package/dist/tools/twitter.js +0 -67
package/dist/tools/scanner.js
CHANGED
|
@@ -85,13 +85,116 @@ function scoreDipReversal(c, minLiquidity = DEFAULT_MIN_LIQ) {
|
|
|
85
85
|
buyPressure5m: bp,
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
|
-
// ── Fetch
|
|
88
|
+
// ── Fetch helpers ─────────────────────────────────────────────────────────────
|
|
89
89
|
async function fetchJson(url) {
|
|
90
90
|
const res = await fetch(url, { signal: AbortSignal.timeout(15000) });
|
|
91
91
|
if (!res.ok)
|
|
92
92
|
throw new Error(`HTTP ${res.status} from ${new URL(url).hostname}`);
|
|
93
93
|
return res.json();
|
|
94
94
|
}
|
|
95
|
+
async function fetchBasePools(minLiquidity, limit) {
|
|
96
|
+
const [trendingRes, newPoolsRes] = await Promise.allSettled([
|
|
97
|
+
fetchJson("https://api.geckoterminal.com/api/v2/networks/base/trending_pools?page=1"),
|
|
98
|
+
fetchJson("https://api.geckoterminal.com/api/v2/networks/base/new_pools?page=1"),
|
|
99
|
+
]);
|
|
100
|
+
const rawPools = [
|
|
101
|
+
...(trendingRes.status === "fulfilled" ? trendingRes.value.data ?? [] : []),
|
|
102
|
+
...(newPoolsRes.status === "fulfilled" ? newPoolsRes.value.data ?? [] : []),
|
|
103
|
+
];
|
|
104
|
+
if (!rawPools.length)
|
|
105
|
+
throw new Error("GeckoTerminal returned no pools. Try again in a moment.");
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
const deduped = rawPools.filter(p => {
|
|
108
|
+
if (!p?.attributes || !p.id)
|
|
109
|
+
return false;
|
|
110
|
+
if (seen.has(p.id))
|
|
111
|
+
return false;
|
|
112
|
+
seen.add(p.id);
|
|
113
|
+
return true;
|
|
114
|
+
});
|
|
115
|
+
return deduped
|
|
116
|
+
.map((p) => {
|
|
117
|
+
const a = p.attributes ?? {};
|
|
118
|
+
const txns = a.transactions ?? {};
|
|
119
|
+
const pc = a.price_change_percentage ?? {};
|
|
120
|
+
const vol = a.volume_usd ?? {};
|
|
121
|
+
const liq = parseFloat(a.reserve_in_usd ?? "0");
|
|
122
|
+
const tokenRel = p.relationships?.base_token?.data?.id ?? "";
|
|
123
|
+
const mint = tokenRel.includes("_") ? tokenRel.split("_")[1] : tokenRel;
|
|
124
|
+
if (!mint?.startsWith("0x"))
|
|
125
|
+
return null;
|
|
126
|
+
return {
|
|
127
|
+
mint,
|
|
128
|
+
symbol: (a.name ?? "").split(" / ")[0] || mint.slice(0, 8),
|
|
129
|
+
priceUsd: parseFloat(a.base_token_price_usd ?? "0"),
|
|
130
|
+
priceChange5m: parseFloat(pc.m5 ?? "0"),
|
|
131
|
+
priceChange1h: parseFloat(pc.h1 ?? "0"),
|
|
132
|
+
priceChange6h: parseFloat(pc.h6 ?? "0"),
|
|
133
|
+
priceChange24h: parseFloat(pc.h24 ?? "0"),
|
|
134
|
+
volume1h: parseFloat(vol.h1 ?? "0"),
|
|
135
|
+
liquidity: liq,
|
|
136
|
+
buys5m: txns.m5?.buys ?? 0,
|
|
137
|
+
sells5m: txns.m5?.sells ?? 0,
|
|
138
|
+
buys1h: txns.h1?.buys ?? 0,
|
|
139
|
+
sells1h: txns.h1?.sells ?? 0,
|
|
140
|
+
};
|
|
141
|
+
})
|
|
142
|
+
.filter((c) => c !== null && c.liquidity >= minLiquidity)
|
|
143
|
+
.slice(0, limit);
|
|
144
|
+
}
|
|
145
|
+
function scoreMomentum(c, minLiquidity = DEFAULT_MIN_LIQ) {
|
|
146
|
+
const pc5m = c.priceChange5m;
|
|
147
|
+
const pc1h = c.priceChange1h;
|
|
148
|
+
const pc6h = c.priceChange6h;
|
|
149
|
+
const pc24h = c.priceChange24h;
|
|
150
|
+
const liq = c.liquidity;
|
|
151
|
+
const vol1h = c.volume1h;
|
|
152
|
+
const totalTxns5m = c.buys5m + c.sells5m;
|
|
153
|
+
const buyRatio5m = totalTxns5m > 0 ? c.buys5m / totalTxns5m : 0;
|
|
154
|
+
const totalTxns1h = c.buys1h + c.sells1h;
|
|
155
|
+
const buyRatio1h = totalTxns1h > 0 ? c.buys1h / totalTxns1h : 0;
|
|
156
|
+
const bp = buyRatio5m * 100;
|
|
157
|
+
// Hard gates — all must pass
|
|
158
|
+
const gateFailures = [];
|
|
159
|
+
if (pc1h < 3)
|
|
160
|
+
gateFailures.push(`1h momentum weak (${pc1h.toFixed(1)}% < 3%)`);
|
|
161
|
+
if (pc5m < 0.5)
|
|
162
|
+
gateFailures.push(`5m not accelerating (${pc5m.toFixed(1)}% < 0.5%)`);
|
|
163
|
+
if (totalTxns5m > 5 && buyRatio5m < 0.55)
|
|
164
|
+
gateFailures.push(`buy pressure low (${bp.toFixed(0)}% < 55%)`);
|
|
165
|
+
if (liq < minLiquidity)
|
|
166
|
+
gateFailures.push(`liquidity $${(liq / 1000).toFixed(0)}k < $${(minLiquidity / 1000).toFixed(0)}k min`);
|
|
167
|
+
if (pc24h > 150)
|
|
168
|
+
gateFailures.push(`already parabolic (24h ${pc24h.toFixed(0)}%)`);
|
|
169
|
+
if (gateFailures.length > 0) {
|
|
170
|
+
return { score: 0, passed: false, pattern: null, gateFailures, buyPressure5m: bp };
|
|
171
|
+
}
|
|
172
|
+
// 1. Momentum strength (0–25 pts)
|
|
173
|
+
const momentumPts = pc1h >= 20 ? 25 : pc1h >= 10 ? 20 : pc1h >= 6 ? 15 : pc1h >= 3 ? 8 : 3;
|
|
174
|
+
// 2. 5m acceleration (0–20 pts)
|
|
175
|
+
const accelPts = pc5m >= 5 ? 20 : pc5m >= 3 ? 16 : pc5m >= 2 ? 12 : pc5m >= 1 ? 7 : 3;
|
|
176
|
+
// 3. Buy pressure (0–15 pts)
|
|
177
|
+
const bpPts = bp >= 70 ? 15 : bp >= 65 ? 12 : bp >= 60 ? 9 : bp >= 55 ? 5 : 2;
|
|
178
|
+
// 4. Volume & activity (0–15 pts)
|
|
179
|
+
const actPts = vol1h >= 100000 && totalTxns1h >= 200 ? 15
|
|
180
|
+
: vol1h >= 50000 && totalTxns1h >= 100 ? 12
|
|
181
|
+
: vol1h >= 20000 && totalTxns1h >= 40 ? 8
|
|
182
|
+
: vol1h >= 5000 && totalTxns1h >= 10 ? 4 : 1;
|
|
183
|
+
// 5. Trend continuation (0–15 pts)
|
|
184
|
+
let trendPts = 0;
|
|
185
|
+
if (pc6h > 5 && pc24h > 5)
|
|
186
|
+
trendPts = 15;
|
|
187
|
+
else if (pc6h > 0 && pc24h > 0)
|
|
188
|
+
trendPts = 10;
|
|
189
|
+
else if (pc6h > 0)
|
|
190
|
+
trendPts = 5;
|
|
191
|
+
// 6. Sentiment acceleration (0–10 pts)
|
|
192
|
+
const sentAccel = buyRatio5m - buyRatio1h;
|
|
193
|
+
const sentPts = sentAccel >= 0.10 ? 10 : sentAccel >= 0.05 ? 7 : sentAccel >= 0 ? 3 : 0;
|
|
194
|
+
const score = Math.max(0, Math.min(100, momentumPts + accelPts + bpPts + actPts + trendPts + sentPts));
|
|
195
|
+
const pattern = pc1h >= 15 ? "BREAKOUT" : pc1h >= 8 ? "MOMENTUM" : pc1h >= 3 ? "PUSH" : "WEAK-PUSH";
|
|
196
|
+
return { score, passed: true, pattern, gateFailures: [], buyPressure5m: bp };
|
|
197
|
+
}
|
|
95
198
|
// ── Schemas ───────────────────────────────────────────────────────────────────
|
|
96
199
|
const AddressSchema = zod_1.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "must be a valid 0x address");
|
|
97
200
|
const ScoreTokenSchema = zod_1.z.object({ address: AddressSchema, minLiquidity: zod_1.z.number().positive().optional() });
|
|
@@ -101,6 +204,11 @@ const ScanDipsSchema = zod_1.z.object({
|
|
|
101
204
|
minLiquidity: zod_1.z.number().positive().optional(),
|
|
102
205
|
limit: zod_1.z.number().int().min(1).max(100).optional(),
|
|
103
206
|
}).default({});
|
|
207
|
+
const ScanMomentumSchema = zod_1.z.object({
|
|
208
|
+
minScore: zod_1.z.number().min(0).max(100).optional(),
|
|
209
|
+
minLiquidity: zod_1.z.number().positive().optional(),
|
|
210
|
+
limit: zod_1.z.number().int().min(1).max(100).optional(),
|
|
211
|
+
}).default({});
|
|
104
212
|
// ── Tool definitions ──────────────────────────────────────────────────────────
|
|
105
213
|
exports.SCANNER_TOOLS = [
|
|
106
214
|
{
|
|
@@ -139,6 +247,22 @@ exports.SCANNER_TOOLS = [
|
|
|
139
247
|
required: [],
|
|
140
248
|
},
|
|
141
249
|
},
|
|
250
|
+
{
|
|
251
|
+
name: "scan_momentum",
|
|
252
|
+
description: "Scan Base pools for momentum breakout setups — tokens with strong 1h+ upward momentum that are still accelerating. " +
|
|
253
|
+
"The inverse of scan_dips: finds BREAKOUT / MOMENTUM / PUSH patterns instead of dip-reversals. " +
|
|
254
|
+
"Gates: 1h > +3%, 5m still rising, buy pressure > 55%, not already parabolic (24h < 150%). " +
|
|
255
|
+
"Returns scored and ranked candidates. No API keys required.",
|
|
256
|
+
inputSchema: {
|
|
257
|
+
type: "object",
|
|
258
|
+
properties: {
|
|
259
|
+
minScore: { type: "number", description: "Min score to include (default 50)" },
|
|
260
|
+
minLiquidity: { type: "number", description: "Min pool liquidity in USD (default 50000)" },
|
|
261
|
+
limit: { type: "number", description: "Max pools to scan (default 40, max 100)" },
|
|
262
|
+
},
|
|
263
|
+
required: [],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
142
266
|
];
|
|
143
267
|
// ── Handlers ──────────────────────────────────────────────────────────────────
|
|
144
268
|
async function handleScannerTool(name, args) {
|
|
@@ -279,58 +403,13 @@ async function handleScannerTool(name, args) {
|
|
|
279
403
|
if (!parsed.success)
|
|
280
404
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
281
405
|
const { minScore = DEFAULT_MIN_SCORE, minLiquidity = DEFAULT_MIN_LIQ, limit = 40 } = parsed.data;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
...(trendingRes.status === "fulfilled" ? trendingRes.value.data ?? [] : []),
|
|
289
|
-
...(newPoolsRes.status === "fulfilled" ? newPoolsRes.value.data ?? [] : []),
|
|
290
|
-
];
|
|
291
|
-
if (!rawPools.length) {
|
|
292
|
-
return { content: [{ type: "text", text: "GeckoTerminal returned no pools. Try again in a moment." }], isError: true };
|
|
406
|
+
let candidates;
|
|
407
|
+
try {
|
|
408
|
+
candidates = await fetchBasePools(minLiquidity, limit);
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
return { content: [{ type: "text", text: err.message }], isError: true };
|
|
293
412
|
}
|
|
294
|
-
// Deduplicate by pool id
|
|
295
|
-
const seen = new Set();
|
|
296
|
-
const pools = rawPools.filter(p => {
|
|
297
|
-
if (!p?.attributes || !p.id)
|
|
298
|
-
return false;
|
|
299
|
-
if (seen.has(p.id))
|
|
300
|
-
return false;
|
|
301
|
-
seen.add(p.id);
|
|
302
|
-
return true;
|
|
303
|
-
});
|
|
304
|
-
// Map GeckoTerminal pool → Candidate
|
|
305
|
-
const candidates = pools
|
|
306
|
-
.map((p) => {
|
|
307
|
-
const a = p.attributes ?? {};
|
|
308
|
-
const txns = a.transactions ?? {};
|
|
309
|
-
const pc = a.price_change_percentage ?? {};
|
|
310
|
-
const vol = a.volume_usd ?? {};
|
|
311
|
-
const liq = parseFloat(a.reserve_in_usd ?? "0");
|
|
312
|
-
const tokenRel = p.relationships?.base_token?.data?.id ?? "";
|
|
313
|
-
const mint = tokenRel.includes("_") ? tokenRel.split("_")[1] : tokenRel;
|
|
314
|
-
if (!mint?.startsWith("0x"))
|
|
315
|
-
return null;
|
|
316
|
-
return {
|
|
317
|
-
mint,
|
|
318
|
-
symbol: (a.name ?? "").split(" / ")[0] || mint.slice(0, 8),
|
|
319
|
-
priceUsd: parseFloat(a.base_token_price_usd ?? "0"),
|
|
320
|
-
priceChange5m: parseFloat(pc.m5 ?? "0"),
|
|
321
|
-
priceChange1h: parseFloat(pc.h1 ?? "0"),
|
|
322
|
-
priceChange6h: parseFloat(pc.h6 ?? "0"),
|
|
323
|
-
priceChange24h: parseFloat(pc.h24 ?? "0"),
|
|
324
|
-
volume1h: parseFloat(vol.h1 ?? "0"),
|
|
325
|
-
liquidity: liq,
|
|
326
|
-
buys5m: txns.m5?.buys ?? 0,
|
|
327
|
-
sells5m: txns.m5?.sells ?? 0,
|
|
328
|
-
buys1h: txns.h1?.buys ?? 0,
|
|
329
|
-
sells1h: txns.h1?.sells ?? 0,
|
|
330
|
-
};
|
|
331
|
-
})
|
|
332
|
-
.filter((c) => c !== null && c.liquidity >= minLiquidity)
|
|
333
|
-
.slice(0, limit);
|
|
334
413
|
const scored = candidates
|
|
335
414
|
.map(c => ({ ...c, ...scoreDipReversal(c, minLiquidity) }))
|
|
336
415
|
.filter(c => c.passed && c.score >= minScore)
|
|
@@ -371,5 +450,57 @@ async function handleScannerTool(name, args) {
|
|
|
371
450
|
lines.push(`Next steps: \`score_token\` for full breakdown · \`check_token\` for rug check before buying`);
|
|
372
451
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
373
452
|
}
|
|
453
|
+
// ── scan_momentum ──────────────────────────────────────────────────────────
|
|
454
|
+
if (name === "scan_momentum") {
|
|
455
|
+
const parsed = ScanMomentumSchema.safeParse(args ?? {});
|
|
456
|
+
if (!parsed.success)
|
|
457
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
458
|
+
const { minScore = DEFAULT_MIN_SCORE, minLiquidity = DEFAULT_MIN_LIQ, limit = 40 } = parsed.data;
|
|
459
|
+
let candidates;
|
|
460
|
+
try {
|
|
461
|
+
candidates = await fetchBasePools(minLiquidity, limit);
|
|
462
|
+
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
return { content: [{ type: "text", text: err.message }], isError: true };
|
|
465
|
+
}
|
|
466
|
+
const scored = candidates
|
|
467
|
+
.map(c => ({ ...c, ...scoreMomentum(c, minLiquidity) }))
|
|
468
|
+
.filter(c => c.passed && c.score >= minScore)
|
|
469
|
+
.sort((a, b) => b.score - a.score);
|
|
470
|
+
if (!scored.length) {
|
|
471
|
+
return {
|
|
472
|
+
content: [{
|
|
473
|
+
type: "text",
|
|
474
|
+
text: [
|
|
475
|
+
`## Momentum Scan — No Breakouts Found`,
|
|
476
|
+
``,
|
|
477
|
+
`Scanned **${candidates.length} pools** on Base. None passed the momentum gates with score ≥ ${minScore}.`,
|
|
478
|
+
``,
|
|
479
|
+
`When nothing breaks out, the market may be in consolidation or distribution.`,
|
|
480
|
+
``,
|
|
481
|
+
`Try: \`scan_dips\` to look for reversal opportunities instead.`,
|
|
482
|
+
].join("\n"),
|
|
483
|
+
}],
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
const sign = (n) => n >= 0 ? `+${n.toFixed(1)}` : n.toFixed(1);
|
|
487
|
+
const lines = [
|
|
488
|
+
`## Momentum Scan — ${scored.length} Breakout${scored.length !== 1 ? "s" : ""} Found`,
|
|
489
|
+
`Scanned **${candidates.length} pools** · Score ≥ ${minScore} · Liq ≥ $${(minLiquidity / 1000).toFixed(0)}k`,
|
|
490
|
+
``,
|
|
491
|
+
];
|
|
492
|
+
for (const c of scored.slice(0, 10)) {
|
|
493
|
+
const bar = "█".repeat(Math.round(c.score / 10)).padEnd(10, "░");
|
|
494
|
+
lines.push(`### ${c.symbol} · ${c.score}/100 \`${bar}\``);
|
|
495
|
+
lines.push(`**Pattern:** ${c.pattern} · **Liq:** $${(c.liquidity / 1000).toFixed(0)}k · **1h:** ${sign(c.priceChange1h)}% · **5m:** ${sign(c.priceChange5m)}%`);
|
|
496
|
+
lines.push(`**Buy pressure:** ${c.buyPressure5m.toFixed(0)}% · **Vol 1h:** $${(c.volume1h / 1000).toFixed(0)}k`);
|
|
497
|
+
lines.push(`**Trend:** 6h ${sign(c.priceChange6h)}% / 24h ${sign(c.priceChange24h)}%`);
|
|
498
|
+
lines.push(`\`${c.mint}\``);
|
|
499
|
+
lines.push(``);
|
|
500
|
+
}
|
|
501
|
+
lines.push(`---`);
|
|
502
|
+
lines.push(`Next steps: \`score_token\` for dip-reversal score · \`check_token\` for rug check before buying`);
|
|
503
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
504
|
+
}
|
|
374
505
|
return null;
|
|
375
506
|
}
|
package/dist/tools/swarm.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.handleSwarmTool = handleSwarmTool;
|
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
const convex_js_1 = require("../convex.js");
|
|
7
7
|
const market_js_1 = require("./market.js");
|
|
8
|
+
const memory_js_1 = require("./memory.js");
|
|
8
9
|
function formatDate(ts) {
|
|
9
10
|
return new Date(ts).toUTCString();
|
|
10
11
|
}
|
|
@@ -37,34 +38,6 @@ exports.SWARM_TOOLS = [
|
|
|
37
38
|
description: "Get the current status of the swarm: active agents, shared memory snapshot, execution scores, and recent runs.",
|
|
38
39
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
39
40
|
},
|
|
40
|
-
{
|
|
41
|
-
name: "write_swarm_memory",
|
|
42
|
-
description: "Write a key-value pair to the swarm's shared memory.",
|
|
43
|
-
inputSchema: {
|
|
44
|
-
type: "object",
|
|
45
|
-
properties: {
|
|
46
|
-
agentId: { type: "string", description: "ID of the agent writing this memory entry" },
|
|
47
|
-
key: { type: "string", description: "Memory key" },
|
|
48
|
-
value: { type: "string", description: "Value to store" },
|
|
49
|
-
ttlSeconds: { type: "number", description: "Optional TTL in seconds" },
|
|
50
|
-
},
|
|
51
|
-
required: ["agentId", "key", "value"],
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: "get_swarm_memory",
|
|
56
|
-
description: "Read a value from the swarm's shared memory by key.",
|
|
57
|
-
inputSchema: {
|
|
58
|
-
type: "object",
|
|
59
|
-
properties: { key: { type: "string", description: "Memory key to read" } },
|
|
60
|
-
required: ["key"],
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
name: "get_execution_scores",
|
|
65
|
-
description: "Get the self-improvement scores for all skills.",
|
|
66
|
-
inputSchema: { type: "object", properties: {}, required: [] },
|
|
67
|
-
},
|
|
68
41
|
{
|
|
69
42
|
name: "swarm_research",
|
|
70
43
|
description: "Research any topic using the multi-agent swarm — automatically starts the swarm if needed, " +
|
|
@@ -82,21 +55,22 @@ exports.SWARM_TOOLS = [
|
|
|
82
55
|
{
|
|
83
56
|
name: "trigger_agent",
|
|
84
57
|
description: "Run a single swarm agent immediately — automatically starts the swarm if needed. " +
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
58
|
+
"Agents: market-monitor (live prices), sentiment-tracker (social/sentiment), " +
|
|
59
|
+
"memory-manager (compress memory), risk-verifier (evaluate risk), " +
|
|
60
|
+
"onchain-analyst (on-chain data: wallets, flows, TVL), " +
|
|
61
|
+
"news-aggregator (latest news + narrative tracking). " +
|
|
88
62
|
"Results are saved to vault automatically.",
|
|
89
63
|
inputSchema: {
|
|
90
64
|
type: "object",
|
|
91
65
|
properties: {
|
|
92
66
|
agentId: {
|
|
93
67
|
type: "string",
|
|
94
|
-
enum: ["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor"],
|
|
68
|
+
enum: ["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor", "onchain-analyst", "news-aggregator"],
|
|
95
69
|
description: "Which agent to run",
|
|
96
70
|
},
|
|
97
71
|
params: {
|
|
98
72
|
type: "object",
|
|
99
|
-
description: "Agent-specific params. market-monitor: { token: 'BTC' }. sentiment-tracker: { token: 'ETH' } or { topic: 'Layer 2s' }.
|
|
73
|
+
description: "Agent-specific params. market-monitor: { token: 'BTC' }. sentiment-tracker: { token: 'ETH' } or { topic: 'Layer 2s' }. onchain-analyst: { address: '0x...' } or { protocol: 'Morpho' }. news-aggregator: { topic: 'Base ecosystem' }.",
|
|
100
74
|
},
|
|
101
75
|
},
|
|
102
76
|
required: ["agentId"],
|
|
@@ -122,12 +96,10 @@ const StartSwarmSchema = zod_1.z.object({
|
|
|
122
96
|
byok: zod_1.z.boolean().optional(),
|
|
123
97
|
}).optional(),
|
|
124
98
|
});
|
|
125
|
-
const WriteMemorySchema = zod_1.z.object({ agentId: zod_1.z.string().min(1), key: zod_1.z.string().min(1), value: zod_1.z.string(), ttlSeconds: zod_1.z.number().optional() });
|
|
126
|
-
const GetMemorySchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
|
|
127
99
|
const ResearchSchema = zod_1.z.object({ topic: zod_1.z.string().min(1), depth: zod_1.z.enum(["quick", "standard", "deep"]).optional() });
|
|
128
100
|
const BriefSchema = zod_1.z.object({ limit: zod_1.z.number().optional() });
|
|
129
101
|
const TriggerAgentSchema = zod_1.z.object({
|
|
130
|
-
agentId: zod_1.z.enum(["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor"]),
|
|
102
|
+
agentId: zod_1.z.enum(["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor", "onchain-analyst", "news-aggregator"]),
|
|
131
103
|
params: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
|
|
132
104
|
});
|
|
133
105
|
const NO_KEY_MSG = `🔑 Swarm tools require a NoelClaw API key.\n\n` +
|
|
@@ -232,46 +204,14 @@ async function handleSwarmTool(name, args) {
|
|
|
232
204
|
}
|
|
233
205
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
234
206
|
}
|
|
235
|
-
case "write_swarm_memory": {
|
|
236
|
-
const parsed = WriteMemorySchema.safeParse(args);
|
|
237
|
-
if (!parsed.success)
|
|
238
|
-
return { content: [{ type: "text", text: `Invalid input: ${String(parsed.error.issues[0].path[0])} ${parsed.error.issues[0].message}` }], isError: true };
|
|
239
|
-
const { agentId, key, value, ttlSeconds } = parsed.data;
|
|
240
|
-
await (0, convex_js_1.callConvex)("/swarm/memory/write", "POST", { agentId, key, value, ttlSeconds }, "write_swarm_memory");
|
|
241
|
-
return { content: [{ type: "text", text: `✅ Memory written: [${agentId}] ${key}${ttlSeconds ? ` (expires in ${ttlSeconds}s)` : ""}` }] };
|
|
242
|
-
}
|
|
243
|
-
case "get_swarm_memory": {
|
|
244
|
-
const parsed = GetMemorySchema.safeParse(args);
|
|
245
|
-
if (!parsed.success)
|
|
246
|
-
return { content: [{ type: "text", text: `Invalid input: key ${parsed.error.issues[0].message}` }], isError: true };
|
|
247
|
-
const data = await (0, convex_js_1.callConvex)(`/swarm/memory/read?key=${encodeURIComponent(parsed.data.key)}`, "GET", undefined, "get_swarm_memory");
|
|
248
|
-
if (data.error)
|
|
249
|
-
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
250
|
-
if (data.value === null || data.value === undefined)
|
|
251
|
-
return { content: [{ type: "text", text: `No value found for key: ${parsed.data.key}` }] };
|
|
252
|
-
return { content: [{ type: "text", text: `**${parsed.data.key}**: ${data.value}` }] };
|
|
253
|
-
}
|
|
254
|
-
case "get_execution_scores": {
|
|
255
|
-
const data = await (0, convex_js_1.callConvex)("/swarm/scores", "GET", undefined, "get_execution_scores");
|
|
256
|
-
if (data.error)
|
|
257
|
-
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
258
|
-
const scores = data.scores ?? [];
|
|
259
|
-
if (!scores.length)
|
|
260
|
-
return { content: [{ type: "text", text: "No execution scores yet. Run some swarm agents to build a history." }] };
|
|
261
|
-
const sorted = scores.sort((a, b) => b.lastScore - a.lastScore);
|
|
262
|
-
const lines = [
|
|
263
|
-
`**Execution Scores**`, ``,
|
|
264
|
-
`| Skill | Score | W | L | Avg Duration | Last Adapted |`,
|
|
265
|
-
`|-------|-------|---|---|--------------|--------------|`,
|
|
266
|
-
...sorted.map((s) => `| ${s.skillName} | ${(s.lastScore * 100).toFixed(0)}% | ${s.successCount} | ${s.failCount} | ${Math.round(s.avgDurationMs / 1000)}s | ${new Date(s.lastAdaptedAt).toUTCString()} |`),
|
|
267
|
-
];
|
|
268
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
269
|
-
}
|
|
270
207
|
case "swarm_research": {
|
|
271
208
|
const parsed = ResearchSchema.safeParse(args);
|
|
272
209
|
if (!parsed.success)
|
|
273
210
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
274
211
|
const { topic, depth = "standard" } = parsed.data;
|
|
212
|
+
// Check what's already known before launching — show value immediately
|
|
213
|
+
const [priorMem] = await Promise.allSettled([(0, memory_js_1.searchSupermemory)(topic, 4)]);
|
|
214
|
+
const priorResults = priorMem.status === "fulfilled" ? priorMem.value : [];
|
|
275
215
|
await (0, convex_js_1.callConvex)("/swarm/start", "POST", {}, "start_swarm").catch(() => { });
|
|
276
216
|
let data;
|
|
277
217
|
try {
|
|
@@ -282,17 +222,26 @@ async function handleSwarmTool(name, args) {
|
|
|
282
222
|
}
|
|
283
223
|
if (data.error)
|
|
284
224
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
225
|
+
const priorSection = priorResults.length > 0
|
|
226
|
+
? [
|
|
227
|
+
``,
|
|
228
|
+
`**Prior knowledge loaded (${priorResults.length} memories):**`,
|
|
229
|
+
...priorResults.map(r => `• ${r.metadata?.title ?? r.content.slice(0, 80).replace(/\n/g, " ")}`),
|
|
230
|
+
`Agents will build on this — no re-discovering what you already know.`,
|
|
231
|
+
]
|
|
232
|
+
: [``, `No prior knowledge found — agents starting fresh.`];
|
|
285
233
|
return {
|
|
286
234
|
content: [{
|
|
287
235
|
type: "text",
|
|
288
236
|
text: [
|
|
289
237
|
`🔬 **Swarm Research Started**`,
|
|
290
|
-
`Topic: ${topic}`,
|
|
291
|
-
|
|
238
|
+
`Topic: ${topic} · Depth: ${depth}`,
|
|
239
|
+
...priorSection,
|
|
292
240
|
``,
|
|
293
|
-
data.message ?? "Research triggered. Findings will appear in vault.",
|
|
241
|
+
data.message ?? "Research triggered. Findings will appear in vault automatically.",
|
|
294
242
|
``,
|
|
295
|
-
|
|
243
|
+
`**Next:** \`swarm_reflect focus: "${topic}"\` to consolidate findings`,
|
|
244
|
+
`Or: \`memory_insight topic: "${topic}"\` to see full intelligence report`,
|
|
296
245
|
].join("\n"),
|
|
297
246
|
}],
|
|
298
247
|
};
|
|
@@ -303,9 +252,16 @@ async function handleSwarmTool(name, args) {
|
|
|
303
252
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
304
253
|
const { agentId, params = {} } = parsed.data;
|
|
305
254
|
await (0, convex_js_1.callConvex)("/swarm/start", "POST", {}, "start_swarm").catch(() => { });
|
|
255
|
+
// Auto-load prior context so agent doesn't re-discover what's already known
|
|
256
|
+
const contextQuery = [agentId, params?.token, params?.topic, params?.protocol].filter(Boolean).join(" ");
|
|
257
|
+
const priorContext = await (0, memory_js_1.searchSupermemory)(contextQuery, 3);
|
|
258
|
+
const priorSummary = priorContext.length > 0
|
|
259
|
+
? priorContext.map(r => r.content.slice(0, 150)).join(" | ")
|
|
260
|
+
: null;
|
|
261
|
+
const enrichedParams = priorSummary ? { ...params, priorContext: priorSummary } : params;
|
|
306
262
|
let data;
|
|
307
263
|
try {
|
|
308
|
-
data = await (0, convex_js_1.callConvex)("/swarm/trigger", "POST", { agentId, params }, "trigger_agent");
|
|
264
|
+
data = await (0, convex_js_1.callConvex)("/swarm/trigger", "POST", { agentId, params: enrichedParams }, "trigger_agent");
|
|
309
265
|
}
|
|
310
266
|
catch (err) {
|
|
311
267
|
return swarmAuthError(err);
|
|
@@ -313,15 +269,17 @@ async function handleSwarmTool(name, args) {
|
|
|
313
269
|
if (data.error)
|
|
314
270
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
315
271
|
const resultText = data.result ? `\n\`\`\`json\n${JSON.stringify(data.result, null, 2).slice(0, 800)}\n\`\`\`` : "";
|
|
272
|
+
const contextNote = priorContext.length > 0 ? `🧠 ${priorContext.length} prior memory entries injected into agent context.` : "";
|
|
316
273
|
return {
|
|
317
274
|
content: [{
|
|
318
275
|
type: "text",
|
|
319
276
|
text: [
|
|
320
277
|
`⚡ **${agentId} triggered**`,
|
|
321
|
-
|
|
278
|
+
contextNote,
|
|
322
279
|
resultText,
|
|
323
280
|
``,
|
|
324
|
-
`
|
|
281
|
+
`Findings saved to vault automatically.`,
|
|
282
|
+
`Use \`swarm_reflect\` to consolidate or \`memory_insight topic: "${contextQuery.split(" ")[1] ?? agentId}"\` for full report.`,
|
|
325
283
|
].filter(Boolean).join("\n"),
|
|
326
284
|
}],
|
|
327
285
|
};
|
|
@@ -350,7 +308,7 @@ async function handleSwarmTool(name, args) {
|
|
|
350
308
|
lines.push(`**[${e.agentId}]** ${e.title}`);
|
|
351
309
|
lines.push(` _${e.key}_ · v${e.version} · ${formatDate(e.updatedAt)}`);
|
|
352
310
|
}
|
|
353
|
-
lines.push(`\nUse \`
|
|
311
|
+
lines.push(`\nUse \`memory_context topic: "<topic>"\` to load full content for any research area.`);
|
|
354
312
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
355
313
|
}
|
|
356
314
|
default:
|