hvip-mcp-server 0.4.3 → 0.6.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
@@ -4805,11 +4805,11 @@ var require_core = __commonJS({
4805
4805
  Ajv2.ValidationError = validation_error_1.default;
4806
4806
  Ajv2.MissingRefError = ref_error_1.default;
4807
4807
  exports2.default = Ajv2;
4808
- function checkOptions(checkOpts, options, msg, log = "error") {
4808
+ function checkOptions(checkOpts, options, msg, log4 = "error") {
4809
4809
  for (const key in checkOpts) {
4810
4810
  const opt = key;
4811
4811
  if (opt in options)
4812
- this.logger[log](`${msg}: option ${key}. ${checkOpts[opt]}`);
4812
+ this.logger[log4](`${msg}: option ${key}. ${checkOpts[opt]}`);
4813
4813
  }
4814
4814
  }
4815
4815
  function getSchEnv(keyRef) {
@@ -4856,13 +4856,13 @@ var require_core = __commonJS({
4856
4856
  }, warn() {
4857
4857
  }, error() {
4858
4858
  } };
4859
- function getLogger(logger) {
4860
- if (logger === false)
4859
+ function getLogger(logger2) {
4860
+ if (logger2 === false)
4861
4861
  return noLogs;
4862
- if (logger === void 0)
4862
+ if (logger2 === void 0)
4863
4863
  return console;
4864
- if (logger.log && logger.warn && logger.error)
4865
- return logger;
4864
+ if (logger2.log && logger2.warn && logger2.error)
4865
+ return logger2;
4866
4866
  throw new Error("logger must implement log, warn and error methods");
4867
4867
  }
4868
4868
  var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i;
@@ -10980,10 +10980,94 @@ var require_websocket_server = __commonJS({
10980
10980
  );
10981
10981
  })();
10982
10982
 
10983
+ // src/dns-fix.ts
10984
+ var import_node_dns = __toESM(require("node:dns"));
10985
+
10986
+ // src/utils/logger.ts
10987
+ var LEVEL_EMOJI = {
10988
+ [0 /* DEBUG */]: "\u{1F50D}",
10989
+ [1 /* INFO */]: "\u2139\uFE0F",
10990
+ [2 /* WARN */]: "\u26A0\uFE0F",
10991
+ [3 /* ERROR */]: "\u274C",
10992
+ [4 /* SILENT */]: ""
10993
+ };
10994
+ function resolveLevel() {
10995
+ const raw = (process.env.LOG_LEVEL || "").toUpperCase();
10996
+ if (raw === "DEBUG") return 0 /* DEBUG */;
10997
+ if (raw === "WARN" || raw === "WARNING") return 2 /* WARN */;
10998
+ if (raw === "ERROR") return 3 /* ERROR */;
10999
+ if (raw === "SILENT" || raw === "OFF") return 4 /* SILENT */;
11000
+ return 1 /* INFO */;
11001
+ }
11002
+ var currentLevel = resolveLevel();
11003
+ var LoggerImpl = class {
11004
+ constructor(tag) {
11005
+ this.tag = tag;
11006
+ }
11007
+ tag;
11008
+ debug(msg) {
11009
+ this.write(0 /* DEBUG */, msg);
11010
+ }
11011
+ info(msg) {
11012
+ this.write(1 /* INFO */, msg);
11013
+ }
11014
+ warn(msg) {
11015
+ this.write(2 /* WARN */, msg);
11016
+ }
11017
+ error(msg) {
11018
+ this.write(3 /* ERROR */, msg);
11019
+ }
11020
+ write(level, msg) {
11021
+ if (level < currentLevel) return;
11022
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
11023
+ const emoji2 = LEVEL_EMOJI[level];
11024
+ const tag = this.tag ? `[${this.tag}]` : "";
11025
+ const prefix = emoji2 ? `${emoji2} ${tag}` : tag;
11026
+ process.stderr.write(`${ts} ${prefix} ${msg}
11027
+ `);
11028
+ }
11029
+ };
11030
+ function logger(tag) {
11031
+ return new LoggerImpl(tag);
11032
+ }
11033
+
11034
+ // src/dns-fix.ts
11035
+ var log = logger("dns-fix");
11036
+ var FALLBACK_DNS = ["8.8.8.8", "1.1.1.1", "8.8.4.4"];
11037
+ function checkAndFixDns() {
11038
+ try {
11039
+ const servers = import_node_dns.default.getServers();
11040
+ if (servers.some((s) => FALLBACK_DNS.includes(s))) {
11041
+ return;
11042
+ }
11043
+ const hasLocalhost = servers.some((s) => s.startsWith("127."));
11044
+ if (!hasLocalhost) {
11045
+ return;
11046
+ }
11047
+ const testDomain = "one.one.one.one";
11048
+ import_node_dns.default.resolve4(testDomain, (err) => {
11049
+ if (err && (err.code === "ECONNREFUSED" || err.code === "ENOTFOUND" || err.code === "ETIMEOUT")) {
11050
+ log.warn(`\u7CFB\u7EDF DNS (${servers.join(", ")}) \u4E0D\u53EF\u8FBE (${err.code})\uFF0C\u5207\u6362\u81F3\u516C\u5171 DNS: ${FALLBACK_DNS.join(", ")}`);
11051
+ import_node_dns.default.setServers(FALLBACK_DNS);
11052
+ import_node_dns.default.resolve4("www.okx.com", (verifyErr) => {
11053
+ if (verifyErr) {
11054
+ log.warn(`\u516C\u5171 DNS \u4E5F\u65E0\u6CD5\u89E3\u6790: ${verifyErr.code}`);
11055
+ } else {
11056
+ log.info(`DNS \u4FEE\u590D\u6210\u529F\uFF0C\u5F53\u524D\u670D\u52A1\u5668: ${import_node_dns.default.getServers().join(", ")}`);
11057
+ }
11058
+ });
11059
+ }
11060
+ });
11061
+ } catch (e) {
11062
+ log.warn(`DNS \u68C0\u6D4B\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
11063
+ }
11064
+ }
11065
+ checkAndFixDns();
11066
+
10983
11067
  // src/index.ts
10984
11068
  var import_node_http = require("node:http");
10985
- var import_node_fs = require("node:fs");
10986
- var import_node_path = require("node:path");
11069
+ var import_node_fs2 = require("node:fs");
11070
+ var import_node_path2 = require("node:path");
10987
11071
 
10988
11072
  // node_modules/zod/v3/external.js
10989
11073
  var external_exports = {};
@@ -23859,8 +23943,8 @@ var Server = class extends Protocol {
23859
23943
  this._loggingLevels = /* @__PURE__ */ new Map();
23860
23944
  this.LOG_LEVEL_SEVERITY = new Map(LoggingLevelSchema.options.map((level, index) => [level, index]));
23861
23945
  this.isMessageIgnored = (level, sessionId) => {
23862
- const currentLevel = this._loggingLevels.get(sessionId);
23863
- return currentLevel ? this.LOG_LEVEL_SEVERITY.get(level) < this.LOG_LEVEL_SEVERITY.get(currentLevel) : false;
23946
+ const currentLevel2 = this._loggingLevels.get(sessionId);
23947
+ return currentLevel2 ? this.LOG_LEVEL_SEVERITY.get(level) < this.LOG_LEVEL_SEVERITY.get(currentLevel2) : false;
23864
23948
  };
23865
23949
  this._capabilities = options?.capabilities ?? {};
23866
23950
  this._instructions = options?.instructions;
@@ -37557,6 +37641,7 @@ function registerWsTools(server, auth) {
37557
37641
 
37558
37642
  // src/adapters/agent-hub.ts
37559
37643
  var MAX_ROOM_MESSAGES = 200;
37644
+ var log2 = logger("AgentHub");
37560
37645
  var AgentHub = class {
37561
37646
  wss = null;
37562
37647
  agents = /* @__PURE__ */ new Map();
@@ -37564,30 +37649,53 @@ var AgentHub = class {
37564
37649
  rooms = /* @__PURE__ */ new Map();
37565
37650
  heartbeatTimer = null;
37566
37651
  version = "0.0.0";
37652
+ startTime = 0;
37567
37653
  port = 0;
37654
+ // public: updated after WS negotiate, read by hub for dashboard URL
37655
+ registryCount = 0;
37656
+ // set by hub-server after HubRegistry loads
37568
37657
  db = null;
37569
37658
  token = "";
37570
37659
  // PSK 鉴权令牌
37660
+ costTracker = null;
37661
+ // HubCosts 实例
37571
37662
  // ── 持久化绑定 ──
37663
+ setCostTracker(ct) {
37664
+ this.costTracker = ct;
37665
+ }
37572
37666
  setDB(db) {
37573
37667
  this.db = db;
37574
37668
  const rows = db.loadTasks();
37669
+ let orphanCount = 0;
37575
37670
  for (const r of rows) {
37576
- this.tasks.set(r.taskId, {
37577
- status: r.status,
37578
- assignedTo: r.assignedTo || void 0,
37579
- claimedAt: r.claimedAt || void 0,
37580
- result: r.result || void 0,
37581
- branch: r.branch || void 0
37582
- });
37671
+ if (r.status === "assigned" && r.assignedTo && !this.agents.has(r.assignedTo)) {
37672
+ this.tasks.set(r.taskId, {
37673
+ status: "unassigned",
37674
+ assignedTo: void 0,
37675
+ claimedAt: void 0,
37676
+ result: r.result || void 0,
37677
+ branch: r.branch || void 0
37678
+ });
37679
+ db.saveTask({ taskId: r.taskId, status: "unassigned" });
37680
+ orphanCount++;
37681
+ } else {
37682
+ this.tasks.set(r.taskId, {
37683
+ status: r.status,
37684
+ assignedTo: r.assignedTo || void 0,
37685
+ claimedAt: r.claimedAt || void 0,
37686
+ result: r.result || void 0,
37687
+ branch: r.branch || void 0
37688
+ });
37689
+ }
37583
37690
  }
37584
37691
  if (rows.length > 0) {
37585
- console.log(`[AgentHub] \u4ECE DB \u6062\u590D ${rows.length} \u4E2A\u4EFB\u52A1`);
37692
+ log2.info(`\u4ECE DB \u6062\u590D ${rows.length} \u4E2A\u4EFB\u52A1` + (orphanCount > 0 ? `\uFF0C\u91CA\u653E ${orphanCount} \u4E2A\u5B64\u513F\u4EFB\u52A1` : ""));
37586
37693
  }
37587
37694
  }
37588
37695
  // ── 启动 ──
37589
37696
  start(port, host = "0.0.0.0", version2 = "0.0.0", token = "") {
37590
37697
  this.version = version2;
37698
+ this.startTime = Date.now();
37591
37699
  this.token = token;
37592
37700
  const startWss = (p) => {
37593
37701
  return new Promise((resolve2) => {
@@ -37623,12 +37731,10 @@ var AgentHub = class {
37623
37731
  return ok === true ? true : false;
37624
37732
  }).then((ok) => {
37625
37733
  if (!ok) {
37626
- process.stderr.write(`[AgentHub] WS Hub \u8DF3\u8FC7\uFF08\u7AEF\u53E3 ${ports.join("/")} \u4E0D\u53EF\u7528\uFF09
37627
- `);
37734
+ log2.warn(`WS Hub \u8DF3\u8FC7\uFF08\u7AEF\u53E3 ${ports.join("/")} \u4E0D\u53EF\u7528\uFF09`);
37628
37735
  return;
37629
37736
  }
37630
- process.stderr.write(`[AgentHub] WS Hub v${version2} ws://${host}:${this.port}
37631
- `);
37737
+ log2.info(`WS Hub v${version2} ws://${host}:${this.port}`);
37632
37738
  this.setupHub();
37633
37739
  });
37634
37740
  }
@@ -37660,17 +37766,17 @@ var AgentHub = class {
37660
37766
  if (agentId) this.handleDisconnect(agentId);
37661
37767
  });
37662
37768
  ws.on("error", (e) => {
37663
- process.stderr.write(`[AgentHub] WS \u9519\u8BEF: ${e.message}
37664
- `);
37769
+ log2.error(`WS \u9519\u8BEF: ${e.message}`);
37665
37770
  });
37666
37771
  });
37667
37772
  this.heartbeatTimer = setInterval(() => {
37668
37773
  const now = Date.now();
37669
37774
  for (const [id, a] of this.agents) {
37775
+ if (id.startsWith("term-") || id.startsWith("dashboard")) continue;
37670
37776
  if (now - a.lastSeen > 12e4) {
37671
37777
  a.ws.close();
37672
37778
  this.agents.delete(id);
37673
- console.log("[AgentHub] Agent \u5FC3\u8DF3\u8D85\u65F6: " + id);
37779
+ log2.warn("Agent \u5FC3\u8DF3\u8D85\u65F6: " + id);
37674
37780
  }
37675
37781
  }
37676
37782
  }, 3e4);
@@ -37686,9 +37792,29 @@ var AgentHub = class {
37686
37792
  case "task:claim":
37687
37793
  this.handleClaim(msg);
37688
37794
  break;
37795
+ case "task:progress":
37796
+ this.broadcast(msg);
37797
+ break;
37798
+ // 流式文本转发
37799
+ case "task:tool":
37800
+ this.broadcast(msg);
37801
+ break;
37802
+ // 工具调用转发
37689
37803
  case "task:done":
37690
37804
  this.handleDone(msg);
37691
37805
  break;
37806
+ case "task:assign":
37807
+ this.handleAssign(msg);
37808
+ break;
37809
+ // Chronos AI 调度指令
37810
+ case "task:unassign":
37811
+ this.handleUnassign(msg);
37812
+ break;
37813
+ // Chronos 释放卡住任务
37814
+ case "task:reject":
37815
+ this.handleReject(msg);
37816
+ break;
37817
+ // Worker 忙时拒绝任务
37692
37818
  // ── Room ──
37693
37819
  case "room:join":
37694
37820
  this.handleRoomJoin(authAgentId, msg);
@@ -37727,7 +37853,7 @@ var AgentHub = class {
37727
37853
  lastSeen: Date.now()
37728
37854
  });
37729
37855
  this.joinRoom(agentId, "#lobby");
37730
- console.log(`[AgentHub] Agent \u6CE8\u518C: ${agentId} (${name}) skills: [${capabilities.join(", ")}]`);
37856
+ log2.info(`Agent \u6CE8\u518C: ${agentId} (${name}) skills: [${capabilities.join(", ")}]`);
37731
37857
  this.send(ws, {
37732
37858
  type: "agent:registered",
37733
37859
  agentId,
@@ -37735,6 +37861,13 @@ var AgentHub = class {
37735
37861
  message: `\u5DF2\u6CE8\u518C\u3002\u53EF\u7528\u4EFB\u52A1: ${this.getUnassignedTasks().join(", ") || "\u65E0"}`,
37736
37862
  pendingTasks: this.getUnassignedTasks()
37737
37863
  });
37864
+ this.broadcast({
37865
+ type: "agent:update",
37866
+ agentId,
37867
+ name,
37868
+ capabilities,
37869
+ status: "idle"
37870
+ });
37738
37871
  const agentVersion = String(msg.version || "");
37739
37872
  if (agentVersion && agentVersion !== this.version) {
37740
37873
  this.send(ws, {
@@ -37756,7 +37889,8 @@ var AgentHub = class {
37756
37889
  }
37757
37890
  handleDisconnect(agentId) {
37758
37891
  const info = this.agents.get(agentId);
37759
- console.log(`[AgentHub] Agent \u79BB\u7EBF: ${agentId} (${info?.name || "?"})`);
37892
+ log2.info(`Agent \u79BB\u7EBF: ${agentId} (${info?.name || "?"})`);
37893
+ this.broadcast({ type: "agent:offline", agentId });
37760
37894
  for (const [roomId, room] of this.rooms) {
37761
37895
  if (room.members.has(agentId)) {
37762
37896
  room.members.delete(agentId);
@@ -37796,8 +37930,12 @@ var AgentHub = class {
37796
37930
  return;
37797
37931
  }
37798
37932
  if (task.status === "assigned" && task.assignedTo !== agentId) {
37799
- this.sendTo(agentId, { type: "error", message: `\u4EFB\u52A1 ${taskId} \u5DF2\u88AB ${task.assignedTo} \u8BA4\u9886` });
37800
- return;
37933
+ const assignedWorker = task.assignedTo ? this.agents.get(task.assignedTo) : null;
37934
+ if (assignedWorker) {
37935
+ this.sendTo(agentId, { type: "error", message: `\u4EFB\u52A1 ${taskId} \u5DF2\u88AB ${task.assignedTo} \u8BA4\u9886` });
37936
+ return;
37937
+ }
37938
+ log2.info(`${agentId} \u63A5\u7BA1\u5B64\u513F\u4EFB\u52A1 ${taskId}\uFF08\u539F ${task.assignedTo} \u5DF2\u79BB\u7EBF\uFF09`);
37801
37939
  }
37802
37940
  task.status = "assigned";
37803
37941
  task.assignedTo = agentId;
@@ -37805,7 +37943,7 @@ var AgentHub = class {
37805
37943
  const a = this.agents.get(agentId);
37806
37944
  if (a) a.status = "working";
37807
37945
  this.joinRoom(agentId, `#task-${taskId}`);
37808
- console.log(`[AgentHub] ${agentId} \u8BA4\u9886 ${taskId}`);
37946
+ log2.info(`${agentId} \u8BA4\u9886 ${taskId}`);
37809
37947
  this.db?.saveTask({ taskId, status: "assigned", title: this.getTaskTitle(taskId), assignedTo: agentId, claimedAt: task.claimedAt });
37810
37948
  this.sendTo(agentId, {
37811
37949
  type: "task:assigned",
@@ -37821,18 +37959,44 @@ var AgentHub = class {
37821
37959
  const agentId = String(msg.agentId || "");
37822
37960
  const result = String(msg.result || "");
37823
37961
  const branch = String(msg.branch || "");
37962
+ const error2 = msg.error ? String(msg.error) : "";
37963
+ const usage = msg.usage;
37964
+ const steps = Number(msg.steps || 0);
37965
+ if (this.costTracker && usage?.inputTokens) {
37966
+ this.costTracker.record({
37967
+ agentId,
37968
+ taskId,
37969
+ model: usage.model || "claude-sonnet-4-6",
37970
+ inputTokens: usage.inputTokens || 0,
37971
+ outputTokens: usage.outputTokens || 0,
37972
+ purpose: "task"
37973
+ });
37974
+ }
37824
37975
  const task = this.tasks.get(taskId);
37825
37976
  if (!task) {
37826
37977
  this.sendTo(agentId, { type: "error", message: `\u4EFB\u52A1 ${taskId} \u4E0D\u5B58\u5728` });
37827
37978
  return;
37828
37979
  }
37980
+ if (error2 || /^❌|^执行失败/i.test(result)) {
37981
+ task.status = "unassigned";
37982
+ task.assignedTo = void 0;
37983
+ task.claimedAt = void 0;
37984
+ const a2 = this.agents.get(agentId);
37985
+ if (a2) a2.status = "idle";
37986
+ this.db?.saveTask({ taskId, status: "unassigned" });
37987
+ log2.info(`\u4EFB\u52A1\u6267\u884C\u5931\u8D25\uFF0C\u91CA\u653E\u91CD\u8BD5: ${taskId} \u2014 ${error2 || result.slice(0, 80)}`);
37988
+ this.broadcast({ type: "agent:update", agentId, status: "idle" });
37989
+ this.broadcast({ type: "task:released", taskId, reason: `\u6267\u884C\u5931\u8D25: ${error2 || result.slice(0, 60)}` });
37990
+ return;
37991
+ }
37829
37992
  task.status = "done";
37830
37993
  task.result = result;
37831
37994
  task.branch = branch;
37832
37995
  const a = this.agents.get(agentId);
37833
37996
  if (a) a.status = "idle";
37834
- console.log(`[AgentHub] ${agentId} \u5B8C\u6210 ${taskId}: ${result}`);
37997
+ log2.info(`${agentId} \u5B8C\u6210 ${taskId}: ${result}`);
37835
37998
  this.db?.saveTask({ taskId, status: "done", title: this.getTaskTitle(taskId), assignedTo: agentId, result, branch });
37999
+ this.broadcast({ type: "agent:update", agentId, status: "idle" });
37836
38000
  this.sendToRoom(`#task-${taskId}`, agentId, `\u5DF2\u5B8C\u6210 ${taskId}: ${result} (branch: ${branch})\uFF0C\u7B49\u5F85\u5BA1\u6838\u3002`);
37837
38001
  this.sendToRoom("#review", agentId, `${taskId} \u63D0\u4EA4\u5B8C\u6210\uFF0Cbranch: ${branch}`);
37838
38002
  this.broadcast({
@@ -37844,41 +38008,125 @@ var AgentHub = class {
37844
38008
  message: `${agentId} \u5DF2\u5B8C\u6210 ${taskId}\uFF0C\u7B49\u5F85\u5BA1\u6838`
37845
38009
  });
37846
38010
  }
38011
+ // ── Chronos 释放卡住任务 ──
38012
+ handleUnassign(msg) {
38013
+ const taskId = String(msg.taskId || "");
38014
+ const reason = String(msg.reason || "Chronos \u5DE1\u68C0\u91CA\u653E");
38015
+ const task = this.tasks.get(taskId);
38016
+ if (!task) {
38017
+ log2.warn(`Chronos \u91CA\u653E\u5931\u8D25: \u4EFB\u52A1 ${taskId} \u4E0D\u5B58\u5728`);
38018
+ return;
38019
+ }
38020
+ if (task.status !== "assigned") {
38021
+ return;
38022
+ }
38023
+ const oldAgentId = task.assignedTo;
38024
+ task.status = "unassigned";
38025
+ task.assignedTo = void 0;
38026
+ task.claimedAt = void 0;
38027
+ if (oldAgentId) {
38028
+ const oldWorker = this.agents.get(oldAgentId);
38029
+ if (oldWorker && oldWorker.status === "working") {
38030
+ oldWorker.status = "idle";
38031
+ }
38032
+ }
38033
+ this.db?.saveTask({ taskId, status: "unassigned" });
38034
+ log2.warn(`Chronos \u91CA\u653E\u5361\u4F4F\u4EFB\u52A1: ${taskId} (\u539F ${oldAgentId || "?"}) \u2014 ${reason}`);
38035
+ this.broadcast({
38036
+ type: "task:released",
38037
+ taskId,
38038
+ reason: `Chronos \u91CA\u653E: ${reason}`
38039
+ });
38040
+ }
38041
+ // ── Worker 拒绝任务(忙碌)──
38042
+ handleReject(msg) {
38043
+ const taskId = String(msg.taskId || "");
38044
+ const agentId = String(msg.agentId || "");
38045
+ const reason = String(msg.reason || "Worker busy");
38046
+ const task = this.tasks.get(taskId);
38047
+ if (!task) return;
38048
+ if (task.status !== "assigned") return;
38049
+ task.status = "unassigned";
38050
+ task.assignedTo = void 0;
38051
+ task.claimedAt = void 0;
38052
+ const a = this.agents.get(agentId);
38053
+ if (a && a.status === "working") a.status = "idle";
38054
+ this.db?.saveTask({ taskId, status: "unassigned" });
38055
+ log2.info(`\u4EFB\u52A1\u88AB\u62D2\u7EDD: ${taskId} by ${agentId} \u2014 ${reason}`);
38056
+ this.broadcast({ type: "task:released", taskId, reason: `Worker \u62D2\u7EDD: ${reason}` });
38057
+ }
37847
38058
  // ── 注册任务(自动派发给空闲 Agent) ──
37848
- registerTask(taskId, title) {
38059
+ /** 检查是否有空闲的 WS Worker(非 dashboard、非 CLI spawn) */
38060
+ hasIdleWorker() {
38061
+ for (const [id, a] of this.agents) {
38062
+ if (a.status === "idle" && !id.startsWith("dashboard") && !id.startsWith("term-")) {
38063
+ return true;
38064
+ }
38065
+ }
38066
+ return false;
38067
+ }
38068
+ registerTask(taskId, title, promptB64) {
37849
38069
  if (!this.tasks.has(taskId)) {
37850
38070
  this.tasks.set(taskId, { status: "unassigned" });
37851
38071
  }
37852
- if (title) {
37853
- const t = this.tasks.get(taskId);
37854
- t.title = title;
37855
- }
37856
- console.log(`[AgentHub] \u4EFB\u52A1\u6CE8\u518C: ${taskId} "${title || taskId}"`);
38072
+ const task = this.tasks.get(taskId);
38073
+ if (title) task.title = title;
38074
+ if (promptB64) task.promptB64 = promptB64;
38075
+ log2.info(`\u4EFB\u52A1\u6CE8\u518C: ${taskId} "${title || taskId}"`);
37857
38076
  this.broadcast({ type: "task:announced", taskId, title: title || taskId });
37858
- const idleAgents = [...this.agents.entries()].filter(([, a]) => a.status === "idle" && !a.agentId.startsWith("dashboard"));
38077
+ const hasChronos = [...this.agents.values()].some(
38078
+ (a) => a.agentId === "chronos-dispatcher" && a.status !== "offline"
38079
+ );
38080
+ if (hasChronos) {
38081
+ log2.info(`Chronos \u5728\u7EBF\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85 AI \u8C03\u5EA6`);
38082
+ return;
38083
+ }
38084
+ const idleAgents = [...this.agents.entries()].filter(([, a]) => a.status === "idle" && !a.agentId.startsWith("dashboard") && !a.agentId.startsWith("term-"));
37859
38085
  if (idleAgents.length === 0) {
37860
- console.log(`[AgentHub] \u6CA1\u6709\u7A7A\u95F2 Agent\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85\u624B\u52A8 spawn`);
38086
+ log2.warn(`\u6CA1\u6709\u7A7A\u95F2 Agent\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85\u624B\u52A8 spawn`);
37861
38087
  return;
37862
38088
  }
37863
38089
  const match = idleAgents.find(([, a]) => a.capabilities.includes(taskId)) || idleAgents[0];
37864
38090
  if (match) {
37865
- console.log(`[AgentHub] \u81EA\u52A8\u6D3E\u53D1 ${taskId} \u2192 ${match[1].name} (${match[0]})`);
37866
- this.dispatchTaskTo(taskId, match[0]);
38091
+ log2.info(`\u81EA\u52A8\u6D3E\u53D1 ${taskId} \u2192 ${match[1].name} (${match[0]})`);
38092
+ this.dispatchTaskTo(taskId, match[0], promptB64);
37867
38093
  }
37868
38094
  }
37869
38095
  // ── 派发任务 ──
37870
- dispatchTaskTo(taskId, agentId) {
38096
+ dispatchTaskTo(taskId, agentId, promptB64) {
37871
38097
  if (!this.tasks.has(taskId)) {
37872
38098
  this.tasks.set(taskId, { status: "unassigned" });
37873
38099
  }
37874
38100
  const task = this.tasks.get(taskId);
37875
38101
  if (task.status === "assigned") return;
37876
- this.sendTo(agentId, {
38102
+ const msg = {
37877
38103
  type: "task:dispatch",
37878
38104
  taskId,
37879
- title: this.getTaskTitle(taskId),
37880
- url: `https://github.com/okx-wallet-H/hvip-mcp/blob/master/tasks/${taskId}.md`
37881
- });
38105
+ title: this.getTaskTitle(taskId)
38106
+ };
38107
+ if (promptB64) msg.promptB64 = promptB64;
38108
+ else if (task.promptB64) msg.promptB64 = task.promptB64;
38109
+ this.sendTo(agentId, msg);
38110
+ }
38111
+ // ── Chronos 调度指令 ──
38112
+ handleAssign(msg) {
38113
+ const taskId = String(msg.taskId || "");
38114
+ const targetAgentId = String(msg.agentId || "");
38115
+ const assignedBy = String(msg.assignedBy || "chronos");
38116
+ if (!taskId || !targetAgentId) return;
38117
+ const targetWorker = this.agents.get(targetAgentId);
38118
+ if (!targetWorker) {
38119
+ log2.warn(`Chronos \u6307\u6D3E\u5931\u8D25: Worker ${targetAgentId} \u4E0D\u5728\u7EBF`);
38120
+ return;
38121
+ }
38122
+ if (targetWorker.status !== "idle") {
38123
+ log2.warn(`Chronos \u6307\u6D3E\u5931\u8D25: Worker ${targetAgentId} \u5FD9\u788C\u4E2D`);
38124
+ return;
38125
+ }
38126
+ log2.info(`Chronos \u8C03\u5EA6: ${taskId} \u2192 ${targetWorker.name}`);
38127
+ const task = this.tasks.get(taskId);
38128
+ const promptB64 = task?.promptB64;
38129
+ this.dispatchTaskTo(taskId, targetAgentId, promptB64);
37882
38130
  }
37883
38131
  // ── 审核 + 自动通知房间 ──
37884
38132
  reviewTask(taskId, verdict, feedback) {
@@ -37926,7 +38174,7 @@ var AgentHub = class {
37926
38174
  });
37927
38175
  }
37928
38176
  this.sendToRoomMembers(room, { type: "room:member_joined", roomId, agentId });
37929
- console.log(`[AgentHub] ${agentId} \u2192 ${roomId}`);
38177
+ log2.info(`${agentId} \u2192 ${roomId}`);
37930
38178
  }
37931
38179
  leaveRoom(agentId, roomId) {
37932
38180
  const room = this.rooms.get(roomId);
@@ -38043,7 +38291,7 @@ var AgentHub = class {
38043
38291
  branch: t.branch
38044
38292
  }));
38045
38293
  const rooms = this.getRooms();
38046
- return { agents, tasks, rooms, agentCount: agents.length, taskCount: tasks.length };
38294
+ 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` };
38047
38295
  }
38048
38296
  // ── 关闭 ──
38049
38297
  close() {
@@ -38628,6 +38876,146 @@ function registerCodeGraphTools(server) {
38628
38876
  );
38629
38877
  }
38630
38878
 
38879
+ // src/adapters/api-credits.ts
38880
+ var import_node_sqlite = require("node:sqlite");
38881
+ var import_node_fs = require("node:fs");
38882
+ var import_node_path = require("node:path");
38883
+ var log3 = logger("Credits");
38884
+ var CREDIT_COST = {
38885
+ READ: 1,
38886
+ WRITE: 5,
38887
+ FUND_TRANSFER: 10,
38888
+ ADMIN: 20
38889
+ };
38890
+ var ApiCredits = class {
38891
+ db;
38892
+ cache = /* @__PURE__ */ new Map();
38893
+ cacheLoaded = false;
38894
+ constructor(dbPath = ".hub/api-credits.db") {
38895
+ const dir = (0, import_node_path.dirname)(dbPath);
38896
+ if (!(0, import_node_fs.existsSync)(dir)) (0, import_node_fs.mkdirSync)(dir, { recursive: true });
38897
+ this.db = new import_node_sqlite.DatabaseSync(dbPath);
38898
+ this.db.exec("PRAGMA journal_mode=WAL");
38899
+ this.init();
38900
+ }
38901
+ init() {
38902
+ this.db.exec(`
38903
+ CREATE TABLE IF NOT EXISTS api_keys (
38904
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38905
+ key TEXT UNIQUE NOT NULL,
38906
+ client_name TEXT NOT NULL,
38907
+ credits REAL NOT NULL DEFAULT 0,
38908
+ total_used INTEGER NOT NULL DEFAULT 0,
38909
+ enabled INTEGER NOT NULL DEFAULT 1,
38910
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
38911
+ last_used_at TEXT
38912
+ );
38913
+ CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(key);
38914
+ `);
38915
+ this.loadCache();
38916
+ }
38917
+ loadCache() {
38918
+ const rows = this.db.prepare("SELECT * FROM api_keys").all();
38919
+ for (const r of rows) this.cache.set(r.key, r);
38920
+ this.cacheLoaded = true;
38921
+ log3.info(`\u5DF2\u52A0\u8F7D ${rows.length} \u4E2A API Key`);
38922
+ }
38923
+ /** 查找 Key — 缓存命中或直接查 DB */
38924
+ lookup(key) {
38925
+ if (!this.cacheLoaded) this.loadCache();
38926
+ const cached2 = this.cache.get(key);
38927
+ if (cached2) return cached2;
38928
+ const row = this.db.prepare("SELECT * FROM api_keys WHERE key = ?").get(key);
38929
+ if (row) {
38930
+ this.cache.set(key, row);
38931
+ return row;
38932
+ }
38933
+ return null;
38934
+ }
38935
+ /** 校验并扣费 */
38936
+ deduct(key, riskLevel) {
38937
+ const record2 = this.lookup(key);
38938
+ if (!record2) return { ok: false, error: "\u65E0\u6548\u7684 API Key" };
38939
+ if (!record2.enabled) return { ok: false, error: "API Key \u5DF2\u7981\u7528" };
38940
+ const cost = CREDIT_COST[riskLevel] || 1;
38941
+ if (record2.credits < cost) return { ok: false, error: `\u79EF\u5206\u4E0D\u8DB3 (\u9700\u8981 ${cost}\uFF0C\u5269\u4F59 ${record2.credits})` };
38942
+ record2.credits -= cost;
38943
+ record2.totalUsed++;
38944
+ record2.lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
38945
+ this.db.prepare("UPDATE api_keys SET credits = ?, total_used = ?, last_used_at = ? WHERE id = ?").run(record2.credits, record2.totalUsed, record2.lastUsedAt, record2.id);
38946
+ return { ok: true, remaining: record2.credits };
38947
+ }
38948
+ /** 管理:创建 Key */
38949
+ createKey(clientName, initialCredits = 1e3) {
38950
+ const key = "hvip-" + Array.from(
38951
+ { length: 32 },
38952
+ () => "abcdefghijklmnopqrstuvwxyz0123456789"[Math.floor(Math.random() * 36)]
38953
+ ).join("");
38954
+ const result = this.db.prepare(
38955
+ "INSERT INTO api_keys (key, client_name, credits) VALUES (?, ?, ?)"
38956
+ ).run(key, clientName, initialCredits);
38957
+ const record2 = {
38958
+ id: result.lastInsertRowid,
38959
+ key,
38960
+ clientName,
38961
+ credits: initialCredits,
38962
+ totalUsed: 0,
38963
+ enabled: true,
38964
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
38965
+ lastUsedAt: null
38966
+ };
38967
+ this.cache.set(key, record2);
38968
+ return record2;
38969
+ }
38970
+ /** 管理:充值 */
38971
+ topUp(key, amount) {
38972
+ const record2 = this.lookup(key);
38973
+ if (!record2) return { ok: false, error: "Key \u4E0D\u5B58\u5728" };
38974
+ if (amount <= 0) return { ok: false, error: "\u5145\u503C\u91D1\u989D\u5FC5\u987B > 0" };
38975
+ record2.credits += amount;
38976
+ this.db.prepare("UPDATE api_keys SET credits = ? WHERE id = ?").run(record2.credits, record2.id);
38977
+ return { ok: true, newBalance: record2.credits };
38978
+ }
38979
+ /** 管理:启用/禁用 Key */
38980
+ setEnabled(key, enabled) {
38981
+ const record2 = this.lookup(key);
38982
+ if (!record2) return false;
38983
+ record2.enabled = enabled;
38984
+ this.db.prepare("UPDATE api_keys SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, record2.id);
38985
+ return true;
38986
+ }
38987
+ /** 管理:列出所有 Key (含 Key 前缀,完整 Key 仅创建时返回) */
38988
+ listAll() {
38989
+ return [...this.cache.values()].map((r) => ({
38990
+ id: r.id,
38991
+ clientName: r.clientName,
38992
+ credits: r.credits,
38993
+ totalUsed: r.totalUsed,
38994
+ enabled: r.enabled,
38995
+ createdAt: r.createdAt,
38996
+ lastUsedAt: r.lastUsedAt,
38997
+ keyPrefix: r.key.slice(0, 12) + "..." + r.key.slice(-8)
38998
+ }));
38999
+ }
39000
+ /** 客户端:查询余额 */
39001
+ getBalance(key) {
39002
+ const record2 = this.lookup(key);
39003
+ if (!record2) return null;
39004
+ return { clientName: record2.clientName, credits: record2.credits, totalUsed: record2.totalUsed };
39005
+ }
39006
+ close() {
39007
+ try {
39008
+ this.db.close();
39009
+ } catch {
39010
+ }
39011
+ }
39012
+ };
39013
+ var instance = null;
39014
+ function getApiCredits(dbPath) {
39015
+ if (!instance) instance = new ApiCredits(dbPath);
39016
+ return instance;
39017
+ }
39018
+
38631
39019
  // src/index.ts
38632
39020
  function resolveExecutionMode() {
38633
39021
  if (process.env.MCP_EXECUTION_ENABLED === "false") {
@@ -38700,7 +39088,7 @@ function registerAllTools(server, auth, readOnly) {
38700
39088
  if (risk !== "READ") {
38701
39089
  skipped++;
38702
39090
  skipLog.push(`${name} (${risk})`);
38703
- return;
39091
+ return server;
38704
39092
  }
38705
39093
  return orig(name, ...args);
38706
39094
  };
@@ -38732,22 +39120,46 @@ function registerAllTools(server, auth, readOnly) {
38732
39120
  return { skipped, skipLog };
38733
39121
  }
38734
39122
  async function startHttp(server, version2, auth, readOnly, skipped) {
38735
- const port = parseInt(process.env.PORT || "3000", 10);
39123
+ let port = parseInt(process.env.PORT || "3000", 10);
38736
39124
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
38737
39125
  process.stderr.write(`[hvip] \u274C \u65E0\u6548\u7AEF\u53E3: ${process.env.PORT}\uFF0C\u4F7F\u7528\u9ED8\u8BA4 3000
38738
39126
  `);
39127
+ port = 3e3;
38739
39128
  }
38740
39129
  const host = process.env.HOST || "127.0.0.1";
38741
- const token = process.env.MCP_AUTH_TOKEN || "";
39130
+ const adminToken = process.env.MCP_AUTH_TOKEN || "";
39131
+ const creditsEnabled = process.env.MCP_CREDITS !== "false" && process.env.MCP_CREDITS !== "0";
39132
+ const credits = creditsEnabled ? getApiCredits() : null;
38742
39133
  const transport = new StreamableHTTPServerTransport({
38743
39134
  sessionIdGenerator: void 0
38744
39135
  // stateless
38745
39136
  });
38746
39137
  await server.connect(transport);
38747
39138
  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) {
39139
+ const provided = req.headers["authorization"]?.replace(/^Bearer\s+/i, "") || "";
39140
+ if (req.url !== "/health" && req.url !== "/") {
39141
+ if (adminToken && provided === adminToken) {
39142
+ } else if (credits && provided) {
39143
+ const record2 = credits.lookup(provided);
39144
+ if (!record2) {
39145
+ res.writeHead(401, { "Content-Type": "application/json" });
39146
+ res.end(JSON.stringify({ error: "Unauthorized \u2014 \u65E0\u6548\u7684 API Key" }));
39147
+ return;
39148
+ }
39149
+ if (!record2.enabled) {
39150
+ res.writeHead(403, { "Content-Type": "application/json" });
39151
+ res.end(JSON.stringify({ error: "Forbidden \u2014 API Key \u5DF2\u7981\u7528" }));
39152
+ return;
39153
+ }
39154
+ if (req.method === "POST" && req.url === "/mcp") {
39155
+ const result = credits.deduct(provided, "READ");
39156
+ if (!result.ok) {
39157
+ res.writeHead(402, { "Content-Type": "application/json" });
39158
+ res.end(JSON.stringify({ error: result.error, remaining: credits.lookup(provided)?.credits || 0 }));
39159
+ return;
39160
+ }
39161
+ }
39162
+ } else if (adminToken) {
38751
39163
  res.writeHead(401, { "Content-Type": "application/json" });
38752
39164
  res.end(JSON.stringify({ error: "Unauthorized \u2014 \u8BF7\u5728 Authorization \u5934\u643A\u5E26\u6709\u6548 token" }));
38753
39165
  return;
@@ -38764,15 +39176,15 @@ async function startHttp(server, version2, auth, readOnly, skipped) {
38764
39176
  if (req.method === "GET" && (req.url === "/" || req.url === "/index.html")) {
38765
39177
  try {
38766
39178
  const paths = [
38767
- (0, import_node_path.join)(__dirname, "web", "index.html"),
39179
+ (0, import_node_path2.join)(__dirname, "web", "index.html"),
38768
39180
  // published npm (dist/index.js → dist/web/)
38769
- (0, import_node_path.join)(__dirname, "..", "src", "web", "index.html")
39181
+ (0, import_node_path2.join)(__dirname, "..", "src", "web", "index.html")
38770
39182
  // dev (dist/index.js → ../src/web/)
38771
39183
  ];
38772
39184
  let html = "";
38773
39185
  for (const p of paths) {
38774
- if ((0, import_node_fs.existsSync)(p)) {
38775
- html = (0, import_node_fs.readFileSync)(p, "utf-8");
39186
+ if ((0, import_node_fs2.existsSync)(p)) {
39187
+ html = (0, import_node_fs2.readFileSync)(p, "utf-8");
38776
39188
  break;
38777
39189
  }
38778
39190
  }
@@ -38832,7 +39244,7 @@ async function startHttp(server, version2, auth, readOnly, skipped) {
38832
39244
  const platform = process.platform;
38833
39245
  const cmd = platform === "win32" ? `start ${openUrl}` : platform === "darwin" ? `open ${openUrl}` : `xdg-open ${openUrl}`;
38834
39246
  import("node:child_process").then((cp) => {
38835
- cp.exec(cmd, () => {
39247
+ cp.exec(cmd, { windowsHide: true }, () => {
38836
39248
  });
38837
39249
  });
38838
39250
  });
@@ -38863,12 +39275,33 @@ async function startStdio(server, version2, auth, readOnly, skipped, skipLog) {
38863
39275
  }
38864
39276
  const wsHost = process.env.WS_BIND_HOST || "127.0.0.1";
38865
39277
  const wsToken = process.env.HUB_AUTH_TOKEN || "";
38866
- startAgentHub(parseInt(process.env.WS_AGENT_PORT || "9321"), wsHost, version2, wsToken);
39278
+ startAgentHub(parseInt(process.env.WS_AGENT_PORT || "9321", 10), wsHost, version2, wsToken);
38867
39279
  const transport = new StdioServerTransport();
38868
39280
  await server.connect(transport);
38869
39281
  }
39282
+ var _cachedVersion = null;
39283
+ function loadVersion() {
39284
+ if (_cachedVersion) return _cachedVersion;
39285
+ try {
39286
+ const paths = [
39287
+ (0, import_node_path2.join)(__dirname, "..", "package.json"),
39288
+ (0, import_node_path2.join)(__dirname, "..", "..", "..", "package.json")
39289
+ ];
39290
+ for (const p of paths) {
39291
+ if ((0, import_node_fs2.existsSync)(p)) {
39292
+ const pkg = JSON.parse((0, import_node_fs2.readFileSync)(p, "utf-8"));
39293
+ if (pkg.version) {
39294
+ _cachedVersion = String(pkg.version);
39295
+ return _cachedVersion;
39296
+ }
39297
+ }
39298
+ }
39299
+ } catch {
39300
+ }
39301
+ return "0.0.0";
39302
+ }
38870
39303
  async function main() {
38871
- const VERSION = "0.3.2";
39304
+ const VERSION = loadVersion();
38872
39305
  const argv = process.argv.slice(2);
38873
39306
  if (argv.includes("--help") || argv.includes("-h")) printHelpAndExit(VERSION);
38874
39307
  if (argv.includes("--version") || argv.includes("-v")) {