hvip-mcp-server 0.3.1 → 0.4.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/index.js CHANGED
@@ -26557,11 +26557,19 @@ async function request(method, path3, options = {}) {
26557
26557
  headers["OK-ACCESS-PASSPHRASE"] = options.auth.passphrase;
26558
26558
  if (options.auth.isDemo) headers["x-simulated-trading"] = "1";
26559
26559
  }
26560
- const res = await fetch(BASE + fullPath, {
26561
- method,
26562
- headers,
26563
- ...bodyStr ? { body: bodyStr } : {}
26564
- });
26560
+ const controller = new AbortController();
26561
+ const timer = setTimeout(() => controller.abort(), 3e4);
26562
+ let res;
26563
+ try {
26564
+ res = await fetch(BASE + fullPath, {
26565
+ method,
26566
+ headers,
26567
+ body: bodyStr || void 0,
26568
+ signal: controller.signal
26569
+ });
26570
+ } finally {
26571
+ clearTimeout(timer);
26572
+ }
26565
26573
  if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
26566
26574
  const json = await res.json();
26567
26575
  if (json.code !== "0") throw new Error(`OKX ${json.code}: ${json.msg ?? "unknown error"}`);
@@ -27274,7 +27282,6 @@ function classifyRisk(toolNameOrDesc) {
27274
27282
  const readSpecials = [
27275
27283
  "agent_simulate_order",
27276
27284
  "okx_preflight_check",
27277
- "okx_agent_feedback",
27278
27285
  "agent_catalog",
27279
27286
  "agent_catalog_detail",
27280
27287
  "agent_hub_status",
@@ -27297,12 +27304,12 @@ function classifyRisk(toolNameOrDesc) {
27297
27304
  "codegraph_status",
27298
27305
  "codegraph_query",
27299
27306
  "agent_get_preference",
27300
- "agent_set_preference",
27301
27307
  "agent_simulate_transfer",
27302
27308
  "agent_read_only_trade",
27303
27309
  "okx_event_instruments"
27304
27310
  ];
27305
27311
  if (readSpecials.includes(toolName)) return "READ";
27312
+ if (["okx_agent_feedback", "agent_set_preference"].includes(toolName)) return "WRITE";
27306
27313
  if (toolName.startsWith("xlayer_call")) return "WRITE";
27307
27314
  return "READ";
27308
27315
  }
@@ -28509,25 +28516,6 @@ function registerAccountTools(server, auth) {
28509
28516
  }
28510
28517
  }
28511
28518
  );
