@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.
@@ -85,13 +85,116 @@ function scoreDipReversal(c, minLiquidity = DEFAULT_MIN_LIQ) {
85
85
  buyPressure5m: bp,
86
86
  };
87
87
  }
88
- // ── Fetch helper ──────────────────────────────────────────────────────────────
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
- // Fetch trending + new pools on Base from GeckoTerminal in parallel
283
- const [trendingRes, newPoolsRes] = await Promise.allSettled([
284
- fetchJson("https://api.geckoterminal.com/api/v2/networks/base/trending_pools?page=1"),
285
- fetchJson("https://api.geckoterminal.com/api/v2/networks/base/new_pools?page=1"),
286
- ]);
287
- const rawPools = [
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
  }
@@ -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
- "Costs 100 credits per call. " +
86
- "market-monitor fetches live price data, sentiment-tracker analyzes sentiment, " +
87
- "memory-manager compresses ephemeral memory, risk-verifier evaluates an action for risk. " +
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' }. risk-verifier: { type: 'swap', params: { ... } }.",
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
- `Depth: ${depth}`,
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
- `Use \`vault_context topic: "${topic}"\` to retrieve results once complete.`,
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
- `100 credits charged.`,
278
+ contextNote,
322
279
  resultText,
323
280
  ``,
324
- `Use \`vault_context topic: "${agentId.replace("-", " ")}"\` to retrieve findings.`,
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 \`vault_context topic: "<topic>"\` to load full content for any research area.`);
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: