hvip-mcp-server 0.3.2 → 0.4.1

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
@@ -27282,7 +27282,6 @@ function classifyRisk(toolNameOrDesc) {
27282
27282
  const readSpecials = [
27283
27283
  "agent_simulate_order",
27284
27284
  "okx_preflight_check",
27285
- "okx_agent_feedback",
27286
27285
  "agent_catalog",
27287
27286
  "agent_catalog_detail",
27288
27287
  "agent_hub_status",
@@ -27305,12 +27304,12 @@ function classifyRisk(toolNameOrDesc) {
27305
27304
  "codegraph_status",
27306
27305
  "codegraph_query",
27307
27306
  "agent_get_preference",
27308
- "agent_set_preference",
27309
27307
  "agent_simulate_transfer",
27310
27308
  "agent_read_only_trade",
27311
27309
  "okx_event_instruments"
27312
27310
  ];
27313
27311
  if (readSpecials.includes(toolName)) return "READ";
27312
+ if (["okx_agent_feedback", "agent_set_preference"].includes(toolName)) return "WRITE";
27314
27313
  if (toolName.startsWith("xlayer_call")) return "WRITE";
27315
27314
  return "READ";
27316
27315
  }
@@ -28517,25 +28516,6 @@ function registerAccountTools(server, auth) {
28517
28516
  }
28518
28517
  }
28519
28518
  );
28520
- registerTool(
28521
- server,
28522
- "okx_get_order",
28523
- "READ",
28524
- "CAT:[\u8D26\u6237] | \u2192 \u8BF7\u5148\u8C03\u7528 agent_catalog",
28525
- {
28526
- instId: external_exports.string().describe("\u4EA7\u54C1ID"),
28527
- ordId: external_exports.string().describe("\u8BA2\u5355ID")
28528
- },
28529
- async ({ instId, ordId }) => {
28530
- if (!auth) return toError(AUTH_REQUIRED);
28531
- try {
28532
- const data = await privateApi.getOrder(auth, instId, ordId);
28533
- return toResult(data);
28534
- } catch (e) {
28535
- return toError(e);
28536
- }
28537
- }
28538
- );
28539
28519
  registerTool(
28540
28520
  server,
28541
28521
  "okx_get_orders_history",
@@ -34709,8 +34689,18 @@ function registerAgentUtils(server, auth) {
34709
34689
  },
34710
34690
  async ({ title, what, tools, pain, suggestion }) => {
34711
34691
  try {
34712
- const logDir = process.env.OKX_FEEDBACK_DIR || os.homedir();
34692
+ const logDir = path.join(os.homedir(), ".hvip");
34693
+ fs.mkdirSync(logDir, { recursive: true });
34713
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
+ }
34714
34704
  const entry = JSON.stringify({
34715
34705
  time: (/* @__PURE__ */ new Date()).toISOString(),
34716
34706
  title,
@@ -35185,11 +35175,615 @@ function registerAgentUtils(server, auth) {
35185
35175
  }
35186
35176
  }
35187
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
+ );
35188
35782
  registerTool(
35189
35783
  server,
35190
35784
  "agent_get_preference",
35191
35785
  "READ",
35192
- '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",
35193
35787
  {
35194
35788
  key: external_exports.string().optional().describe("\u504F\u597D\u952E\u540D\uFF0C\u4E0D\u586B\u8FD4\u56DE\u5168\u90E8")
35195
35789
  },
@@ -35201,7 +35795,14 @@ function registerAgentUtils(server, auth) {
35201
35795
  if (fs.existsSync(prefPath)) {
35202
35796
  prefs = JSON.parse(fs.readFileSync(prefPath, "utf-8"));
35203
35797
  }
35204
- } 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
+ `);
35205
35806
  }
35206
35807
  const result = key ? { key, value: prefs[key] ?? null, found: key in prefs } : { all: prefs, count: Object.keys(prefs).length };
35207
35808
  return toResult({
@@ -35233,7 +35834,14 @@ function registerAgentUtils(server, auth) {
35233
35834
  if (fs.existsSync(prefPath)) {
35234
35835
  prefs = JSON.parse(fs.readFileSync(prefPath, "utf-8"));
35235
35836
  }
35236
- } 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
+ `);
35237
35845
  }
35238
35846
  prefs[key] = value;
35239
35847
  fs.writeFileSync(prefPath, JSON.stringify(prefs, null, 2), "utf-8");
@@ -36691,6 +37299,8 @@ var WsManager = class {
36691
37299
  }
36692
37300
  }
36693
37301
  } catch {
37302
+ process.stderr.write(`[WS] \u26A0\uFE0F \u7578\u5F62 WS \u6D88\u606F: ${raw.toString().slice(0, 80)}
37303
+ `);
36694
37304
  }
36695
37305
  });
