@noelclaw/mcp 1.5.7 → 2.2.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/dist/convex.js +3 -1
- package/dist/index.js +71 -16
- package/dist/llm.js +64 -0
- package/dist/server.js +23 -18
- package/dist/tools/agents.js +79 -0
- package/dist/tools/automation.js +38 -0
- package/dist/tools/base.js +269 -0
- package/dist/tools/coder.js +347 -0
- package/dist/tools/defi.js +106 -1
- package/dist/tools/humanizer.js +15 -35
- package/dist/tools/insight.js +7 -33
- package/dist/tools/miroshark.js +57 -13
- package/dist/tools/scanner.js +375 -0
- package/dist/tools/swarm.js +127 -0
- package/dist/tools/vault.js +198 -1
- package/package.json +1 -1
package/dist/tools/defi.js
CHANGED
|
@@ -6,6 +6,24 @@ const zod_1 = require("zod");
|
|
|
6
6
|
const convex_js_1 = require("../convex.js");
|
|
7
7
|
const wallet_js_1 = require("../wallet.js");
|
|
8
8
|
exports.DEFI_TOOLS = [
|
|
9
|
+
{
|
|
10
|
+
name: "get_portfolio",
|
|
11
|
+
description: "Get current token balances and total portfolio value for your MCP wallet on Base. Always call this before swapping to confirm available balance.",
|
|
12
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "estimate_swap",
|
|
16
|
+
description: "Preview a swap — get the expected output amount and price impact without executing. Use this before swap_tokens to confirm the rate is acceptable.",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
fromToken: { type: "string", description: "Token to sell: ETH, USDC, USDT, DAI, WETH" },
|
|
21
|
+
toToken: { type: "string", description: "Token to buy: ETH, USDC, USDT, DAI, WETH" },
|
|
22
|
+
amount: { type: "string", description: "Amount to swap (e.g. '0.01', '50', '100%')" },
|
|
23
|
+
},
|
|
24
|
+
required: ["fromToken", "toToken", "amount"],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
9
27
|
{
|
|
10
28
|
name: "swap_tokens",
|
|
11
29
|
description: "Swap tokens on Base mainnet via 0x Permit2. Supported: ETH, USDC, USDT, DAI, WETH. Amount is human-readable. Signed and broadcast locally from your wallet.",
|
|
@@ -32,11 +50,65 @@ exports.DEFI_TOOLS = [
|
|
|
32
50
|
required: ["token", "toAddress", "amount"],
|
|
33
51
|
},
|
|
34
52
|
},
|
|
53
|
+
{
|
|
54
|
+
name: "scan_wallet",
|
|
55
|
+
description: "AI-powered portfolio scan — concentration risk, volatility exposure, Base ecosystem opportunities, and a concrete 3-step action plan based on your actual holdings. Requires wallet auth.",
|
|
56
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
57
|
+
},
|
|
35
58
|
];
|
|
36
59
|
const SwapSchema = zod_1.z.object({ fromToken: zod_1.z.string().min(1), toToken: zod_1.z.string().min(1), amount: zod_1.z.string().min(1) });
|
|
37
60
|
const SendSchema = zod_1.z.object({ token: zod_1.z.string().min(1), toAddress: zod_1.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "must be a valid 0x address"), amount: zod_1.z.string().min(1) });
|
|
61
|
+
const BUY_DECIMALS = { USDC: 6, USDT: 6, DAI: 18, ETH: 18, WETH: 18 };
|
|
62
|
+
function formatTokenAmount(raw, token) {
|
|
63
|
+
const dec = BUY_DECIMALS[token.toUpperCase()] ?? 18;
|
|
64
|
+
return (parseInt(raw) / Math.pow(10, dec)).toFixed(dec === 6 ? 2 : 6);
|
|
65
|
+
}
|
|
38
66
|
async function handleDefiTool(name, args) {
|
|
39
67
|
switch (name) {
|
|
68
|
+
case "get_portfolio": {
|
|
69
|
+
const result = await (0, convex_js_1.callConvex)("/mcp/defi/portfolio", "GET", undefined, "get_portfolio");
|
|
70
|
+
if (result.error)
|
|
71
|
+
return { content: [{ type: "text", text: `Portfolio fetch failed: ${result.error}` }], isError: true };
|
|
72
|
+
const balances = result.balances ?? [];
|
|
73
|
+
const totalUsd = result.totalUsd ?? balances.reduce((s, b) => s + (b.valueUsd ?? 0), 0);
|
|
74
|
+
if (!balances.length) {
|
|
75
|
+
return { content: [{ type: "text", text: "Your wallet has no tokens yet. Send ETH or USDC on Base to get started." }] };
|
|
76
|
+
}
|
|
77
|
+
const lines = [`**Portfolio** — Total: $${totalUsd.toFixed(2)}`, ""];
|
|
78
|
+
for (const b of balances) {
|
|
79
|
+
const value = b.valueUsd != null ? ` ($${Number(b.valueUsd).toFixed(2)})` : "";
|
|
80
|
+
lines.push(`• **${b.token ?? b.symbol}**: ${Number(b.balance ?? b.amount).toLocaleString(undefined, { maximumFractionDigits: 6 })}${value}`);
|
|
81
|
+
}
|
|
82
|
+
lines.push("", `Wallet: \`${result.address ?? "unknown"}\``);
|
|
83
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
84
|
+
}
|
|
85
|
+
case "estimate_swap": {
|
|
86
|
+
const parsed = SwapSchema.safeParse(args);
|
|
87
|
+
if (!parsed.success)
|
|
88
|
+
return { content: [{ type: "text", text: `Invalid input: ${String(parsed.error.issues[0].path[0])} ${parsed.error.issues[0].message}` }], isError: true };
|
|
89
|
+
const { fromToken, toToken, amount } = parsed.data;
|
|
90
|
+
const result = await (0, convex_js_1.callConvex)("/mcp/defi/swap", "POST", { fromToken, toToken, amount }, "estimate_swap");
|
|
91
|
+
if (!result.success)
|
|
92
|
+
return { content: [{ type: "text", text: `Estimate failed: ${result.error}` }], isError: true };
|
|
93
|
+
const q = result.quote;
|
|
94
|
+
const buyHuman = formatTokenAmount(q.buyAmount, q.buyToken ?? toToken);
|
|
95
|
+
const sellHuman = formatTokenAmount(q.sellAmount ?? "0", q.sellToken ?? fromToken);
|
|
96
|
+
const priceImpact = q.estimatedPriceImpact != null ? `${Number(q.estimatedPriceImpact).toFixed(3)}%` : "< 0.01%";
|
|
97
|
+
return {
|
|
98
|
+
content: [{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: [
|
|
101
|
+
`**Swap Estimate** (not executed)`,
|
|
102
|
+
``,
|
|
103
|
+
`You sell: **${sellHuman} ${(q.sellToken ?? fromToken).toUpperCase()}**`,
|
|
104
|
+
`You get: **~${buyHuman} ${(q.buyToken ?? toToken).toUpperCase()}**`,
|
|
105
|
+
`Price impact: ${priceImpact}`,
|
|
106
|
+
``,
|
|
107
|
+
`Run \`swap_tokens\` with the same params to execute.`,
|
|
108
|
+
].join("\n"),
|
|
109
|
+
}],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
40
112
|
case "swap_tokens": {
|
|
41
113
|
const parsed = SwapSchema.safeParse(args);
|
|
42
114
|
if (!parsed.success)
|
|
@@ -47,7 +119,7 @@ async function handleDefiTool(name, args) {
|
|
|
47
119
|
if (!result.success)
|
|
48
120
|
return { content: [{ type: "text", text: `Swap failed: ${result.error}` }], isError: true };
|
|
49
121
|
const txHash = await (0, wallet_js_1.signAndBroadcast)(wallet, result.quote);
|
|
50
|
-
const buyAmountHuman = (
|
|
122
|
+
const buyAmountHuman = formatTokenAmount(result.quote.buyAmount, result.quote.buyToken ?? toToken);
|
|
51
123
|
return {
|
|
52
124
|
content: [{
|
|
53
125
|
type: "text",
|
|
@@ -78,6 +150,39 @@ async function handleDefiTool(name, args) {
|
|
|
78
150
|
}],
|
|
79
151
|
};
|
|
80
152
|
}
|
|
153
|
+
case "scan_wallet": {
|
|
154
|
+
const data = await (0, convex_js_1.callConvex)("/wallet/scan", "GET", undefined, "scan_wallet");
|
|
155
|
+
if (data.error) {
|
|
156
|
+
return { content: [{ type: "text", text: `Scan failed: ${data.error}` }], isError: true };
|
|
157
|
+
}
|
|
158
|
+
const total = (data.totalUsd ?? 0).toFixed(2);
|
|
159
|
+
const topHoldings = (data.holdings ?? [])
|
|
160
|
+
.slice(0, 5)
|
|
161
|
+
.map((h) => `• **${h.token}**: $${(h.valueUsd ?? 0).toFixed(2)}${h.pct != null ? ` (${h.pct}%)` : ""}`)
|
|
162
|
+
.join("\n");
|
|
163
|
+
const header = [
|
|
164
|
+
`**Portfolio Scan** — Total: $${total}`,
|
|
165
|
+
`Wallet: \`${data.address ?? "unknown"}\``,
|
|
166
|
+
``,
|
|
167
|
+
`**Holdings:**`,
|
|
168
|
+
topHoldings,
|
|
169
|
+
``,
|
|
170
|
+
].join("\n");
|
|
171
|
+
let body;
|
|
172
|
+
if (data.analysis) {
|
|
173
|
+
body = data.analysis;
|
|
174
|
+
}
|
|
175
|
+
else if (data.analysisError) {
|
|
176
|
+
body = `*AI analysis unavailable: ${data.analysisError}*`;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
body = "*AI analysis not available*";
|
|
180
|
+
}
|
|
181
|
+
const footer = data.tokensUsed
|
|
182
|
+
? `\n\n*Tokens used: ${data.tokensUsed} · Scanned: ${data.scannedAt ?? ""}*`
|
|
183
|
+
: "";
|
|
184
|
+
return { content: [{ type: "text", text: header + body + footer }] };
|
|
185
|
+
}
|
|
81
186
|
default:
|
|
82
187
|
return null;
|
|
83
188
|
}
|
package/dist/tools/humanizer.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HUMANIZER_TOOLS = void 0;
|
|
4
4
|
exports.handleHumanizerTool = handleHumanizerTool;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const llm_js_1 = require("../llm.js");
|
|
5
7
|
exports.HUMANIZER_TOOLS = [
|
|
6
8
|
{
|
|
7
9
|
name: "humanize_text",
|
|
@@ -25,6 +27,10 @@ exports.HUMANIZER_TOOLS = [
|
|
|
25
27
|
},
|
|
26
28
|
},
|
|
27
29
|
];
|
|
30
|
+
const HumanizerSchema = zod_1.z.object({
|
|
31
|
+
text: zod_1.z.string().min(1),
|
|
32
|
+
voice_sample: zod_1.z.string().optional(),
|
|
33
|
+
});
|
|
28
34
|
const HUMANIZER_SYSTEM = `You are a text editor that removes signs of AI-generated writing.
|
|
29
35
|
|
|
30
36
|
Your job: rewrite the input so it sounds natural, direct, and human — without changing the meaning.
|
|
@@ -75,45 +81,19 @@ If a voice sample is provided, match its tone, rhythm, and vocabulary. Otherwise
|
|
|
75
81
|
async function handleHumanizerTool(name, args) {
|
|
76
82
|
if (name !== "humanize_text")
|
|
77
83
|
return null;
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
80
|
-
return { content: [{ type: "text", text:
|
|
84
|
+
const parsed = HumanizerSchema.safeParse(args);
|
|
85
|
+
if (!parsed.success) {
|
|
86
|
+
return { content: [{ type: "text", text: `Invalid input: text ${parsed.error.issues[0].message}` }], isError: true };
|
|
81
87
|
}
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const userMsg = a.voice_sample
|
|
87
|
-
? `VOICE SAMPLE (match this style):\n${a.voice_sample}\n\n---\n\nTEXT TO HUMANIZE:\n${a.text}`
|
|
88
|
-
: a.text;
|
|
88
|
+
const { text, voice_sample } = parsed.data;
|
|
89
|
+
const userMsg = voice_sample
|
|
90
|
+
? `VOICE SAMPLE (match this style):\n${voice_sample}\n\n---\n\nTEXT TO HUMANIZE:\n${text}`
|
|
91
|
+
: text;
|
|
89
92
|
try {
|
|
90
|
-
const
|
|
91
|
-
method: "POST",
|
|
92
|
-
headers: {
|
|
93
|
-
"Content-Type": "application/json",
|
|
94
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
95
|
-
},
|
|
96
|
-
body: JSON.stringify({
|
|
97
|
-
model: "MiniMax-M2.7",
|
|
98
|
-
messages: [
|
|
99
|
-
{ role: "system", content: HUMANIZER_SYSTEM },
|
|
100
|
-
{ role: "user", content: userMsg },
|
|
101
|
-
],
|
|
102
|
-
temperature: 0.7,
|
|
103
|
-
max_tokens: 4096,
|
|
104
|
-
}),
|
|
105
|
-
signal: AbortSignal.timeout(30000),
|
|
106
|
-
});
|
|
107
|
-
if (!res.ok) {
|
|
108
|
-
const err = await res.text();
|
|
109
|
-
return { content: [{ type: "text", text: `API error: ${res.status} ${err.slice(0, 200)}` }], isError: true };
|
|
110
|
-
}
|
|
111
|
-
const data = await res.json();
|
|
112
|
-
const raw = data?.choices?.[0]?.message?.content ?? "";
|
|
113
|
-
const output = raw.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
93
|
+
const output = await (0, llm_js_1.callLLM)(HUMANIZER_SYSTEM, userMsg, 4096);
|
|
114
94
|
if (!output)
|
|
115
95
|
return { content: [{ type: "text", text: "Empty response from model" }], isError: true };
|
|
116
|
-
return { content: [{ type: "text", text: output }] };
|
|
96
|
+
return { content: [{ type: "text", text: output.trim() }] };
|
|
117
97
|
}
|
|
118
98
|
catch (err) {
|
|
119
99
|
return { content: [{ type: "text", text: `Humanizer error: ${err.message}` }], isError: true };
|
package/dist/tools/insight.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.INSIGHT_TOOLS = void 0;
|
|
|
4
4
|
exports.handleInsightTool = handleInsightTool;
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
const convex_js_1 = require("../convex.js");
|
|
7
|
+
const llm_js_1 = require("../llm.js");
|
|
7
8
|
exports.INSIGHT_TOOLS = [
|
|
8
9
|
{
|
|
9
10
|
name: "ask_noel",
|
|
@@ -30,34 +31,7 @@ const AskNoelSchema = zod_1.z.object({
|
|
|
30
31
|
question: zod_1.z.string().min(1),
|
|
31
32
|
messages: zod_1.z.array(zod_1.z.object({ role: zod_1.z.enum(["user", "assistant"]), content: zod_1.z.string() })).optional(),
|
|
32
33
|
});
|
|
33
|
-
const BANKR_LLM_URL = "https://llm.bankr.bot/v1/chat/completions";
|
|
34
|
-
const BANKR_MODEL = process.env.BANKR_MODEL ?? "grok-3";
|
|
35
34
|
const NOEL_SYSTEM_PROMPT = `You are Noel, a crypto AI analyst with deep expertise in DeFi, on-chain data, market structure, and trading psychology. You provide sharp, direct analysis — no fluff, no disclaimers. You understand narratives, liquidity flows, whale behavior, and how sentiment drives price. When asked about a token or market, give your honest read with supporting reasoning.`;
|
|
36
|
-
async function askViaBankr(question, messages) {
|
|
37
|
-
const res = await fetch(BANKR_LLM_URL, {
|
|
38
|
-
method: "POST",
|
|
39
|
-
headers: {
|
|
40
|
-
"X-API-Key": process.env.BANKR_API_KEY,
|
|
41
|
-
"Content-Type": "application/json",
|
|
42
|
-
},
|
|
43
|
-
body: JSON.stringify({
|
|
44
|
-
model: BANKR_MODEL,
|
|
45
|
-
messages: [
|
|
46
|
-
{ role: "system", content: NOEL_SYSTEM_PROMPT },
|
|
47
|
-
...messages,
|
|
48
|
-
{ role: "user", content: question },
|
|
49
|
-
],
|
|
50
|
-
max_tokens: 1024,
|
|
51
|
-
}),
|
|
52
|
-
signal: AbortSignal.timeout(30000),
|
|
53
|
-
});
|
|
54
|
-
if (!res.ok) {
|
|
55
|
-
const err = await res.text().catch(() => res.statusText);
|
|
56
|
-
throw new Error(`Bankr LLM error ${res.status}: ${err.slice(0, 200)}`);
|
|
57
|
-
}
|
|
58
|
-
const data = await res.json();
|
|
59
|
-
return data.choices?.[0]?.message?.content ?? "No response from model";
|
|
60
|
-
}
|
|
61
35
|
async function handleInsightTool(name, args) {
|
|
62
36
|
if (name !== "ask_noel")
|
|
63
37
|
return null;
|
|
@@ -65,18 +39,18 @@ async function handleInsightTool(name, args) {
|
|
|
65
39
|
if (!parsed.success)
|
|
66
40
|
return { content: [{ type: "text", text: `Invalid input: question ${parsed.error.issues[0].message}` }], isError: true };
|
|
67
41
|
const { question, messages = [] } = parsed.data;
|
|
68
|
-
//
|
|
69
|
-
if (process.env.BANKR_API_KEY) {
|
|
42
|
+
// Try local LLM first (Anthropic from Claude Desktop, or Bankr if configured)
|
|
43
|
+
if (process.env.ANTHROPIC_API_KEY || process.env.BANKR_API_KEY) {
|
|
70
44
|
try {
|
|
71
|
-
const
|
|
45
|
+
const history = messages.map(m => ({ role: m.role, content: m.content }));
|
|
46
|
+
const answer = await (0, llm_js_1.callLLM)(NOEL_SYSTEM_PROMPT, question, 1024, history);
|
|
72
47
|
return { content: [{ type: "text", text: answer }] };
|
|
73
48
|
}
|
|
74
49
|
catch (err) {
|
|
75
|
-
|
|
76
|
-
console.error(`Bankr LLM failed, falling back to Convex: ${err.message}`);
|
|
50
|
+
console.error(`Local LLM failed, falling back to Convex: ${err.message}`);
|
|
77
51
|
}
|
|
78
52
|
}
|
|
79
|
-
// Fallback: route through Convex backend
|
|
53
|
+
// Fallback: route through Convex backend (uses server's configured key)
|
|
80
54
|
const data = await (0, convex_js_1.callConvex)("/mcp/chat", "POST", {
|
|
81
55
|
question,
|
|
82
56
|
agentId: "noel-default",
|
package/dist/tools/miroshark.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MIROSHARK_TOOLS = void 0;
|
|
4
4
|
exports.handleMirosharkTool = handleMirosharkTool;
|
|
5
|
+
const llm_js_1 = require("../llm.js");
|
|
6
|
+
const convex_js_1 = require("../convex.js");
|
|
5
7
|
const CONVEX_SITE = process.env.NOELCLAW_CONVEX_URL ?? "https://api.noelclaw.com";
|
|
6
8
|
exports.MIROSHARK_TOOLS = [
|
|
7
9
|
{
|
|
@@ -239,25 +241,67 @@ async function handleMirosharkTool(name, args) {
|
|
|
239
241
|
};
|
|
240
242
|
}
|
|
241
243
|
if (runnerStatus === "completed" || runnerStatus === "stopped") {
|
|
242
|
-
|
|
243
|
-
const actionsData = await miroJson(`/miroshark/api/simulation/${simId}/actions?limit=10`, "GET").catch(() => ({ actions: [] }));
|
|
244
|
+
const actionsData = await miroJson(`/miroshark/api/simulation/${simId}/actions?limit=50`, "GET").catch(() => ({ actions: [] }));
|
|
244
245
|
const actions = actionsData?.actions ?? [];
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
246
|
+
const totalActions = runStatus.total_actions_count ?? actions.length;
|
|
247
|
+
const rounds = runStatus.current_round ?? "?";
|
|
248
|
+
const ACTION_EMOJI = {
|
|
249
|
+
tweet: "🐦", post: "📝", sell: "📉", buy: "📈",
|
|
250
|
+
article: "📰", comment: "💬", alert: "🚨", analyze: "🔍",
|
|
251
|
+
};
|
|
252
|
+
// Format agent feed
|
|
253
|
+
const feed = actions.slice(0, 20).map((act) => {
|
|
254
|
+
const who = act.agent_name ?? act.agent_id ?? "agent";
|
|
255
|
+
const what = (act.action_type ?? act.type ?? "action").toLowerCase();
|
|
256
|
+
const emoji = ACTION_EMOJI[what] ?? "•";
|
|
257
|
+
const content = act.content ?? act.text ?? "";
|
|
258
|
+
return `${emoji} **${who}** [${what}]${content ? `: ${String(content).slice(0, 120)}` : ""}`;
|
|
259
|
+
});
|
|
260
|
+
// Generate AI brief from agent activity
|
|
261
|
+
let brief = "";
|
|
262
|
+
if (actions.length > 0 && (process.env.ANTHROPIC_API_KEY || process.env.BANKR_API_KEY)) {
|
|
263
|
+
const activitySummary = actions.slice(0, 30).map((act) => {
|
|
254
264
|
const who = act.agent_name ?? act.agent_id ?? "agent";
|
|
255
265
|
const what = act.action_type ?? act.type ?? "action";
|
|
256
266
|
const content = act.content ?? act.text ?? "";
|
|
257
|
-
|
|
267
|
+
return `${who} [${what}]: ${String(content).slice(0, 150)}`;
|
|
268
|
+
}).join("\n");
|
|
269
|
+
try {
|
|
270
|
+
brief = await (0, llm_js_1.callLLM)("You are a market intelligence analyst. Given a MiroShark multi-agent simulation log, extract: 1) Key market sentiment, 2) Dominant narrative, 3) Top 3 agent behaviors, 4) Outlook. Be concise and direct — max 150 words.", `Simulation activity log:\n${activitySummary}`, 400);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// brief stays empty — non-critical
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Auto-save to vault if key is available
|
|
277
|
+
const savedToVault = false;
|
|
278
|
+
if (brief) {
|
|
279
|
+
try {
|
|
280
|
+
await (0, convex_js_1.callConvex)("/vault/save", "POST", {
|
|
281
|
+
key: `miroshark-${simId.slice(0, 8)}`,
|
|
282
|
+
value: brief,
|
|
283
|
+
tags: ["miroshark", "simulation", "research"],
|
|
284
|
+
}, "vault_save");
|
|
258
285
|
}
|
|
286
|
+
catch {
|
|
287
|
+
// non-critical
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const lines = [
|
|
291
|
+
`**MiroShark \`${simId}\`** — ${runnerStatus}`,
|
|
292
|
+
`Rounds: ${rounds} · Actions: ${totalActions} agents`,
|
|
293
|
+
"",
|
|
294
|
+
];
|
|
295
|
+
if (brief) {
|
|
296
|
+
lines.push("**🧠 AI Brief:**", brief, "");
|
|
297
|
+
}
|
|
298
|
+
if (feed.length > 0) {
|
|
299
|
+
lines.push(`**Agent Feed** (${Math.min(actions.length, 20)} of ${totalActions}):`);
|
|
300
|
+
lines.push(...feed);
|
|
301
|
+
}
|
|
302
|
+
if (brief) {
|
|
303
|
+
lines.push("", `_Findings auto-saved to vault as \`miroshark-${simId.slice(0, 8)}\`_`);
|
|
259
304
|
}
|
|
260
|
-
lines.push("", `Full transcript: \`miroshark_status\` returns results above.`);
|
|
261
305
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
262
306
|
}
|
|
263
307
|
// Fallback: unknown state
|