28512
- registerTool(
28513
- server,
28514
- "okx_get_order",
28515
- "READ",
28516
- "CAT:[\u8D26\u6237] | \u2192 \u8BF7\u5148\u8C03\u7528 agent_catalog",
28517
- {
28518
- instId: external_exports.string().describe("\u4EA7\u54C1ID"),
28519
- ordId: external_exports.string().describe("\u8BA2\u5355ID")
28520
- },
28521
- async ({ instId, ordId }) => {
28522
- if (!auth) return toError(AUTH_REQUIRED);
28523
- try {
28524
- const data = await privateApi.getOrder(auth, instId, ordId);
28525
- return toResult(data);
28526
- } catch (e) {
28527
- return toError(e);
28528
- }
28529
- }
28530
- );
28531
28519
  registerTool(
28532
28520
  server,
28533
28521
  "okx_get_orders_history",
@@ -30056,6 +30044,8 @@ function registerFundingTools(server, auth) {
30056
30044
  },
30057
30045
  async ({ ccy, amt, dest, toAddr, chain, fee }) => {
30058
30046
  if (!auth) return toError(AUTH_REQUIRED);
30047
+ if (dest === "4" && !toAddr) return toError(new Error("\u94FE\u4E0A\u63D0\u5E01\u5FC5\u987B\u63D0\u4F9B toAddr\uFF08\u76EE\u6807\u5730\u5740\uFF09"));
30048
+ if (dest === "4" && toAddr && !/^[A-Za-z0-9]{20,}$/.test(toAddr)) return toError(new Error(`\u76EE\u6807\u5730\u5740 "${toAddr}" \u683C\u5F0F\u65E0\u6548`));
30059
30049
  try {
30060
30050
  const body = { ccy, amt, dest };
30061
30051
  if (toAddr) body.toAddr = toAddr;
@@ -34699,8 +34689,18 @@ function registerAgentUtils(server, auth) {
34699
34689
  },
34700
34690
  async ({ title, what, tools, pain, suggestion }) => {
34701
34691
  try {
34702
- const logDir = process.env.OKX_FEEDBACK_DIR || os.homedir();
34692
+ const logDir = path.join(os.homedir(), ".hvip");
34693
+ fs.mkdirSync(logDir, { recursive: true });
34703
34694
  const logFile = path.join(logDir, "hvip-mcp-feedback.log");
34695
+ try {
34696
+ const stat = fs.statSync(logFile);
34697
+ if (stat.size > 10 * 1024 * 1024) {
34698
+ const rotated = logFile + ".1";
34699
+ if (fs.existsSync(rotated)) fs.unlinkSync(rotated);
34700
+ fs.renameSync(logFile, rotated);
34701
+ }
34702
+ } catch {
34703
+ }
34704
34704
  const entry = JSON.stringify({
34705
34705
  time: (/* @__PURE__ */ new Date()).toISOString(),
34706
34706
  title,
@@ -35175,11 +35175,615 @@ function registerAgentUtils(server, auth) {
35175
35175
  }
35176
35176
  }
35177
35177
  );
35178
+ registerTool(
35179
+ server,
35180
+ "agent_market_sentiment",
35181
+ "READ",
35182
+ "CAT:[\u5E02\u573A\u626B\u63CF] | \u5E02\u573A\u60C5\u7EEA\u7EFC\u5408\u5206\u6790\uFF1A\u591A\u7A7A\u6BD4+PCR+\u8D44\u91D1\u8D39\u7387+\u5927\u6237\u60C5\u7EEA\u2192\u65B9\u5411\u8BC4\u5206\uFF0C\u6216\u626B\u63CF\u6781\u7AEF\u8D39\u7387\u5957\u5229\u673A\u4F1A",
35183
+ {
35184
+ mode: external_exports.enum(["sentiment", "funding"]).optional().default("sentiment").describe("sentiment=\u5E02\u573A\u60C5\u7EEA\u8BC4\u5206, funding=\u8D44\u91D1\u8D39\u7387\u5F02\u5E38\u626B\u63CF"),
35185
+ instType: external_exports.enum(["SWAP", "FUTURES", "SPOT"]).optional().default("SWAP").describe("\u4EA7\u54C1\u7C7B\u578B"),
35186
+ topN: external_exports.number().int().min(5).max(50).optional().default(10).describe("\u5206\u6790\u54C1\u79CD\u6570\u91CF"),
35187
+ sampleCcy: external_exports.string().optional().describe("\u57FA\u51C6\u5E01\u79CD(BTC/ETH)\uFF0C\u4EC5 sentiment \u6A21\u5F0F"),
35188
+ threshold: external_exports.number().optional().default(1e-3).describe("\u8D39\u7387\u5F02\u5E38\u9608\u503C\uFF0C\u4EC5 funding \u6A21\u5F0F")
35189
+ },
35190
+ async ({ mode, instType, topN, sampleCcy, threshold }) => {
35191
+ try {
35192
+ const type = instType || "SWAP";
35193
+ const n = topN || 10;
35194
+ const tickersAll = await publicApi.getTickers(type);
35195
+ const tickers = (tickersAll || []).slice(0, n);
35196
+ if (mode === "funding") {
35197
+ const fetchers = {};
35198
+ for (const t of tickers) fetchers[t.instId] = publicApi.getFundingRate(t.instId);
35199
+ const { get: get2, errors: errors2 } = await fetchAllSettled(fetchers);
35200
+ const results = tickers.map((t) => {
35201
+ const fr = get2(t.instId)?.[0] || {};
35202
+ const rate = parseFloat(fr.fundingRate || "0");
35203
+ const annualized = rate * 365 * 3;
35204
+ return {
35205
+ instId: t.instId,
35206
+ last: t.last,
35207
+ fundingRate: fr.fundingRate || "N/A",
35208
+ annualizedRate: `${(annualized * 100).toFixed(2)}%`,
35209
+ nextFundingTime: fr.nextFundingTime || "N/A",
35210
+ isExtreme: Math.abs(rate) > (threshold || 1e-3),
35211
+ direction: rate > 0 ? "\u7A7A\u5934\u4ED8\u591A\u5934" : "\u591A\u5934\u4ED8\u7A7A\u5934",
35212
+ arbitrageHint: rate > (threshold || 1e-3) ? `\u8D44\u91D1\u8D39\u7387\u6781\u9AD8(${(annualized * 100).toFixed(0)}%\u5E74\u5316)\uFF0C\u73B0\u8D27\u4E70\u5165+\u5408\u7EA6\u505A\u7A7A\u53EF\u5957\u5229` : rate < -(threshold || 1e-3) ? `\u8D44\u91D1\u8D39\u7387\u6781\u8D1F\uFF0C\u73B0\u8D27\u5356\u51FA+\u5408\u7EA6\u505A\u591A\u53EF\u5957\u5229` : null
35213
+ };
35214
+ }).sort((a, b) => Math.abs(parseFloat(b.fundingRate === "N/A" ? "0" : b.fundingRate)) - Math.abs(parseFloat(a.fundingRate === "N/A" ? "0" : a.fundingRate)));
35215
+ const opportunities = results.filter((r) => r.isExtreme);
35216
+ return toResult({
35217
+ tsIso: (/* @__PURE__ */ new Date()).toISOString(),
35218
+ mode,
35219
+ scanned: results.length,
35220
+ extremeCount: opportunities.length,
35221
+ threshold: `${((threshold || 1e-3) * 365 * 3 * 100).toFixed(0)}% \u5E74\u5316`,
35222
+ topOpportunities: opportunities.slice(0, 10),
35223
+ allResults: results,
35224
+ errors: errors2,
35225
+ _summary: opportunities.length > 0 ? `\u53D1\u73B0 ${opportunities.length} \u4E2A\u5F02\u5E38\u8D39\u7387\uFF1A${opportunities.slice(0, 3).map((o) => `${o.instId}(${o.annualizedRate})`).join(", ")}\u3002` : `\u5DF2\u626B\u63CF ${results.length} \u4E2A\u54C1\u79CD\uFF0C\u65E0\u6781\u7AEF\u8D39\u7387\u3002`
35226
+ });
35227
+ }
35228
+ const ccy = sampleCcy || "BTC";
35229
+ const firstInstId = tickers[0]?.instId || "BTC-USDT-SWAP";
35230
+ const { get, errors } = await fetchAllSettled({
35231
+ lsRatio: publicApi.getLongShortRatio(ccy),
35232
+ pcr: publicApi.getOptionPutCallRatio(ccy),
35233
+ takerVol: publicApi.getTakerVolume(ccy, type),
35234
+ oi: publicApi.getOpenInterest(type),
35235
+ topLS: publicApi.getTopTraderLongShortRatio(firstInstId)
35236
+ });
35237
+ const lsData = get("lsRatio")?.[0] || {};
35238
+ const pcrData = get("pcr")?.[0] || {};
35239
+ const takerData = get("takerVol")?.[0] || {};
35240
+ const oiData = get("oi");
35241
+ const topLSData = get("topLS")?.[0] || {};
35242
+ const frFetchers = {};
35243
+ for (const t of tickers) frFetchers[t.instId] = publicApi.getFundingRate(t.instId);
35244
+ const frResults = await fetchAllSettled(frFetchers);
35245
+ const perCoin = tickers.map((t) => {
35246
+ const frData = frResults.get(t.instId)?.[0] || {};
35247
+ const rate = parseFloat(frData.fundingRate || "0");
35248
+ const changePct = t.open24h && t.last ? (parseFloat(t.last) - parseFloat(t.open24h)) / parseFloat(t.open24h) * 100 : 0;
35249
+ let score = 0;
35250
+ const reasons = [];
35251
+ if (rate > 5e-4) {
35252
+ score -= 1;
35253
+ reasons.push(`\u8D44\u91D1\u8D39\u7387\u6781\u9AD8\u2192\u504F\u7A7A`);
35254
+ } else if (rate > 1e-4) {
35255
+ score -= 0.5;
35256
+ reasons.push(`\u8D39\u7387\u504F\u9AD8`);
35257
+ } else if (rate < -5e-4) {
35258
+ score += 1;
35259
+ reasons.push(`\u8D39\u7387\u6781\u8D1F\u2192\u504F\u591A`);
35260
+ }
35261
+ if (changePct > 3) {
35262
+ score += 1;
35263
+ reasons.push(`24h\u6DA8${changePct.toFixed(1)}%`);
35264
+ } else if (changePct < -3) {
35265
+ score -= 1;
35266
+ reasons.push(`24h\u8DCC${Math.abs(changePct).toFixed(1)}%`);
35267
+ }
35268
+ return {
35269
+ instId: t.instId,
35270
+ last: t.last,
35271
+ change24h: `${changePct.toFixed(2)}%`,
35272
+ fundingRate: frData.fundingRate || "N/A",
35273
+ fundingRateAnnual: rate ? `${(rate * 365 * 3 * 100).toFixed(2)}%` : "N/A",
35274
+ sentimentScore: score,
35275
+ sentiment: score > 1 ? "\u{1F7E2} \u504F\u591A" : score < -1 ? "\u{1F534} \u504F\u7A7A" : "\u{1F7E1} \u4E2D\u6027",
35276
+ reasons
35277
+ };
35278
+ });
35279
+ let globalScore = 0;
35280
+ const lsValue = parseFloat(lsData.longShortRatio || "1");
35281
+ if (lsValue > 1.2) globalScore += 1;
35282
+ else if (lsValue < 0.8) globalScore -= 1;
35283
+ const pcrValue = parseFloat(pcrData.oiRatio || "1");
35284
+ if (pcrValue > 0.8) globalScore -= 1;
35285
+ else if (pcrValue < 0.5) globalScore += 1;
35286
+ return toResult({
35287
+ tsIso: (/* @__PURE__ */ new Date()).toISOString(),
35288
+ mode,
35289
+ globalSentiment: globalScore > 0 ? "\u{1F7E2} \u504F\u5411\u591A\u5934" : globalScore < 0 ? "\u{1F534} \u504F\u5411\u7A7A\u5934" : "\u{1F7E1} \u4E2D\u6027",
35290
+ globalMetrics: {
35291
+ longShortRatio: lsData.longShortRatio || "N/A",
35292
+ putCallOiRatio: pcrData.oiRatio || "N/A",
35293
+ takerBuyVol: takerData.buyVol || "N/A",
35294
+ takerSellVol: takerData.sellVol || "N/A",
35295
+ contractTopLS: topLSData.longShortRatio || "N/A",
35296
+ openInterest: oiData.length > 0 ? oiData[0]?.oi : "N/A"
35297
+ },
35298
+ perCoin: perCoin.slice(0, n),
35299
+ errors,
35300
+ _summary: `${mode === "sentiment" ? "\u5E02\u573A\u60C5\u7EEA" : "\u8D39\u7387\u626B\u63CF"}\uFF1A${perCoin.filter((c) => c.sentimentScore > 1).length} \u4E2A\u504F\u591A\uFF0C${perCoin.filter((c) => c.sentimentScore < -1).length} \u4E2A\u504F\u7A7A\u3002`
35301
+ });
35302
+ } catch (e) {
35303
+ return toError(e);
35304
+ }
35305
+ }
35306
+ );
35307
+ registerTool(
35308
+ server,
35309
+ "agent_asset_center",
35310
+ "READ",
35311
+ "CAT:[\u8D26\u6237\u8D44\u4EA7] | \u8D44\u4EA7\u6307\u6325\u4E2D\u5FC3\uFF1A\u8D44\u91D1\u5206\u5E03\u3001\u7406\u8D22\u6536\u76CA\u3001\u5B50\u8D26\u6237\u4E00\u89C8\uFF0C\u4E09\u5408\u4E00",
35312
+ {
35313
+ mode: external_exports.enum(["all", "funds", "earn", "subaccounts"]).optional().default("all").describe("all=\u5168\u666F, funds=\u8D44\u91D1\u5206\u5E03\u4E0E\u5212\u8F6C, earn=\u7406\u8D22\u6536\u76CA, subaccounts=\u5B50\u8D26\u6237"),
35314
+ ccy: external_exports.string().optional().describe("\u5E01\u79CD\u8FC7\u6EE4\uFF0C\u4EC5 funds \u6A21\u5F0F"),
35315
+ subMode: external_exports.enum(["list", "detail"]).optional().default("list").describe("\u5B50\u8D26\u6237\u6A21\u5F0F\uFF1Alist=\u5217\u8868, detail=\u542B\u4F59\u989D")
35316
+ },
35317
+ async ({ mode, ccy, subMode }) => {
35318
+ if (!auth) return toError(AUTH_REQUIRED);
35319
+ try {
35320
+ const fetchers = {};
35321
+ if (mode === "all" || mode === "funds") {
35322
+ fetchers.funding = privateApi.getBalance_funding(auth, ccy);
35323
+ fetchers.trading = privateApi.getBalance(auth, ccy);
35324
+ }
35325
+ if (mode === "all" || mode === "earn") {
35326
+ fetchers.savings = privateApi.getSavingsBalance(auth);
35327
+ fetchers.ethStaking = privateApi.getEthStakingBalance(auth);
35328
+ fetchers.solStaking = privateApi.getSolStakingBalance(auth);
35329
+ fetchers.stableRewards = privateApi.getStableRewardsProductInfo(auth);
35330
+ fetchers.stakingOrders = privateApi.getStakingOrders(auth);
35331
+ }
35332
+ if (mode === "all" || mode === "subaccounts") {
35333
+ fetchers.subList = privateApi.listSubAccounts(auth);
35334
+ }
35335
+ const { get, errors } = await fetchAllSettled(fetchers);
35336
+ const result = { tsIso: (/* @__PURE__ */ new Date()).toISOString(), mode, errors };
35337
+ if (mode === "all" || mode === "funds") {
35338
+ const fundData = get("funding")?.[0] || {};
35339
+ const tradeData = get("trading")?.[0] || {};
35340
+ const fundDetails = fundData.details || fundData.detail || [];
35341
+ const tradeDetails = tradeData.details || tradeData.detail || [];
35342
+ const map = /* @__PURE__ */ new Map();
35343
+ for (const d of fundDetails) map.set(d.ccy, { ccy: d.ccy, fundingAvail: d.availBal || d.cashBal || "0", tradingAvail: "0" });
35344
+ for (const d of tradeDetails) {
35345
+ const e = map.get(d.ccy);
35346
+ if (e) e.tradingAvail = d.availBal || d.cashBal || "0";
35347
+ else map.set(d.ccy, { ccy: d.ccy, fundingAvail: "0", tradingAvail: d.availBal || d.cashBal || "0" });
35348
+ }
35349
+ result.funds = {
35350
+ fundingTotalEq: fundData.totalEq || "N/A",
35351
+ tradingTotalEq: tradeData.totalEq || "N/A",
35352
+ currencies: [...map.values()].filter((d) => parseFloat(d.fundingAvail) > 0 || parseFloat(d.tradingAvail) > 0)
35353
+ };
35354
+ }
35355
+ if (mode === "all" || mode === "earn") {
35356
+ const savings = get("savings");
35357
+ const eth = get("ethStaking");
35358
+ const sol = get("solStaking");
35359
+ const earnProducts = [];
35360
+ for (const arr of [savings, eth, sol]) {
35361
+ if (Array.isArray(arr)) for (const s of arr) earnProducts.push({
35362
+ ccy: s.ccy || "?",
35363
+ product: s.productId || s.investType || "earn",
35364
+ balance: s.amt || s.investAmt || "0",
35365
+ apy: s.rate || s.apy || "N/A"
35366
+ });
35367
+ }
35368
+ result.earn = {
35369
+ savingsCount: Array.isArray(savings) ? savings.length : 0,
35370
+ ethStakingCount: Array.isArray(eth) ? eth.length : 0,
35371
+ solStakingCount: Array.isArray(sol) ? sol.length : 0,
35372
+ stakingOrderCount: Array.isArray(get("stakingOrders")) ? get("stakingOrders").length : 0,
35373
+ products: earnProducts
35374
+ };
35375
+ }
35376
+ if (mode === "all" || mode === "subaccounts") {
35377
+ const subList = get("subList") || [];
35378
+ if (subMode === "detail" && subList.length > 0) {
35379
+ const balFetchers = {};
35380
+ for (const s of subList) balFetchers[s.subAcct] = privateApi.getSubAccountFundingBalance(auth, s.subAcct);
35381
+ const balRes = await fetchAllSettled(balFetchers);
35382
+ result.subaccounts = subList.map((s) => {
35383
+ const bal = balRes.get(s.subAcct)?.[0] || {};
35384
+ return { subAcct: s.subAcct, label: s.label, totalEq: bal.totalEq || "N/A" };
35385
+ });
35386
+ } else {
35387
+ result.subaccounts = subList.map((s) => ({ subAcct: s.subAcct, label: s.label, enable: s.enable }));
35388
+ }
35389
+ result.subaccountCount = subList.length;
35390
+ }
35391
+ const parts = [];
35392
+ if (result.funds) parts.push(`\u8D44\u91D1\u8D26\u6237${result.funds.currencies?.length || 0}\u4E2A\u5E01\u79CD`);
35393
+ if (result.earn) parts.push(`\u7406\u8D22${result.earn.products?.length || 0}\u4E2A\u6301\u4ED3`);
35394
+ if (result.subaccounts) parts.push(`${result.subaccountCount}\u4E2A\u5B50\u8D26\u6237`);
35395
+ result._summary = "\u8D44\u4EA7\u5168\u666F\uFF1A" + parts.join("\uFF0C") + "\u3002";
35396
+ return toResult(result);
35397
+ } catch (e) {
35398
+ return toError(e);
35399
+ }
35400
+ }
35401
+ );
35402
+ registerTool(
35403
+ server,
35404
+ "agent_strategy_center",
35405
+ "READ",
35406
+ "CAT:[\u7B56\u7565\u4EA4\u6613] | \u7B56\u7565\u4EA4\u6613\u4E2D\u5FC3\uFF1A\u6D3B\u8DC3\u7B56\u7565\u4EEA\u8868\u76D8 or \u7F51\u683C\u53C2\u6570\u667A\u80FD\u63A8\u8350",
35407
+ {
35408
+ mode: external_exports.enum(["dashboard", "grid_advice"]).default("dashboard").describe("dashboard=\u6240\u6709\u6D3B\u8DC3\u7B56\u7565\u4E00\u89C8, grid_advice=\u6CE2\u52A8\u7387\u5206\u6790+AI\u7F51\u683C\u63A8\u8350"),
35409
+ instType: external_exports.enum(["SPOT", "SWAP", "FUTURES"]).optional().describe("\u4EA7\u54C1\u7C7B\u578B\u8FC7\u6EE4"),
35410
+ instId: external_exports.string().optional().describe("\u4EA4\u6613\u54C1\u79CD(grid_advice\u6A21\u5F0F\u5FC5\u586B)\uFF0C\u5982 BTC-USDT"),
35411
+ direction: external_exports.enum(["long", "short", "neutral"]).optional().default("neutral").describe("\u7F51\u683C\u65B9\u5411(grid_advice)")
35412
+ },
35413
+ async ({ mode, instType, instId, direction }) => {
35414
+ if (!auth) return toError(AUTH_REQUIRED);
35415
+ try {
35416
+ if (mode === "grid_advice") {
35417
+ if (!instId) return toError(new Error("grid_advice \u6A21\u5F0F\u9700\u8981 instId \u53C2\u6570"));
35418
+ const { get: get2, errors: errors2 } = await fetchAllSettled({
35419
+ aiParam: publicApi.getGridAiParam(instId, "grid"),
35420
+ candles: publicApi.getCandles(instId, "1D", 30),
35421
+ ticker: publicApi.getTicker(instId)
35422
+ });
35423
+ const aiParam = get2("aiParam")?.[0] || {};
35424
+ const candles = get2("candles") || [];
35425
+ const tickerData = get2("ticker")?.[0] || {};
35426
+ const closes = candles.map((c) => parseFloat(c[4] || "0")).filter((n) => n > 0);
35427
+ let volatility = 0;
35428
+ if (closes.length >= 2) {
35429
+ const returns = closes.slice(1).map((c, i) => (c - closes[i]) / closes[i]);
35430
+ const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
35431
+ volatility = Math.sqrt(returns.reduce((a, r) => a + (r - mean) ** 2, 0) / returns.length) * Math.sqrt(365);
35432
+ }
35433
+ const lastPrice = parseFloat(tickerData.last || "0");
35434
+ const high30 = closes.length > 0 ? Math.max(...closes) : lastPrice * 1.1;
35435
+ const low30 = closes.length > 0 ? Math.min(...closes) : lastPrice * 0.9;
35436
+ const gUpper = aiParam.upperPx || (high30 * 1.05).toFixed(2);
35437
+ const gLower = aiParam.lowerPx || (low30 * 0.95).toFixed(2);
35438
+ const gCount = aiParam.gridNum || Math.min(20, Math.floor((parseFloat(String(gUpper)) - parseFloat(String(gLower))) / (lastPrice * 0.01)) || 10);
35439
+ return toResult({
35440
+ tsIso: (/* @__PURE__ */ new Date()).toISOString(),
35441
+ mode,
35442
+ instId,
35443
+ currentPrice: lastPrice,
35444
+ volatility: `${(volatility * 100).toFixed(1)}%`,
35445
+ range30d: { high: high30, low: low30 },
35446
+ recommendation: { direction: direction || "neutral", upperPrice: gUpper, lowerPrice: gLower, gridCount: gCount },
35447
+ errors: errors2,
35448
+ _summary: `${instId} 30\u65E5\u5E74\u5316\u6CE2\u52A8\u7387 ${(volatility * 100).toFixed(1)}%\u3002AI\u63A8\u8350\u7F51\u683C [${gLower}, ${gUpper}]\uFF0C${gCount} \u6863\u3002`,
35449
+ tip: "\u786E\u8BA4\u540E\u7528 okx_create_grid_order \u521B\u5EFA\u7F51\u683C\u3002"
35450
+ });
35451
+ }
35452
+ const { get, errors } = await fetchAllSettled({
35453
+ algoOrders: privateApi.getOrdersAlgoPending(auth, void 0, instType || void 0),
35454
+ gridOrders: privateApi.getGridOrdersPending(auth),
35455
+ recurringOrders: privateApi.getRecurringOrdersPending(auth),
35456
+ signalBots: privateApi.getSignalBotsPending(auth, instType || void 0),
35457
+ spreadOrders: privateApi.getSpreadOrdersPending(auth)
35458
+ });
35459
+ const counts = {
35460
+ algo: Array.isArray(get("algoOrders")) ? get("algoOrders").length : 0,
35461
+ grid: Array.isArray(get("gridOrders")) ? get("gridOrders").length : 0,
35462
+ recurring: Array.isArray(get("recurringOrders")) ? get("recurringOrders").length : 0,
35463
+ signal: Array.isArray(get("signalBots")) ? get("signalBots").length : 0,
35464
+ spread: Array.isArray(get("spreadOrders")) ? get("spreadOrders").length : 0
35465
+ };
35466
+ const totalActive = Object.values(counts).reduce((a, b) => a + b, 0);
35467
+ return toResult({
35468
+ tsIso: (/* @__PURE__ */ new Date()).toISOString(),
35469
+ mode,
35470
+ totalActive,
35471
+ breakdown: counts,
35472
+ details: {
35473
+ algoOrders: Array.isArray(get("algoOrders")) ? get("algoOrders").slice(0, 15).map((o) => ({ algoId: o.algoId, instId: o.instId, ordType: o.ordType, side: o.side, state: o.state })) : [],
35474
+ gridOrders: Array.isArray(get("gridOrders")) ? get("gridOrders").slice(0, 10).map((o) => ({ algoId: o.algoId, instId: o.instId, state: o.state })) : [],
35475
+ recurringOrders: Array.isArray(get("recurringOrders")) ? get("recurringOrders").slice(0, 10).map((o) => ({ algoId: o.algoId, instId: o.instId, period: o.period, state: o.state })) : [],
35476
+ signalBots: Array.isArray(get("signalBots")) ? get("signalBots").slice(0, 10).map((o) => ({ signalId: o.signalId || o.algoId, instId: o.instId, state: o.state })) : [],
35477
+ spreadOrders: Array.isArray(get("spreadOrders")) ? get("spreadOrders").slice(0, 10).map((o) => ({ algoId: o.algoId, sprdId: o.sprdId, state: o.state })) : []
35478
+ },
35479
+ errors,
35480
+ _summary: totalActive > 0 ? `\u6D3B\u8DC3\u7B56\u7565 ${totalActive} \u4E2A\uFF1A\u6761\u4EF6\u59D4\u6258 ${counts.algo} / \u7F51\u683C ${counts.grid} / \u5B9A\u6295 ${counts.recurring} / \u4FE1\u53F7 ${counts.signal} / \u4EF7\u5DEE ${counts.spread}` : "\u5F53\u524D\u65E0\u6D3B\u8DC3\u7B56\u7565\u3002",
35481
+ tip: "\u9700\u8981\u7F51\u683C\u5EFA\u8BAE\uFF1F\u7528 mode=grid_advice\u3002"
35482
+ });
35483
+ } catch (e) {
35484
+ return toError(e);
35485
+ }
35486
+ }
35487
+ );
35488
+ registerTool(
35489
+ server,
35490
+ "agent_stop_loss_master",
35491
+ "WRITE",
35492
+ "CAT:[\u98CE\u9669\u98CE\u63A7] | \u5168\u5C40\u98CE\u63A7\uFF1A\u6279\u91CF\u8BBE\u6B62\u635F\u6761\u4EF6\u5355 / \u4E00\u952E\u5168\u90E8\u64A4\u5355 / \u67E5\u770B\u98CE\u63A7\u72B6\u6001",
35493
+ {
35494
+ instId: external_exports.string().optional().describe("\u6307\u5B9A\u54C1\u79CD\uFF0C\u4E0D\u586B\u5219\u5BF9\u6240\u6709\u6301\u4ED3\u64CD\u4F5C"),
35495
+ stopLossPct: external_exports.string().optional().describe("\u6B62\u635F\u767E\u5206\u6BD4\uFF0C\u5982 '5' \u8868\u793A\u4E8F\u635F 5% \u6B62\u635F"),
35496
+ mode: external_exports.enum(["preview", "set_stop_loss", "cancel_all"]).describe("preview=\u67E5\u770B\u98CE\u63A7\u72B6\u6001, set_stop_loss=\u8BBE\u6B62\u635F\u5355, cancel_all=\u5168\u90E8\u64A4\u5355")
35497
+ },
35498
+ async ({ instId, stopLossPct, mode }) => {
35499
+ if (!auth) return toError(AUTH_REQUIRED);
35500
+ try {
35501
+ const positions = await privateApi.getPositions(auth);
35502
+ const active = (positions || []).filter(
35503
+ (p) => parseFloat(p.avgPx || "0") > 0 && parseFloat(p.pos || "0") !== "0"
35504
+ );
35505
+ const targets = instId ? active.filter((p) => p.instId === instId) : active;
35506
+ if (targets.length === 0) {
35507
+ return toResult({ tsIso: (/* @__PURE__ */ new Date()).toISOString(), mode, message: instId ? `${instId} \u65E0\u6D3B\u8DC3\u6301\u4ED3` : "\u65E0\u4EFB\u4F55\u6D3B\u8DC3\u6301\u4ED3" });
35508
+ }
35509
+ if (mode === "preview") {
35510
+ const pct = parseFloat(stopLossPct || "5");
35511
+ return toResult({
35512
+ tsIso: (/* @__PURE__ */ new Date()).toISOString(),
35513
+ mode,
35514
+ positions: targets.map((p) => ({
35515
+ instId: p.instId,
35516
+ posSide: p.posSide,
35517
+ pos: p.pos,
35518
+ avgPx: p.avgPx,
35519
+ markPx: p.markPx,
35520
+ upl: p.upl,
35521
+ uplRatio: p.uplRatio,
35522
+ liqPx: p.liqPx,
35523
+ lever: p.lever,
35524
+ suggestedStopPx: p.avgPx ? p.posSide === "long" ? (parseFloat(p.avgPx) * (1 - pct / 100)).toFixed(2) : (parseFloat(p.avgPx) * (1 + pct / 100)).toFixed(2) : "N/A"
35525
+ })),
35526
+ _summary: `\u98CE\u63A7\u9884\u89C8\uFF1A${targets.length} \u4E2A\u6301\u4ED3\uFF0C\u5EFA\u8BAE\u6B62\u635F\u5E45\u5EA6 ${pct}%\u3002`
35527
+ });
35528
+ }
35529
+ if (mode === "set_stop_loss") {
35530
+ if (!stopLossPct) return toError(new Error("set_stop_loss \u6A21\u5F0F\u9700\u8981 stopLossPct \u53C2\u6570"));
35531
+ const pct = parseFloat(stopLossPct);
35532
+ if (isNaN(pct) || pct <= 0 || pct >= 50) return toError(new Error("stopLossPct \u5FC5\u987B\u5728 1-50 \u4E4B\u95F4"));
35533
+ const actions = [];
35534
+ for (const p of targets) {
35535
+ const avgPx = parseFloat(p.avgPx);
35536
+ const isLong = p.posSide === "long";
35537
+ const stopPx = isLong ? (avgPx * (1 - pct / 100)).toFixed(2) : (avgPx * (1 + pct / 100)).toFixed(2);
35538
+ try {
35539
+ await privateApi.placeAlgoOrder(auth, {
35540
+ instId: p.instId,
35541
+ tdMode: "cross",
35542
+ side: isLong ? "sell" : "buy",
35543
+ ordType: "conditional",
35544
+ sz: Math.abs(parseFloat(p.pos)).toString(),
35545
+ tpTriggerPx: stopPx,
35546
+ tpOrdPx: "-1"
35547
+ });
35548
+ actions.push({ instId: p.instId, posSide: p.posSide, stopPx, status: "placed" });
35549
+ } catch (e) {
35550
+ actions.push({ instId: p.instId, posSide: p.posSide, stopPx, status: "failed", error: e.message });
35551
+ }
35552
+ }
35553
+ return toResult({
35554
+ tsIso: (/* @__PURE__ */ new Date()).toISOString(),
35555
+ mode,
35556
+ actions,
35557
+ _summary: `\u5DF2\u4E3A ${actions.filter((a) => a.status === "placed").length}/${actions.length} \u4E2A\u6301\u4ED3\u8BBE\u6B62\u635F\uFF08${stopLossPct}%\uFF09\u3002`
35558
+ });
35559
+ }
35560
+ await privateApi.massCancel(auth, instId ? "SWAP" : "SWAP");
35561
+ return toResult({ tsIso: (/* @__PURE__ */ new Date()).toISOString(), mode, cancelled: true, _summary: "\u5DF2\u5168\u90E8\u64A4\u5355\u3002" });
35562
+ } catch (e) {
35563
+ return toError(e);
35564
+ }
35565
+ }
35566
+ );
35567
+ registerTool(
35568
+ server,
35569
+ "agent_copy_trader_search",
35570
+ "READ",
35571
+ "CAT:[\u8DDF\u5355] | \u6309\u6536\u76CA\u7387/\u80DC\u7387/\u56DE\u64A4\u7B5B\u9009\u6700\u4F18\u5E26\u5355\u5458\uFF0C\u9644\u8DDF\u5355\u6307\u5F15",
35572
+ {
35573
+ instType: external_exports.enum(["SPOT", "SWAP"]).optional().default("SWAP"),
35574
+ sortBy: external_exports.enum(["pnl", "winRate", "copyCount"]).optional().default("pnl"),
35575
+ topN: external_exports.number().int().min(1).max(20).optional().default(10)
35576
+ },
35577
+ async ({ instType, sortBy, topN }) => {
35578
+ try {
35579
+ const type = instType || "SWAP";
35580
+ const n = topN || 10;
35581
+ const traders = await publicApi.getPublicLeadTraders(type);
35582
+ if (!Array.isArray(traders) || traders.length === 0) {
35583
+ return toResult({ tsIso: (/* @__PURE__ */ new Date()).toISOString(), traders: [], _summary: "\u5F53\u524D\u65E0\u53EF\u7528\u5E26\u5355\u5458\u3002" });
35584
+ }
35585
+ const batch = traders.slice(0, n);
35586
+ const fetchers = {};
35587
+ for (const t of batch) {
35588
+ const code = t.uniqueCode || t.copyTraderId;
35589
+ if (code) fetchers[code] = publicApi.getLeadTraderStats(code, type, "30");
35590
+ }
35591
+ const statsRes = await fetchAllSettled(fetchers);
35592
+ const ranked = batch.map((t) => {
35593
+ const code = t.uniqueCode || t.copyTraderId;
35594
+ const stats = statsRes.get(code)?.[0] || {};
35595
+ return {
35596
+ name: t.nickName || t.name || code,
35597
+ uniqueCode: code,
35598
+ pnl: stats.pnl || "N/A",
35599
+ winRate: stats.winRate || "N/A",
35600
+ sharpeRatio: stats.sharpeRatio || "N/A",
35601
+ copyCount: stats.copyCount || "0",
35602
+ maxDrawdown: stats.maxDrawdown || t.maxDrawdown || "N/A"
35603
+ };
35604
+ });
35605
+ if (sortBy === "pnl") ranked.sort((a, b) => parseFloat(b.pnl === "N/A" ? "0" : b.pnl) - parseFloat(a.pnl === "N/A" ? "0" : a.pnl));
35606
+ else if (sortBy === "winRate") ranked.sort((a, b) => parseFloat(b.winRate === "N/A" ? "0" : b.winRate) - parseFloat(a.winRate === "N/A" ? "0" : a.winRate));
35607
+ else ranked.sort((a, b) => parseInt(b.copyCount) - parseInt(a.copyCount));
35608
+ return toResult({
35609
+ tsIso: (/* @__PURE__ */ new Date()).toISOString(),
35610
+ instType: type,
35611
+ sortBy,
35612
+ topTraders: ranked,
35613
+ _summary: `Top ${ranked.length} \u5E26\u5355\u5458 (\u6309${sortBy === "pnl" ? "\u6536\u76CA" : sortBy === "winRate" ? "\u80DC\u7387" : "\u8DDF\u5355\u4EBA\u6570"}\u6392\u5E8F)\u3002`,
35614
+ tip: "\u786E\u8BA4\u5E26\u5355\u5458\u540E\uFF0C\u4F7F\u7528 okx_first_copy_settings { uniqueCode } \u2192 okx_copy_trader \u5F00\u59CB\u8DDF\u5355\u3002"
35615
+ });
35616
+ } catch (e) {
35617
+ return toError(e);
35618
+ }
35619
+ }
35620
+ );
35621
+ registerTool(
35622
+ server,
35623
+ "agent_option_scanner",
35624
+ "READ",
35625
+ "CAT:[\u5E02\u573A\u626B\u63CF] | \u671F\u6743\u5168\u666F\u626B\u63CF\uFF1A\u6982\u8981+OI\u5230\u671F/\u884C\u6743\u4EF7\u5206\u5E03+PCR+\u5927\u5B97\u4EA4\u6613\u91CF",
35626
+ { uly: external_exports.string().describe("\u6807\u7684\uFF0C\u5982 BTC-USD\u3001ETH-USD\u3002\u5FC5\u586B") },
35627
+ async ({ uly }) => {
35628
+ try {
35629
+ const { get, errors } = await fetchAllSettled({
35630
+ summary: publicApi.getOptSummary(uly),
35631
+ oiExpiry: publicApi.getOptionOiExpiry(uly),
35632
+ pcr: publicApi.getOptionPutCallRatio(uly),
35633
+ blockVol: publicApi.getOptionTakerBlockVolume(uly)
35634
+ });
35635
+ const summary = get("summary") || [];
35636
+ const oiExpiry = get("oiExpiry") || [];
35637
+ const pcrData = get("pcr")?.[0] || {};
35638
+ const blockData = get("blockVol")?.[0] || {};
35639
+ return toResult({
35640
+ tsIso: (/* @__PURE__ */ new Date()).toISOString(),
35641
+ underlying: uly,
35642
+ summary: summary.slice(0, 20).map((s) => ({
35643
+ instId: s.instId,
35644
+ expTime: s.expTime,
35645
+ strike: s.strike,
35646
+ optionType: s.optType,
35647
+ markPx: s.markPx,
35648
+ delta: s.delta,
35649
+ oi: s.oi,
35650
+ vol24h: s.vol24h
35651
+ })),
35652
+ oiByExpiry: oiExpiry.map((e) => ({ expTime: e.expTime, callOi: e.callOi, putOi: e.putOi })),
35653
+ putCallRatio: { oiRatio: pcrData.oiRatio || "N/A", volRatio: pcrData.volRatio || "N/A" },
35654
+ blockTrades: blockData.callVol !== void 0 ? { callVol: blockData.callVol, putVol: blockData.putVol } : null,
35655
+ errors,
35656
+ _summary: `${uly} \u671F\u6743\u626B\u63CF\uFF1A${summary.length} \u4E2A\u5408\u7EA6\uFF0CPCR(\u6301\u4ED3) ${pcrData.oiRatio || "N/A"}\u3002`
35657
+ });
35658
+ } catch (e) {
35659
+ return toError(e);
35660
+ }
35661
+ }
35662
+ );
35663
+ registerTool(
35664
+ server,
35665
+ "agent_prediction_arbitrage",
35666
+ "READ",
35667
+ "CAT:[\u9884\u6D4B\u5E02\u573A] | \u626B\u63CF\u9884\u6D4B\u5E02\u573A\u4E8B\u4EF6\uFF0C\u53D1\u73B0 YES+NO \u4EF7\u5DEE < 1.0 \u7684\u65E0\u98CE\u9669\u5957\u5229\u673A\u4F1A",
35668
+ {},
35669
+ async () => {
35670
+ try {
35671
+ const events = await publicApi.searchPredictionsEvents("");
35672
+ const evtList = Array.isArray(events) ? events.slice(0, 10) : [];
35673
+ const fetchers = {};
35674
+ for (const evt of evtList) {
35675
+ const id = evt.eventId || evt.event_id;
35676
+ if (id) fetchers[`evt_${id}`] = publicApi.getPredictionsEvent(id);
35677
+ const mktId = evt.marketId || evt.market_id;
35678
+ if (mktId) fetchers[`mkt_${mktId}`] = publicApi.getPredictionsMarket(mktId);
35679
+ }
35680
+ const { get } = await fetchAllSettled(fetchers);
35681
+ const opportunities = [];
35682
+ for (const evt of evtList) {
35683
+ const id = evt.eventId || evt.event_id;
35684
+ const mktId = evt.marketId || evt.market_id;
35685
+ const mktData = get(`mkt_${mktId}`);
35686
+ const yesPrice = parseFloat(mktData?.yesPrice || evt.yesPrice || "0");
35687
+ const noPrice = parseFloat(mktData?.noPrice || evt.noPrice || "0");
35688
+ const total = yesPrice + noPrice;
35689
+ if (total > 0 && total < 0.99) {
35690
+ opportunities.push({
35691
+ eventId: id,
35692
+ title: evt.title || "?",
35693
+ yesPrice: yesPrice.toFixed(4),
35694
+ noPrice: noPrice.toFixed(4),
35695
+ total: total.toFixed(4),
35696
+ discount: ((1 - total) * 100).toFixed(2) + "%"
35697
+ });
35698
+ }
35699
+ }
35700
+ return toResult({
35701
+ tsIso: (/* @__PURE__ */ new Date()).toISOString(),
35702
+ eventsScanned: evtList.length,
35703
+ opportunities,
35704
+ _summary: opportunities.length > 0 ? `\u53D1\u73B0 ${opportunities.length} \u4E2A\u5957\u5229\u673A\u4F1A\uFF1A${opportunities.slice(0, 3).map((o) => `${o.title}(${o.discount})`).join(", ")}` : `\u5DF2\u626B\u63CF ${evtList.length} \u4E2A\u4E8B\u4EF6\uFF0C\u5F53\u524D\u65E0 YES+NO<1.0 \u7684\u5957\u5229\u673A\u4F1A\u3002`,
35705
+ tip: "\u786E\u8BA4\u673A\u4F1A\u540E\u7528 okx_predictions_place_order \u6216 okx_event_place_order \u5206\u522B\u4E70\u5165 YES \u548C NO\u3002"
35706
+ });
35707
+ } catch (e) {
35708
+ return toError(e);
35709
+ }
35710
+ }
35711
+ );
35712
+ registerTool(
35713
+ server,
35714
+ "agent_technical_report",
35715
+ "READ",
35716
+ "CAT:[\u6280\u672F\u6307\u6807] | \u591A\u5468\u671F(1H/4H/1D)\u6280\u672F\u6307\u6807\u7EFC\u5408\u8BA1\u7B97\uFF0C\u8F93\u51FA\u65B9\u5411\u5171\u8BC6\u4FE1\u53F7",
35717
+ {
35718
+ instId: external_exports.string().describe("\u4EA4\u6613\u54C1\u79CD\uFF0C\u5982 BTC-USDT\u3002\u5FC5\u586B"),
35719
+ bars: external_exports.array(external_exports.enum(["1H", "4H", "1D", "1W"])).optional().default(["1H", "4H", "1D"]).describe("K\u7EBF\u5468\u671F")
35720
+ },
35721
+ async ({ instId, bars }) => {
35722
+ try {
35723
+ const periods = bars || ["1H", "4H", "1D"];
35724
+ const fetchers = { ticker: publicApi.getTicker(instId) };
35725
+ for (const bar of periods) fetchers[`c_${bar}`] = publicApi.getCandles(instId, bar, 100);
35726
+ const { get, errors } = await fetchAllSettled(fetchers);
35727
+ const tickerData = get("ticker")?.[0] || {};
35728
+ const lastPrice = parseFloat(tickerData.last || "0");
35729
+ const periodResults = [];
35730
+ for (const bar of periods) {
35731
+ const candles = get(`c_${bar}`) || [];
35732
+ if (candles.length < 20) {
35733
+ periodResults.push({ period: bar, status: "\u6570\u636E\u4E0D\u8DB3" });
35734
+ continue;
35735
+ }
35736
+ const closes = candles.map((c) => parseFloat(c[4] || "0"));
35737
+ const sma20 = closes.slice(-20).reduce((a, b) => a + b, 0) / 20;
35738
+ const ema = (data, period) => {
35739
+ const k = 2 / (period + 1);
35740
+ let e = data[0];
35741
+ for (let i = 1; i < data.length; i++) e = data[i] * k + e * (1 - k);
35742
+ return e;
35743
+ };
35744
+ const macd = ema(closes, 12) - ema(closes, 26);
35745
+ const rsiData = closes.slice(-15);
35746
+ let gain = 0, loss = 0;
35747
+ for (let i = 1; i < rsiData.length; i++) {
35748
+ const d = rsiData[i] - rsiData[i - 1];
35749
+ if (d > 0) gain += d;
35750
+ else loss -= d;
35751
+ }
35752
+ const rsi = loss === 0 ? 100 : 100 - 100 / (1 + gain / 14 / (loss / 14));
35753
+ const signals = [];
35754
+ if (lastPrice > sma20) signals.push("\u4EF7\u683C>SMA20(\u504F\u591A)");
35755
+ else signals.push("\u4EF7\u683C<SMA20(\u504F\u7A7A)");
35756
+ if (macd > 0) signals.push("MACD>0(\u504F\u591A)");
35757
+ else signals.push("MACD<0(\u504F\u7A7A)");
35758
+ if (rsi > 70) signals.push("RSI\u8D85\u4E70(\u504F\u7A7A)");
35759
+ else if (rsi < 30) signals.push("RSI\u8D85\u5356(\u504F\u591A)");
35760
+ const bull = signals.filter((s) => s.includes("\u504F\u591A")).length;
35761
+ const bear = signals.filter((s) => s.includes("\u504F\u7A7A")).length;
35762
+ periodResults.push({ period: bar, lastPrice, sma20: sma20.toFixed(2), macd: macd.toFixed(6), rsi: rsi.toFixed(1), signals, direction: bull > bear ? "\u{1F7E2} \u504F\u591A" : bear > bull ? "\u{1F534} \u504F\u7A7A" : "\u{1F7E1} \u4E2D\u6027" });
35763
+ }
35764
+ const bullPeriods = periodResults.filter((p) => p.direction?.includes("\u504F\u591A")).map((p) => p.period);
35765
+ const bearPeriods = periodResults.filter((p) => p.direction?.includes("\u504F\u7A7A")).map((p) => p.period);
35766
+ const consensus = bullPeriods.length > bearPeriods.length ? `\u591A\u6570\u5468\u671F\u504F\u591A(${bullPeriods.join(",")})` : bearPeriods.length > bullPeriods.length ? `\u591A\u6570\u5468\u671F\u504F\u7A7A(${bearPeriods.join(",")})` : "\u591A\u7A7A\u5206\u6B67";
35767
+ return toResult({
35768
+ tsIso: (/* @__PURE__ */ new Date()).toISOString(),
35769
+ instId,
35770
+ currentPrice: lastPrice,
35771
+ consensus,
35772
+ periods: periodResults,
35773
+ errors,
35774
+ _summary: `${instId} \u591A\u5468\u671FTA\uFF1A${consensus}\u3002\u5F53\u524D\u4EF7 $${lastPrice}\u3002`,
35775
+ disclaimer: "\u4EE5\u4E0A\u4E3AL1\u57FA\u7840\u6307\u6807\u8BA1\u7B97\uFF0C\u4E0D\u6784\u6210\u6295\u8D44\u5EFA\u8BAE\u3002\u8BE6\u7EC6\u591A\u6307\u6807\u5206\u6790\u53EF\u4F7F\u7528 okx_indicator_batch\u3002"
35776
+ });
35777
+ } catch (e) {
35778
+ return toError(e);
35779
+ }
35780
+ }
35781
+ );
35178
35782
  registerTool(
35179
35783
  server,
35180
35784
  "agent_get_preference",
35181
35785
  "READ",
35182
- 'CAT:[\u7CFB\u7EDF] | ## \u529F\u80FD\uFF1A\u8BFB\u53D6 Agent \u6301\u4E45\u5316\u504F\u597D\uFF0C\u8DE8\u4F1A\u8BDD\u4FDD\u7559\n## \u573A\u666F\uFF1AAgent \u5728\u65B0\u4F1A\u8BDD\u4E2D\u6062\u590D\u7528\u6237\u504F\u597D\uFF08\u5982"\u53EA\u505A\u73B0\u8D27""\u9ED8\u8BA4\u4EA4\u6613\u5BF9BTC-USDT""\u98CE\u9669\u504F\u597D\u4F4E"\uFF09\n## \u5173\u952E\u8BCD\uFF1A\u504F\u597D, preference, \u8BB0\u5FC6, \u6301\u4E45\u5316, \u7528\u6237\u753B\u50CF\n## \u53C2\u6570\uFF1A\n## - key: \u504F\u597D\u952E\u540D\u3002\u4E0D\u586B\u8FD4\u56DE\u5168\u90E8\u504F\u597D\n## \u9274\u6743\uFF1APUBLIC \u2014 \u672C\u5730\u8BFB\u53D6\n## \u98CE\u9669\uFF1AREAD \u2014 \u53EA\u8BFB\n## \u8FD4\u56DE\u91CF\uFF1A\u5FAE\u5C0F ~300B\n## \u5173\u8054\uFF1Aagent_set_preference \u8BBE\u7F6E\u504F\u597D \u2192 \u672C\u5DE5\u5177 \u2192 Agent \u6839\u636E\u504F\u597D\u8C03\u6574\u7B56\u7565',
35786
+ "CAT:[\u7CFB\u7EDF] | \u8BFB\u53D6 Agent \u6301\u4E45\u5316\u504F\u597D\uFF0C\u8DE8\u4F1A\u8BDD\u4FDD\u7559",
35183
35787
  {
35184
35788
  key: external_exports.string().optional().describe("\u504F\u597D\u952E\u540D\uFF0C\u4E0D\u586B\u8FD4\u56DE\u5168\u90E8")
35185
35789
  },
@@ -35191,7 +35795,14 @@ function registerAgentUtils(server, auth) {
35191
35795
  if (fs.existsSync(prefPath)) {
35192
35796
  prefs = JSON.parse(fs.readFileSync(prefPath, "utf-8"));
35193
35797
  }
35194
- } catch {
35798
+ } catch (e) {
35799
+ const corrupted = prefPath + ".corrupted." + (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
35800
+ try {
35801
+ fs.renameSync(prefPath, corrupted);
35802
+ } catch {
35803
+ }
35804
+ process.stderr.write(`[hvip] \u26A0\uFE0F preferences.json \u635F\u574F\uFF0C\u5DF2\u5907\u4EFD\u4E3A ${corrupted}\uFF0C\u91CD\u7F6E\u4E3A\u7A7A
35805
+ `);
35195
35806
  }
35196
35807
  const result = key ? { key, value: prefs[key] ?? null, found: key in prefs } : { all: prefs, count: Object.keys(prefs).length };
35197
35808
  return toResult({
@@ -35223,7 +35834,14 @@ function registerAgentUtils(server, auth) {
35223
35834
  if (fs.existsSync(prefPath)) {
35224
35835
  prefs = JSON.parse(fs.readFileSync(prefPath, "utf-8"));
35225
35836
  }
35226
- } catch {
35837
+ } catch (e) {
35838
+ const corrupted = prefPath + ".corrupted." + (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
35839
+ try {
35840
+ fs.renameSync(prefPath, corrupted);
35841
+ } catch {
35842
+ }
35843
+ process.stderr.write(`[hvip] \u26A0\uFE0F preferences.json \u635F\u574F\uFF0C\u5DF2\u5907\u4EFD\u4E3A ${corrupted}\uFF0C\u91CD\u7F6E\u4E3A\u7A7A
35844
+ `);
35227
35845
  }
35228
35846
  prefs[key] = value;
35229
35847
  fs.writeFileSync(prefPath, JSON.stringify(prefs, null, 2), "utf-8");
@@ -36654,7 +37272,11 @@ var WsManager = class {
36654
37272
  hint: parsed.code === "60001" ? "\u9891\u9053\u540D\u4E0D\u652F\u6301\u6216\u53C2\u6570\u9519\u8BEF\uFF0C\u8BF7\u68C0\u67E5 channel \u503C" : parsed.code === "60003" ? "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u8FDE" : parsed.code === "60009" ? "\u79C1\u6709\u9891\u9053\u9700\u8981\u5148 login\uFF08API Key \u7B7E\u540D\uFF09" : `OKX WS \u9519\u8BEF ${parsed.code}`
36655
37273
  }
36656
37274
  });
36657
- if (this.events.length > 1e4) this.events = this.events.slice(-5e3);
37275
+ if (this.events.length > 1e4) {
37276
+ if (this.events.length === 10001) process.stderr.write(`[WS] \u26A0\uFE0F \u4E8B\u4EF6\u7F13\u51B2\u5DF2\u6EE1(>10000)\uFF0C\u5F00\u59CB\u622A\u65AD\u65E7\u4E8B\u4EF6
37277
+ `);
37278
+ this.events = this.events.slice(-5e3);
37279
+ }
36658
37280
  return;
36659
37281
  }
36660
37282
  if (parsed.arg && parsed.data) {
@@ -36667,12 +37289,18 @@ var WsManager = class {
36667
37289
  ts: Date.now(),
36668
37290
  data: parsed.data
36669
37291
  });
36670
- if (this.events.length > 1e4) this.events = this.events.slice(-5e3);
37292
+ if (this.events.length > 1e4) {
37293
+ if (this.events.length === 10001) process.stderr.write(`[WS] \u26A0\uFE0F \u4E8B\u4EF6\u7F13\u51B2\u5DF2\u6EE1(>10000)\uFF0C\u5F00\u59CB\u622A\u65AD\u65E7\u4E8B\u4EF6
37294
+ `);
37295
+ this.events = this.events.slice(-5e3);
37296
+ }
36671
37297
  break;
36672
37298
  }
36673
37299
  }
36674
37300
  }
36675
37301
  } catch {
37302
+ process.stderr.write(`[WS] \u26A0\uFE0F \u7578\u5F62 WS \u6D88\u606F: ${raw.toString().slice(0, 80)}
37303
+ `);
36676
37304
  }
36677
37305
  });
36678
37306
  ws.on("error", (err) => {
@@ -36938,6 +37566,8 @@ var AgentHub = class {
36938
37566
  version = "0.0.0";
36939
37567
  port = 0;
36940
37568
  db = null;
37569
+ token = "";
37570
+ // PSK 鉴权令牌
36941
37571
  // ── 持久化绑定 ──
36942
37572
  setDB(db) {
36943
37573
  this.db = db;
@@ -36956,8 +37586,9 @@ var AgentHub = class {
36956
37586
  }
36957
37587
  }
36958
37588
  // ── 启动 ──
36959
- start(port, host = "0.0.0.0", version2 = "0.0.0") {
37589
+ start(port, host = "0.0.0.0", version2 = "0.0.0", token = "") {
36960
37590
  this.version = version2;
37591
+ this.token = token;
36961
37592
  const startWss = (p) => {
36962
37593
  return new Promise((resolve2) => {
36963
37594
  const wss = new import_websocket_server.default({ port: p, host });
@@ -37005,7 +37636,16 @@ var AgentHub = class {
37005
37636
  if (!this.wss) return;
37006
37637
  this.ensureRoom("#lobby");
37007
37638
  this.ensureRoom("#review");
37008
- this.wss.on("connection", (ws) => {
37639
+ this.wss.on("connection", (ws, req) => {
37640
+ if (this.token) {
37641
+ const urlParams = new URL(req.url || "/", "http://localhost").searchParams;
37642
+ const provided = urlParams.get("token") || "";
37643
+ if (provided !== this.token) {
37644
+ ws.send(JSON.stringify({ type: "error", message: "\u672A\u6388\u6743 \u2014 \u8BF7\u5728 WS URL \u4E2D\u643A\u5E26 ?token=..." }));
37645
+ ws.close();
37646
+ return;
37647
+ }
37648
+ }
37009
37649
  let agentId = null;
37010
37650
  ws.on("message", (raw) => {
37011
37651
  try {
@@ -37019,7 +37659,9 @@ var AgentHub = class {
37019
37659
  ws.on("close", () => {
37020
37660
  if (agentId) this.handleDisconnect(agentId);
37021
37661
  });
37022
- ws.on("error", () => {
37662
+ ws.on("error", (e) => {
37663
+ process.stderr.write(`[AgentHub] WS \u9519\u8BEF: ${e.message}
37664
+ `);
37023
37665
  });
37024
37666
  });
37025
37667
  this.heartbeatTimer = setInterval(() => {
@@ -37202,7 +37844,7 @@ var AgentHub = class {
37202
37844
  message: `${agentId} \u5DF2\u5B8C\u6210 ${taskId}\uFF0C\u7B49\u5F85\u5BA1\u6838`
37203
37845
  });
37204
37846
  }
37205
- // ── 注册任务(不派发,仅登记) ──
37847
+ // ── 注册任务(自动派发给空闲 Agent) ──
37206
37848
  registerTask(taskId, title) {
37207
37849
  if (!this.tasks.has(taskId)) {
37208
37850
  this.tasks.set(taskId, { status: "unassigned" });
@@ -37212,6 +37854,17 @@ var AgentHub = class {
37212
37854
  t.title = title;
37213
37855
  }
37214
37856
  console.log(`[AgentHub] \u4EFB\u52A1\u6CE8\u518C: ${taskId} "${title || taskId}"`);
37857
+ this.broadcast({ type: "task:announced", taskId, title: title || taskId });
37858
+ const idleAgents = [...this.agents.entries()].filter(([, a]) => a.status === "idle" && !a.agentId.startsWith("dashboard"));
37859
+ if (idleAgents.length === 0) {
37860
+ console.log(`[AgentHub] \u6CA1\u6709\u7A7A\u95F2 Agent\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85\u624B\u52A8 spawn`);
37861
+ return;
37862
+ }
37863
+ const match = idleAgents.find(([, a]) => a.capabilities.includes(taskId)) || idleAgents[0];
37864
+ if (match) {
37865
+ console.log(`[AgentHub] \u81EA\u52A8\u6D3E\u53D1 ${taskId} \u2192 ${match[1].name} (${match[0]})`);
37866
+ this.dispatchTaskTo(taskId, match[0]);
37867
+ }
37215
37868
  }
37216
37869
  // ── 派发任务 ──
37217
37870
  dispatchTaskTo(taskId, agentId) {
@@ -37405,8 +38058,8 @@ var AgentHub = class {
37405
38058
  }
37406
38059
  };
37407
38060
  var agentHub = new AgentHub();
37408
- function startAgentHub(port, host = "0.0.0.0", version2 = "0.0.0") {
37409
- agentHub.start(port, host, version2);
38061
+ function startAgentHub(port, host = "0.0.0.0", version2 = "0.0.0", token = "") {
38062
+ agentHub.start(port, host, version2, token);
37410
38063
  }
37411
38064
 
37412
38065
  // src/tools/agent-hub.ts
@@ -38080,16 +38733,29 @@ function registerAllTools(server, auth, readOnly) {
38080
38733
  }
38081
38734
  async function startHttp(server, version2, auth, readOnly, skipped) {
38082
38735
  const port = parseInt(process.env.PORT || "3000", 10);
38736
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
38737
+ process.stderr.write(`[hvip] \u274C \u65E0\u6548\u7AEF\u53E3: ${process.env.PORT}\uFF0C\u4F7F\u7528\u9ED8\u8BA4 3000
38738
+ `);
38739
+ }
38083
38740
  const host = process.env.HOST || "127.0.0.1";
38741
+ const token = process.env.MCP_AUTH_TOKEN || "";
38084
38742
  const transport = new StreamableHTTPServerTransport({
38085
38743
  sessionIdGenerator: void 0
38086
38744
  // stateless
38087
38745
  });
38088
38746
  await server.connect(transport);
38089
38747
  const httpServer = (0, import_node_http.createServer)(async (req, res) => {
38090
- res.setHeader("Access-Control-Allow-Origin", "*");
38748
+ if (token && req.url !== "/health") {
38749
+ const provided = req.headers["authorization"]?.replace(/^Bearer\s+/i, "") || "";
38750
+ if (provided !== token) {
38751
+ res.writeHead(401, { "Content-Type": "application/json" });
38752
+ res.end(JSON.stringify({ error: "Unauthorized \u2014 \u8BF7\u5728 Authorization \u5934\u643A\u5E26\u6709\u6548 token" }));
38753
+ return;
38754
+ }
38755
+ }
38756
+ res.setHeader("Access-Control-Allow-Origin", `http://${host}:${port}`);
38091
38757
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
38092
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
38758
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id, Authorization");
38093
38759
  if (req.method === "OPTIONS") {
38094
38760
  res.writeHead(204);
38095
38761
  res.end();
@@ -38119,13 +38785,6 @@ async function startHttp(server, version2, auth, readOnly, skipped) {
38119
38785
  }
38120
38786
  return;
38121
38787
  }
38122
- if (req.method === "GET" && req.url === "/config") {
38123
- res.writeHead(200, { "Content-Type": "application/json" });
38124
- res.end(JSON.stringify({
38125
- claudeApiKey: process.env.CLAUDE_API_KEY || ""
38126
- }));
38127
- return;
38128
- }
38129
38788
  if (req.method === "GET" && req.url === "/health") {
38130
38789
  res.writeHead(200, { "Content-Type": "application/json" });
38131
38790
  res.end(JSON.stringify({
@@ -38203,12 +38862,13 @@ async function startStdio(server, version2, auth, readOnly, skipped, skipLog) {
38203
38862
  `);
38204
38863
  }
38205
38864
  const wsHost = process.env.WS_BIND_HOST || "127.0.0.1";
38206
- startAgentHub(parseInt(process.env.WS_AGENT_PORT || "9321"), wsHost, version2);
38865
+ const wsToken = process.env.HUB_AUTH_TOKEN || "";
38866
+ startAgentHub(parseInt(process.env.WS_AGENT_PORT || "9321"), wsHost, version2, wsToken);
38207
38867
  const transport = new StdioServerTransport();
38208
38868
  await server.connect(transport);
38209
38869
  }
38210
38870
  async function main() {
38211
- const VERSION = "0.3.1";
38871
+ const VERSION = "0.3.2";
38212
38872
  const argv = process.argv.slice(2);
38213
38873
  if (argv.includes("--help") || argv.includes("-h")) printHelpAndExit(VERSION);
38214
38874
  if (argv.includes("--version") || argv.includes("-v")) {