36696
37306
  ws.on("error", (err) => {
@@ -36956,6 +37566,8 @@ var AgentHub = class {
36956
37566
  version = "0.0.0";
36957
37567
  port = 0;
36958
37568
  db = null;
37569
+ token = "";
37570
+ // PSK 鉴权令牌
36959
37571
  // ── 持久化绑定 ──
36960
37572
  setDB(db) {
36961
37573
  this.db = db;
@@ -36974,8 +37586,9 @@ var AgentHub = class {
36974
37586
  }
36975
37587
  }
36976
37588
  // ── 启动 ──
36977
- start(port, host = "0.0.0.0", version2 = "0.0.0") {
37589
+ start(port, host = "0.0.0.0", version2 = "0.0.0", token = "") {
36978
37590
  this.version = version2;
37591
+ this.token = token;
36979
37592
  const startWss = (p) => {
36980
37593
  return new Promise((resolve2) => {
36981
37594
  const wss = new import_websocket_server.default({ port: p, host });
@@ -37023,7 +37636,16 @@ var AgentHub = class {
37023
37636
  if (!this.wss) return;
37024
37637
  this.ensureRoom("#lobby");
37025
37638
  this.ensureRoom("#review");
37026
- 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
+ }
37027
37649
  let agentId = null;
37028
37650
  ws.on("message", (raw) => {
37029
37651
  try {
@@ -37037,7 +37659,9 @@ var AgentHub = class {
37037
37659
  ws.on("close", () => {
37038
37660
  if (agentId) this.handleDisconnect(agentId);
37039
37661
  });
37040
- ws.on("error", () => {
37662
+ ws.on("error", (e) => {
37663
+ process.stderr.write(`[AgentHub] WS \u9519\u8BEF: ${e.message}
37664
+ `);
37041
37665
  });
37042
37666
  });
37043
37667
  this.heartbeatTimer = setInterval(() => {
@@ -37220,7 +37844,7 @@ var AgentHub = class {
37220
37844
  message: `${agentId} \u5DF2\u5B8C\u6210 ${taskId}\uFF0C\u7B49\u5F85\u5BA1\u6838`
37221
37845
  });
37222
37846
  }
37223
- // ── 注册任务(不派发,仅登记) ──
37847
+ // ── 注册任务(自动派发给空闲 Agent) ──
37224
37848
  registerTask(taskId, title) {
37225
37849
  if (!this.tasks.has(taskId)) {
37226
37850
  this.tasks.set(taskId, { status: "unassigned" });
@@ -37230,6 +37854,17 @@ var AgentHub = class {
37230
37854
  t.title = title;
37231
37855
  }
37232
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
+ }
37233
37868
  }
37234
37869
  // ── 派发任务 ──
37235
37870
  dispatchTaskTo(taskId, agentId) {
@@ -37423,8 +38058,8 @@ var AgentHub = class {
37423
38058
  }
37424
38059
  };
37425
38060
  var agentHub = new AgentHub();
37426
- function startAgentHub(port, host = "0.0.0.0", version2 = "0.0.0") {
37427
- 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);
37428
38063
  }
37429
38064
 
37430
38065
  // src/tools/agent-hub.ts
@@ -38098,16 +38733,29 @@ function registerAllTools(server, auth, readOnly) {
38098
38733
  }
38099
38734
  async function startHttp(server, version2, auth, readOnly, skipped) {
38100
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
+ }
38101
38740
  const host = process.env.HOST || "127.0.0.1";
38741
+ const token = process.env.MCP_AUTH_TOKEN || "";
38102
38742
  const transport = new StreamableHTTPServerTransport({
38103
38743
  sessionIdGenerator: void 0
38104
38744
  // stateless
38105
38745
  });
38106
38746
  await server.connect(transport);
38107
38747
  const httpServer = (0, import_node_http.createServer)(async (req, res) => {
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
+ }
38108
38756
  res.setHeader("Access-Control-Allow-Origin", `http://${host}:${port}`);
38109
38757
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
38110
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
38758
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id, Authorization");
38111
38759
  if (req.method === "OPTIONS") {
38112
38760
  res.writeHead(204);
38113
38761
  res.end();
@@ -38214,12 +38862,13 @@ async function startStdio(server, version2, auth, readOnly, skipped, skipLog) {
38214
38862
  `);
38215
38863
  }
38216
38864
  const wsHost = process.env.WS_BIND_HOST || "127.0.0.1";
38217
- 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);
38218
38867
  const transport = new StdioServerTransport();
38219
38868
  await server.connect(transport);
38220
38869
  }
38221
38870
  async function main() {
38222
- const VERSION = "0.3.1";
38871
+ const VERSION = "0.3.2";
38223
38872
  const argv = process.argv.slice(2);
38224
38873
  if (argv.includes("--help") || argv.includes("-h")) printHelpAndExit(VERSION);
38225
38874
  if (argv.includes("--version") || argv.includes("-v")) {