@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.
@@ -29,16 +29,17 @@ exports.MEMORY_TOOLS = [
29
29
  name: "memory_add",
30
30
  description: "Add content to your Noelclaw semantic memory — no setup needed, no extra API keys. " +
31
31
  "Unlike vault_save, memory_add is instant: no versioning, no type required. " +
32
- "Use for notes, decisions, preferences, URLs, or anything you want to find later " +
33
- "with natural language. Memory is indexed semantically 'what did I say about ETH yield?' " +
32
+ "Use for notes, decisions, preferences, or anything you want to find later with natural language. " +
33
+ "Pass sourceUrl to fetch and index any web page, GitHub repo, or Notion page automatically " +
34
+ "searchable in ~30s. Memory is indexed semantically — 'what did I say about ETH yield?' " +
34
35
  "will find it even without exact keywords.",
35
36
  inputSchema: {
36
37
  type: "object",
37
38
  properties: {
38
- content: { type: "string", description: "Content to remember — text, markdown, or a note" },
39
+ content: { type: "string", description: "Content to remember — text, markdown, or a note. Use a short title if providing sourceUrl." },
39
40
  title: { type: "string", description: "Optional title for this memory" },
40
41
  tags: { type: "array", items: { type: "string" }, description: "Tags for grouping" },
41
- sourceUrl: { type: "string", description: "Optional URL Noelclaw will fetch and index the page content automatically" },
42
+ sourceUrl: { type: "string", description: "URL to fetch and index automatically (GitHub, Notion, web page, etc.). Content becomes searchable in ~30s." },
42
43
  },
43
44
  required: ["content"],
44
45
  },
@@ -47,7 +48,7 @@ exports.MEMORY_TOOLS = [
47
48
  name: "memory_search",
48
49
  description: "Semantic search across all your Noelclaw memories. Understands meaning, not just keywords — " +
49
50
  "'low risk crypto yield' matches 'conservative DeFi strategies'. " +
50
- "Also searches memories auto-synced from vault_save and vault_remember.",
51
+ "Also searches memories auto-synced from vault_save.",
51
52
  inputSchema: {
52
53
  type: "object",
53
54
  properties: {
@@ -61,7 +62,7 @@ exports.MEMORY_TOOLS = [
61
62
  name: "memory_context",
62
63
  description: "Retrieve the most semantically relevant memories for a topic, formatted as AI-ready context. " +
63
64
  "Use at the start of research tasks to prime with everything stored about a topic. " +
64
- "Smarter than vault_contextuses vector search, not just keyword matching.",
65
+ "Uses vector searchfinds semantically related content, not just exact keyword matches.",
65
66
  inputSchema: {
66
67
  type: "object",
67
68
  properties: {
@@ -82,19 +83,44 @@ exports.MEMORY_TOOLS = [
82
83
  },
83
84
  },
84
85
  {
85
- name: "memory_connect",
86
- description: "Fetch and index content from an external URL into your semantic memory. " +
87
- "Works with any web page, GitHub repo/file, or Notion page URL. " +
88
- "Noelclaw crawls the URL and indexes the full content — searchable in ~30s. " +
89
- "For full Google Drive / Gmail / Notion workspace sync, connect via the Noelclaw dashboard.",
86
+ name: "memory_list",
87
+ description: "List your most recent Noelclaw memories without a search query. " +
88
+ "Useful to browse what's stored or audit before clearing. " +
89
+ "Sorted by most recently added.",
90
90
  inputSchema: {
91
91
  type: "object",
92
92
  properties: {
93
- url: { type: "string", description: "URL to fetch and index (GitHub, Notion, web page, etc.)" },
94
- title: { type: "string", description: "Optional title for this memory" },
95
- tags: { type: "array", items: { type: "string" }, description: "Optional tags" },
93
+ limit: { type: "number", description: "Max memories to return (default 20)" },
94
+ tag: { type: "string", description: "Optional: filter by tag" },
95
+ },
96
+ required: [],
97
+ },
98
+ },
99
+ {
100
+ name: "memory_delete",
101
+ description: "Delete a specific memory by its ID. Get IDs from memory_search or memory_list results. " +
102
+ "This permanently removes the memory from your semantic store.",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: {
106
+ id: { type: "string", description: "Memory ID to delete (from memory_search or memory_list results)" },
107
+ },
108
+ required: ["id"],
109
+ },
110
+ },
111
+ {
112
+ name: "memory_insight",
113
+ description: "Get a full intelligence report on any topic — combines semantic memory AND vault entries, " +
114
+ "then identifies knowledge gaps and suggests next actions. " +
115
+ "Use this before starting any research or trade decision to see everything Noelclaw already knows. " +
116
+ "Returns: confidence level, what you know, coverage timeline, gaps, and recommended next steps.",
117
+ inputSchema: {
118
+ type: "object",
119
+ properties: {
120
+ topic: { type: "string", description: "Topic to analyze — token, protocol, strategy, or any concept" },
121
+ depth: { type: "string", enum: ["quick", "standard", "deep"], description: "How many sources to pull (default: standard)" },
96
122
  },
97
- required: ["url"],
123
+ required: ["topic"],
98
124
  },
99
125
  },
100
126
  ];
@@ -113,10 +139,14 @@ const ContextSchema = zod_1.z.object({
113
139
  topic: zod_1.z.string().min(1),
114
140
  limit: zod_1.z.number().optional(),
115
141
  });
116
- const ConnectSchema = zod_1.z.object({
117
- url: zod_1.z.string().url(),
118
- title: zod_1.z.string().optional(),
119
- tags: zod_1.z.array(zod_1.z.string()).optional(),
142
+ const ListSchema = zod_1.z.object({
143
+ limit: zod_1.z.number().optional(),
144
+ tag: zod_1.z.string().optional(),
145
+ });
146
+ const DeleteMemSchema = zod_1.z.object({ id: zod_1.z.string().min(1) });
147
+ const InsightSchema = zod_1.z.object({
148
+ topic: zod_1.z.string().min(1),
149
+ depth: zod_1.z.enum(["quick", "standard", "deep"]).optional(),
120
150
  });
121
151
  // ─── Handler ─────────────────────────────────────────────────────────────────
122
152
  async function handleMemoryTool(name, args) {
@@ -174,7 +204,7 @@ async function handleMemoryTool(name, args) {
174
204
  const { topic, limit = 8 } = parsed.data;
175
205
  const results = await searchSupermemory(topic, limit);
176
206
  if (!results.length)
177
- return { content: [{ type: "text", text: `No semantic context found for: "${topic}"\nBuild your memory base with vault_save, vault_remember, or memory_add.` }] };
207
+ return { content: [{ type: "text", text: `No semantic context found for: "${topic}"\nBuild your memory base with vault_save or memory_add.` }] };
178
208
  const contextParts = results.map((r, i) => {
179
209
  const title = r.metadata?.title ? `### ${r.metadata.title}` : `### Memory ${i + 1}`;
180
210
  return `${title}\n${r.content}`;
@@ -210,8 +240,8 @@ async function handleMemoryTool(name, args) {
210
240
  `Status: ${status === "ok" ? "✅ Active" : status === "not_configured" ? "⏳ Setting up" : "⚠️ " + status}`,
211
241
  ``,
212
242
  `**Auto-synced sources:**`,
213
- `• vault_save + vault_remember — ✅`,
214
- `• memory_add + memory_connect — ✅`,
243
+ `• vault_save — ✅`,
244
+ `• memory_add (URL indexing) — ✅`,
215
245
  `• Google Drive / Gmail / Notion — connect at noelclaw.com`,
216
246
  ``,
217
247
  `**Capabilities:** Semantic search · Vector context · 81.6% LongMemEval`,
@@ -219,31 +249,117 @@ async function handleMemoryTool(name, args) {
219
249
  }],
220
250
  };
221
251
  }
222
- case "memory_connect": {
223
- const parsed = ConnectSchema.safeParse(args);
252
+ case "memory_list": {
253
+ const parsed = ListSchema.safeParse(args ?? {});
224
254
  if (!parsed.success)
225
255
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
226
- const { url, title, tags } = parsed.data;
227
- const inferredTitle = title ?? url.split("/").filter(Boolean).pop() ?? url;
228
- const data = await (0, convex_js_1.callConvex)("/memory/add", "POST", {
229
- content: inferredTitle,
230
- sourceUrl: url,
231
- metadata: { title: inferredTitle, tags, source: "memory_connect", connectedAt: Date.now() },
232
- }).catch((err) => ({ error: err.message }));
256
+ const { limit = 20, tag } = parsed.data;
257
+ const data = await (0, convex_js_1.callConvex)("/memory/list", "POST", { n: limit, tag }).catch((err) => ({ error: err.message }));
233
258
  if (data?.error)
234
259
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
235
- return {
236
- content: [{
237
- type: "text",
238
- text: [
239
- `✅ **Connected**: ${url}`,
240
- `Memory ID: \`${data?.id ?? "queued"}\``,
241
- ``,
242
- `Noelclaw is fetching and indexing the content in the background.`,
243
- `Search it in ~30s: \`memory_search query: "${inferredTitle.slice(0, 40)}"\``,
244
- ].join("\n"),
245
- }],
246
- };
260
+ const results = data?.results ?? [];
261
+ if (!results.length)
262
+ return { content: [{ type: "text", text: `No memories stored yet. Use \`memory_add\` to start building your knowledge base.` }] };
263
+ const header = `🧠 **Memories** (${results.length} shown${tag ? `, tag: ${tag}` : ""})`;
264
+ const rows = results.map((r, i) => {
265
+ const title = r.metadata?.title ?? "";
266
+ const preview = r.content.slice(0, 100).replace(/\n/g, " ");
267
+ return `${i + 1}. \`${r.id}\`${title ? ` **${title}**` : ""}\n ${preview}${r.content.length > 100 ? "…" : ""}`;
268
+ });
269
+ return { content: [{ type: "text", text: [header, "", ...rows].join("\n") }] };
270
+ }
271
+ case "memory_delete": {
272
+ const parsed = DeleteMemSchema.safeParse(args);
273
+ if (!parsed.success)
274
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
275
+ const data = await (0, convex_js_1.callConvex)("/memory/delete", "POST", { id: parsed.data.id }).catch((err) => ({ error: err.message }));
276
+ if (data?.error)
277
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
278
+ return { content: [{ type: "text", text: `🗑️ Memory deleted: \`${parsed.data.id}\`` }] };
279
+ }
280
+ case "memory_insight": {
281
+ const parsed = InsightSchema.safeParse(args);
282
+ if (!parsed.success)
283
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
284
+ const { topic, depth = "standard" } = parsed.data;
285
+ const memLimit = depth === "deep" ? 15 : depth === "quick" ? 5 : 8;
286
+ // Pull from semantic memory and vault in parallel
287
+ const [memResults, vaultData] = await Promise.all([
288
+ searchSupermemory(topic, memLimit),
289
+ (0, convex_js_1.callConvex)(`/vault/search?q=${encodeURIComponent(topic)}&limit=6`, "GET", undefined, "memory_insight").catch(() => ({ results: [] })),
290
+ ]);
291
+ const vaultResults = vaultData.results ?? [];
292
+ const total = memResults.length + vaultResults.length;
293
+ if (!total) {
294
+ return {
295
+ content: [{
296
+ type: "text",
297
+ text: [
298
+ `🔮 **Intelligence Report: "${topic}"**`,
299
+ ``,
300
+ `No knowledge found yet.`,
301
+ ``,
302
+ `**Start building:**`,
303
+ `• \`swarm_research topic: "${topic}"\` — run deep research now`,
304
+ `• \`memory_add content: "..." \` — add a manual note`,
305
+ `• \`swarm_watch topic: "${topic}"\` — start monitoring`,
306
+ ].join("\n"),
307
+ }],
308
+ };
309
+ }
310
+ // Confidence tier
311
+ const confidence = total >= 10 ? "🟢 High" : total >= 4 ? "🟡 Medium" : "🔴 Low";
312
+ // Timeline from metadata timestamps
313
+ const timestamps = memResults.map(r => r.metadata?.addedAt).filter(Boolean);
314
+ const oldest = timestamps.length ? Math.min(...timestamps) : null;
315
+ const newest = timestamps.length ? Math.max(...timestamps) : null;
316
+ const daysSinceUpdate = newest ? Math.round((Date.now() - newest) / 86400000) : null;
317
+ // Knowledge summary lines
318
+ const memLines = memResults.slice(0, 6).map(r => {
319
+ const title = r.metadata?.title ?? r.content.slice(0, 70).replace(/\n/g, " ");
320
+ const score = r.score != null ? ` [${(r.score * 100).toFixed(0)}%]` : "";
321
+ return ` •${score} ${title}`;
322
+ });
323
+ const vaultLines = vaultResults.slice(0, 4).map((r) => ` • [vault/${r.type}] ${r.title} — v${r.version}`);
324
+ // Gap analysis
325
+ const gaps = [];
326
+ if (daysSinceUpdate !== null && daysSinceUpdate > 7) {
327
+ gaps.push(`Stale data — last update ${daysSinceUpdate} day${daysSinceUpdate !== 1 ? "s" : ""} ago`);
328
+ }
329
+ if (!vaultResults.some((r) => r.type === "research")) {
330
+ gaps.push("No formal research saved — only informal notes exist");
331
+ }
332
+ if (memResults.length < 3) {
333
+ gaps.push("Thin coverage — fewer than 3 semantic memories on this topic");
334
+ }
335
+ if (!vaultResults.some((r) => r.type === "execution")) {
336
+ gaps.push("No execution history — no trades or actions logged");
337
+ }
338
+ const lines = [
339
+ `🔮 **Intelligence Report: "${topic}"**`,
340
+ `Confidence: ${confidence} · ${memResults.length} semantic memories · ${vaultResults.length} vault entries`,
341
+ oldest ? `Coverage: ${new Date(oldest).toLocaleDateString("en-US")} – ${daysSinceUpdate === 0 ? "today" : daysSinceUpdate !== null ? `${daysSinceUpdate}d ago` : "unknown"}` : "",
342
+ ``,
343
+ `**What you know:**`,
344
+ ...memLines,
345
+ ...(vaultLines.length ? ["", "**Vault entries:**", ...vaultLines] : []),
346
+ ``,
347
+ ];
348
+ if (gaps.length) {
349
+ lines.push(`**⚠️ Knowledge gaps:**`);
350
+ gaps.forEach(g => lines.push(` • ${g}`));
351
+ lines.push("");
352
+ }
353
+ lines.push(`**Suggested actions:**`);
354
+ if (gaps.some(g => g.includes("research") || g.includes("Stale"))) {
355
+ lines.push(`• \`swarm_research topic: "${topic}"\` — refresh with deep research`);
356
+ }
357
+ if (gaps.some(g => g.includes("Stale"))) {
358
+ lines.push(`• \`trigger_agent agentId: "market-monitor" params: { token: "${topic.split(" ")[0].toUpperCase()}" }\``);
359
+ }
360
+ lines.push(`• \`memory_context topic: "${topic}"\` — inject full context into your next prompt`);
361
+ lines.push(`• \`swarm_watch topic: "${topic}"\` — monitor this topic continuously`);
362
+ return { content: [{ type: "text", text: lines.filter(l => l !== undefined).join("\n") }] };
247
363
  }
248
364
  default:
249
365
  return null;
@@ -179,6 +179,9 @@ async function handleMirosharkTool(name, args) {
179
179
  return { content: [{ type: "text", text: "simulation_id is required" }], isError: true };
180
180
  }
181
181
  const simId = a.simulation_id.trim();
182
+ if (!/^[a-zA-Z0-9_-]{5,100}$/.test(simId)) {
183
+ return { content: [{ type: "text", text: "Invalid simulation_id format." }], isError: true };
184
+ }
182
185
  try {
183
186
  // Check run status first
184
187
  const runStatus = await miroJson(`/miroshark/api/simulation/${simId}/run-status`, "GET").catch(() => ({ runner_status: "idle" }));
@@ -273,15 +276,20 @@ async function handleMirosharkTool(name, args) {
273
276
  // brief stays empty — non-critical
274
277
  }
275
278
  }
276
- // Auto-save to vault if key is available
277
- const savedToVault = false;
279
+ // Auto-save to vault if brief was generated
280
+ let savedToVault = false;
278
281
  if (brief) {
279
282
  try {
280
283
  await (0, convex_js_1.callConvex)("/vault/save", "POST", {
284
+ type: "research",
285
+ title: `MiroShark: ${a.scenario?.slice(0, 80) ?? simId}`,
286
+ content: brief,
281
287
  key: `miroshark-${simId.slice(0, 8)}`,
282
- value: brief,
288
+ agentId: "miroshark",
283
289
  tags: ["miroshark", "simulation", "research"],
290
+ commitMsg: "miroshark auto-save",
284
291
  }, "vault_save");
292
+ savedToVault = true;
285
293
  }
286
294
  catch {
287
295
  // non-critical
@@ -299,7 +307,7 @@ async function handleMirosharkTool(name, args) {
299
307
  lines.push(`**Agent Feed** (${Math.min(actions.length, 20)} of ${totalActions}):`);
300
308
  lines.push(...feed);
301
309
  }
302
- if (brief) {
310
+ if (savedToVault) {
303
311
  lines.push("", `_Findings auto-saved to vault as \`miroshark-${simId.slice(0, 8)}\`_`);
304
312
  }
305
313
  return { content: [{ type: "text", text: lines.join("\n") }] };
@@ -326,6 +334,9 @@ async function handleMirosharkTool(name, args) {
326
334
  return { content: [{ type: "text", text: "simulation_id is required" }], isError: true };
327
335
  }
328
336
  const simId = a.simulation_id.trim();
337
+ if (!/^[a-zA-Z0-9_-]{5,100}$/.test(simId)) {
338
+ return { content: [{ type: "text", text: "Invalid simulation_id format." }], isError: true };
339
+ }
329
340
  try {
330
341
  await miroJson(`/miroshark/api/simulation/${simId}/stop`, "POST", {});
331
342
  return { content: [{ type: "text", text: `⏹️ Simulation \`${simId}\` stopped.` }] };
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OS_TOOLS = void 0;
4
+ exports.handleOsTool = handleOsTool;
5
+ const zod_1 = require("zod");
6
+ const convex_js_1 = require("../convex.js");
7
+ const market_js_1 = require("./market.js");
8
+ const memory_js_1 = require("./memory.js");
9
+ exports.OS_TOOLS = [
10
+ {
11
+ name: "noel_status",
12
+ description: "Full system dashboard for the Noelclaw AI OS — memory usage, swarm health, active automations, " +
13
+ "recent vault research, and execution scores. Like `htop` but for your AI operating system. " +
14
+ "Run this to get a complete picture of what's running and what your OS currently knows.",
15
+ inputSchema: { type: "object", properties: {}, required: [] },
16
+ },
17
+ {
18
+ name: "noel_boot",
19
+ description: "Boot sequence for the Noelclaw AI OS — starts the swarm, loads live market prices, checks active automations, " +
20
+ "and returns a unified briefing. One command to wake up the entire operating system. " +
21
+ "Run this first to prime the system before any trading or research session.",
22
+ inputSchema: {
23
+ type: "object",
24
+ properties: {
25
+ focus: {
26
+ type: "string",
27
+ description: "Optional: token or topic to focus today's session on (e.g. 'ETH', 'Base ecosystem')",
28
+ },
29
+ },
30
+ required: [],
31
+ },
32
+ },
33
+ {
34
+ name: "noel_shutdown",
35
+ description: "Clean shutdown of the Noelclaw AI OS — stops the swarm, saves a session summary to vault, and returns a final briefing. " +
36
+ "Run at the end of a trading or research session to persist all findings before signing off.",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: {
40
+ note: {
41
+ type: "string",
42
+ description: "Optional: a note to save with the session summary (e.g. 'Closed ETH position, watching BTC')",
43
+ },
44
+ },
45
+ required: [],
46
+ },
47
+ },
48
+ ];
49
+ const BootSchema = zod_1.z.object({ focus: zod_1.z.string().optional() });
50
+ const ShutdownSchema = zod_1.z.object({ note: zod_1.z.string().max(500).optional() });
51
+ async function handleOsTool(name, args) {
52
+ switch (name) {
53
+ case "noel_status": {
54
+ const [memRes, swarmRes, autoRes, vaultRes, scoresRes] = await Promise.allSettled([
55
+ (0, convex_js_1.callConvex)("/memory/profile", "GET"),
56
+ (0, convex_js_1.callConvex)("/swarm/status", "GET", undefined, "get_swarm_status"),
57
+ (0, convex_js_1.callConvex)("/automations/list", "GET", undefined, "list_automations"),
58
+ (0, convex_js_1.callConvex)("/vault/list?type=research&limit=5", "GET", undefined, "noel_status"),
59
+ (0, convex_js_1.callConvex)("/swarm/scores", "GET", undefined, "get_execution_scores"),
60
+ ]);
61
+ const mem = memRes.status === "fulfilled" ? memRes.value : null;
62
+ const swarm = swarmRes.status === "fulfilled" ? swarmRes.value : null;
63
+ const autos = autoRes.status === "fulfilled" ? autoRes.value : null;
64
+ const vault = vaultRes.status === "fulfilled" ? vaultRes.value : null;
65
+ const scores = scoresRes.status === "fulfilled" ? scoresRes.value : null;
66
+ const automations = autos?.automations ?? [];
67
+ const activeAutos = automations.filter((a) => a.status === "active");
68
+ const vaultEntries = vault?.entries ?? [];
69
+ const allScores = (scores?.scores ?? []).sort((a, b) => b.lastScore - a.lastScore);
70
+ const topScore = allScores[0];
71
+ const swarmActive = swarm?.active ?? false;
72
+ const memTotal = mem?.total ?? 0;
73
+ const memStatus = mem?.status ?? "unknown";
74
+ const lines = [
75
+ `**Noelclaw AI OS — System Status**`,
76
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
77
+ ``,
78
+ `🧠 **Memory** ${memStatus === "ok" ? "✅" : "⚠️"} ${memTotal} memories · Space: ${mem?.space ?? "—"}`,
79
+ `🤖 **Swarm** ${swarmActive ? "✅ Active" : "⏹️ Offline"} · ${swarm?.memory?.length ?? 0} shared memory entries`,
80
+ `⚡ **Automations** ${activeAutos.length} active of ${automations.length} total`,
81
+ allScores.length
82
+ ? `📊 **Top Skill** ${topScore.skillName} — ${((topScore.lastScore ?? 0) * 100).toFixed(0)}% · ${topScore.successCount}W/${topScore.failCount}L`
83
+ : `📊 **Skills** No execution history yet`,
84
+ ``,
85
+ ];
86
+ if (activeAutos.length > 0) {
87
+ lines.push(`**Active Automations:**`);
88
+ for (const a of activeAutos.slice(0, 5)) {
89
+ const next = a.nextRunAt ? ` · next ${new Date(a.nextRunAt).toUTCString()}` : "";
90
+ lines.push(` • ${a.name} — ${a.triggerType}${next}`);
91
+ }
92
+ lines.push("");
93
+ }
94
+ if (vaultEntries.length > 0) {
95
+ lines.push(`**Recent Research:**`);
96
+ for (const e of vaultEntries) {
97
+ lines.push(` • [${e.agentId ?? "vault"}] ${e.title}`);
98
+ }
99
+ lines.push("");
100
+ }
101
+ lines.push(swarmActive
102
+ ? `💡 Run \`swarm_pulse\` for a live snapshot from all agents.`
103
+ : `💡 Run \`noel_boot\` to wake up the full system.`);
104
+ return { content: [{ type: "text", text: lines.join("\n") }] };
105
+ }
106
+ case "noel_boot": {
107
+ const parsed = BootSchema.safeParse(args ?? {});
108
+ if (!parsed.success)
109
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
110
+ const { focus } = parsed.data;
111
+ const [swarmRes, marketRes, autoRes, memRes, focusRes] = await Promise.allSettled([
112
+ (0, convex_js_1.callConvex)("/swarm/start", "POST", {}, "start_swarm"),
113
+ (0, market_js_1.fetchMarketSnapshot)(),
114
+ (0, convex_js_1.callConvex)("/automations/list", "GET", undefined, "list_automations"),
115
+ (0, convex_js_1.callConvex)("/memory/profile", "GET"),
116
+ focus ? (0, memory_js_1.searchSupermemory)(focus, 4) : Promise.resolve([]),
117
+ ]);
118
+ const swarm = swarmRes.status === "fulfilled" ? swarmRes.value : null;
119
+ const market = marketRes.status === "fulfilled" ? marketRes.value : null;
120
+ const autos = autoRes.status === "fulfilled" ? autoRes.value : null;
121
+ const mem = memRes.status === "fulfilled" ? memRes.value : null;
122
+ const focusMem = focusRes.status === "fulfilled" ? focusRes.value : [];
123
+ const automations = autos?.automations ?? [];
124
+ const activeAutos = automations.filter((a) => a.status === "active");
125
+ const memTotal = mem?.total ?? 0;
126
+ const p = (n) => `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
127
+ const lines = [
128
+ `🚀 **Noelclaw AI OS — Boot Complete**`,
129
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
130
+ `${new Date().toUTCString()}`,
131
+ ``,
132
+ `**Subsystems:**`,
133
+ ` 🤖 Swarm ${swarm?.success ? `✅ Online · session ${swarm.sessionId ?? "active"}` : "⚠️ Could not start"}`,
134
+ ` 🧠 Memory ✅ ${memTotal} memories ready`,
135
+ ` ⚡ Automations ${activeAutos.length} active of ${automations.length}`,
136
+ ``,
137
+ ];
138
+ if (market) {
139
+ lines.push(`**Market Snapshot:**`);
140
+ lines.push(` BTC ${p(market.btc)} · ETH ${p(market.eth)} · SOL ${p(market.sol)}`);
141
+ lines.push("");
142
+ }
143
+ if (activeAutos.length > 0) {
144
+ lines.push(`**Active Automations:**`);
145
+ for (const a of activeAutos.slice(0, 5)) {
146
+ const next = a.nextRunAt ? ` · runs ${new Date(a.nextRunAt).toUTCString()}` : "";
147
+ lines.push(` • ${a.name}${next}`);
148
+ }
149
+ lines.push("");
150
+ }
151
+ if (focus && focusMem.length > 0) {
152
+ lines.push(`**Memory context for "${focus}" (${focusMem.length} items):**`);
153
+ for (const r of focusMem) {
154
+ const title = r.metadata?.title ?? r.content.slice(0, 70).replace(/\n/g, " ");
155
+ lines.push(` • ${title}`);
156
+ }
157
+ lines.push("");
158
+ }
159
+ lines.push(`**Quick actions:**`);
160
+ lines.push(` • \`swarm_pulse\` — live readings from all agents`);
161
+ if (focus) {
162
+ lines.push(` • \`swarm_research topic: "${focus}"\` — deep research session`);
163
+ lines.push(` • \`memory_insight topic: "${focus}"\` — full intelligence report`);
164
+ }
165
+ else {
166
+ lines.push(` • \`swarm_research topic: "BTC"\` — start morning research`);
167
+ lines.push(` • \`noel_status\` — full system dashboard`);
168
+ }
169
+ return { content: [{ type: "text", text: lines.join("\n") }] };
170
+ }
171
+ case "noel_shutdown": {
172
+ const parsed = ShutdownSchema.safeParse(args ?? {});
173
+ if (!parsed.success)
174
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
175
+ const { note } = parsed.data;
176
+ const [swarmRes, vaultRes] = await Promise.allSettled([
177
+ (0, convex_js_1.callConvex)("/swarm/stop", "POST", {}, "stop_swarm"),
178
+ (0, convex_js_1.callConvex)("/vault/list?type=research&limit=5", "GET", undefined, "noel_shutdown"),
179
+ ]);
180
+ const swarm = swarmRes.status === "fulfilled" ? swarmRes.value : null;
181
+ const vault = vaultRes.status === "fulfilled" ? vaultRes.value : null;
182
+ const vaultEntries = vault?.entries ?? [];
183
+ const now = new Date();
184
+ const sessionKey = `session/shutdown-${now.toISOString().slice(0, 10)}-${now.getHours()}h`;
185
+ const summaryContent = [
186
+ `# Session Shutdown — ${now.toUTCString()}`,
187
+ note ? `\nNote: ${note}` : "",
188
+ `\n## Recent Research`,
189
+ ...vaultEntries.map((e) => `• [${e.agentId ?? "vault"}] ${e.title}`),
190
+ ].filter(Boolean).join("\n");
191
+ await (0, convex_js_1.callConvex)("/vault/save", "POST", {
192
+ type: "memory",
193
+ title: `Session: ${now.toLocaleDateString("en-US")}${note ? ` — ${note.slice(0, 50)}` : ""}`,
194
+ content: summaryContent,
195
+ key: sessionKey,
196
+ agentId: "os",
197
+ tags: ["session", "shutdown"],
198
+ commitMsg: "noel_shutdown session save",
199
+ }, "noel_shutdown").catch(() => { });
200
+ const lines = [
201
+ `⏹️ **Noelclaw AI OS — Shutdown**`,
202
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
203
+ `${now.toUTCString()}`,
204
+ ``,
205
+ `🤖 Swarm ${swarm?.success ? "✅ Stopped" : "⚠️ Already offline"}`,
206
+ `💾 Session ✅ Saved to vault: \`${sessionKey}\``,
207
+ note ? `📝 Note ${note}` : "",
208
+ ``,
209
+ ];
210
+ if (vaultEntries.length > 0) {
211
+ lines.push(`**Session research (${vaultEntries.length} entries):**`);
212
+ for (const e of vaultEntries) {
213
+ lines.push(` • [${e.agentId ?? "vault"}] ${e.title}`);
214
+ }
215
+ lines.push("");
216
+ }
217
+ lines.push(`Run \`noel_boot\` to start a new session.`);
218
+ return { content: [{ type: "text", text: lines.filter(l => l !== undefined && l !== "").join("\n") }] };
219
+ }
220
+ default:
221
+ return null;
222
+ }
223
+ }