hvip-mcp-server 0.5.0 → 0.6.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.
@@ -3723,7 +3723,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
3723
3723
  // src/utils/logger.ts
3724
3724
  var LEVEL_EMOJI = {
3725
3725
  [0 /* DEBUG */]: "\u{1F50D}",
3726
- [1 /* INFO */]: "",
3726
+ [1 /* INFO */]: "\u2139\uFE0F",
3727
3727
  [2 /* WARN */]: "\u26A0\uFE0F",
3728
3728
  [3 /* ERROR */]: "\u274C",
3729
3729
  [4 /* SILENT */]: ""
@@ -3778,7 +3778,11 @@ var AgentHub = class {
3778
3778
  rooms = /* @__PURE__ */ new Map();
3779
3779
  heartbeatTimer = null;
3780
3780
  version = "0.0.0";
3781
+ startTime = 0;
3781
3782
  port = 0;
3783
+ // public: updated after WS negotiate, read by hub for dashboard URL
3784
+ registryCount = 0;
3785
+ // set by hub-server after HubRegistry loads
3782
3786
  db = null;
3783
3787
  token = "";
3784
3788
  // PSK 鉴权令牌
@@ -3820,6 +3824,7 @@ var AgentHub = class {
3820
3824
  // ── 启动 ──
3821
3825
  start(port, host2 = "0.0.0.0", version = "0.0.0", token2 = "") {
3822
3826
  this.version = version;
3827
+ this.startTime = Date.now();
3823
3828
  this.token = token2;
3824
3829
  const startWss = (p) => {
3825
3830
  return new Promise((resolve) => {
@@ -4415,7 +4420,7 @@ var AgentHub = class {
4415
4420
  branch: t.branch
4416
4421
  }));
4417
4422
  const rooms = this.getRooms();
4418
- return { agents, tasks, rooms, agentCount: agents.length, taskCount: tasks.length };
4423
+ return { agents, tasks, rooms, agentCount: agents.length, taskCount: tasks.length, version: this.version, uptime: this.startTime ? Math.floor((Date.now() - this.startTime) / 1e3) : 0, registry: `${this.registryCount || 24} plugins` };
4419
4424
  }
4420
4425
  // ── 关闭 ──
4421
4426
  close() {
@@ -4986,9 +4991,11 @@ var HubCosts = class {
4986
4991
  const entries = all.filter((e) => e.timestamp.startsWith(slot));
4987
4992
  hourly.push({ hour: slot, calls: entries.length, cost: entries.reduce((s, e) => s + e.cost, 0) });
4988
4993
  }
4994
+ const budget = parseFloat(process.env.HUB_DAILY_BUDGET || "5.00");
4989
4995
  return {
4990
4996
  today: sum(todayEntries),
4991
4997
  total: sum(all),
4998
+ budget,
4992
4999
  byModel,
4993
5000
  byAgent,
4994
5001
  hourly
@@ -5005,11 +5012,29 @@ var HubCosts = class {
5005
5012
  var CircuitBreaker = class {
5006
5013
  circuits = /* @__PURE__ */ new Map();
5007
5014
  failureThreshold = 3;
5008
- cooldownMs = 6e4;
5009
- // 1 minute default
5015
+ baseCooldownMs = 6e4;
5016
+ // 1 minute base
5017
+ maxCooldownMs = 3e5;
5018
+ // 5 minutes max
5019
+ listeners = /* @__PURE__ */ new Set();
5010
5020
  constructor(opts) {
5011
5021
  if (opts?.failureThreshold) this.failureThreshold = opts.failureThreshold;
5012
- if (opts?.cooldownMs) this.cooldownMs = opts.cooldownMs;
5022
+ if (opts?.cooldownMs) this.baseCooldownMs = opts.cooldownMs;
5023
+ if (opts?.maxCooldownMs) this.maxCooldownMs = opts.maxCooldownMs;
5024
+ }
5025
+ /** 注册状态变更监听 */
5026
+ onStateChange(handler) {
5027
+ this.listeners.add(handler);
5028
+ return () => this.listeners.delete(handler);
5029
+ }
5030
+ /** 内部通知 */
5031
+ notify(event, name, entry) {
5032
+ for (const h of this.listeners) {
5033
+ try {
5034
+ h(event, name, entry);
5035
+ } catch {
5036
+ }
5037
+ }
5013
5038
  }
5014
5039
  /** 获取或创建熔断器 */
5015
5040
  get(name) {
@@ -5024,7 +5049,8 @@ var CircuitBreaker = class {
5024
5049
  lastFailure: null,
5025
5050
  lastSuccess: null,
5026
5051
  openedAt: null,
5027
- cooldownMs: this.cooldownMs
5052
+ cooldownMs: this.baseCooldownMs,
5053
+ backoffMultiplier: 1
5028
5054
  };
5029
5055
  this.circuits.set(name, c);
5030
5056
  }
@@ -5039,6 +5065,7 @@ var CircuitBreaker = class {
5039
5065
  const elapsed = Date.now() - new Date(c.openedAt).getTime();
5040
5066
  if (elapsed >= c.cooldownMs) {
5041
5067
  c.state = "HALF_OPEN";
5068
+ this.notify("half_open", name, c);
5042
5069
  return true;
5043
5070
  }
5044
5071
  }
@@ -5052,27 +5079,36 @@ var CircuitBreaker = class {
5052
5079
  if (c.state === "HALF_OPEN") {
5053
5080
  c.state = "CLOSED";
5054
5081
  c.failures = 0;
5082
+ c.backoffMultiplier = 1;
5083
+ this.notify("closed", name, c);
5055
5084
  }
5056
5085
  if (c.state === "CLOSED") {
5057
5086
  c.failures = 0;
5058
5087
  }
5059
5088
  }
5060
- /** 调用失败 */
5089
+ /** 调用失败 — 带指数退避 */
5061
5090
  onFailure(name) {
5062
5091
  const c = this.get(name);
5063
5092
  c.failures++;
5064
5093
  c.failureCount++;
5065
5094
  c.lastFailure = (/* @__PURE__ */ new Date()).toISOString();
5066
5095
  if (c.failures >= this.failureThreshold) {
5096
+ const backoffMs = Math.min(
5097
+ this.baseCooldownMs * Math.pow(2, c.backoffMultiplier - 1),
5098
+ this.maxCooldownMs
5099
+ );
5100
+ c.cooldownMs = backoffMs;
5101
+ c.backoffMultiplier++;
5067
5102
  c.state = "OPEN";
5068
5103
  c.openedAt = (/* @__PURE__ */ new Date()).toISOString();
5104
+ this.notify("opened", name, c);
5069
5105
  }
5070
5106
  }
5071
- /** 包裹一个异步函数 */
5072
- async wrap(name, fn, fallback) {
5107
+ /** 包裹一个异步函数,支持重试和 fallback */
5108
+ async wrap(name, fn, fallback, retries = 0) {
5073
5109
  if (!this.canCall(name)) {
5074
5110
  if (fallback) return fallback();
5075
- throw new Error(`Circuit OPEN: ${name}`);
5111
+ throw new Error(`Circuit OPEN: ${name} (\u51B7\u5374\u4E2D, ${this.get(name).cooldownMs}ms)`);
5076
5112
  }
5077
5113
  try {
5078
5114
  const result = await fn();
@@ -5080,6 +5116,11 @@ var CircuitBreaker = class {
5080
5116
  return result;
5081
5117
  } catch (e) {
5082
5118
  this.onFailure(name);
5119
+ if (retries > 0) {
5120
+ const wait = Math.min(1e3 * Math.pow(2, 3 - retries), 9e3);
5121
+ await new Promise((r) => setTimeout(r, wait));
5122
+ return this.wrap(name, fn, fallback, retries - 1);
5123
+ }
5083
5124
  if (fallback) return fallback();
5084
5125
  throw e;
5085
5126
  }
@@ -5088,16 +5129,31 @@ var CircuitBreaker = class {
5088
5129
  status() {
5089
5130
  return [...this.circuits.values()];
5090
5131
  }
5091
- /** 重置指定熔断器 */
5132
+ /** 重置指定熔断器 — 立即清除状态 */
5092
5133
  reset(name) {
5093
- this.circuits.delete(name);
5134
+ const c = this.circuits.get(name);
5135
+ if (c) {
5136
+ this.notify("reset", name, c);
5137
+ this.circuits.delete(name);
5138
+ }
5139
+ }
5140
+ /** 重置所有熔断器 */
5141
+ resetAll() {
5142
+ for (const [name, c] of this.circuits) {
5143
+ this.notify("reset", name, c);
5144
+ }
5145
+ this.circuits.clear();
5094
5146
  }
5095
5147
  };
5096
- var circuitBreaker = new CircuitBreaker({ failureThreshold: 3, cooldownMs: 6e4 });
5148
+ var circuitBreaker = new CircuitBreaker({
5149
+ failureThreshold: 3,
5150
+ cooldownMs: 6e4,
5151
+ maxCooldownMs: 3e5
5152
+ });
5097
5153
 
5098
5154
  // src/hub-server.ts
5099
- var import_node_fs2 = require("node:fs");
5100
- var import_node_path2 = require("node:path");
5155
+ var import_node_fs3 = require("node:fs");
5156
+ var import_node_path3 = require("node:path");
5101
5157
 
5102
5158
  // src/adapters/hub-templates.ts
5103
5159
  var TASK_TEMPLATES = [
@@ -5934,10 +5990,150 @@ NEXT: <\u540E\u7EED\u8DDF\u8FDB\u5EFA\u8BAE>
5934
5990
  }
5935
5991
  ];
5936
5992
 
5993
+ // src/adapters/api-credits.ts
5994
+ var import_node_sqlite = require("node:sqlite");
5995
+ var import_node_fs2 = require("node:fs");
5996
+ var import_node_path2 = require("node:path");
5997
+ var log5 = logger("Credits");
5998
+ var CREDIT_COST = {
5999
+ READ: 1,
6000
+ WRITE: 5,
6001
+ FUND_TRANSFER: 10,
6002
+ ADMIN: 20
6003
+ };
6004
+ var ApiCredits = class {
6005
+ db;
6006
+ cache = /* @__PURE__ */ new Map();
6007
+ cacheLoaded = false;
6008
+ constructor(dbPath2 = ".hub/api-credits.db") {
6009
+ const dir = (0, import_node_path2.dirname)(dbPath2);
6010
+ if (!(0, import_node_fs2.existsSync)(dir)) (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
6011
+ this.db = new import_node_sqlite.DatabaseSync(dbPath2);
6012
+ this.db.exec("PRAGMA journal_mode=WAL");
6013
+ this.init();
6014
+ }
6015
+ init() {
6016
+ this.db.exec(`
6017
+ CREATE TABLE IF NOT EXISTS api_keys (
6018
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
6019
+ key TEXT UNIQUE NOT NULL,
6020
+ client_name TEXT NOT NULL,
6021
+ credits REAL NOT NULL DEFAULT 0,
6022
+ total_used INTEGER NOT NULL DEFAULT 0,
6023
+ enabled INTEGER NOT NULL DEFAULT 1,
6024
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
6025
+ last_used_at TEXT
6026
+ );
6027
+ CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(key);
6028
+ `);
6029
+ this.loadCache();
6030
+ }
6031
+ loadCache() {
6032
+ const rows = this.db.prepare("SELECT * FROM api_keys").all();
6033
+ for (const r of rows) this.cache.set(r.key, r);
6034
+ this.cacheLoaded = true;
6035
+ log5.info(`\u5DF2\u52A0\u8F7D ${rows.length} \u4E2A API Key`);
6036
+ }
6037
+ /** 查找 Key — 缓存命中或直接查 DB */
6038
+ lookup(key) {
6039
+ if (!this.cacheLoaded) this.loadCache();
6040
+ const cached = this.cache.get(key);
6041
+ if (cached) return cached;
6042
+ const row = this.db.prepare("SELECT * FROM api_keys WHERE key = ?").get(key);
6043
+ if (row) {
6044
+ this.cache.set(key, row);
6045
+ return row;
6046
+ }
6047
+ return null;
6048
+ }
6049
+ /** 校验并扣费 */
6050
+ deduct(key, riskLevel) {
6051
+ const record = this.lookup(key);
6052
+ if (!record) return { ok: false, error: "\u65E0\u6548\u7684 API Key" };
6053
+ if (!record.enabled) return { ok: false, error: "API Key \u5DF2\u7981\u7528" };
6054
+ const cost = CREDIT_COST[riskLevel] || 1;
6055
+ if (record.credits < cost) return { ok: false, error: `\u79EF\u5206\u4E0D\u8DB3 (\u9700\u8981 ${cost}\uFF0C\u5269\u4F59 ${record.credits})` };
6056
+ record.credits -= cost;
6057
+ record.totalUsed++;
6058
+ record.lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
6059
+ this.db.prepare("UPDATE api_keys SET credits = ?, total_used = ?, last_used_at = ? WHERE id = ?").run(record.credits, record.totalUsed, record.lastUsedAt, record.id);
6060
+ return { ok: true, remaining: record.credits };
6061
+ }
6062
+ /** 管理:创建 Key */
6063
+ createKey(clientName, initialCredits = 1e3) {
6064
+ const key = "hvip-" + Array.from(
6065
+ { length: 32 },
6066
+ () => "abcdefghijklmnopqrstuvwxyz0123456789"[Math.floor(Math.random() * 36)]
6067
+ ).join("");
6068
+ const result = this.db.prepare(
6069
+ "INSERT INTO api_keys (key, client_name, credits) VALUES (?, ?, ?)"
6070
+ ).run(key, clientName, initialCredits);
6071
+ const record = {
6072
+ id: result.lastInsertRowid,
6073
+ key,
6074
+ clientName,
6075
+ credits: initialCredits,
6076
+ totalUsed: 0,
6077
+ enabled: true,
6078
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6079
+ lastUsedAt: null
6080
+ };
6081
+ this.cache.set(key, record);
6082
+ return record;
6083
+ }
6084
+ /** 管理:充值 */
6085
+ topUp(key, amount) {
6086
+ const record = this.lookup(key);
6087
+ if (!record) return { ok: false, error: "Key \u4E0D\u5B58\u5728" };
6088
+ if (amount <= 0) return { ok: false, error: "\u5145\u503C\u91D1\u989D\u5FC5\u987B > 0" };
6089
+ record.credits += amount;
6090
+ this.db.prepare("UPDATE api_keys SET credits = ? WHERE id = ?").run(record.credits, record.id);
6091
+ return { ok: true, newBalance: record.credits };
6092
+ }
6093
+ /** 管理:启用/禁用 Key */
6094
+ setEnabled(key, enabled) {
6095
+ const record = this.lookup(key);
6096
+ if (!record) return false;
6097
+ record.enabled = enabled;
6098
+ this.db.prepare("UPDATE api_keys SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, record.id);
6099
+ return true;
6100
+ }
6101
+ /** 管理:列出所有 Key (含 Key 前缀,完整 Key 仅创建时返回) */
6102
+ listAll() {
6103
+ return [...this.cache.values()].map((r) => ({
6104
+ id: r.id,
6105
+ clientName: r.clientName,
6106
+ credits: r.credits,
6107
+ totalUsed: r.totalUsed,
6108
+ enabled: r.enabled,
6109
+ createdAt: r.createdAt,
6110
+ lastUsedAt: r.lastUsedAt,
6111
+ keyPrefix: r.key.slice(0, 12) + "..." + r.key.slice(-8)
6112
+ }));
6113
+ }
6114
+ /** 客户端:查询余额 */
6115
+ getBalance(key) {
6116
+ const record = this.lookup(key);
6117
+ if (!record) return null;
6118
+ return { clientName: record.clientName, credits: record.credits, totalUsed: record.totalUsed };
6119
+ }
6120
+ close() {
6121
+ try {
6122
+ this.db.close();
6123
+ } catch {
6124
+ }
6125
+ }
6126
+ };
6127
+ var instance = null;
6128
+ function getApiCredits(dbPath2) {
6129
+ if (!instance) instance = new ApiCredits(dbPath2);
6130
+ return instance;
6131
+ }
6132
+
5937
6133
  // src/hub-server.ts
5938
- var envPath = (0, import_node_path2.join)(process.cwd(), ".env");
5939
- if ((0, import_node_fs2.existsSync)(envPath)) {
5940
- (0, import_node_fs2.readFileSync)(envPath, "utf-8").split(/\r?\n/).forEach((line) => {
6134
+ var envPath = (0, import_node_path3.join)(process.cwd(), ".env");
6135
+ if ((0, import_node_fs3.existsSync)(envPath)) {
6136
+ (0, import_node_fs3.readFileSync)(envPath, "utf-8").split(/\r?\n/).forEach((line) => {
5941
6137
  const hashIdx = line.indexOf("#");
5942
6138
  if (hashIdx >= 0) line = line.substring(0, hashIdx);
5943
6139
  const m = line.match(/^\s*([^#\s=]+)\s*=\s*(.*)$/);
@@ -5951,11 +6147,11 @@ if ((0, import_node_fs2.existsSync)(envPath)) {
5951
6147
  }
5952
6148
  });
5953
6149
  }
5954
- var VERSION = "0.4.3";
6150
+ var VERSION = "0.6.1";
5955
6151
  function getDashboardHtml(host2, port) {
5956
- const paths = [(0, import_node_path2.join)(__dirname, "web", "dashboard.html"), (0, import_node_path2.join)(__dirname, "..", "src", "web", "dashboard.html")];
6152
+ const paths = [(0, import_node_path3.join)(__dirname, "web", "dashboard.html"), (0, import_node_path3.join)(__dirname, "..", "src", "web", "dashboard.html")];
5957
6153
  for (const p of paths) {
5958
- if ((0, import_node_fs2.existsSync)(p)) return (0, import_node_fs2.readFileSync)(p, "utf-8").replace("HUB_HOST", host2).replace("WS_PORT = 0", "WS_PORT = " + port);
6154
+ if ((0, import_node_fs3.existsSync)(p)) return (0, import_node_fs3.readFileSync)(p, "utf-8").replace("HUB_HOST", host2).replace("WS_PORT = 0", "WS_PORT = " + port);
5959
6155
  }
5960
6156
  return "<html><body><h2>dashboard.html not found</h2></body></html>";
5961
6157
  }
@@ -5970,7 +6166,7 @@ var wsPort = parseInt(flag("port") || process.env.HUB_PORT || "9321", 10);
5970
6166
  var host = flag("host") || process.env.HUB_HOST || "127.0.0.1";
5971
6167
  var webPort = parseInt(flag("web-port") || process.env.HUB_WEB_PORT || "3000", 10);
5972
6168
  var dbPath = flag("db") || process.env.HUB_DB_PATH || ".hub/hub.db";
5973
- var log5 = logger("Hub");
6169
+ var log6 = logger("Hub");
5974
6170
  var anthropicKey = process.env.ANTHROPIC_API_KEY || "";
5975
6171
  var openaiKey = process.env.OPENAI_API_KEY || "";
5976
6172
  var openaiBase = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
@@ -5986,7 +6182,7 @@ var MAX_CONCURRENT_WORKERS = 3;
5986
6182
  function spawnWorker(taskId) {
5987
6183
  const activeWorkers = workers.filter((w) => w.exitCode === null && !w.killed).length;
5988
6184
  if (activeWorkers >= MAX_CONCURRENT_WORKERS) {
5989
- log5.warn(`Worker \u5E76\u53D1\u4E0A\u9650 (${MAX_CONCURRENT_WORKERS})\uFF0C${taskId} \u7B49\u5F85\u4E0B\u6B21\u8C03\u5EA6`);
6185
+ log6.warn(`Worker \u5E76\u53D1\u4E0A\u9650 (${MAX_CONCURRENT_WORKERS})\uFF0C${taskId} \u7B49\u5F85\u4E0B\u6B21\u8C03\u5EA6`);
5990
6186
  return { ok: false, error: `\u5E76\u53D1\u4E0A\u9650 ${MAX_CONCURRENT_WORKERS}\uFF0C\u5F53\u524D ${activeWorkers} \u6D3B\u8DC3` };
5991
6187
  }
5992
6188
  const hubUrl = `ws://127.0.0.1:${wsPort}`;
@@ -6000,23 +6196,23 @@ function spawnWorker(taskId) {
6000
6196
  promptB64 = Buffer.from(p, "utf-8").toString("base64");
6001
6197
  }
6002
6198
  }
6003
- const workerArgs = ["dist/hub-worker.js", "--task", taskId, "--hub", hubUrl, "--repo", repoPath, "--web-port", String(webPort)];
6199
+ const workerArgs = ["dist/hub-worker-v2.js", "--task", taskId, "--hub", hubUrl, "--repo", repoPath, "--web-port", String(webPort)];
6004
6200
  if (promptB64) workerArgs.push("--prompt-b64", promptB64);
6005
6201
  try {
6006
6202
  const worker = (0, import_node_child_process.spawn)("node", workerArgs, { cwd: repoPath, stdio: "pipe", detached: true, windowsHide: true });
6007
6203
  worker.stdout?.on("data", (d) => process.stderr.write(`[Worker-${taskId}] ${d}`));
6008
6204
  worker.stderr?.on("data", (d) => process.stderr.write(`[Worker-${taskId}] ${d}`));
6009
- worker.on("error", (e) => log5.error(`Worker \u542F\u52A8\u5931\u8D25: ${e.message}`));
6205
+ worker.on("error", (e) => log6.error(`Worker \u542F\u52A8\u5931\u8D25: ${e.message}`));
6010
6206
  worker.on("close", (code) => {
6011
- log5.info(`Worker-${taskId} \u9000\u51FA (${code})`);
6207
+ log6.info(`Worker-${taskId} \u9000\u51FA (${code})`);
6012
6208
  const idx = workers.indexOf(worker);
6013
6209
  if (idx >= 0) workers.splice(idx, 1);
6014
6210
  });
6015
6211
  workers.push(worker);
6016
- log5.info(`\u6D3B\u8DC3 Worker: ${workers.length}`);
6212
+ log6.info(`\u6D3B\u8DC3 Worker: ${workers.length}`);
6017
6213
  return { ok: true, workerPid: worker.pid };
6018
6214
  } catch (e) {
6019
- log5.error(`Worker \u542F\u52A8\u5F02\u5E38: ${e.message}`);
6215
+ log6.error(`Worker \u542F\u52A8\u5F02\u5E38: ${e.message}`);
6020
6216
  return { ok: false, error: e.message };
6021
6217
  }
6022
6218
  }
@@ -6049,21 +6245,21 @@ function startHttpServer() {
6049
6245
  return;
6050
6246
  }
6051
6247
  if (_req.method === "GET" && _req.url?.startsWith("/v2/")) {
6052
- const v2Dir = (0, import_node_path2.join)(__dirname, "..", "dashboard-v2", "dist");
6248
+ const v2Dir = (0, import_node_path3.join)(__dirname, "..", "dashboard-v2", "dist");
6053
6249
  let filePath;
6054
6250
  if (_req.url === "/v2/") {
6055
- filePath = (0, import_node_path2.join)(v2Dir, "index.html");
6251
+ filePath = (0, import_node_path3.join)(v2Dir, "index.html");
6056
6252
  } else {
6057
- filePath = (0, import_node_path2.join)(v2Dir, _req.url.replace(/^\/v2\//, ""));
6253
+ filePath = (0, import_node_path3.join)(v2Dir, _req.url.replace(/^\/v2\//, ""));
6058
6254
  }
6059
- if (!(0, import_node_fs2.existsSync)(filePath) || !filePath.includes(".")) {
6060
- filePath = (0, import_node_path2.join)(v2Dir, "index.html");
6255
+ if (!(0, import_node_fs3.existsSync)(filePath) || !filePath.includes(".")) {
6256
+ filePath = (0, import_node_path3.join)(v2Dir, "index.html");
6061
6257
  }
6062
- if ((0, import_node_fs2.existsSync)(filePath)) {
6258
+ if ((0, import_node_fs3.existsSync)(filePath)) {
6063
6259
  const ext = filePath.split(".").pop() || "html";
6064
6260
  const mime = { html: "text/html; charset=utf-8", js: "application/javascript", css: "text/css", svg: "image/svg+xml", png: "image/png", ico: "image/x-icon" };
6065
6261
  res.writeHead(200, { "Content-Type": mime[ext] || "application/octet-stream" });
6066
- res.end((0, import_node_fs2.readFileSync)(filePath));
6262
+ res.end((0, import_node_fs3.readFileSync)(filePath));
6067
6263
  } else {
6068
6264
  res.writeHead(404);
6069
6265
  res.end("dashboard-v2 not found");
@@ -6071,11 +6267,11 @@ function startHttpServer() {
6071
6267
  return;
6072
6268
  }
6073
6269
  if (_req.method === "GET" && _req.url === "/chat") {
6074
- const paths = [(0, import_node_path2.join)(__dirname, "web", "index.html"), (0, import_node_path2.join)(__dirname, "..", "src", "web", "index.html")];
6270
+ const paths = [(0, import_node_path3.join)(__dirname, "web", "index.html"), (0, import_node_path3.join)(__dirname, "..", "src", "web", "index.html")];
6075
6271
  for (const p of paths) {
6076
- if ((0, import_node_fs2.existsSync)(p)) {
6272
+ if ((0, import_node_fs3.existsSync)(p)) {
6077
6273
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
6078
- res.end((0, import_node_fs2.readFileSync)(p, "utf-8"));
6274
+ res.end((0, import_node_fs3.readFileSync)(p, "utf-8"));
6079
6275
  return;
6080
6276
  }
6081
6277
  }
@@ -6117,11 +6313,9 @@ function startHttpServer() {
6117
6313
  }
6118
6314
  agentHub.registerTask(taskId, title || taskId, promptB64 || void 0);
6119
6315
  db?.saveTask({ taskId, status: "unassigned", title: title || taskId });
6120
- let spawned = false;
6121
- let workerPid = 0;
6122
- log5.info(`\u65B0\u4EFB\u52A1: ${taskId} "${title}"${spawned ? " \u{1F916} \u5DF2\u62C9\u8D77" : ""}`);
6316
+ log6.info(`\u65B0\u4EFB\u52A1: ${taskId} "${title}"`);
6123
6317
  res.writeHead(201, { "Content-Type": "application/json; charset=utf-8" });
6124
- res.end(JSON.stringify({ ok: true, taskId, title, spawned, workerPid }));
6318
+ res.end(JSON.stringify({ ok: true, taskId, title }));
6125
6319
  } catch {
6126
6320
  res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
6127
6321
  res.end(JSON.stringify({ error: "JSON \u89E3\u6790\u5931\u8D25" }));
@@ -6176,8 +6370,15 @@ function startHttpServer() {
6176
6370
  res.end(JSON.stringify(entries));
6177
6371
  return;
6178
6372
  }
6179
- if (_req.method === "GET" && _req.url === "/api/memory") {
6180
- const entries = memory.recent(30);
6373
+ if (_req.method === "GET" && (_req.url === "/api/memory" || _req.url?.startsWith("/api/memory?"))) {
6374
+ const url = _req.url.startsWith("/api/memory?") ? new import_node_url.URL(_req.url, `http://${host}:${webPort}`) : null;
6375
+ const type = url?.searchParams.get("type") || "";
6376
+ let entries;
6377
+ if (type && type !== "all" && ["memory", "doc", "directive", "skill", "strategy"].includes(type)) {
6378
+ entries = memory.byType(type, 30);
6379
+ } else {
6380
+ entries = memory.recent(30);
6381
+ }
6181
6382
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6182
6383
  res.end(JSON.stringify(entries));
6183
6384
  return;
@@ -6268,10 +6469,10 @@ function startHttpServer() {
6268
6469
  return;
6269
6470
  }
6270
6471
  if (_req.method === "GET" && _req.url === "/api/traders") {
6271
- const stateFile = (0, import_node_path2.join)(process.cwd(), ".hub", "trader-state.json");
6272
- if ((0, import_node_fs2.existsSync)(stateFile)) {
6472
+ const stateFile = (0, import_node_path3.join)(process.cwd(), ".hub", "trader-state.json");
6473
+ if ((0, import_node_fs3.existsSync)(stateFile)) {
6273
6474
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6274
- res.end((0, import_node_fs2.readFileSync)(stateFile, "utf-8"));
6475
+ res.end((0, import_node_fs3.readFileSync)(stateFile, "utf-8"));
6275
6476
  } else {
6276
6477
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6277
6478
  res.end(JSON.stringify({ traders: {}, history: [], round: 0 }));
@@ -6279,22 +6480,39 @@ function startHttpServer() {
6279
6480
  return;
6280
6481
  }
6281
6482
  if (_req.method === "GET" && _req.url === "/api/traders/leaderboard") {
6282
- const stateFile = (0, import_node_path2.join)(process.cwd(), ".hub", "trader-state.json");
6283
- if ((0, import_node_fs2.existsSync)(stateFile)) {
6284
- const state = JSON.parse((0, import_node_fs2.readFileSync)(stateFile, "utf-8"));
6285
- const rankings = Object.values(state.traders || {}).map((t) => ({
6286
- id: t.id,
6287
- name: t.name,
6288
- emoji: t.emoji,
6289
- title: t.title,
6290
- style: t.style,
6291
- capital: t.capital,
6292
- totalPnl: t.totalPnl,
6293
- totalPnlPct: t.totalPnlPct,
6294
- tradeCount: t.tradeCount || 0,
6295
- winCount: t.winCount || 0,
6296
- openPositions: (t.openPositions || []).filter((p) => !p.closed).length
6297
- })).sort((a, b) => b.totalPnlPct - a.totalPnlPct);
6483
+ const stateFile = (0, import_node_path3.join)(process.cwd(), ".hub", "trader-state.json");
6484
+ if ((0, import_node_fs3.existsSync)(stateFile)) {
6485
+ const state = JSON.parse((0, import_node_fs3.readFileSync)(stateFile, "utf-8"));
6486
+ const rankings = Object.values(state.traders || {}).map((t) => {
6487
+ const positions = (t.openPositions || []).filter((p) => !p.closed);
6488
+ const initialCapital = 1e4;
6489
+ return {
6490
+ id: t.id,
6491
+ name: t.name,
6492
+ emoji: t.emoji,
6493
+ capital: t.capital,
6494
+ equity: t.equity || t.capital,
6495
+ totalPnl: t.totalPnl,
6496
+ totalPnlPct: t.totalPnlPct,
6497
+ unrealizedPnl: t.unrealizedPnl || 0,
6498
+ tradeCount: t.tradeCount || 0,
6499
+ winCount: t.winCount || 0,
6500
+ totalFees: t.totalFees || 0,
6501
+ totalFunding: t.totalFunding || 0,
6502
+ openPositions: positions.length,
6503
+ positions: positions.map((p) => ({
6504
+ symbol: p.symbol,
6505
+ direction: p.direction,
6506
+ leverage: p.leverage,
6507
+ entryPrice: p.entryPrice,
6508
+ currentPrice: p.currentPrice,
6509
+ margin: p.margin,
6510
+ liquidationPrice: p.liquidationPrice,
6511
+ unrealizedPnl: p.unrealizedPnl,
6512
+ unrealizedPnlPct: p.unrealizedPnlPct
6513
+ }))
6514
+ };
6515
+ }).sort((a, b) => b.totalPnlPct - a.totalPnlPct);
6298
6516
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6299
6517
  res.end(JSON.stringify(rankings));
6300
6518
  } else {
@@ -6303,11 +6521,48 @@ function startHttpServer() {
6303
6521
  }
6304
6522
  return;
6305
6523
  }
6524
+ if (_req.method === "GET" && _req.url?.startsWith("/api/traders/history")) {
6525
+ const url = new import_node_url.URL(_req.url, `http://${host}:${webPort}`);
6526
+ const traderId = url.searchParams.get("trader") || "";
6527
+ const limit = parseInt(url.searchParams.get("limit") || "50");
6528
+ const histFile = (0, import_node_path3.join)(process.cwd(), ".hub", "trade-history.jsonl");
6529
+ if ((0, import_node_fs3.existsSync)(histFile)) {
6530
+ try {
6531
+ const lines = (0, import_node_fs3.readFileSync)(histFile, "utf-8").trim().split("\n");
6532
+ let entries = lines.map((l) => {
6533
+ try {
6534
+ return JSON.parse(l);
6535
+ } catch {
6536
+ return null;
6537
+ }
6538
+ }).filter(Boolean);
6539
+ if (traderId) entries = entries.filter((e) => e.traderId === traderId);
6540
+ entries = entries.slice(-limit);
6541
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6542
+ res.end(JSON.stringify(entries));
6543
+ } catch {
6544
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6545
+ res.end(JSON.stringify([]));
6546
+ }
6547
+ } else {
6548
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6549
+ res.end(JSON.stringify([]));
6550
+ }
6551
+ return;
6552
+ }
6553
+ if (_req.method === "GET" && _req.url === "/api/traders/risk") {
6554
+ const mode = process.env.AI_TRADER_MODE || "simulate";
6555
+ const maxOrder = parseFloat(process.env.TRADER_MAX_ORDER_USD || "100");
6556
+ const dailyLimit = parseFloat(process.env.TRADER_DAILY_LOSS_LIMIT || "500");
6557
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6558
+ res.end(JSON.stringify({ mode, maxOrderUsd: maxOrder, dailyLossLimit: dailyLimit }));
6559
+ return;
6560
+ }
6306
6561
  if (_req.method === "GET" && _req.url === "/api/signals") {
6307
- const sigFile = (0, import_node_path2.join)(process.cwd(), ".hub", "signals.json");
6308
- if ((0, import_node_fs2.existsSync)(sigFile)) {
6562
+ const sigFile = (0, import_node_path3.join)(process.cwd(), ".hub", "signals.json");
6563
+ if ((0, import_node_fs3.existsSync)(sigFile)) {
6309
6564
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6310
- res.end((0, import_node_fs2.readFileSync)(sigFile, "utf-8"));
6565
+ res.end((0, import_node_fs3.readFileSync)(sigFile, "utf-8"));
6311
6566
  } else {
6312
6567
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6313
6568
  res.end(JSON.stringify({ signals: [], lastRun: null }));
@@ -6392,6 +6647,86 @@ function startHttpServer() {
6392
6647
  res.end(JSON.stringify({ circuits, openCount, healthy: openCount === 0 }));
6393
6648
  return;
6394
6649
  }
6650
+ const apiCredits = getApiCredits();
6651
+ if (_req.method === "GET" && _req.url === "/api/credits/balance") {
6652
+ const key = _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") || "";
6653
+ const balance = apiCredits.getBalance(key);
6654
+ if (!balance) {
6655
+ res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
6656
+ res.end(JSON.stringify({ error: "\u65E0\u6548\u7684 API Key" }));
6657
+ } else {
6658
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6659
+ res.end(JSON.stringify(balance));
6660
+ }
6661
+ return;
6662
+ }
6663
+ if (_req.method === "GET" && _req.url === "/api/admin/keys") {
6664
+ if (token && _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") !== token) {
6665
+ res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
6666
+ res.end(JSON.stringify({ error: "\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650" }));
6667
+ return;
6668
+ }
6669
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6670
+ res.end(JSON.stringify(apiCredits.listAll()));
6671
+ return;
6672
+ }
6673
+ if (_req.method === "POST" && _req.url === "/api/admin/keys") {
6674
+ if (token && _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") !== token) {
6675
+ res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
6676
+ res.end(JSON.stringify({ error: "\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650" }));
6677
+ return;
6678
+ }
6679
+ const chunks = [];
6680
+ _req.on("data", (c) => chunks.push(c));
6681
+ _req.on("end", () => {
6682
+ try {
6683
+ const { clientName, initialCredits } = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
6684
+ if (!clientName) {
6685
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
6686
+ res.end(JSON.stringify({ error: "\u7F3A\u5C11 clientName" }));
6687
+ return;
6688
+ }
6689
+ const record = apiCredits.createKey(clientName, initialCredits || 1e3);
6690
+ res.writeHead(201, { "Content-Type": "application/json; charset=utf-8" });
6691
+ res.end(JSON.stringify(record));
6692
+ } catch {
6693
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
6694
+ res.end(JSON.stringify({ error: "JSON \u89E3\u6790\u5931\u8D25" }));
6695
+ }
6696
+ });
6697
+ return;
6698
+ }
6699
+ if (_req.method === "POST" && _req.url === "/api/admin/keys/topup") {
6700
+ if (token && _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") !== token) {
6701
+ res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
6702
+ res.end(JSON.stringify({ error: "\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650" }));
6703
+ return;
6704
+ }
6705
+ const chunks = [];
6706
+ _req.on("data", (c) => chunks.push(c));
6707
+ _req.on("end", () => {
6708
+ try {
6709
+ const { key, amount } = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
6710
+ if (!key || !amount) {
6711
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
6712
+ res.end(JSON.stringify({ error: "\u7F3A\u5C11 key \u6216 amount" }));
6713
+ return;
6714
+ }
6715
+ const result = apiCredits.topUp(key, amount);
6716
+ if (!result.ok) {
6717
+ res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
6718
+ res.end(JSON.stringify({ error: result.error }));
6719
+ return;
6720
+ }
6721
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6722
+ res.end(JSON.stringify({ ok: true, newBalance: result.newBalance }));
6723
+ } catch {
6724
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
6725
+ res.end(JSON.stringify({ error: "JSON \u89E3\u6790\u5931\u8D25" }));
6726
+ }
6727
+ });
6728
+ return;
6729
+ }
6395
6730
  if (_req.method === "POST" && _req.url === "/mcp") {
6396
6731
  const chunks = [];
6397
6732
  _req.on("data", (c) => chunks.push(c));
@@ -6422,6 +6757,7 @@ function startHttpServer() {
6422
6757
  }
6423
6758
  result = JSON.stringify(parsed);
6424
6759
  } catch {
6760
+ log6.warn("MCP \u54CD\u5E94\u683C\u5F0F\u89E3\u6790\u5931\u8D25");
6425
6761
  }
6426
6762
  }
6427
6763
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
@@ -6516,8 +6852,15 @@ function startHttpServer() {
6516
6852
  res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
6517
6853
  res.end(JSON.stringify({ error: "Not Found" }));
6518
6854
  });
6855
+ httpServer.on("error", (err) => {
6856
+ if (err.code === "EADDRINUSE") {
6857
+ log6.error(`\u7AEF\u53E3 ${webPort} \u88AB\u5360\u7528\uFF0C\u5C1D\u8BD5 ${webPort + 1}`);
6858
+ } else {
6859
+ log6.error(`HTTP Server \u9519\u8BEF: ${err.message}`);
6860
+ }
6861
+ });
6519
6862
  httpServer.listen(webPort, host, () => {
6520
- log5.info(`\u{1F310} \u4EEA\u8868\u76D8 \u2192 http://${host}:${webPort}`);
6863
+ log6.info(`\u{1F310} \u4EEA\u8868\u76D8 \u2192 http://${host}:${webPort}`);
6521
6864
  });
6522
6865
  }
6523
6866
  var schedules = [
@@ -6565,6 +6908,17 @@ var schedules = [
6565
6908
  failCount: 0,
6566
6909
  timer: null
6567
6910
  },
6911
+ {
6912
+ id: "sched-signals",
6913
+ name: "\u{1F6A6} VBT\u4FE1\u53F7\u5237\u65B0",
6914
+ interval: 4 * 36e5,
6915
+ template: "vbt-signal-refresh",
6916
+ params: { script: "scripts/refresh-signals.mjs" },
6917
+ lastRun: 0,
6918
+ count: 0,
6919
+ failCount: 0,
6920
+ timer: null
6921
+ },
6568
6922
  // ════════ 24x7 守护岗 ════════
6569
6923
  {
6570
6924
  id: "guard-dashboard",
@@ -6650,7 +7004,7 @@ function runScheduledJob(job) {
6650
7004
  (t) => t.status === "assigned"
6651
7005
  ).filter((t) => t.taskId.startsWith(job.id + "-"));
6652
7006
  if (prevTasks.length > 0) {
6653
- log5.warn(`Scheduler: \u23ED\uFE0F ${job.name} \u4E0A\u6B21\u4EFB\u52A1\u672A\u5B8C\u6210 (${prevTasks.map((t) => t.taskId).join(",")})\uFF0C\u8DF3\u8FC7`);
7007
+ log6.warn(`Scheduler: \u23ED\uFE0F ${job.name} \u4E0A\u6B21\u4EFB\u52A1\u672A\u5B8C\u6210 (${prevTasks.map((t) => t.taskId).join(",")})\uFF0C\u8DF3\u8FC7`);
6654
7008
  scheduleNext(job);
6655
7009
  return;
6656
7010
  }
@@ -6659,17 +7013,33 @@ function runScheduledJob(job) {
6659
7013
  ).filter((t) => t.taskId.startsWith(job.id + "-"));
6660
7014
  for (const o of orphans) {
6661
7015
  db?.saveTask({ taskId: o.taskId, status: "done", result: "[Scheduler] \u81EA\u52A8\u6E05\u7406\u5B64\u513F\u4EFB\u52A1" });
6662
- log5.warn(`Scheduler: \u{1F9F9} \u6E05\u7406\u5B64\u513F: ${o.taskId}`);
7016
+ log6.warn(`Scheduler: \u{1F9F9} \u6E05\u7406\u5B64\u513F: ${o.taskId}`);
6663
7017
  }
6664
7018
  const taskId = `${job.id}-${Date.now().toString(36)}`;
6665
7019
  const title = `[\u5B9A\u65F6] ${job.name} #${job.count + 1}`;
6666
- log5.info(`Scheduler: \u23F0 ${job.name} \u2192 ${taskId}`);
7020
+ if (job.template === "vbt-signal-refresh") {
7021
+ log6.info(`Scheduler: \u{1F504} ${job.name}`);
7022
+ try {
7023
+ const { execSync: execSync2 } = require("node:child_process");
7024
+ const out = execSync2(`node scripts/refresh-signals.mjs`, { cwd: process.cwd(), encoding: "utf-8", timeout: 6e4, windowsHide: true, maxBuffer: 2e5 });
7025
+ log6.info(`Scheduler: ${job.name} \u5B8C\u6210 \u2014 ${out.trim().split("\n").length} \u884C\u8F93\u51FA`);
7026
+ } catch (e) {
7027
+ log6.error(`Scheduler: ${job.name} \u5931\u8D25: ${e.message}`);
7028
+ job.failCount++;
7029
+ }
7030
+ job.count++;
7031
+ job.lastRun = Date.now();
7032
+ scheduleNext(job);
7033
+ return;
7034
+ }
7035
+ log6.info(`Scheduler: \u23F0 ${job.name} \u2192 ${taskId}`);
6667
7036
  const tpl = TASK_TEMPLATES.find((t) => t.id === job.template);
6668
7037
  let promptB64 = "";
6669
7038
  if (tpl) {
6670
7039
  try {
6671
7040
  promptB64 = Buffer.from(tpl.buildPrompt(job.params), "utf-8").toString("base64");
6672
7041
  } catch {
7042
+ log6.warn(`promptB64 \u6784\u5EFA\u5931\u8D25: ${job.taskId}`);
6673
7043
  }
6674
7044
  }
6675
7045
  taskMeta.set(taskId, { templateId: job.template, params: job.params });
@@ -6686,7 +7056,7 @@ function scheduleNext(job) {
6686
7056
  }
6687
7057
  function startScheduler() {
6688
7058
  for (const job of schedules) scheduleNext(job);
6689
- log5.info(`Scheduler: ${schedules.length} \u4E2A\u5B9A\u65F6\u4EFB\u52A1 (\u9996\u6B2130s\u540E\u89E6\u53D1\uFF0CChronos \u2192 V2 Worker \u96F6\u5F39\u7A97)`);
7059
+ log6.info(`Scheduler: ${schedules.length} \u4E2A\u5B9A\u65F6\u4EFB\u52A1 (\u9996\u6B2130s\u540E\u89E6\u53D1\uFF0CChronos \u2192 V2 Worker \u96F6\u5F39\u7A97)`);
6690
7060
  }
6691
7061
  var banner = [
6692
7062
  `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`,
@@ -6701,38 +7071,47 @@ var db = new HubDB(dbPath);
6701
7071
  if (db.open()) {
6702
7072
  agentHub.setDB(db);
6703
7073
  const stats = db.stats();
6704
- log5.info(`DB \u72B6\u6001: ${stats.taskCount} tasks, ${stats.messageCount} messages`);
7074
+ log6.info(`DB \u72B6\u6001: ${stats.taskCount} tasks, ${stats.messageCount} messages`);
6705
7075
  }
6706
7076
  var memoryPath = flag("memory-db") || process.env.HUB_MEMORY_DB || ".hub/memory.db";
6707
7077
  var memory = new HubMemory(memoryPath);
6708
7078
  var memOk = memory.open();
6709
7079
  if (memOk) {
6710
7080
  const ms = memory.stats();
6711
- log5.info(`\u{1F9E0} \u8BB0\u5FC6: ${ms.total} \u6761 (doc:${ms.byType.doc || 0} directive:${ms.byType.directive || 0} memory:${ms.byType.memory || 0} skill:${ms.byType.skill || 0})`);
7081
+ log6.info(`\u{1F9E0} \u8BB0\u5FC6: ${ms.total} \u6761 (doc:${ms.byType.doc || 0} directive:${ms.byType.directive || 0} memory:${ms.byType.memory || 0} skill:${ms.byType.skill || 0})`);
6712
7082
  }
6713
7083
  var registryPath = flag("registry-db") || process.env.HUB_REGISTRY_DB || ".hub/registry.db";
6714
7084
  var registry = new HubRegistry(registryPath);
6715
7085
  var regOk = registry.open();
6716
- if (!regOk) {
6717
- log5.warn(`\u26A0\uFE0F Registry \u6253\u5F00\u5931\u8D25\uFF0C\u5546\u5E97\u529F\u80FD\u4E0D\u53EF\u7528`);
7086
+ if (regOk) {
7087
+ agentHub.registryCount = registry.all().length;
7088
+ } else {
7089
+ log6.warn(`\u26A0\uFE0F Registry \u6253\u5F00\u5931\u8D25\uFF0C\u5546\u5E97\u529F\u80FD\u4E0D\u53EF\u7528`);
6718
7090
  }
6719
7091
  var costsPath = flag("costs-db") || process.env.HUB_COSTS_DB || ".hub/llm-costs.jsonl";
6720
7092
  var costTracker = new HubCosts(costsPath);
6721
7093
  agentHub.setCostTracker(costTracker);
6722
- log5.info(`\u{1F4B0} \u6210\u672C\u8FFD\u8E2A: ${costsPath}`);
7094
+ log6.info(`\u{1F4B0} \u6210\u672C\u8FFD\u8E2A: ${costsPath}`);
6723
7095
  startHttpServer();
6724
7096
  startScheduler();
6725
7097
  var mcpProcess = null;
6726
- try {
6727
- mcpProcess = (0, import_node_child_process.spawn)("node", ["dist/index.js", "start:http"], { cwd: process.cwd(), stdio: "pipe", detached: true, env: { ...process.env, PORT: "9222" } });
6728
- mcpProcess.stderr?.on("data", (d) => process.stderr.write(d));
6729
- log5.info("\u{1F6E0} MCP Server \u542F\u52A8\u4E2D \u2192 http://127.0.0.1:9222");
6730
- } catch {
6731
- log5.warn("\u26A0\uFE0F MCP Server \u542F\u52A8\u5931\u8D25");
7098
+ if (process.env.HUB_SPAWN_MCP === "1") {
7099
+ try {
7100
+ mcpProcess = (0, import_node_child_process.spawn)("node", ["dist/index.js", "start:http"], { cwd: process.cwd(), stdio: "pipe", detached: true, windowsHide: true, env: { ...process.env, PORT: "9222" } });
7101
+ mcpProcess.stderr?.on("data", (d) => process.stderr.write(d));
7102
+ log6.info("\u{1F6E0} MCP Server \u542F\u52A8\u4E2D \u2192 http://127.0.0.1:9222");
7103
+ } catch {
7104
+ log6.warn("\u26A0\uFE0F MCP Server \u542F\u52A8\u5931\u8D25");
7105
+ }
7106
+ } else {
7107
+ log6.info("\u{1F6E0} MCP Server \u7531 PM2 \u72EC\u7ACB\u7BA1\u7406 (hvip-mcp :9222)\uFF0C\u8DF3\u8FC7\u5B50\u8FDB\u7A0B\u6D3E\u751F");
6732
7108
  }
6733
7109
  agentHub.start(wsPort, host, VERSION, token);
7110
+ setTimeout(() => {
7111
+ wsPort = agentHub.port || wsPort;
7112
+ }, 2e3);
6734
7113
  function shutdown() {
6735
- log5.info("\u6B63\u5728\u5173\u95ED...");
7114
+ log6.info("\u6B63\u5728\u5173\u95ED...");
6736
7115
  for (const w of workers) {
6737
7116
  try {
6738
7117
  w.kill();