@finchagentic/mcp 4.0.0 → 4.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 CHANGED
@@ -4,7 +4,6 @@ exports.PaymentRequiredError = exports.CONVEX_SITE = void 0;
4
4
  exports.buildPaymentHeader = buildPaymentHeader;
5
5
  exports.callConvex = callConvex;
6
6
  exports.callConvexRaw = callConvexRaw;
7
- exports.notifyTelegram = notifyTelegram;
8
7
  const wallet_js_1 = require("./wallet.js");
9
8
  const config_js_1 = require("./config.js");
10
9
  exports.CONVEX_SITE = process.env.FINCH_CONVEX_URL ?? "https://befitting-porcupine-276.convex.site";
@@ -66,10 +65,6 @@ async function callConvex(path, method, body, toolName = "unknown", timeoutMs =
66
65
  headers["X-User-Grok-Key"] = process.env.GROK_API_KEY;
67
66
  if (process.env.BANKR_API_KEY)
68
67
  headers["X-User-Bankr-Key"] = process.env.BANKR_API_KEY;
69
- if (process.env.TELEGRAM_BOT_TOKEN)
70
- headers["X-User-Telegram-Token"] = process.env.TELEGRAM_BOT_TOKEN;
71
- if (process.env.TELEGRAM_CHAT_ID)
72
- headers["X-User-Telegram-Chat"] = process.env.TELEGRAM_CHAT_ID;
73
68
  let lastError = null;
74
69
  for (let attempt = 0; attempt < RETRY_DELAYS.length; attempt++) {
75
70
  if (attempt > 0) {
@@ -95,7 +90,13 @@ async function callConvex(path, method, body, toolName = "unknown", timeoutMs =
95
90
  `${b.alternative ? `Alternative: ${b.alternative}` : ""}`);
96
91
  }
97
92
  if (RETRY_STATUSES.has(res.status) && attempt < RETRY_DELAYS.length) {
98
- lastError = new Error(`Finch API error: ${res.status}`);
93
+ // Capture the actual body so a deterministic error (e.g. "unknown
94
+ // token") that happens to come back on a 500 still surfaces its real
95
+ // message if retries exhaust - previously this discarded the body
96
+ // entirely and threw a bare "Finch API error: 500", hiding exactly the
97
+ // information the caller needed to fix the request.
98
+ const bodyText = await res.text().catch(() => "");
99
+ lastError = new Error(`Finch API error ${res.status}: ${bodyText.slice(0, 300) || "(no body)"}`);
99
100
  continue;
100
101
  }
101
102
  if (!res.ok)
@@ -112,10 +113,14 @@ async function callConvexRaw(path, toolName = "unknown", timeoutMs = 60000) {
112
113
  const headers = {};
113
114
  const apiKey = process.env.FINCH_API_KEY;
114
115
  const sessionToken = (0, config_js_1.getSavedToken)();
115
- const authHeader = apiKey
116
- ? `Bearer ${apiKey}`
117
- : sessionToken
118
- ? `Bearer ${sessionToken}`
116
+ // Same precedence as callConvex() above - prefer session token over API
117
+ // key. These two functions previously disagreed (this one checked apiKey
118
+ // first), so the same env/config could pick a different credential
119
+ // depending on which helper a tool happened to call.
120
+ const authHeader = sessionToken
121
+ ? `Bearer ${sessionToken}`
122
+ : apiKey
123
+ ? `Bearer ${apiKey}`
119
124
  : null;
120
125
  if (authHeader) {
121
126
  headers["Authorization"] = authHeader;
@@ -141,11 +146,3 @@ async function callConvexRaw(path, toolName = "unknown", timeoutMs = 60000) {
141
146
  throw new Error(`Finch API error: ${res.status}`);
142
147
  return res.text();
143
148
  }
144
- async function notifyTelegram(userId, message) {
145
- try {
146
- return await callConvex("/user/telegram/notify", "POST", { userId, message }, "set_telegram");
147
- }
148
- catch (error) {
149
- return { sent: false, reason: error.message ?? String(error) };
150
- }
151
- }
package/dist/index.js CHANGED
@@ -108,24 +108,23 @@ async function main() {
108
108
  (0, config_js_1.hydrateEnvFromConfig)();
109
109
  process.stderr.write(BANNER);
110
110
  const CAT_RULES = [
111
- { label: "Market", match: n => /^(get_market_data|get_token_data|compare_tokens|market_overview|token_history)$/.test(n) },
111
+ { label: "Market", match: n => /^(get_market_data|get_token_data|compare_tokens|market_overview|token_history|get_base_token_data|stock_fundamentals|stock_insider|stock_events)$/.test(n) },
112
112
  { label: "Insight", match: n => /^(ask_finch|market_thesis|trade_plan)$/.test(n) },
113
113
  { label: "DeFi", match: n => n === "get_defi_yields" },
114
114
  { label: "Base MCP", match: n => n.startsWith("base_mcp_") },
115
115
  { label: "RH MCP", match: n => n.startsWith("rh_") },
116
116
  { label: "Automation", match: n => /^(create_automation|list_automations|pause_automation|delete_automation|get_automation_runs|run_automation)$/.test(n) },
117
- { label: "Framework", match: n => /^(list_playbooks|run_playbook|get_finch_ledger)$/.test(n) },
118
117
  { label: "Vault", match: n => n.startsWith("vault_") },
119
118
  { label: "Wallet", match: n => /^(get_wallet_address|get_wallet_balance|wallet_sign_message)$/.test(n) },
119
+ { label: "Staking", match: n => /^(stake_finch|unstake_finch|stake_finch_status|stake_auto_restake)$/.test(n) },
120
120
  { label: "MiroShark", match: n => n.startsWith("miroshark_") },
121
121
  { label: "Scanner", match: n => /^(scan_market|score_token|check_token)$/.test(n) },
122
- { label: "Agents", match: n => n.startsWith("agent_") || n === "list_agents" || n === "hire_agent" },
122
+ { label: "Agents", match: n => n.startsWith("agent_") },
123
123
  { label: "Coder", match: n => n === "audit_contract" },
124
- { label: "Base", match: n => /^(query_vaults|list_markets|prepare_deposit|chain_stats)$/.test(n) },
125
124
  { label: "Memory", match: n => n.startsWith("memory_") },
126
125
  { label: "OS", match: n => /^(finch_status|finch_diagnostics|finch_shell_chat)$/.test(n) },
127
126
  { label: "Research", match: n => /^(web_scrape|web_search|deep_research|research_compare|research_chain)$/.test(n) },
128
- { label: "Monitor", match: n => /^(schedule_research|create_monitor|list_monitors|cancel_monitor)$/.test(n) },
127
+ { label: "Monitor", match: n => /^(schedule_research|list_monitors|cancel_monitor)$/.test(n) },
129
128
  { label: "GitHub", match: n => n.startsWith("github_") },
130
129
  { label: "Chronicle", match: n => n.startsWith("chronicle_") },
131
130
  { label: "Packets", match: n => n.startsWith("packet_") },
package/dist/llm.js CHANGED
@@ -4,13 +4,10 @@ exports.hasDirectLLMKey = hasDirectLLMKey;
4
4
  exports.grokLiveSearchHits = grokLiveSearchHits;
5
5
  exports.isGrokActive = isGrokActive;
6
6
  exports.callLLM = callLLM;
7
- const wallet_js_1 = require("./wallet.js");
8
- const config_js_1 = require("./config.js");
9
7
  const ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
10
8
  const BANKR_URL = "https://llm.bankr.bot/v1/chat/completions";
11
9
  const GROK_URL = "https://api.x.ai/v1/chat/completions";
12
10
  const OPENAI_URL = "https://api.openai.com/v1/chat/completions";
13
- const CONVEX_SITE = process.env.FINCH_CONVEX_URL ?? "https://befitting-porcupine-276.convex.site";
14
11
  /**
15
12
  * Returns true if Grok is the currently active LLM provider, based on env.
16
13
  * Useful for tools that want to conditionally enable Grok-specific features
@@ -145,51 +142,18 @@ async function callLLM(systemPrompt, userPrompt, maxTokens = 1024, history = [],
145
142
  return callOpenAI(openaiKey, systemPrompt, userPrompt, maxTokens, history, timeoutMs, options.model);
146
143
  if (grokKey)
147
144
  return callGrok(grokKey, systemPrompt, userPrompt, maxTokens, history, timeoutMs, options.liveSearch, options.model);
148
- // Fallback: route through Convex backend - owner covers cost
149
- return callViaConvex(systemPrompt, userPrompt, history, timeoutMs);
150
- }
151
- async function callViaConvex(systemPrompt, userPrompt, history, timeoutMs) {
152
- const fullQuestion = systemPrompt
153
- ? `[System: ${systemPrompt}]\n\n${userPrompt}`
154
- : userPrompt;
155
- const headers = { "Content-Type": "application/json" };
156
- const apiKey = process.env.FINCH_API_KEY;
157
- let sessionToken = process.env.FINCH_SESSION_TOKEN;
158
- // Read from config file this is the resolved session token after login
159
- try {
160
- const cfg = (0, config_js_1.readConfig)();
161
- if (cfg.sessionToken)
162
- sessionToken = cfg.sessionToken;
163
- }
164
- catch { /* ignore */ }
165
- // Prefer session token (resolved by backend) over API key for /mcp/chat
166
- if (sessionToken) {
167
- headers["Authorization"] = `Bearer ${sessionToken}`;
168
- }
169
- else if (apiKey) {
170
- headers["Authorization"] = `Bearer ${apiKey}`;
171
- }
172
- else {
173
- try {
174
- const { address, signature, timestamp } = await (0, wallet_js_1.signRequest)("ask_finch");
175
- headers["X-Wallet-Address"] = address;
176
- headers["X-Wallet-Signature"] = signature;
177
- headers["X-Wallet-Timestamp"] = timestamp;
178
- }
179
- catch { /* proceed without wallet auth */ }
180
- }
181
- const res = await fetch(`${CONVEX_SITE}/mcp/chat`, {
182
- method: "POST",
183
- headers,
184
- body: JSON.stringify({ question: fullQuestion, messages: history }),
185
- signal: AbortSignal.timeout(timeoutMs),
186
- });
187
- if (!res.ok) {
188
- const body = await res.text().catch(() => "");
189
- throw new Error(`LLM error ${res.status}: ${body.slice(0, 200)}`);
190
- }
191
- const data = await res.json();
192
- return data.answer ?? "";
145
+ // No provider key configured - BYOK is required. This tool needs its own
146
+ // multi-step reasoning (planning, synthesis, critique) that an MCP host's
147
+ // model cannot do on the tool's behalf, so there is no free path: bring
148
+ // your own key or don't use tools that need one (ask_finch, deep_research,
149
+ // market_thesis, trade_plan, scheduled agent runs). Fails clearly instead
150
+ // of silently billing the Finch deployment owner for every anonymous
151
+ // install of this package.
152
+ throw new Error("No LLM provider configured. This tool needs its own key to do multi-step " +
153
+ "reasoning server-side - set one of BANKR_API_KEY, ANTHROPIC_API_KEY, " +
154
+ "OPENAI_API_KEY, or GROK_API_KEY as an environment variable (see the " +
155
+ "Configuration section of the README), then retry. Most other Finch tools " +
156
+ "don't need this - only ones that do their own internal LLM reasoning do.");
193
157
  }
194
158
  async function callAnthropic(apiKey, systemPrompt, userPrompt, maxTokens, history, timeoutMs, modelOverride) {
195
159
  const messages = [...history, { role: "user", content: userPrompt }];
@@ -263,7 +227,18 @@ async function callOpenAI(apiKey, systemPrompt, userPrompt, maxTokens, history,
263
227
  throw new Error(`OpenAI error ${res.status}: ${body.slice(0, 200)}`);
264
228
  }
265
229
  const data = await res.json();
266
- return data.choices?.[0]?.message?.content ?? "";
230
+ // Most OpenAI-compatible gateways put `choices` at the top level, but
231
+ // OPENAI_BASE_URL can point at anything that speaks this API shape - at
232
+ // least one (9Router) wraps the whole payload in `{ data: {...}, success:
233
+ // true }` for some models (confirmed live: routers9/glm5.2 returns this
234
+ // wrapper while routers9/tencent/hy3 on the SAME endpoint/key returns
235
+ // top-level choices). Without this fallback the call silently returns ""
236
+ // instead of throwing - worse than an error, since every caller of
237
+ // callLLM() just gets an empty synthesis with no indication anything failed.
238
+ const content = data.choices?.[0]?.message?.content ?? data.data?.choices?.[0]?.message?.content;
239
+ if (!content)
240
+ throw new Error(`OpenAI-compatible endpoint returned no content (model: ${model})`);
241
+ return content;
267
242
  }
268
243
  async function callGrok(apiKey, systemPrompt, userPrompt, maxTokens, history, timeoutMs, liveSearch, modelOverride) {
269
244
  const model = modelOverride ?? process.env.FINCH_MODEL ?? process.env.GROK_MODEL ?? "grok-4-fast-reasoning";
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getLocalMemoryFileConfig = getLocalMemoryFileConfig;
37
+ exports.fileMemoryAdd = fileMemoryAdd;
38
+ exports.fileMemoryDeleteByVaultKey = fileMemoryDeleteByVaultKey;
39
+ exports.fileMemorySearch = fileMemorySearch;
40
+ exports.fileMemoryList = fileMemoryList;
41
+ exports.fileMemoryDelete = fileMemoryDelete;
42
+ exports.fileMemoryProfile = fileMemoryProfile;
43
+ const fs = __importStar(require("fs"));
44
+ const os = __importStar(require("os"));
45
+ const path = __importStar(require("path"));
46
+ const crypto = __importStar(require("crypto"));
47
+ function getLocalMemoryFileConfig() {
48
+ return { dir: path.join(os.homedir(), ".finch", "memory") };
49
+ }
50
+ function indexPath(cfg) {
51
+ return path.join(cfg.dir, "index.json");
52
+ }
53
+ function readIndex(cfg) {
54
+ try {
55
+ const raw = fs.readFileSync(indexPath(cfg), "utf8");
56
+ const parsed = JSON.parse(raw);
57
+ return { memories: parsed.memories ?? [] };
58
+ }
59
+ catch {
60
+ return { memories: [] };
61
+ }
62
+ }
63
+ // Atomic write: temp file + rename, matching local-vault.ts's pattern so a
64
+ // crash mid-write can't corrupt the index every memory depends on.
65
+ function writeIndex(cfg, idx) {
66
+ fs.mkdirSync(cfg.dir, { recursive: true });
67
+ const tmp = indexPath(cfg) + `.tmp-${process.pid}`;
68
+ fs.writeFileSync(tmp, JSON.stringify(idx, null, 2), "utf8");
69
+ fs.renameSync(tmp, indexPath(cfg));
70
+ }
71
+ function toResult(m) {
72
+ return {
73
+ id: m.id,
74
+ content: m.content,
75
+ metadata: { title: m.title, tags: m.tags, source: m.source, sourceUrl: m.sourceUrl, contentHash: m.contentHash, pinned: m.pinned, addedAt: m.addedAt, vaultKey: m.vaultKey },
76
+ };
77
+ }
78
+ function fileMemoryAdd(cfg, content, metadata, sourceUrl) {
79
+ const idx = readIndex(cfg);
80
+ const id = crypto.randomBytes(8).toString("hex");
81
+ const row = {
82
+ id,
83
+ content,
84
+ title: metadata.title,
85
+ tags: metadata.tags,
86
+ source: metadata.source,
87
+ sourceUrl,
88
+ contentHash: metadata.contentHash ?? "",
89
+ pinned: metadata.pinned,
90
+ addedAt: metadata.addedAt ?? Date.now(),
91
+ vaultKey: metadata.vaultKey,
92
+ };
93
+ idx.memories.push(row);
94
+ writeIndex(cfg, idx);
95
+ return { id };
96
+ }
97
+ /** Remove every memory row mirroring a given vault entry - called by
98
+ * vault_delete so its "PERMANENT... cannot be undone" claim is actually true,
99
+ * instead of leaving the content fully recoverable via memory_search. */
100
+ function fileMemoryDeleteByVaultKey(cfg, vaultKey) {
101
+ const idx = readIndex(cfg);
102
+ const next = idx.memories.filter((m) => m.vaultKey !== vaultKey);
103
+ const removed = idx.memories.length - next.length;
104
+ if (removed > 0)
105
+ writeIndex(cfg, { memories: next });
106
+ return removed;
107
+ }
108
+ function fileMemorySearch(cfg, query, limit) {
109
+ const idx = readIndex(cfg);
110
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
111
+ const scored = [];
112
+ for (const m of idx.memories) {
113
+ const hay = `${m.title ?? ""}\n${(m.tags ?? []).join(" ")}\n${m.content}`.toLowerCase();
114
+ let score = 0;
115
+ for (const t of terms) {
116
+ if ((m.title ?? "").toLowerCase().includes(t))
117
+ score += 3;
118
+ if ((m.tags ?? []).some((tag) => tag.toLowerCase().includes(t)))
119
+ score += 2;
120
+ score += Math.min(hay.split(t).length - 1, 5);
121
+ }
122
+ if (score > 0)
123
+ scored.push({ m, score });
124
+ }
125
+ scored.sort((a, b) => b.score - a.score);
126
+ const top = scored.slice(0, limit);
127
+ const maxScore = top[0]?.score || 1;
128
+ return top.map(({ m, score }) => ({ ...toResult(m), score: score / maxScore }));
129
+ }
130
+ function fileMemoryList(cfg, limit, tag) {
131
+ const idx = readIndex(cfg);
132
+ let rows = [...idx.memories].sort((a, b) => b.addedAt - a.addedAt);
133
+ if (tag)
134
+ rows = rows.filter((m) => (m.tags ?? []).includes(tag));
135
+ return rows.slice(0, limit).map(toResult);
136
+ }
137
+ function fileMemoryDelete(cfg, id) {
138
+ const idx = readIndex(cfg);
139
+ const next = idx.memories.filter((m) => m.id !== id);
140
+ if (next.length === idx.memories.length)
141
+ throw new Error(`Memory not found: ${id}`);
142
+ writeIndex(cfg, { memories: next });
143
+ }
144
+ function fileMemoryProfile(cfg) {
145
+ const idx = readIndex(cfg);
146
+ return { total: idx.memories.length, status: "ok", space: "local" };
147
+ }
@@ -6,20 +6,25 @@ exports.localMemoryAdd = localMemoryAdd;
6
6
  exports.localMemorySearch = localMemorySearch;
7
7
  exports.localMemoryList = localMemoryList;
8
8
  exports.localMemoryDelete = localMemoryDelete;
9
+ exports.localMemoryDeleteByVaultKey = localMemoryDeleteByVaultKey;
9
10
  exports.localMemoryProfile = localMemoryProfile;
10
11
  const config_js_1 = require("./config.js");
12
+ const local_memory_file_js_1 = require("./local-memory-file.js");
11
13
  const DEFAULT_URL = "http://localhost:6767";
12
14
  const REACHABILITY_TIMEOUT_MS = 1500;
13
15
  const CALL_TIMEOUT_MS = 15000;
14
- // Returns config only when the user has explicitly opted into local memory
15
- // (memoryBackend: "local") and a key is present. Callers should still treat
16
- // a null return as "use Convex" - this never throws.
16
+ // Returns config only when the user has explicitly opted into a local
17
+ // backend. Callers should still treat a null return as "use Convex" - this
18
+ // never throws.
17
19
  function getLocalMemoryConfig() {
18
20
  try {
19
21
  const cfg = (0, config_js_1.readConfig)();
20
- if (cfg.memoryBackend !== "local" || !cfg.supermemoryApiKey)
21
- return null;
22
- return { url: cfg.supermemoryUrl ?? DEFAULT_URL, apiKey: cfg.supermemoryApiKey };
22
+ if (cfg.memoryBackend === "local-file")
23
+ return { kind: "file", ...(0, local_memory_file_js_1.getLocalMemoryFileConfig)() };
24
+ if (cfg.memoryBackend === "local" && cfg.supermemoryApiKey) {
25
+ return { kind: "supermemory", url: cfg.supermemoryUrl ?? DEFAULT_URL, apiKey: cfg.supermemoryApiKey };
26
+ }
27
+ return null;
23
28
  }
24
29
  catch {
25
30
  return null;
@@ -28,7 +33,10 @@ function getLocalMemoryConfig() {
28
33
  // Requires a genuine 2xx, not just "something answered" - a stale/wrong
29
34
  // supermemoryApiKey returns 401 (server is up, but every real memory call
30
35
  // will fail the same way), and that must show as unhealthy, not healthy.
36
+ // The file backend is always "reachable" - it's a directory, not a server.
31
37
  async function isLocalMemoryReachable(cfg) {
38
+ if (cfg.kind === "file")
39
+ return true;
32
40
  try {
33
41
  const res = await fetch(`${cfg.url}/v3/search`, {
34
42
  method: "POST",
@@ -43,6 +51,8 @@ async function isLocalMemoryReachable(cfg) {
43
51
  }
44
52
  }
45
53
  async function localMemoryAdd(cfg, content, metadata, sourceUrl) {
54
+ if (cfg.kind === "file")
55
+ return (0, local_memory_file_js_1.fileMemoryAdd)(cfg, content, metadata, sourceUrl);
46
56
  const res = await fetch(`${cfg.url}/v3/documents`, {
47
57
  method: "POST",
48
58
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${cfg.apiKey}` },
@@ -55,6 +65,8 @@ async function localMemoryAdd(cfg, content, metadata, sourceUrl) {
55
65
  return { id: data.id ?? data.documentId ?? "saved" };
56
66
  }
57
67
  async function localMemorySearch(cfg, query, limit) {
68
+ if (cfg.kind === "file")
69
+ return (0, local_memory_file_js_1.fileMemorySearch)(cfg, query, limit);
58
70
  const res = await fetch(`${cfg.url}/v3/search`, {
59
71
  method: "POST",
60
72
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${cfg.apiKey}` },
@@ -76,6 +88,8 @@ async function localMemorySearch(cfg, query, limit) {
76
88
  // server, so callers pass "*" and post-filter by tag client-side, matching
77
89
  // how the Convex-side /memory/list already does its own tag post-filter.
78
90
  async function localMemoryList(cfg, limit, tag) {
91
+ if (cfg.kind === "file")
92
+ return (0, local_memory_file_js_1.fileMemoryList)(cfg, limit, tag);
79
93
  const rows = await localMemorySearch(cfg, "*", Math.max(limit * (tag ? 3 : 1), limit));
80
94
  const filtered = tag ? rows.filter((r) => Array.isArray(r.metadata?.tags) && r.metadata.tags.includes(tag)) : rows;
81
95
  return filtered.slice(0, limit);
@@ -84,6 +98,10 @@ async function localMemoryList(cfg, limit, tag) {
84
98
  // time this was written - throws a distinct error so callers can surface a
85
99
  // clear "not supported locally yet" message instead of a generic failure.
86
100
  async function localMemoryDelete(cfg, id) {
101
+ if (cfg.kind === "file") {
102
+ (0, local_memory_file_js_1.fileMemoryDelete)(cfg, id);
103
+ return;
104
+ }
87
105
  const res = await fetch(`${cfg.url}/v3/documents/${encodeURIComponent(id)}`, {
88
106
  method: "DELETE",
89
107
  headers: { Authorization: `Bearer ${cfg.apiKey}` },
@@ -92,11 +110,26 @@ async function localMemoryDelete(cfg, id) {
92
110
  if (!res.ok)
93
111
  throw new Error(`local supermemory delete failed: HTTP ${res.status}`);
94
112
  }
95
- // No dedicated count endpoint is documented for the local server, so this
96
- // approximates via a capped wildcard search - accurate up to `SAMPLE_LIMIT`,
97
- // reported as a floor ("200+") beyond that rather than a false exact count.
113
+ /**
114
+ * Remove memory rows mirroring a deleted vault entry - called by vault_delete.
115
+ * "file" backend: direct filter+delete, exact. Self-hosted supermemory server:
116
+ * no documented "delete by metadata filter" endpoint, so this is a no-op that
117
+ * returns 0 rather than guessing at an unconfirmed API - vault_delete surfaces
118
+ * that count so a 0 on that backend doesn't get silently mistaken for success.
119
+ */
120
+ function localMemoryDeleteByVaultKey(cfg, vaultKey) {
121
+ if (cfg.kind === "file")
122
+ return (0, local_memory_file_js_1.fileMemoryDeleteByVaultKey)(cfg, vaultKey);
123
+ return 0;
124
+ }
125
+ // No dedicated count endpoint is documented for the local supermemory server,
126
+ // so that path approximates via a capped wildcard search - accurate up to
127
+ // `PROFILE_SAMPLE_LIMIT`, reported as a floor ("200+") beyond that rather
128
+ // than a false exact count. The file backend counts exactly.
98
129
  const PROFILE_SAMPLE_LIMIT = 200;
99
130
  async function localMemoryProfile(cfg) {
131
+ if (cfg.kind === "file")
132
+ return { ...(0, local_memory_file_js_1.fileMemoryProfile)(cfg), approximate: false };
100
133
  const rows = await localMemorySearch(cfg, "*", PROFILE_SAMPLE_LIMIT);
101
134
  return { total: rows.length, status: "ok", space: "local", approximate: rows.length >= PROFILE_SAMPLE_LIMIT };
102
135
  }
@@ -200,6 +200,30 @@ exports.OUTPUT_SCHEMAS = {
200
200
  required: ["input", "resolved"],
201
201
  },
202
202
  // ── SEC equities ────────────────────────────────────────────────────────────
203
+ stock_fundamentals: {
204
+ type: "object",
205
+ properties: {
206
+ ticker: { type: "string" },
207
+ companyName: string_null,
208
+ cik: string_null,
209
+ quote: {
210
+ type: ["object", "null"],
211
+ additionalProperties: true,
212
+ description: "{ price, currency, prevClose, prevDate } - null when a live quote wasn't available",
213
+ },
214
+ quarterly: {
215
+ type: "object",
216
+ additionalProperties: { type: "array", items: { type: "object", additionalProperties: true } },
217
+ description: "Per-concept arrays of { end, val, form, fy, fp } facts, most recent last",
218
+ },
219
+ annual: {
220
+ type: "object",
221
+ additionalProperties: { type: "array", items: { type: "object", additionalProperties: true } },
222
+ description: "Per-concept arrays of { end, val, form, fy, fp } facts, most recent last",
223
+ },
224
+ },
225
+ required: ["ticker", "quarterly", "annual"],
226
+ },
203
227
  stock_events: {
204
228
  type: "object",
205
229
  properties: {
@@ -415,6 +439,53 @@ exports.OUTPUT_SCHEMAS = {
415
439
  },
416
440
  required: ["count", "packets"],
417
441
  },
442
+ // ── vault ───────────────────────────────────────────────────────────────────
443
+ vault_list: {
444
+ type: "object",
445
+ properties: {
446
+ type: string_null,
447
+ count: { type: "number" },
448
+ entries: {
449
+ type: "array",
450
+ items: {
451
+ type: "object",
452
+ properties: {
453
+ key: { type: "string" },
454
+ title: string_null,
455
+ type: string_null,
456
+ version: { type: "number" },
457
+ size: number_null,
458
+ updatedAt: number_null,
459
+ isPinned: { type: "boolean" },
460
+ },
461
+ required: ["key"],
462
+ },
463
+ },
464
+ },
465
+ required: ["count", "entries"],
466
+ },
467
+ vault_search: {
468
+ type: "object",
469
+ properties: {
470
+ query: { type: "string" },
471
+ count: { type: "number" },
472
+ results: {
473
+ type: "array",
474
+ items: {
475
+ type: "object",
476
+ properties: {
477
+ key: { type: "string" },
478
+ title: string_null,
479
+ type: string_null,
480
+ score: number_null,
481
+ preview: string_null,
482
+ },
483
+ required: ["key"],
484
+ },
485
+ },
486
+ },
487
+ required: ["query", "count", "results"],
488
+ },
418
489
  // ── memory ──────────────────────────────────────────────────────────────────
419
490
  memory_search: {
420
491
  type: "object",
@@ -453,14 +524,6 @@ exports.OUTPUT_SCHEMAS = {
453
524
  required: ["count", "memories"],
454
525
  },
455
526
  // ── agents ──────────────────────────────────────────────────────────────────
456
- list_agents: {
457
- type: "object",
458
- properties: {
459
- count: { type: "number" },
460
- agents: { type: "array", items: { type: "object", additionalProperties: true } },
461
- },
462
- required: ["count", "agents"],
463
- },
464
527
  agent_ledger: {
465
528
  type: "object",
466
529
  properties: {
@@ -470,15 +533,6 @@ exports.OUTPUT_SCHEMAS = {
470
533
  },
471
534
  required: ["name", "count", "versions"],
472
535
  },
473
- agent_runs: {
474
- type: "object",
475
- properties: {
476
- name: { type: "string" },
477
- count: { type: "number" },
478
- runs: { type: "array", items: { type: "object", additionalProperties: true } },
479
- },
480
- required: ["name", "count", "runs"],
481
- },
482
536
  // ── scanner / orders / wallet ────────────────────────────────────────────────
483
537
  scan_market: {
484
538
  type: "object",
package/dist/resources.js CHANGED
@@ -50,7 +50,7 @@ async function listVaultResources(cursor) {
50
50
  try {
51
51
  const offset = decodeCursor(cursor);
52
52
  // Over-fetch by 1 so we can tell if there's another page without a count call.
53
- const data = await (0, convex_js_1.callConvex)(`/vault/list?limit=${PAGE_SIZE + 1}&offset=${offset}`, "GET", undefined, "list_resources");
53
+ const data = await (0, convex_js_1.callConvex)(`/vault/list?limit=${PAGE_SIZE + 1}&offset=${offset}`, "GET", undefined, "vault_list");
54
54
  const allEntries = data.entries ?? data.results ?? [];
55
55
  const hasMore = allEntries.length > PAGE_SIZE;
56
56
  const entries = hasMore ? allEntries.slice(0, PAGE_SIZE) : allEntries;
@@ -77,21 +77,16 @@ async function readVaultResource(uri) {
77
77
  throw new Error(`Unknown resource URI: ${uri}`);
78
78
  }
79
79
  const key = decodeURIComponent(uri.slice(URI_PREFIX.length));
80
- const data = await (0, convex_js_1.callConvex)(`/vault/entry?key=${encodeURIComponent(key)}`, "GET", undefined, "read_resource");
80
+ const data = await (0, convex_js_1.callConvex)(`/vault/entry?key=${encodeURIComponent(key)}`, "GET", undefined, "vault_read");
81
81
  if (data.error) {
82
82
  throw new Error(`Vault entry not found: ${key}`);
83
83
  }
84
- // Blob-backed entry - preview lives in `content`, real payload streams
85
- // from /vault/blob. Same flow vault_read uses internally.
86
- let body = data.content ?? "";
87
- if (data.contentFileId) {
88
- try {
89
- body = await (0, convex_js_1.callConvexRaw)(`/vault/blob?id=${encodeURIComponent(data.contentFileId)}`, "read_resource");
90
- }
91
- catch (err) {
92
- body = (data.content ?? "") + `\n\n_(could not load full blob: ${err.message})_`;
93
- }
94
- }
84
+ // No blob-storage tier exists on the backend (see app/convex/vault.ts) -
85
+ // oversized content is rejected at save time, so `contentFileId` never
86
+ // comes back here. A `/vault/blob` fallback used to live here but the route
87
+ // was never registered in http.ts either; removed as dead code rather than
88
+ // fixed against a storage tier that doesn't exist.
89
+ const body = data.content ?? "";
95
90
  const mime = mimeForContentType(data.contentType);
96
91
  // Only prepend a friendly header for markdown (the default) - JSON/code
97
92
  // payloads must remain valid in their own grammar so consuming tools can