@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/LICENSE +1 -1
- package/README.md +96 -249
- package/dist/agent-loop.js +139 -69
- package/dist/annotations.js +14 -13
- package/dist/cli.js +78 -21
- package/dist/config.js +16 -16
- package/dist/convex.js +15 -18
- package/dist/index.js +4 -5
- package/dist/llm.js +24 -49
- package/dist/local-memory-file.js +147 -0
- package/dist/local-memory.js +42 -9
- package/dist/output-schemas.js +71 -17
- package/dist/resources.js +8 -13
- package/dist/server.js +27 -13
- package/dist/token-gate.js +2 -2
- package/dist/tool-filter.js +13 -4
- package/dist/tools/agents.js +66 -394
- package/dist/tools/base-mcp.js +7 -19
- package/dist/tools/base.js +34 -20
- package/dist/tools/chronicle.js +4 -4
- package/dist/tools/deep-research.js +10 -5
- package/dist/tools/defi.js +36 -35
- package/dist/tools/equity.js +9 -1
- package/dist/tools/insight.js +25 -29
- package/dist/tools/memory.js +76 -108
- package/dist/tools/monitor.js +7 -7
- package/dist/tools/os.js +10 -6
- package/dist/tools/packets.js +5 -5
- package/dist/tools/research.js +2 -2
- package/dist/tools/rh-mcp.js +25 -2
- package/dist/tools/rh-orders.js +201 -123
- package/dist/tools/scanner.js +30 -0
- package/dist/tools/stake.js +329 -0
- package/dist/tools/vault.js +122 -42
- package/dist/wallet.js +212 -24
- package/package.json +9 -21
- package/dist/tools/framework.js +0 -150
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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_")
|
|
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|
|
|
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
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/local-memory.js
CHANGED
|
@@ -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
|
|
15
|
-
//
|
|
16
|
-
//
|
|
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
|
|
21
|
-
return
|
|
22
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
}
|
package/dist/output-schemas.js
CHANGED
|
@@ -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, "
|
|
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, "
|
|
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
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|