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.
@@ -3720,8 +3720,57 @@ var import_subprotocol = __toESM(require_subprotocol(), 1);
3720
3720
  var import_websocket = __toESM(require_websocket(), 1);
3721
3721
  var import_websocket_server = __toESM(require_websocket_server(), 1);
3722
3722
 
3723
+ // src/utils/logger.ts
3724
+ var LEVEL_EMOJI = {
3725
+ [0 /* DEBUG */]: "\u{1F50D}",
3726
+ [1 /* INFO */]: "\u2139\uFE0F",
3727
+ [2 /* WARN */]: "\u26A0\uFE0F",
3728
+ [3 /* ERROR */]: "\u274C",
3729
+ [4 /* SILENT */]: ""
3730
+ };
3731
+ function resolveLevel() {
3732
+ const raw = (process.env.LOG_LEVEL || "").toUpperCase();
3733
+ if (raw === "DEBUG") return 0 /* DEBUG */;
3734
+ if (raw === "WARN" || raw === "WARNING") return 2 /* WARN */;
3735
+ if (raw === "ERROR") return 3 /* ERROR */;
3736
+ if (raw === "SILENT" || raw === "OFF") return 4 /* SILENT */;
3737
+ return 1 /* INFO */;
3738
+ }
3739
+ var currentLevel = resolveLevel();
3740
+ var LoggerImpl = class {
3741
+ constructor(tag) {
3742
+ this.tag = tag;
3743
+ }
3744
+ tag;
3745
+ debug(msg) {
3746
+ this.write(0 /* DEBUG */, msg);
3747
+ }
3748
+ info(msg) {
3749
+ this.write(1 /* INFO */, msg);
3750
+ }
3751
+ warn(msg) {
3752
+ this.write(2 /* WARN */, msg);
3753
+ }
3754
+ error(msg) {
3755
+ this.write(3 /* ERROR */, msg);
3756
+ }
3757
+ write(level, msg) {
3758
+ if (level < currentLevel) return;
3759
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
3760
+ const emoji = LEVEL_EMOJI[level];
3761
+ const tag = this.tag ? `[${this.tag}]` : "";
3762
+ const prefix = emoji ? `${emoji} ${tag}` : tag;
3763
+ process.stderr.write(`${ts} ${prefix} ${msg}
3764
+ `);
3765
+ }
3766
+ };
3767
+ function logger(tag) {
3768
+ return new LoggerImpl(tag);
3769
+ }
3770
+
3723
3771
  // src/adapters/agent-hub.ts
3724
3772
  var MAX_ROOM_MESSAGES = 200;
3773
+ var log = logger("AgentHub");
3725
3774
  var AgentHub = class {
3726
3775
  wss = null;
3727
3776
  agents = /* @__PURE__ */ new Map();
@@ -3729,30 +3778,53 @@ var AgentHub = class {
3729
3778
  rooms = /* @__PURE__ */ new Map();
3730
3779
  heartbeatTimer = null;
3731
3780
  version = "0.0.0";
3781
+ startTime = 0;
3732
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
3733
3786
  db = null;
3734
3787
  token = "";
3735
3788
  // PSK 鉴权令牌
3789
+ costTracker = null;
3790
+ // HubCosts 实例
3736
3791
  // ── 持久化绑定 ──
3792
+ setCostTracker(ct) {
3793
+ this.costTracker = ct;
3794
+ }
3737
3795
  setDB(db2) {
3738
3796
  this.db = db2;
3739
3797
  const rows = db2.loadTasks();
3798
+ let orphanCount = 0;
3740
3799
  for (const r of rows) {
3741
- this.tasks.set(r.taskId, {
3742
- status: r.status,
3743
- assignedTo: r.assignedTo || void 0,
3744
- claimedAt: r.claimedAt || void 0,
3745
- result: r.result || void 0,
3746
- branch: r.branch || void 0
3747
- });
3800
+ if (r.status === "assigned" && r.assignedTo && !this.agents.has(r.assignedTo)) {
3801
+ this.tasks.set(r.taskId, {
3802
+ status: "unassigned",
3803
+ assignedTo: void 0,
3804
+ claimedAt: void 0,
3805
+ result: r.result || void 0,
3806
+ branch: r.branch || void 0
3807
+ });
3808
+ db2.saveTask({ taskId: r.taskId, status: "unassigned" });
3809
+ orphanCount++;
3810
+ } else {
3811
+ this.tasks.set(r.taskId, {
3812
+ status: r.status,
3813
+ assignedTo: r.assignedTo || void 0,
3814
+ claimedAt: r.claimedAt || void 0,
3815
+ result: r.result || void 0,
3816
+ branch: r.branch || void 0
3817
+ });
3818
+ }
3748
3819
  }
3749
3820
  if (rows.length > 0) {
3750
- console.log(`[AgentHub] \u4ECE DB \u6062\u590D ${rows.length} \u4E2A\u4EFB\u52A1`);
3821
+ log.info(`\u4ECE DB \u6062\u590D ${rows.length} \u4E2A\u4EFB\u52A1` + (orphanCount > 0 ? `\uFF0C\u91CA\u653E ${orphanCount} \u4E2A\u5B64\u513F\u4EFB\u52A1` : ""));
3751
3822
  }
3752
3823
  }
3753
3824
  // ── 启动 ──
3754
3825
  start(port, host2 = "0.0.0.0", version = "0.0.0", token2 = "") {
3755
3826
  this.version = version;
3827
+ this.startTime = Date.now();
3756
3828
  this.token = token2;
3757
3829
  const startWss = (p) => {
3758
3830
  return new Promise((resolve) => {
@@ -3788,12 +3860,10 @@ var AgentHub = class {
3788
3860
  return ok === true ? true : false;
3789
3861
  }).then((ok) => {
3790
3862
  if (!ok) {
3791
- process.stderr.write(`[AgentHub] WS Hub \u8DF3\u8FC7\uFF08\u7AEF\u53E3 ${ports.join("/")} \u4E0D\u53EF\u7528\uFF09
3792
- `);
3863
+ log.warn(`WS Hub \u8DF3\u8FC7\uFF08\u7AEF\u53E3 ${ports.join("/")} \u4E0D\u53EF\u7528\uFF09`);
3793
3864
  return;
3794
3865
  }
3795
- process.stderr.write(`[AgentHub] WS Hub v${version} ws://${host2}:${this.port}
3796
- `);
3866
+ log.info(`WS Hub v${version} ws://${host2}:${this.port}`);
3797
3867
  this.setupHub();
3798
3868
  });
3799
3869
  }
@@ -3825,17 +3895,17 @@ var AgentHub = class {
3825
3895
  if (agentId) this.handleDisconnect(agentId);
3826
3896
  });
3827
3897
  ws.on("error", (e) => {
3828
- process.stderr.write(`[AgentHub] WS \u9519\u8BEF: ${e.message}
3829
- `);
3898
+ log.error(`WS \u9519\u8BEF: ${e.message}`);
3830
3899
  });
3831
3900
  });
3832
3901
  this.heartbeatTimer = setInterval(() => {
3833
3902
  const now = Date.now();
3834
3903
  for (const [id, a] of this.agents) {
3904
+ if (id.startsWith("term-") || id.startsWith("dashboard")) continue;
3835
3905
  if (now - a.lastSeen > 12e4) {
3836
3906
  a.ws.close();
3837
3907
  this.agents.delete(id);
3838
- console.log("[AgentHub] Agent \u5FC3\u8DF3\u8D85\u65F6: " + id);
3908
+ log.warn("Agent \u5FC3\u8DF3\u8D85\u65F6: " + id);
3839
3909
  }
3840
3910
  }
3841
3911
  }, 3e4);
@@ -3851,9 +3921,29 @@ var AgentHub = class {
3851
3921
  case "task:claim":
3852
3922
  this.handleClaim(msg);
3853
3923
  break;
3924
+ case "task:progress":
3925
+ this.broadcast(msg);
3926
+ break;
3927
+ // 流式文本转发
3928
+ case "task:tool":
3929
+ this.broadcast(msg);
3930
+ break;
3931
+ // 工具调用转发
3854
3932
  case "task:done":
3855
3933
  this.handleDone(msg);
3856
3934
  break;
3935
+ case "task:assign":
3936
+ this.handleAssign(msg);
3937
+ break;
3938
+ // Chronos AI 调度指令
3939
+ case "task:unassign":
3940
+ this.handleUnassign(msg);
3941
+ break;
3942
+ // Chronos 释放卡住任务
3943
+ case "task:reject":
3944
+ this.handleReject(msg);
3945
+ break;
3946
+ // Worker 忙时拒绝任务
3857
3947
  // ── Room ──
3858
3948
  case "room:join":
3859
3949
  this.handleRoomJoin(authAgentId, msg);
@@ -3892,7 +3982,7 @@ var AgentHub = class {
3892
3982
  lastSeen: Date.now()
3893
3983
  });
3894
3984
  this.joinRoom(agentId, "#lobby");
3895
- console.log(`[AgentHub] Agent \u6CE8\u518C: ${agentId} (${name}) skills: [${capabilities.join(", ")}]`);
3985
+ log.info(`Agent \u6CE8\u518C: ${agentId} (${name}) skills: [${capabilities.join(", ")}]`);
3896
3986
  this.send(ws, {
3897
3987
  type: "agent:registered",
3898
3988
  agentId,
@@ -3900,6 +3990,13 @@ var AgentHub = class {
3900
3990
  message: `\u5DF2\u6CE8\u518C\u3002\u53EF\u7528\u4EFB\u52A1: ${this.getUnassignedTasks().join(", ") || "\u65E0"}`,
3901
3991
  pendingTasks: this.getUnassignedTasks()
3902
3992
  });
3993
+ this.broadcast({
3994
+ type: "agent:update",
3995
+ agentId,
3996
+ name,
3997
+ capabilities,
3998
+ status: "idle"
3999
+ });
3903
4000
  const agentVersion = String(msg.version || "");
3904
4001
  if (agentVersion && agentVersion !== this.version) {
3905
4002
  this.send(ws, {
@@ -3921,7 +4018,8 @@ var AgentHub = class {
3921
4018
  }
3922
4019
  handleDisconnect(agentId) {
3923
4020
  const info = this.agents.get(agentId);
3924
- console.log(`[AgentHub] Agent \u79BB\u7EBF: ${agentId} (${info?.name || "?"})`);
4021
+ log.info(`Agent \u79BB\u7EBF: ${agentId} (${info?.name || "?"})`);
4022
+ this.broadcast({ type: "agent:offline", agentId });
3925
4023
  for (const [roomId, room] of this.rooms) {
3926
4024
  if (room.members.has(agentId)) {
3927
4025
  room.members.delete(agentId);
@@ -3961,8 +4059,12 @@ var AgentHub = class {
3961
4059
  return;
3962
4060
  }
3963
4061
  if (task.status === "assigned" && task.assignedTo !== agentId) {
3964
- this.sendTo(agentId, { type: "error", message: `\u4EFB\u52A1 ${taskId} \u5DF2\u88AB ${task.assignedTo} \u8BA4\u9886` });
3965
- return;
4062
+ const assignedWorker = task.assignedTo ? this.agents.get(task.assignedTo) : null;
4063
+ if (assignedWorker) {
4064
+ this.sendTo(agentId, { type: "error", message: `\u4EFB\u52A1 ${taskId} \u5DF2\u88AB ${task.assignedTo} \u8BA4\u9886` });
4065
+ return;
4066
+ }
4067
+ log.info(`${agentId} \u63A5\u7BA1\u5B64\u513F\u4EFB\u52A1 ${taskId}\uFF08\u539F ${task.assignedTo} \u5DF2\u79BB\u7EBF\uFF09`);
3966
4068
  }
3967
4069
  task.status = "assigned";
3968
4070
  task.assignedTo = agentId;
@@ -3970,7 +4072,7 @@ var AgentHub = class {
3970
4072
  const a = this.agents.get(agentId);
3971
4073
  if (a) a.status = "working";
3972
4074
  this.joinRoom(agentId, `#task-${taskId}`);
3973
- console.log(`[AgentHub] ${agentId} \u8BA4\u9886 ${taskId}`);
4075
+ log.info(`${agentId} \u8BA4\u9886 ${taskId}`);
3974
4076
  this.db?.saveTask({ taskId, status: "assigned", title: this.getTaskTitle(taskId), assignedTo: agentId, claimedAt: task.claimedAt });
3975
4077
  this.sendTo(agentId, {
3976
4078
  type: "task:assigned",
@@ -3986,18 +4088,44 @@ var AgentHub = class {
3986
4088
  const agentId = String(msg.agentId || "");
3987
4089
  const result = String(msg.result || "");
3988
4090
  const branch = String(msg.branch || "");
4091
+ const error = msg.error ? String(msg.error) : "";
4092
+ const usage = msg.usage;
4093
+ const steps = Number(msg.steps || 0);
4094
+ if (this.costTracker && usage?.inputTokens) {
4095
+ this.costTracker.record({
4096
+ agentId,
4097
+ taskId,
4098
+ model: usage.model || "claude-sonnet-4-6",
4099
+ inputTokens: usage.inputTokens || 0,
4100
+ outputTokens: usage.outputTokens || 0,
4101
+ purpose: "task"
4102
+ });
4103
+ }
3989
4104
  const task = this.tasks.get(taskId);
3990
4105
  if (!task) {
3991
4106
  this.sendTo(agentId, { type: "error", message: `\u4EFB\u52A1 ${taskId} \u4E0D\u5B58\u5728` });
3992
4107
  return;
3993
4108
  }
4109
+ if (error || /^❌|^执行失败/i.test(result)) {
4110
+ task.status = "unassigned";
4111
+ task.assignedTo = void 0;
4112
+ task.claimedAt = void 0;
4113
+ const a2 = this.agents.get(agentId);
4114
+ if (a2) a2.status = "idle";
4115
+ this.db?.saveTask({ taskId, status: "unassigned" });
4116
+ log.info(`\u4EFB\u52A1\u6267\u884C\u5931\u8D25\uFF0C\u91CA\u653E\u91CD\u8BD5: ${taskId} \u2014 ${error || result.slice(0, 80)}`);
4117
+ this.broadcast({ type: "agent:update", agentId, status: "idle" });
4118
+ this.broadcast({ type: "task:released", taskId, reason: `\u6267\u884C\u5931\u8D25: ${error || result.slice(0, 60)}` });
4119
+ return;
4120
+ }
3994
4121
  task.status = "done";
3995
4122
  task.result = result;
3996
4123
  task.branch = branch;
3997
4124
  const a = this.agents.get(agentId);
3998
4125
  if (a) a.status = "idle";
3999
- console.log(`[AgentHub] ${agentId} \u5B8C\u6210 ${taskId}: ${result}`);
4126
+ log.info(`${agentId} \u5B8C\u6210 ${taskId}: ${result}`);
4000
4127
  this.db?.saveTask({ taskId, status: "done", title: this.getTaskTitle(taskId), assignedTo: agentId, result, branch });
4128
+ this.broadcast({ type: "agent:update", agentId, status: "idle" });
4001
4129
  this.sendToRoom(`#task-${taskId}`, agentId, `\u5DF2\u5B8C\u6210 ${taskId}: ${result} (branch: ${branch})\uFF0C\u7B49\u5F85\u5BA1\u6838\u3002`);
4002
4130
  this.sendToRoom("#review", agentId, `${taskId} \u63D0\u4EA4\u5B8C\u6210\uFF0Cbranch: ${branch}`);
4003
4131
  this.broadcast({
@@ -4009,41 +4137,125 @@ var AgentHub = class {
4009
4137
  message: `${agentId} \u5DF2\u5B8C\u6210 ${taskId}\uFF0C\u7B49\u5F85\u5BA1\u6838`
4010
4138
  });
4011
4139
  }
4140
+ // ── Chronos 释放卡住任务 ──
4141
+ handleUnassign(msg) {
4142
+ const taskId = String(msg.taskId || "");
4143
+ const reason = String(msg.reason || "Chronos \u5DE1\u68C0\u91CA\u653E");
4144
+ const task = this.tasks.get(taskId);
4145
+ if (!task) {
4146
+ log.warn(`Chronos \u91CA\u653E\u5931\u8D25: \u4EFB\u52A1 ${taskId} \u4E0D\u5B58\u5728`);
4147
+ return;
4148
+ }
4149
+ if (task.status !== "assigned") {
4150
+ return;
4151
+ }
4152
+ const oldAgentId = task.assignedTo;
4153
+ task.status = "unassigned";
4154
+ task.assignedTo = void 0;
4155
+ task.claimedAt = void 0;
4156
+ if (oldAgentId) {
4157
+ const oldWorker = this.agents.get(oldAgentId);
4158
+ if (oldWorker && oldWorker.status === "working") {
4159
+ oldWorker.status = "idle";
4160
+ }
4161
+ }
4162
+ this.db?.saveTask({ taskId, status: "unassigned" });
4163
+ log.warn(`Chronos \u91CA\u653E\u5361\u4F4F\u4EFB\u52A1: ${taskId} (\u539F ${oldAgentId || "?"}) \u2014 ${reason}`);
4164
+ this.broadcast({
4165
+ type: "task:released",
4166
+ taskId,
4167
+ reason: `Chronos \u91CA\u653E: ${reason}`
4168
+ });
4169
+ }
4170
+ // ── Worker 拒绝任务(忙碌)──
4171
+ handleReject(msg) {
4172
+ const taskId = String(msg.taskId || "");
4173
+ const agentId = String(msg.agentId || "");
4174
+ const reason = String(msg.reason || "Worker busy");
4175
+ const task = this.tasks.get(taskId);
4176
+ if (!task) return;
4177
+ if (task.status !== "assigned") return;
4178
+ task.status = "unassigned";
4179
+ task.assignedTo = void 0;
4180
+ task.claimedAt = void 0;
4181
+ const a = this.agents.get(agentId);
4182
+ if (a && a.status === "working") a.status = "idle";
4183
+ this.db?.saveTask({ taskId, status: "unassigned" });
4184
+ log.info(`\u4EFB\u52A1\u88AB\u62D2\u7EDD: ${taskId} by ${agentId} \u2014 ${reason}`);
4185
+ this.broadcast({ type: "task:released", taskId, reason: `Worker \u62D2\u7EDD: ${reason}` });
4186
+ }
4012
4187
  // ── 注册任务(自动派发给空闲 Agent) ──
4013
- registerTask(taskId, title) {
4188
+ /** 检查是否有空闲的 WS Worker(非 dashboard、非 CLI spawn) */
4189
+ hasIdleWorker() {
4190
+ for (const [id, a] of this.agents) {
4191
+ if (a.status === "idle" && !id.startsWith("dashboard") && !id.startsWith("term-")) {
4192
+ return true;
4193
+ }
4194
+ }
4195
+ return false;
4196
+ }
4197
+ registerTask(taskId, title, promptB64) {
4014
4198
  if (!this.tasks.has(taskId)) {
4015
4199
  this.tasks.set(taskId, { status: "unassigned" });
4016
4200
  }
4017
- if (title) {
4018
- const t = this.tasks.get(taskId);
4019
- t.title = title;
4020
- }
4021
- console.log(`[AgentHub] \u4EFB\u52A1\u6CE8\u518C: ${taskId} "${title || taskId}"`);
4201
+ const task = this.tasks.get(taskId);
4202
+ if (title) task.title = title;
4203
+ if (promptB64) task.promptB64 = promptB64;
4204
+ log.info(`\u4EFB\u52A1\u6CE8\u518C: ${taskId} "${title || taskId}"`);
4022
4205
  this.broadcast({ type: "task:announced", taskId, title: title || taskId });
4023
- const idleAgents = [...this.agents.entries()].filter(([, a]) => a.status === "idle" && !a.agentId.startsWith("dashboard"));
4206
+ const hasChronos = [...this.agents.values()].some(
4207
+ (a) => a.agentId === "chronos-dispatcher" && a.status !== "offline"
4208
+ );
4209
+ if (hasChronos) {
4210
+ log.info(`Chronos \u5728\u7EBF\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85 AI \u8C03\u5EA6`);
4211
+ return;
4212
+ }
4213
+ const idleAgents = [...this.agents.entries()].filter(([, a]) => a.status === "idle" && !a.agentId.startsWith("dashboard") && !a.agentId.startsWith("term-"));
4024
4214
  if (idleAgents.length === 0) {
4025
- console.log(`[AgentHub] \u6CA1\u6709\u7A7A\u95F2 Agent\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85\u624B\u52A8 spawn`);
4215
+ log.warn(`\u6CA1\u6709\u7A7A\u95F2 Agent\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85\u624B\u52A8 spawn`);
4026
4216
  return;
4027
4217
  }
4028
4218
  const match = idleAgents.find(([, a]) => a.capabilities.includes(taskId)) || idleAgents[0];
4029
4219
  if (match) {
4030
- console.log(`[AgentHub] \u81EA\u52A8\u6D3E\u53D1 ${taskId} \u2192 ${match[1].name} (${match[0]})`);
4031
- this.dispatchTaskTo(taskId, match[0]);
4220
+ log.info(`\u81EA\u52A8\u6D3E\u53D1 ${taskId} \u2192 ${match[1].name} (${match[0]})`);
4221
+ this.dispatchTaskTo(taskId, match[0], promptB64);
4032
4222
  }
4033
4223
  }
4034
4224
  // ── 派发任务 ──
4035
- dispatchTaskTo(taskId, agentId) {
4225
+ dispatchTaskTo(taskId, agentId, promptB64) {
4036
4226
  if (!this.tasks.has(taskId)) {
4037
4227
  this.tasks.set(taskId, { status: "unassigned" });
4038
4228
  }
4039
4229
  const task = this.tasks.get(taskId);
4040
4230
  if (task.status === "assigned") return;
4041
- this.sendTo(agentId, {
4231
+ const msg = {
4042
4232
  type: "task:dispatch",
4043
4233
  taskId,
4044
- title: this.getTaskTitle(taskId),
4045
- url: `https://github.com/okx-wallet-H/hvip-mcp/blob/master/tasks/${taskId}.md`
4046
- });
4234
+ title: this.getTaskTitle(taskId)
4235
+ };
4236
+ if (promptB64) msg.promptB64 = promptB64;
4237
+ else if (task.promptB64) msg.promptB64 = task.promptB64;
4238
+ this.sendTo(agentId, msg);
4239
+ }
4240
+ // ── Chronos 调度指令 ──
4241
+ handleAssign(msg) {
4242
+ const taskId = String(msg.taskId || "");
4243
+ const targetAgentId = String(msg.agentId || "");
4244
+ const assignedBy = String(msg.assignedBy || "chronos");
4245
+ if (!taskId || !targetAgentId) return;
4246
+ const targetWorker = this.agents.get(targetAgentId);
4247
+ if (!targetWorker) {
4248
+ log.warn(`Chronos \u6307\u6D3E\u5931\u8D25: Worker ${targetAgentId} \u4E0D\u5728\u7EBF`);
4249
+ return;
4250
+ }
4251
+ if (targetWorker.status !== "idle") {
4252
+ log.warn(`Chronos \u6307\u6D3E\u5931\u8D25: Worker ${targetAgentId} \u5FD9\u788C\u4E2D`);
4253
+ return;
4254
+ }
4255
+ log.info(`Chronos \u8C03\u5EA6: ${taskId} \u2192 ${targetWorker.name}`);
4256
+ const task = this.tasks.get(taskId);
4257
+ const promptB64 = task?.promptB64;
4258
+ this.dispatchTaskTo(taskId, targetAgentId, promptB64);
4047
4259
  }
4048
4260
  // ── 审核 + 自动通知房间 ──
4049
4261
  reviewTask(taskId, verdict, feedback) {
@@ -4091,7 +4303,7 @@ var AgentHub = class {
4091
4303
  });
4092
4304
  }
4093
4305
  this.sendToRoomMembers(room, { type: "room:member_joined", roomId, agentId });
4094
- console.log(`[AgentHub] ${agentId} \u2192 ${roomId}`);
4306
+ log.info(`${agentId} \u2192 ${roomId}`);
4095
4307
  }
4096
4308
  leaveRoom(agentId, roomId) {
4097
4309
  const room = this.rooms.get(roomId);
@@ -4208,7 +4420,7 @@ var AgentHub = class {
4208
4420
  branch: t.branch
4209
4421
  }));
4210
4422
  const rooms = this.getRooms();
4211
- 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` };
4212
4424
  }
4213
4425
  // ── 关闭 ──
4214
4426
  close() {
@@ -4254,6 +4466,7 @@ function ensureDir(dbPath2) {
4254
4466
  }
4255
4467
 
4256
4468
  // src/adapters/hub-persistence.ts
4469
+ var log2 = logger("HubDB");
4257
4470
  var HubDB = class {
4258
4471
  db = null;
4259
4472
  dbPath;
@@ -4263,19 +4476,17 @@ var HubDB = class {
4263
4476
  // ── 初始化 ──
4264
4477
  open() {
4265
4478
  if (!isSqliteAvailable()) {
4266
- process.stderr.write("[HubDB] node:sqlite \u4E0D\u53EF\u7528\uFF0C\u8DF3\u8FC7\u6301\u4E45\u5316\n");
4479
+ log2.info("node:sqlite \u4E0D\u53EF\u7528\uFF0C\u8DF3\u8FC7\u6301\u4E45\u5316");
4267
4480
  return false;
4268
4481
  }
4269
4482
  try {
4270
4483
  ensureDir(this.dbPath);
4271
4484
  this.db = openDB(this.dbPath, { create: true });
4272
4485
  this.migrate();
4273
- process.stderr.write(`[HubDB] \u5DF2\u6253\u5F00 ${this.dbPath}
4274
- `);
4486
+ log2.info(`\u5DF2\u6253\u5F00 ${this.dbPath}`);
4275
4487
  return true;
4276
4488
  } catch (e) {
4277
- process.stderr.write(`[HubDB] \u6253\u5F00\u5931\u8D25: ${String(e)}
4278
- `);
4489
+ log2.error(`\u6253\u5F00\u5931\u8D25: ${String(e)}`);
4279
4490
  return false;
4280
4491
  }
4281
4492
  }
@@ -4331,8 +4542,7 @@ var HubDB = class {
4331
4542
  task.reviewedAt || null
4332
4543
  );
4333
4544
  } catch (e) {
4334
- process.stderr.write(`[HubDB] saveTask(${task.taskId}) \u5931\u8D25: ${String(e)}
4335
- `);
4545
+ log2.error(`saveTask(${task.taskId}) \u5931\u8D25: ${String(e)}`);
4336
4546
  }
4337
4547
  }
4338
4548
  loadTasks() {
@@ -4340,8 +4550,7 @@ var HubDB = class {
4340
4550
  try {
4341
4551
  return this.db.prepare("SELECT * FROM hub_tasks ORDER BY taskId").all();
4342
4552
  } catch (e) {
4343
- process.stderr.write(`[HubDB] loadTasks \u5931\u8D25: ${String(e)}
4344
- `);
4553
+ log2.error(`loadTasks \u5931\u8D25: ${String(e)}`);
4345
4554
  return [];
4346
4555
  }
4347
4556
  }
@@ -4356,8 +4565,7 @@ var HubDB = class {
4356
4565
  )
4357
4566
  `).run(roomId, roomId);
4358
4567
  } catch (e) {
4359
- process.stderr.write(`[HubDB] saveMessage(${roomId}) \u5931\u8D25: ${String(e)}
4360
- `);
4568
+ log2.error(`saveMessage(${roomId}) \u5931\u8D25: ${String(e)}`);
4361
4569
  }
4362
4570
  }
4363
4571
  loadMessages(roomId, limit = 50) {
@@ -4367,8 +4575,7 @@ var HubDB = class {
4367
4575
  `SELECT * FROM hub_messages WHERE roomId = ? ORDER BY id DESC LIMIT ?`
4368
4576
  ).all(roomId, limit).reverse();
4369
4577
  } catch (e) {
4370
- process.stderr.write(`[HubDB] loadMessages(${roomId}) \u5931\u8D25: ${String(e)}
4371
- `);
4578
+ log2.error(`loadMessages(${roomId}) \u5931\u8D25: ${String(e)}`);
4372
4579
  return [];
4373
4580
  }
4374
4581
  }
@@ -4386,11 +4593,12 @@ var HubDB = class {
4386
4593
  };
4387
4594
 
4388
4595
  // src/adapters/hub-memory.ts
4596
+ var log3 = logger("Memory");
4389
4597
  var HubMemory = class _HubMemory {
4390
4598
  db = null;
4391
4599
  dbPath;
4392
4600
  decayTimer = null;
4393
- /** 4 种记忆类型的衰减参数 */
4601
+ /** 5 种记忆类型的衰减参数 */
4394
4602
  static DECAY = {
4395
4603
  memory: { rate: 0.94, windowDays: 7 },
4396
4604
  // 7 天后开始衰减
@@ -4398,8 +4606,10 @@ var HubMemory = class _HubMemory {
4398
4606
  // 永不衰减
4399
4607
  directive: { rate: 1, windowDays: 0 },
4400
4608
  // 永不衰减
4401
- skill: { rate: 0.9, windowDays: 14 }
4609
+ skill: { rate: 0.9, windowDays: 14 },
4402
4610
  // 14 天后开始衰减
4611
+ strategy: { rate: 0.95, windowDays: 7 }
4612
+ // 7 天后开始衰减,速度慢于 memory
4403
4613
  };
4404
4614
  static HEAT_THRESHOLD = 3;
4405
4615
  // 衰减周期内被读 >= 次则跳过衰减
@@ -4411,20 +4621,18 @@ var HubMemory = class _HubMemory {
4411
4621
  // ── 生命周期 ─────────────────────────────────────────────────────────
4412
4622
  open() {
4413
4623
  if (!isSqliteAvailable()) {
4414
- process.stderr.write("[Memory] node:sqlite \u4E0D\u53EF\u7528\n");
4624
+ log3.info("node:sqlite \u4E0D\u53EF\u7528");
4415
4625
  return false;
4416
4626
  }
4417
4627
  try {
4418
4628
  ensureDir(this.dbPath);
4419
4629
  this.db = openDB(this.dbPath, { create: true });
4420
4630
  this.migrate();
4421
- process.stderr.write(`[Memory] \u5DF2\u6253\u5F00 ${this.dbPath}
4422
- `);
4631
+ log3.info(`\u5DF2\u6253\u5F00 ${this.dbPath}`);
4423
4632
  this.startDecay();
4424
4633
  return true;
4425
4634
  } catch (e) {
4426
- process.stderr.write(`[Memory] \u6253\u5F00\u5931\u8D25: ${String(e)}
4427
- `);
4635
+ log3.error(`\u6253\u5F00\u5931\u8D25: ${String(e)}`);
4428
4636
  return false;
4429
4637
  }
4430
4638
  }
@@ -4463,10 +4671,18 @@ var HubMemory = class _HubMemory {
4463
4671
  `);
4464
4672
  }
4465
4673
  // ── CRUD ─────────────────────────────────────────────────────────────
4674
+ /** 标准化 tags:接受 string[] 或逗号分隔的字符串,统一返回逗号分隔字符串 */
4675
+ normalizeTags(raw) {
4676
+ if (!raw) return "";
4677
+ if (Array.isArray(raw)) {
4678
+ return raw.map((t) => t.replace(/,/g, "")).filter(Boolean).join(",");
4679
+ }
4680
+ return raw.split(",").map((t) => t.trim()).filter(Boolean).join(",");
4681
+ }
4466
4682
  store(opts) {
4467
4683
  const id = `mem-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
4468
4684
  const type = opts.type || "memory";
4469
- const tags = (opts.tags || []).map((t) => t.replace(/,/g, "")).join(",");
4685
+ const tags = this.normalizeTags(opts.tags);
4470
4686
  const confidence = Math.min(1, Math.max(0, opts.confidence ?? 1));
4471
4687
  const now = (/* @__PURE__ */ new Date()).toISOString();
4472
4688
  this.db.prepare(`
@@ -4590,6 +4806,7 @@ var SEED = [
4590
4806
  { name: "nexus", category: "\u96F6\u914D\u7F6EAgent", description: "\u96F6API\u5BC6\u94A5MCP\u670D\u52A1\u5668: DuckDuckGo\u641C\u7D22/Ollama LLM/\u5185\u5B58\u5B58\u50A8", repo: "black_shadow/nexus", install: "npx @black_shadow/nexus", stars: "\u65B0", tags: "\u96F6\u914D\u7F6E,\u672C\u5730,\u9690\u79C1", verified: true },
4591
4807
  { name: "mcp-server-rag-web-browser", category: "\u77E5\u8BC6\u5E93", description: "Agent\u57FA\u4E8E\u7F51\u9875\u6587\u6863\u505ARAG\u95EE\u7B54, \u81EA\u52A8\u6293\u53D6+\u7D22\u5F15+\u68C0\u7D22", repo: "modelcontextprotocol/servers", install: "npx -y @modelcontextprotocol/server-rag-web-browser", stars: "2k+", tags: "RAG,\u77E5\u8BC6\u5E93,\u7F51\u9875,\u95EE\u7B54", verified: true }
4592
4808
  ];
4809
+ var log4 = logger("Registry");
4593
4810
  var HubRegistry = class {
4594
4811
  constructor(dbPath2) {
4595
4812
  this.dbPath = dbPath2;
@@ -4606,14 +4823,11 @@ var HubRegistry = class {
4606
4823
  const n = this.db.prepare("SELECT COUNT(*) as n FROM registry").get()?.n || 0;
4607
4824
  if (n === 0) {
4608
4825
  SEED.forEach((p) => this.add(p));
4609
- process.stderr.write(`[Registry] \u9884\u7F6E ${SEED.length} \u4E2AMCP\u63D2\u4EF6
4610
- `);
4611
- } else process.stderr.write(`[Registry] \u5DF2\u52A0\u8F7D ${n} \u4E2AMCP\u63D2\u4EF6
4612
- `);
4826
+ log4.info(`\u9884\u7F6E ${SEED.length} \u4E2AMCP\u63D2\u4EF6`);
4827
+ } else log4.info(`\u5DF2\u52A0\u8F7D ${n} \u4E2AMCP\u63D2\u4EF6`);
4613
4828
  return true;
4614
4829
  } catch (e) {
4615
- process.stderr.write(`[Registry] ${String(e)}
4616
- `);
4830
+ log4.error(`\u6253\u5F00\u5931\u8D25: ${String(e)}`);
4617
4831
  return false;
4618
4832
  }
4619
4833
  }
@@ -4628,12 +4842,14 @@ var HubRegistry = class {
4628
4842
  }
4629
4843
  }
4630
4844
  add(p) {
4845
+ if (!this.db) throw new Error("Registry \u672A\u6253\u5F00");
4631
4846
  const id = "mcp-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 5);
4632
4847
  const now = (/* @__PURE__ */ new Date()).toISOString();
4633
4848
  this.db.prepare("INSERT OR REPLACE INTO registry (id,name,category,description,repo,install,stars,tags,verified,createdAt) VALUES(?,?,?,?,?,?,?,?,?,?)").run(id, p.name, p.category, p.description, p.repo, p.install, p.stars, p.tags, p.verified ? 1 : 0, now);
4634
4849
  return { id, ...p, verified: p.verified || false, createdAt: now };
4635
4850
  }
4636
4851
  search(q, category, limit = 30) {
4852
+ if (!this.db) return [];
4637
4853
  let sql = "SELECT * FROM registry WHERE 1=1";
4638
4854
  const params = [];
4639
4855
  if (q) {
@@ -4653,6 +4869,7 @@ var HubRegistry = class {
4653
4869
  return (Array.isArray(rows) ? rows : []).map((r) => this.rowToPlugin(r));
4654
4870
  }
4655
4871
  byCategory() {
4872
+ if (!this.db) return {};
4656
4873
  const rows = this.db.prepare("SELECT * FROM registry ORDER BY category, stars DESC").all();
4657
4874
  const map = {};
4658
4875
  for (const r of Array.isArray(rows) ? rows : []) {
@@ -4663,9 +4880,11 @@ var HubRegistry = class {
4663
4880
  return map;
4664
4881
  }
4665
4882
  all(limit = 100) {
4883
+ if (!this.db) return [];
4666
4884
  return this.db.prepare("SELECT * FROM registry ORDER BY stars DESC LIMIT ?").all(limit).map((r) => this.rowToPlugin(r));
4667
4885
  }
4668
4886
  get(id) {
4887
+ if (!this.db) return null;
4669
4888
  const r = this.db.prepare("SELECT * FROM registry WHERE id=?").get(id);
4670
4889
  return r ? this.rowToPlugin(r) : null;
4671
4890
  }
@@ -4674,9 +4893,218 @@ var HubRegistry = class {
4674
4893
  }
4675
4894
  };
4676
4895
 
4677
- // src/hub-server.ts
4896
+ // src/adapters/hub-costs.ts
4678
4897
  var import_node_fs = require("node:fs");
4679
4898
  var import_node_path = require("node:path");
4899
+ var PRICING = {
4900
+ "claude-sonnet-4-6": { input: 3, output: 15 },
4901
+ "claude-sonnet-4-5": { input: 3, output: 15 },
4902
+ "claude-haiku-4-5": { input: 1, output: 5 },
4903
+ "claude-opus-4-8": { input: 15, output: 75 },
4904
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
4905
+ "gpt-4o": { input: 2.5, output: 10 },
4906
+ "deepseek-v4-pro": { input: 0.55, output: 2.19 }
4907
+ };
4908
+ function getCost(model, inputTokens, outputTokens) {
4909
+ const p = PRICING[model];
4910
+ if (!p) return 0;
4911
+ return inputTokens / 1e6 * p.input + outputTokens / 1e6 * p.output;
4912
+ }
4913
+ var HubCosts = class {
4914
+ filePath;
4915
+ cache = [];
4916
+ cacheLoaded = false;
4917
+ constructor(dbPath2 = ".hub/llm-costs.jsonl") {
4918
+ this.filePath = dbPath2;
4919
+ }
4920
+ /** 记录一次 LLM 调用 */
4921
+ record(entry) {
4922
+ const cost = entry.cost ?? getCost(entry.model, entry.inputTokens, entry.outputTokens);
4923
+ const rec = {
4924
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4925
+ agentId: entry.agentId,
4926
+ taskId: entry.taskId,
4927
+ model: entry.model,
4928
+ inputTokens: entry.inputTokens,
4929
+ outputTokens: entry.outputTokens,
4930
+ cost: Math.round(cost * 1e4) / 1e4,
4931
+ // 4 decimal places
4932
+ purpose: entry.purpose
4933
+ };
4934
+ try {
4935
+ const dir = (0, import_node_path.dirname)(this.filePath);
4936
+ if (!(0, import_node_fs.existsSync)(dir)) (0, import_node_fs.mkdirSync)(dir, { recursive: true });
4937
+ (0, import_node_fs.appendFileSync)(this.filePath, JSON.stringify(rec) + "\n", "utf-8");
4938
+ } catch {
4939
+ }
4940
+ this.cache.push(rec);
4941
+ }
4942
+ /** 加载全部记录 */
4943
+ loadAll() {
4944
+ if (this.cacheLoaded) return this.cache;
4945
+ try {
4946
+ if ((0, import_node_fs.existsSync)(this.filePath)) {
4947
+ const raw = (0, import_node_fs.readFileSync)(this.filePath, "utf-8");
4948
+ this.cache = raw.split("\n").filter(Boolean).map((line) => {
4949
+ try {
4950
+ return JSON.parse(line);
4951
+ } catch {
4952
+ return null;
4953
+ }
4954
+ }).filter(Boolean);
4955
+ }
4956
+ } catch {
4957
+ }
4958
+ this.cacheLoaded = true;
4959
+ return this.cache;
4960
+ }
4961
+ /** 汇总统计 */
4962
+ summary() {
4963
+ const all = this.loadAll();
4964
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4965
+ const todayEntries = all.filter((e) => e.timestamp.startsWith(today));
4966
+ const sum = (entries) => ({
4967
+ calls: entries.length,
4968
+ inputTokens: entries.reduce((s, e) => s + e.inputTokens, 0),
4969
+ outputTokens: entries.reduce((s, e) => s + e.outputTokens, 0),
4970
+ cost: entries.reduce((s, e) => s + e.cost, 0)
4971
+ });
4972
+ const byModel = {};
4973
+ for (const e of all) {
4974
+ if (!byModel[e.model]) byModel[e.model] = { calls: 0, inputTokens: 0, outputTokens: 0, cost: 0 };
4975
+ byModel[e.model].calls++;
4976
+ byModel[e.model].inputTokens += e.inputTokens;
4977
+ byModel[e.model].outputTokens += e.outputTokens;
4978
+ byModel[e.model].cost += e.cost;
4979
+ }
4980
+ const byAgent = {};
4981
+ for (const e of all) {
4982
+ if (!byAgent[e.agentId]) byAgent[e.agentId] = { calls: 0, inputTokens: 0, outputTokens: 0, cost: 0 };
4983
+ byAgent[e.agentId].calls++;
4984
+ byAgent[e.agentId].inputTokens += e.inputTokens;
4985
+ byAgent[e.agentId].outputTokens += e.outputTokens;
4986
+ byAgent[e.agentId].cost += e.cost;
4987
+ }
4988
+ const hourly = [];
4989
+ for (let h = 23; h >= 0; h--) {
4990
+ const slot = new Date(Date.now() - h * 36e5).toISOString().slice(0, 13);
4991
+ const entries = all.filter((e) => e.timestamp.startsWith(slot));
4992
+ hourly.push({ hour: slot, calls: entries.length, cost: entries.reduce((s, e) => s + e.cost, 0) });
4993
+ }
4994
+ const budget = parseFloat(process.env.HUB_DAILY_BUDGET || "5.00");
4995
+ return {
4996
+ today: sum(todayEntries),
4997
+ total: sum(all),
4998
+ budget,
4999
+ byModel,
5000
+ byAgent,
5001
+ hourly
5002
+ };
5003
+ }
5004
+ /** 最近 N 条记录 */
5005
+ recent(n = 50) {
5006
+ const all = this.loadAll();
5007
+ return all.slice(-n);
5008
+ }
5009
+ };
5010
+
5011
+ // src/adapters/circuit-breaker.ts
5012
+ var CircuitBreaker = class {
5013
+ circuits = /* @__PURE__ */ new Map();
5014
+ failureThreshold = 3;
5015
+ cooldownMs = 6e4;
5016
+ // 1 minute default
5017
+ constructor(opts) {
5018
+ if (opts?.failureThreshold) this.failureThreshold = opts.failureThreshold;
5019
+ if (opts?.cooldownMs) this.cooldownMs = opts.cooldownMs;
5020
+ }
5021
+ /** 获取或创建熔断器 */
5022
+ get(name) {
5023
+ let c = this.circuits.get(name);
5024
+ if (!c) {
5025
+ c = {
5026
+ name,
5027
+ state: "CLOSED",
5028
+ failures: 0,
5029
+ successCount: 0,
5030
+ failureCount: 0,
5031
+ lastFailure: null,
5032
+ lastSuccess: null,
5033
+ openedAt: null,
5034
+ cooldownMs: this.cooldownMs
5035
+ };
5036
+ this.circuits.set(name, c);
5037
+ }
5038
+ return c;
5039
+ }
5040
+ /** 调用前检查 — 返回 true 表示可以执行 */
5041
+ canCall(name) {
5042
+ const c = this.get(name);
5043
+ if (c.state === "CLOSED") return true;
5044
+ if (c.state === "HALF_OPEN") return true;
5045
+ if (c.openedAt) {
5046
+ const elapsed = Date.now() - new Date(c.openedAt).getTime();
5047
+ if (elapsed >= c.cooldownMs) {
5048
+ c.state = "HALF_OPEN";
5049
+ return true;
5050
+ }
5051
+ }
5052
+ return false;
5053
+ }
5054
+ /** 调用成功 */
5055
+ onSuccess(name) {
5056
+ const c = this.get(name);
5057
+ c.successCount++;
5058
+ c.lastSuccess = (/* @__PURE__ */ new Date()).toISOString();
5059
+ if (c.state === "HALF_OPEN") {
5060
+ c.state = "CLOSED";
5061
+ c.failures = 0;
5062
+ }
5063
+ if (c.state === "CLOSED") {
5064
+ c.failures = 0;
5065
+ }
5066
+ }
5067
+ /** 调用失败 */
5068
+ onFailure(name) {
5069
+ const c = this.get(name);
5070
+ c.failures++;
5071
+ c.failureCount++;
5072
+ c.lastFailure = (/* @__PURE__ */ new Date()).toISOString();
5073
+ if (c.failures >= this.failureThreshold) {
5074
+ c.state = "OPEN";
5075
+ c.openedAt = (/* @__PURE__ */ new Date()).toISOString();
5076
+ }
5077
+ }
5078
+ /** 包裹一个异步函数 */
5079
+ async wrap(name, fn, fallback) {
5080
+ if (!this.canCall(name)) {
5081
+ if (fallback) return fallback();
5082
+ throw new Error(`Circuit OPEN: ${name}`);
5083
+ }
5084
+ try {
5085
+ const result = await fn();
5086
+ this.onSuccess(name);
5087
+ return result;
5088
+ } catch (e) {
5089
+ this.onFailure(name);
5090
+ if (fallback) return fallback();
5091
+ throw e;
5092
+ }
5093
+ }
5094
+ /** 获取所有熔断器状态 */
5095
+ status() {
5096
+ return [...this.circuits.values()];
5097
+ }
5098
+ /** 重置指定熔断器 */
5099
+ reset(name) {
5100
+ this.circuits.delete(name);
5101
+ }
5102
+ };
5103
+ var circuitBreaker = new CircuitBreaker({ failureThreshold: 3, cooldownMs: 6e4 });
5104
+
5105
+ // src/hub-server.ts
5106
+ var import_node_fs3 = require("node:fs");
5107
+ var import_node_path3 = require("node:path");
4680
5108
 
4681
5109
  // src/adapters/hub-templates.ts
4682
5110
  var TASK_TEMPLATES = [
@@ -5353,16 +5781,328 @@ ACTION: <\u521B\u5EFA\u7684\u4EFB\u52A1ID>
5353
5781
  STATUS: <\u5DF2\u5206\u6D3E/\u5DF2\u4FEE\u590D/\u5DF2\u9A8C\u8BC1>
5354
5782
  NEXT: <\u540E\u7EED\u8DDF\u8FDB\u5EFA\u8BAE>
5355
5783
 
5784
+ \u8F93\u51FA TASK_COMPLETE`
5785
+ },
5786
+ // ════════════ 24x7 守护岗 ════════════
5787
+ {
5788
+ id: "guard-dashboard",
5789
+ name: "\u{1F4CA} \u4EEA\u8868\u76D8\u5B88\u62A4",
5790
+ description: "7x24\u76D1\u63A7\u4EEA\u8868\u76D8: KPI/\u56FE\u8868/Agent\u9762\u677F/\u4EFB\u52A1\u7BA1\u9053/\u5B9E\u65F6\u52A8\u6001/\u4EFB\u52A1\u521B\u5EFA\u5668",
5791
+ prefix: "G",
5792
+ fields: [
5793
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5794
+ ],
5795
+ buildPrompt: (p) => `\u4F60\u662F\u4EEA\u8868\u76D8\u5B88\u62A4\u8005\u3002\u68C0\u67E5 dashboard.html \u7684\u4EEA\u8868\u76D8\u9762\u677F\u4E00\u5207\u6B63\u5E38\u3002
5796
+
5797
+ ## \u68C0\u67E5\u6E05\u5355
5798
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5799
+ |------|--------|
5800
+ | KPI\u5361\u7247 | \u6570\u503C\u662F\u5426\u66F4\u65B0\uFF1F4\u4E2A\u5361\u7247(Agent/\u4EFB\u52A1/\u5B8C\u6210/\u8FA9\u8BBA)\u6570\u636E\u6B63\u786E\uFF1F |
5801
+ | Chart.js\u56FE\u8868 | \u997C\u56FE/\u6298\u7EBF\u56FE\u80FD\u6E32\u67D3\uFF1F\u6570\u636E\u70B9\u6709\u7D2F\u79EF\uFF1F |
5802
+ | Agent\u9762\u677F | working/idle \u6B63\u786E\u5206\u7EC4\uFF1F\u5361\u7247\u5E76\u6392\u663E\u793A\uFF1F |
5803
+ | \u4EFB\u52A1\u9762\u677F | 5\u4E2A\u7B5B\u9009(\u5168\u90E8/\u5F85\u5206\u914D/\u8FDB\u884C\u4E2D/\u5DF2\u5B8C\u6210/\u5DF2\u5BA1\u6838)\u6B63\u5E38\uFF1F\u9636\u6BB5\u7BA1\u9053\u663E\u793A\uFF1F |
5804
+ | \u5B9E\u65F6\u52A8\u6001 | WS\u6D88\u606F\u80FD\u63A8\u9001\uFF1Ffeed\u8BA1\u6570\u6B63\u786E\uFF1F |
5805
+ | \u4EFB\u52A1\u521B\u5EFA\u5668 | \u6A21\u677F\u5361\u7247\u80FD\u9009\uFF1F\u8868\u5355\u80FD\u586B\uFF1F\u521B\u5EFA\u6309\u94AE\u751F\u6548\uFF1F |
5806
+ | \u26A1\u5FEB\u6377\u6309\u94AE | BTC\u884C\u60C5\u5F39\u7A97\uFF1F\u65B0\u5EFA\u4EFB\u52A1\uFF1F |
5807
+
5808
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5809
+ \u8F93\u51FA TASK_COMPLETE`
5810
+ },
5811
+ {
5812
+ id: "guard-debate",
5813
+ name: "\u2696\uFE0F \u8FA9\u8BBA\u5B88\u62A4",
5814
+ description: "7x24\u7EF4\u62A4\u8FA9\u8BBA\u9762\u677F: \u5361\u7247\u5217\u8868/\u804A\u5929\u6D41/\u7ACB\u573A\u8C31/\u5171\u8BC6\u73AF",
5815
+ prefix: "G",
5816
+ fields: [
5817
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5818
+ ],
5819
+ buildPrompt: (p) => `\u4F60\u662F\u8FA9\u8BBA\u9762\u677F\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u8FA9\u8BBA\u529F\u80FD\u4E00\u5207\u6B63\u5E38\u3002
5820
+
5821
+ ## \u68C0\u67E5\u6E05\u5355
5822
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5823
+ |------|--------|
5824
+ | \u5361\u7247\u5217\u8868 | D-\u524D\u7F00\u4EFB\u52A1\u6B63\u786E\u5206\u7EC4\uFF1F\u5171\u8BC6\u5EA6\u8BA1\u7B97\uFF1F\u591A\u7A7A\u8272\u8C31\uFF1F |
5825
+ | \u804A\u5929\u6D41 | \u70B9\u51FB\u5361\u7247\u8FDB\u5165\u804A\u5929\uFF1FAgent1\u5DE6\u6C14\u6CE1/Agent2\u53F3\u6C14\u6CE1\uFF1F |
5826
+ | \u8BBA\u70B9\u63D0\u53D6 | parseKeyPoints\u80FD\u63D0\u53D6\u6570\u636E\uFF1F\u8868\u683C\u884C\u8BC6\u522B\uFF1F |
5827
+ | \u7EFC\u5408\u7ED3\u8BBA | CONCLUSION\u63D0\u53D6\uFF1F\u7EFC\u5408\u6846\u663E\u793A\uFF1F |
5828
+ | \u8FD4\u56DE\u6309\u94AE | closeDebateDetail\u6B63\u5E38\u5DE5\u4F5C\uFF1F\u5361\u7247\u5217\u8868\u6062\u590D\uFF1F |
5829
+
5830
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5831
+ \u8F93\u51FA TASK_COMPLETE`
5832
+ },
5833
+ {
5834
+ id: "guard-terminal",
5835
+ name: "\u{1F5A5} \u7EC8\u7AEF\u5B88\u62A4",
5836
+ description: "7x24\u7EF4\u62A4\u5B9E\u65F6\u7EC8\u7AEF: \u6D41\u5F0F\u8F93\u51FA/Pause/Clear/\u7740\u8272",
5837
+ prefix: "G",
5838
+ fields: [
5839
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5840
+ ],
5841
+ buildPrompt: (p) => `\u4F60\u662F\u5B9E\u65F6\u7EC8\u7AEF\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u7EC8\u7AEF\u529F\u80FD\u4E00\u5207\u6B63\u5E38\u3002
5842
+
5843
+ ## \u68C0\u67E5\u6E05\u5355
5844
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5845
+ |------|--------|
5846
+ | \u6D41\u5F0F\u8F93\u51FA | WS\u6D88\u606F\u80FD\u5B9E\u65F6\u63A8\u9001\u5230\u7EC8\u7AEF\uFF1F\u591AWorker\u8F93\u51FA\u6C47\u805A\uFF1F |
5847
+ | \u7740\u8272 | success/warn/err/info/dim \u5206\u7C7B\u6B63\u786E\uFF1F |
5848
+ | Pause/Clear | \u6682\u505C\u81EA\u52A8\u6EDA\u52A8\uFF1F\u6E05\u7A7A\u7EC8\u7AEF\uFF1F |
5849
+ | Worker\u8BA1\u6570 | \u5728\u7EBFWorker\u6570\u663E\u793A\u6B63\u786E\uFF1F\u25CF LIVE/\u25CB idle\u5207\u6362\uFF1F |
5850
+ | \u65F6\u95F4\u6233 | \u6BCF\u6761\u6D88\u606F\u5E26 HH:MM:SS\uFF1F\u6807\u7B7E\u7740\u8272\uFF1F|
5851
+
5852
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5853
+ \u8F93\u51FA TASK_COMPLETE`
5854
+ },
5855
+ {
5856
+ id: "guard-signals",
5857
+ name: "\u{1F6A6} \u4FE1\u53F7\u5B88\u62A4",
5858
+ description: "7x24\u7EF4\u62A4\u4FE1\u53F7\u9762\u677F: VBT\u4FE1\u53F7\u89E3\u6790/\u65B9\u5411KPI/\u7F6E\u4FE1\u5EA6\u6761",
5859
+ prefix: "G",
5860
+ fields: [
5861
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5862
+ ],
5863
+ buildPrompt: (p) => `\u4F60\u662F\u4FE1\u53F7\u9762\u677F\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u4FE1\u53F7\u529F\u80FD\u4E00\u5207\u6B63\u5E38\u3002
5864
+
5865
+ ## \u68C0\u67E5\u6E05\u5355
5866
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5867
+ |------|--------|
5868
+ | \u4FE1\u53F7\u89E3\u6790 | V-\u524D\u7F00\u4EFB\u52A1\u6B63\u786E\u63D0\u53D6CURRENT_SIGNAL/SHARPE/WIN_RATE\uFF1F |
5869
+ | KPI | \u505A\u591A/\u4E2D\u6027/\u505A\u7A7A\u8BA1\u6570\u6B63\u786E\uFF1F\u5E73\u5747\u80DC\u7387\u8BA1\u7B97\uFF1F |
5870
+ | \u4FE1\u53F7\u5361\u7247 | \u65B9\u5411\u7740\u8272(\u7EFF\u591A/\u7EA2\u7A7A)\uFF1FSharpe/MDD/\u80DC\u7387\u663E\u793A\uFF1F |
5871
+ | \u6700\u8FD1\u4FE1\u53F7 | LAST_5_SIGNALS\u89E3\u6790\uFF1F\u4FE1\u53F7\u5386\u53F2\u663E\u793A\uFF1F |
5872
+ | \u521B\u5EFA\u4FE1\u53F7\u4EFB\u52A1 | vbt-signal\u6A21\u677F\u53EF\u7528\uFF1F\u80FD\u521B\u5EFA\u65B0\u4FE1\u53F7\uFF1F |
5873
+
5874
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5875
+ \u8F93\u51FA TASK_COMPLETE`
5876
+ },
5877
+ {
5878
+ id: "guard-store",
5879
+ name: "\u{1F3EA} \u5546\u5E97\u5B88\u62A4",
5880
+ description: "7x24\u7EF4\u62A4MCP\u63D2\u4EF6\u5546\u5E97: \u641C\u7D22/\u5206\u7C7B/\u5361\u7247\u6E32\u67D3",
5881
+ prefix: "G",
5882
+ fields: [
5883
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5884
+ ],
5885
+ buildPrompt: (p) => `\u4F60\u662FMCP\u5546\u5E97\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u5546\u5E97\u529F\u80FD\u4E00\u5207\u6B63\u5E38\u3002
5886
+
5887
+ ## \u68C0\u67E5\u6E05\u5355
5888
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5889
+ |------|--------|
5890
+ | \u63D2\u4EF6\u52A0\u8F7D | /api/store \u8FD4\u56DE24\u4E2A\u63D2\u4EF6\uFF1F22\u4E2A\u5206\u7C7B\uFF1F |
5891
+ | \u641C\u7D22\u8FC7\u6EE4 | \u8F93\u5165\u5173\u952E\u8BCD\u5B9E\u65F6\u8FC7\u6EE4\uFF1F\u7A7A\u7ED3\u679C\u63D0\u793A\uFF1F |
5892
+ | \u5361\u7247\u6E32\u67D3 | \u540D\u79F0/\u63CF\u8FF0/\u4ED3\u5E93/\u661F\u6807/\u5DF2\u9A8C\u8BC1\u6B63\u786E\u663E\u793A\uFF1F |
5893
+ | \u5206\u7C7B\u5206\u7EC4 | \u6309category\u5206\u7EC4\u663E\u793A\uFF1F\u6807\u9898\u6B63\u786E\uFF1F |
5894
+
5895
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5896
+ \u8F93\u51FA TASK_COMPLETE`
5897
+ },
5898
+ {
5899
+ id: "guard-memory",
5900
+ name: "\u{1F9E0} \u8BB0\u5FC6\u5B88\u62A4",
5901
+ description: "7x24\u7EF4\u62A4\u8BB0\u5FC6\u5E93: CRUD/\u641C\u7D22/\u7C7B\u578B\u8FC7\u6EE4/\u77E5\u8BC6\u6CE8\u5165",
5902
+ prefix: "G",
5903
+ fields: [
5904
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5905
+ ],
5906
+ buildPrompt: (p) => `\u4F60\u662F\u8BB0\u5FC6\u5E93\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u8BB0\u5FC6\u529F\u80FD\u4E00\u5207\u6B63\u5E38\u3002
5907
+
5908
+ ## \u68C0\u67E5\u6E05\u5355
5909
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5910
+ |------|--------|
5911
+ | CRUD | POST\u521B\u5EFA/GET\u5217\u8868/DELETE\u5220\u9664\u6B63\u5E38\uFF1F |
5912
+ | \u641C\u7D22 | /api/memory/search \u5173\u952E\u8BCD\u641C\u7D22\uFF1F |
5913
+ | \u7C7B\u578B\u8FC7\u6EE4 | all/memory/directive/skill/doc/strategy\u7B5B\u9009\uFF1F |
5914
+ | \u77E5\u8BC6\u6CE8\u5165 | Worker\u6267\u884C\u524D\u80FD\u81EA\u52A8\u641C\u8BB0\u5FC6\uFF1F |
5915
+ | \u65B0\u5EFA\u8868\u5355 | \u7C7B\u578B\u4E0B\u62C9/\u6587\u672C/\u6807\u7B7E/\u7F6E\u4FE1\u5EA6\u6ED1\u5757\uFF1F |
5916
+
5917
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5918
+ \u8F93\u51FA TASK_COMPLETE`
5919
+ },
5920
+ {
5921
+ id: "guard-scheduler",
5922
+ name: "\u23F0 \u8C03\u5EA6\u5B88\u62A4",
5923
+ description: "7x24\u5B88\u62A4\u5B9A\u65F6\u4EFB\u52A1: \u68C0\u67E5\u79EF\u538B/\u8865\u53D1Worker/\u76D1\u63A74\u4E2A\u5C97\u4F4D",
5924
+ prefix: "G",
5925
+ fields: [
5926
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5927
+ ],
5928
+ buildPrompt: (p) => `\u4F60\u662F\u8C03\u5EA6\u7CFB\u7EDF\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u5B9A\u65F6\u4EFB\u52A1\u4E00\u5207\u6B63\u5E38\u3002
5929
+
5930
+ ## \u68C0\u67E5\u6E05\u5355
5931
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5932
+ |------|--------|
5933
+ | 4\u4E2A\u5C97\u4F4D | \u5206\u6790\u5E08/\u7814\u7A76\u5458/\u7B56\u5C55\u4EBA/\u5DE5\u7A0B\u5E08\u90FD\u5728\u8DD1\uFF1F |
5934
+ | \u79EF\u538B\u68C0\u67E5 | \u6709 unassigned \u7684\u5B9A\u65F6\u4EFB\u52A1\uFF1F\u6709\u5C31\u8865\u53D1Worker |
5935
+ | \u9632\u91CD\u590D | scheduler\u8DF3\u8FC7\u672A\u5B8C\u6210\u7684\u4EFB\u52A1\uFF1F |
5936
+ | \u95F4\u9694 | 4h/8h/12h/24h \u95F4\u9694\u6B63\u786E\uFF1F |
5937
+ | \u4EA7\u51FA\u8D28\u91CF | \u6700\u8FD1\u5B8C\u6210\u7684\u5B9A\u65F6\u4EFB\u52A1\u7ED3\u679C\u6709\u610F\u4E49\uFF1F |
5938
+
5939
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5356
5940
  \u8F93\u51FA TASK_COMPLETE`
5357
5941
  }
5358
5942
  ];
5359
5943
 
5944
+ // src/adapters/api-credits.ts
5945
+ var import_node_sqlite = require("node:sqlite");
5946
+ var import_node_fs2 = require("node:fs");
5947
+ var import_node_path2 = require("node:path");
5948
+ var log5 = logger("Credits");
5949
+ var CREDIT_COST = {
5950
+ READ: 1,
5951
+ WRITE: 5,
5952
+ FUND_TRANSFER: 10,
5953
+ ADMIN: 20
5954
+ };
5955
+ var ApiCredits = class {
5956
+ db;
5957
+ cache = /* @__PURE__ */ new Map();
5958
+ cacheLoaded = false;
5959
+ constructor(dbPath2 = ".hub/api-credits.db") {
5960
+ const dir = (0, import_node_path2.dirname)(dbPath2);
5961
+ if (!(0, import_node_fs2.existsSync)(dir)) (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
5962
+ this.db = new import_node_sqlite.DatabaseSync(dbPath2);
5963
+ this.db.exec("PRAGMA journal_mode=WAL");
5964
+ this.init();
5965
+ }
5966
+ init() {
5967
+ this.db.exec(`
5968
+ CREATE TABLE IF NOT EXISTS api_keys (
5969
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
5970
+ key TEXT UNIQUE NOT NULL,
5971
+ client_name TEXT NOT NULL,
5972
+ credits REAL NOT NULL DEFAULT 0,
5973
+ total_used INTEGER NOT NULL DEFAULT 0,
5974
+ enabled INTEGER NOT NULL DEFAULT 1,
5975
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
5976
+ last_used_at TEXT
5977
+ );
5978
+ CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(key);
5979
+ `);
5980
+ this.loadCache();
5981
+ }
5982
+ loadCache() {
5983
+ const rows = this.db.prepare("SELECT * FROM api_keys").all();
5984
+ for (const r of rows) this.cache.set(r.key, r);
5985
+ this.cacheLoaded = true;
5986
+ log5.info(`\u5DF2\u52A0\u8F7D ${rows.length} \u4E2A API Key`);
5987
+ }
5988
+ /** 查找 Key — 缓存命中或直接查 DB */
5989
+ lookup(key) {
5990
+ if (!this.cacheLoaded) this.loadCache();
5991
+ const cached = this.cache.get(key);
5992
+ if (cached) return cached;
5993
+ const row = this.db.prepare("SELECT * FROM api_keys WHERE key = ?").get(key);
5994
+ if (row) {
5995
+ this.cache.set(key, row);
5996
+ return row;
5997
+ }
5998
+ return null;
5999
+ }
6000
+ /** 校验并扣费 */
6001
+ deduct(key, riskLevel) {
6002
+ const record = this.lookup(key);
6003
+ if (!record) return { ok: false, error: "\u65E0\u6548\u7684 API Key" };
6004
+ if (!record.enabled) return { ok: false, error: "API Key \u5DF2\u7981\u7528" };
6005
+ const cost = CREDIT_COST[riskLevel] || 1;
6006
+ if (record.credits < cost) return { ok: false, error: `\u79EF\u5206\u4E0D\u8DB3 (\u9700\u8981 ${cost}\uFF0C\u5269\u4F59 ${record.credits})` };
6007
+ record.credits -= cost;
6008
+ record.totalUsed++;
6009
+ record.lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
6010
+ this.db.prepare("UPDATE api_keys SET credits = ?, total_used = ?, last_used_at = ? WHERE id = ?").run(record.credits, record.totalUsed, record.lastUsedAt, record.id);
6011
+ return { ok: true, remaining: record.credits };
6012
+ }
6013
+ /** 管理:创建 Key */
6014
+ createKey(clientName, initialCredits = 1e3) {
6015
+ const key = "hvip-" + Array.from(
6016
+ { length: 32 },
6017
+ () => "abcdefghijklmnopqrstuvwxyz0123456789"[Math.floor(Math.random() * 36)]
6018
+ ).join("");
6019
+ const result = this.db.prepare(
6020
+ "INSERT INTO api_keys (key, client_name, credits) VALUES (?, ?, ?)"
6021
+ ).run(key, clientName, initialCredits);
6022
+ const record = {
6023
+ id: result.lastInsertRowid,
6024
+ key,
6025
+ clientName,
6026
+ credits: initialCredits,
6027
+ totalUsed: 0,
6028
+ enabled: true,
6029
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6030
+ lastUsedAt: null
6031
+ };
6032
+ this.cache.set(key, record);
6033
+ return record;
6034
+ }
6035
+ /** 管理:充值 */
6036
+ topUp(key, amount) {
6037
+ const record = this.lookup(key);
6038
+ if (!record) return { ok: false, error: "Key \u4E0D\u5B58\u5728" };
6039
+ if (amount <= 0) return { ok: false, error: "\u5145\u503C\u91D1\u989D\u5FC5\u987B > 0" };
6040
+ record.credits += amount;
6041
+ this.db.prepare("UPDATE api_keys SET credits = ? WHERE id = ?").run(record.credits, record.id);
6042
+ return { ok: true, newBalance: record.credits };
6043
+ }
6044
+ /** 管理:启用/禁用 Key */
6045
+ setEnabled(key, enabled) {
6046
+ const record = this.lookup(key);
6047
+ if (!record) return false;
6048
+ record.enabled = enabled;
6049
+ this.db.prepare("UPDATE api_keys SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, record.id);
6050
+ return true;
6051
+ }
6052
+ /** 管理:列出所有 Key (含 Key 前缀,完整 Key 仅创建时返回) */
6053
+ listAll() {
6054
+ return [...this.cache.values()].map((r) => ({
6055
+ id: r.id,
6056
+ clientName: r.clientName,
6057
+ credits: r.credits,
6058
+ totalUsed: r.totalUsed,
6059
+ enabled: r.enabled,
6060
+ createdAt: r.createdAt,
6061
+ lastUsedAt: r.lastUsedAt,
6062
+ keyPrefix: r.key.slice(0, 12) + "..." + r.key.slice(-8)
6063
+ }));
6064
+ }
6065
+ /** 客户端:查询余额 */
6066
+ getBalance(key) {
6067
+ const record = this.lookup(key);
6068
+ if (!record) return null;
6069
+ return { clientName: record.clientName, credits: record.credits, totalUsed: record.totalUsed };
6070
+ }
6071
+ close() {
6072
+ try {
6073
+ this.db.close();
6074
+ } catch {
6075
+ }
6076
+ }
6077
+ };
6078
+ var instance = null;
6079
+ function getApiCredits(dbPath2) {
6080
+ if (!instance) instance = new ApiCredits(dbPath2);
6081
+ return instance;
6082
+ }
6083
+
5360
6084
  // src/hub-server.ts
5361
- var VERSION = "0.4.3";
6085
+ var envPath = (0, import_node_path3.join)(process.cwd(), ".env");
6086
+ if ((0, import_node_fs3.existsSync)(envPath)) {
6087
+ (0, import_node_fs3.readFileSync)(envPath, "utf-8").split(/\r?\n/).forEach((line) => {
6088
+ const hashIdx = line.indexOf("#");
6089
+ if (hashIdx >= 0) line = line.substring(0, hashIdx);
6090
+ const m = line.match(/^\s*([^#\s=]+)\s*=\s*(.*)$/);
6091
+ if (m) {
6092
+ const val = m[2].trim();
6093
+ if (!val) {
6094
+ delete process.env[m[1]];
6095
+ } else if (!process.env[m[1]]) {
6096
+ process.env[m[1]] = val;
6097
+ }
6098
+ }
6099
+ });
6100
+ }
6101
+ var VERSION = "0.6.0";
5362
6102
  function getDashboardHtml(host2, port) {
5363
- const paths = [(0, import_node_path.join)(__dirname, "web", "dashboard.html"), (0, import_node_path.join)(__dirname, "..", "src", "web", "dashboard.html")];
6103
+ const paths = [(0, import_node_path3.join)(__dirname, "web", "dashboard.html"), (0, import_node_path3.join)(__dirname, "..", "src", "web", "dashboard.html")];
5364
6104
  for (const p of paths) {
5365
- if ((0, import_node_fs.existsSync)(p)) return (0, import_node_fs.readFileSync)(p, "utf-8").replace("HUB_HOST", host2).replace("WS_PORT = 0", "WS_PORT = " + port);
6105
+ 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);
5366
6106
  }
5367
6107
  return "<html><body><h2>dashboard.html not found</h2></body></html>";
5368
6108
  }
@@ -5377,12 +6117,25 @@ var wsPort = parseInt(flag("port") || process.env.HUB_PORT || "9321", 10);
5377
6117
  var host = flag("host") || process.env.HUB_HOST || "127.0.0.1";
5378
6118
  var webPort = parseInt(flag("web-port") || process.env.HUB_WEB_PORT || "3000", 10);
5379
6119
  var dbPath = flag("db") || process.env.HUB_DB_PATH || ".hub/hub.db";
6120
+ var log6 = logger("Hub");
6121
+ var anthropicKey = process.env.ANTHROPIC_API_KEY || "";
6122
+ var openaiKey = process.env.OPENAI_API_KEY || "";
6123
+ var openaiBase = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
6124
+ var llmModel = process.env.LLM_MODEL || "gpt-4o-mini";
6125
+ var chatProvider = openaiKey ? "openai" : anthropicKey ? "anthropic" : "none";
5380
6126
  var token = process.env.HUB_AUTH_TOKEN || "";
5381
6127
  var workers = [];
6128
+ var mcpSessionId = "";
5382
6129
  function validateTaskId(id) {
5383
6130
  return /^[A-Za-z0-9_-]+$/.test(id) && !id.includes("..") && id.length <= 64;
5384
6131
  }
6132
+ var MAX_CONCURRENT_WORKERS = 3;
5385
6133
  function spawnWorker(taskId) {
6134
+ const activeWorkers = workers.filter((w) => w.exitCode === null && !w.killed).length;
6135
+ if (activeWorkers >= MAX_CONCURRENT_WORKERS) {
6136
+ log6.warn(`Worker \u5E76\u53D1\u4E0A\u9650 (${MAX_CONCURRENT_WORKERS})\uFF0C${taskId} \u7B49\u5F85\u4E0B\u6B21\u8C03\u5EA6`);
6137
+ return { ok: false, error: `\u5E76\u53D1\u4E0A\u9650 ${MAX_CONCURRENT_WORKERS}\uFF0C\u5F53\u524D ${activeWorkers} \u6D3B\u8DC3` };
6138
+ }
5386
6139
  const hubUrl = `ws://127.0.0.1:${wsPort}`;
5387
6140
  const repoPath = process.cwd();
5388
6141
  const meta = taskMeta.get(taskId);
@@ -5394,27 +6147,23 @@ function spawnWorker(taskId) {
5394
6147
  promptB64 = Buffer.from(p, "utf-8").toString("base64");
5395
6148
  }
5396
6149
  }
5397
- const workerArgs = ["dist/hub-worker.js", "--task", taskId, "--hub", hubUrl, "--repo", repoPath, "--web-port", String(webPort)];
6150
+ const workerArgs = ["dist/hub-worker-v2.js", "--task", taskId, "--hub", hubUrl, "--repo", repoPath, "--web-port", String(webPort)];
5398
6151
  if (promptB64) workerArgs.push("--prompt-b64", promptB64);
5399
6152
  try {
5400
- const worker = (0, import_node_child_process.spawn)("node", workerArgs, { cwd: repoPath, stdio: "pipe", detached: true });
6153
+ const worker = (0, import_node_child_process.spawn)("node", workerArgs, { cwd: repoPath, stdio: "pipe", detached: true, windowsHide: true });
5401
6154
  worker.stdout?.on("data", (d) => process.stderr.write(`[Worker-${taskId}] ${d}`));
5402
6155
  worker.stderr?.on("data", (d) => process.stderr.write(`[Worker-${taskId}] ${d}`));
5403
- worker.on("error", (e) => process.stderr.write(`[Hub] Worker \u542F\u52A8\u5931\u8D25: ${e.message}
5404
- `));
6156
+ worker.on("error", (e) => log6.error(`Worker \u542F\u52A8\u5931\u8D25: ${e.message}`));
5405
6157
  worker.on("close", (code) => {
5406
- process.stderr.write(`[Hub] Worker-${taskId} \u9000\u51FA (${code})
5407
- `);
6158
+ log6.info(`Worker-${taskId} \u9000\u51FA (${code})`);
5408
6159
  const idx = workers.indexOf(worker);
5409
6160
  if (idx >= 0) workers.splice(idx, 1);
5410
6161
  });
5411
6162
  workers.push(worker);
5412
- process.stderr.write(`[Hub] \u6D3B\u8DC3 Worker: ${workers.length}
5413
- `);
6163
+ log6.info(`\u6D3B\u8DC3 Worker: ${workers.length}`);
5414
6164
  return { ok: true, workerPid: worker.pid };
5415
6165
  } catch (e) {
5416
- process.stderr.write(`[Hub] Worker \u542F\u52A8\u5F02\u5E38: ${e.message}
5417
- `);
6166
+ log6.error(`Worker \u542F\u52A8\u5F02\u5E38: ${e.message}`);
5418
6167
  return { ok: false, error: e.message };
5419
6168
  }
5420
6169
  }
@@ -5441,6 +6190,46 @@ function startHttpServer() {
5441
6190
  res.end(getDashboardHtml(host, wsPort));
5442
6191
  return;
5443
6192
  }
6193
+ if (_req.method === "GET" && _req.url === "/v2") {
6194
+ res.writeHead(301, { "Location": "/v2/" });
6195
+ res.end();
6196
+ return;
6197
+ }
6198
+ if (_req.method === "GET" && _req.url?.startsWith("/v2/")) {
6199
+ const v2Dir = (0, import_node_path3.join)(__dirname, "..", "dashboard-v2", "dist");
6200
+ let filePath;
6201
+ if (_req.url === "/v2/") {
6202
+ filePath = (0, import_node_path3.join)(v2Dir, "index.html");
6203
+ } else {
6204
+ filePath = (0, import_node_path3.join)(v2Dir, _req.url.replace(/^\/v2\//, ""));
6205
+ }
6206
+ if (!(0, import_node_fs3.existsSync)(filePath) || !filePath.includes(".")) {
6207
+ filePath = (0, import_node_path3.join)(v2Dir, "index.html");
6208
+ }
6209
+ if ((0, import_node_fs3.existsSync)(filePath)) {
6210
+ const ext = filePath.split(".").pop() || "html";
6211
+ 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" };
6212
+ res.writeHead(200, { "Content-Type": mime[ext] || "application/octet-stream" });
6213
+ res.end((0, import_node_fs3.readFileSync)(filePath));
6214
+ } else {
6215
+ res.writeHead(404);
6216
+ res.end("dashboard-v2 not found");
6217
+ }
6218
+ return;
6219
+ }
6220
+ if (_req.method === "GET" && _req.url === "/chat") {
6221
+ const paths = [(0, import_node_path3.join)(__dirname, "web", "index.html"), (0, import_node_path3.join)(__dirname, "..", "src", "web", "index.html")];
6222
+ for (const p of paths) {
6223
+ if ((0, import_node_fs3.existsSync)(p)) {
6224
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
6225
+ res.end((0, import_node_fs3.readFileSync)(p, "utf-8"));
6226
+ return;
6227
+ }
6228
+ }
6229
+ res.writeHead(404);
6230
+ res.end("chat UI not found");
6231
+ return;
6232
+ }
5444
6233
  if (_req.method === "GET" && _req.url === "/api/status") {
5445
6234
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
5446
6235
  res.end(JSON.stringify(agentHub.status()));
@@ -5462,20 +6251,22 @@ function startHttpServer() {
5462
6251
  res.end(JSON.stringify({ error: "taskId \u683C\u5F0F\u65E0\u6548\uFF0C\u4EC5\u5141\u8BB8\u5B57\u6BCD\u6570\u5B57\u4E0B\u5212\u7EBF\u8FDE\u5B57\u7B26" }));
5463
6252
  return;
5464
6253
  }
5465
- agentHub.registerTask(taskId, title || taskId);
5466
- db?.saveTask({ taskId, status: "unassigned", title: title || taskId });
5467
6254
  if (template && params) taskMeta.set(taskId, { templateId: template, params });
5468
- let spawned = false;
5469
- let workerPid = 0;
5470
- if (template) {
5471
- const result = spawnWorker(taskId);
5472
- spawned = result.ok;
5473
- workerPid = result.workerPid || 0;
6255
+ let promptB64 = "";
6256
+ if (template && params) {
6257
+ const tpl = TASK_TEMPLATES.find((t) => t.id === template);
6258
+ if (tpl) {
6259
+ try {
6260
+ promptB64 = Buffer.from(tpl.buildPrompt(params), "utf-8").toString("base64");
6261
+ } catch {
6262
+ }
6263
+ }
5474
6264
  }
5475
- process.stderr.write(`[Hub] \u65B0\u4EFB\u52A1: ${taskId} "${title}"${spawned ? " \u{1F916} \u5DF2\u62C9\u8D77" : ""}
5476
- `);
6265
+ agentHub.registerTask(taskId, title || taskId, promptB64 || void 0);
6266
+ db?.saveTask({ taskId, status: "unassigned", title: title || taskId });
6267
+ log6.info(`\u65B0\u4EFB\u52A1: ${taskId} "${title}"`);
5477
6268
  res.writeHead(201, { "Content-Type": "application/json; charset=utf-8" });
5478
- res.end(JSON.stringify({ ok: true, taskId, title, spawned, workerPid }));
6269
+ res.end(JSON.stringify({ ok: true, taskId, title }));
5479
6270
  } catch {
5480
6271
  res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
5481
6272
  res.end(JSON.stringify({ error: "JSON \u89E3\u6790\u5931\u8D25" }));
@@ -5530,8 +6321,15 @@ function startHttpServer() {
5530
6321
  res.end(JSON.stringify(entries));
5531
6322
  return;
5532
6323
  }
5533
- if (_req.method === "GET" && _req.url === "/api/memory") {
5534
- const entries = memory.recent(30);
6324
+ if (_req.method === "GET" && (_req.url === "/api/memory" || _req.url?.startsWith("/api/memory?"))) {
6325
+ const url = _req.url.startsWith("/api/memory?") ? new import_node_url.URL(_req.url, `http://${host}:${webPort}`) : null;
6326
+ const type = url?.searchParams.get("type") || "";
6327
+ let entries;
6328
+ if (type && type !== "all" && ["memory", "doc", "directive", "skill", "strategy"].includes(type)) {
6329
+ entries = memory.byType(type, 30);
6330
+ } else {
6331
+ entries = memory.recent(30);
6332
+ }
5535
6333
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
5536
6334
  res.end(JSON.stringify(entries));
5537
6335
  return;
@@ -5621,6 +6419,107 @@ function startHttpServer() {
5621
6419
  });
5622
6420
  return;
5623
6421
  }
6422
+ if (_req.method === "GET" && _req.url === "/api/traders") {
6423
+ const stateFile = (0, import_node_path3.join)(process.cwd(), ".hub", "trader-state.json");
6424
+ if ((0, import_node_fs3.existsSync)(stateFile)) {
6425
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6426
+ res.end((0, import_node_fs3.readFileSync)(stateFile, "utf-8"));
6427
+ } else {
6428
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6429
+ res.end(JSON.stringify({ traders: {}, history: [], round: 0 }));
6430
+ }
6431
+ return;
6432
+ }
6433
+ if (_req.method === "GET" && _req.url === "/api/traders/leaderboard") {
6434
+ const stateFile = (0, import_node_path3.join)(process.cwd(), ".hub", "trader-state.json");
6435
+ if ((0, import_node_fs3.existsSync)(stateFile)) {
6436
+ const state = JSON.parse((0, import_node_fs3.readFileSync)(stateFile, "utf-8"));
6437
+ const rankings = Object.values(state.traders || {}).map((t) => {
6438
+ const positions = (t.openPositions || []).filter((p) => !p.closed);
6439
+ const initialCapital = 1e4;
6440
+ return {
6441
+ id: t.id,
6442
+ name: t.name,
6443
+ emoji: t.emoji,
6444
+ capital: t.capital,
6445
+ equity: t.equity || t.capital,
6446
+ totalPnl: t.totalPnl,
6447
+ totalPnlPct: t.totalPnlPct,
6448
+ unrealizedPnl: t.unrealizedPnl || 0,
6449
+ tradeCount: t.tradeCount || 0,
6450
+ winCount: t.winCount || 0,
6451
+ totalFees: t.totalFees || 0,
6452
+ totalFunding: t.totalFunding || 0,
6453
+ openPositions: positions.length,
6454
+ positions: positions.map((p) => ({
6455
+ symbol: p.symbol,
6456
+ direction: p.direction,
6457
+ leverage: p.leverage,
6458
+ entryPrice: p.entryPrice,
6459
+ currentPrice: p.currentPrice,
6460
+ margin: p.margin,
6461
+ liquidationPrice: p.liquidationPrice,
6462
+ unrealizedPnl: p.unrealizedPnl,
6463
+ unrealizedPnlPct: p.unrealizedPnlPct
6464
+ }))
6465
+ };
6466
+ }).sort((a, b) => b.totalPnlPct - a.totalPnlPct);
6467
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6468
+ res.end(JSON.stringify(rankings));
6469
+ } else {
6470
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6471
+ res.end(JSON.stringify([]));
6472
+ }
6473
+ return;
6474
+ }
6475
+ if (_req.method === "GET" && _req.url?.startsWith("/api/traders/history")) {
6476
+ const url = new import_node_url.URL(_req.url, `http://${host}:${webPort}`);
6477
+ const traderId = url.searchParams.get("trader") || "";
6478
+ const limit = parseInt(url.searchParams.get("limit") || "50");
6479
+ const histFile = (0, import_node_path3.join)(process.cwd(), ".hub", "trade-history.jsonl");
6480
+ if ((0, import_node_fs3.existsSync)(histFile)) {
6481
+ try {
6482
+ const lines = (0, import_node_fs3.readFileSync)(histFile, "utf-8").trim().split("\n");
6483
+ let entries = lines.map((l) => {
6484
+ try {
6485
+ return JSON.parse(l);
6486
+ } catch {
6487
+ return null;
6488
+ }
6489
+ }).filter(Boolean);
6490
+ if (traderId) entries = entries.filter((e) => e.traderId === traderId);
6491
+ entries = entries.slice(-limit);
6492
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6493
+ res.end(JSON.stringify(entries));
6494
+ } catch {
6495
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6496
+ res.end(JSON.stringify([]));
6497
+ }
6498
+ } else {
6499
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6500
+ res.end(JSON.stringify([]));
6501
+ }
6502
+ return;
6503
+ }
6504
+ if (_req.method === "GET" && _req.url === "/api/traders/risk") {
6505
+ const mode = process.env.AI_TRADER_MODE || "simulate";
6506
+ const maxOrder = parseFloat(process.env.TRADER_MAX_ORDER_USD || "100");
6507
+ const dailyLimit = parseFloat(process.env.TRADER_DAILY_LOSS_LIMIT || "500");
6508
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6509
+ res.end(JSON.stringify({ mode, maxOrderUsd: maxOrder, dailyLossLimit: dailyLimit }));
6510
+ return;
6511
+ }
6512
+ if (_req.method === "GET" && _req.url === "/api/signals") {
6513
+ const sigFile = (0, import_node_path3.join)(process.cwd(), ".hub", "signals.json");
6514
+ if ((0, import_node_fs3.existsSync)(sigFile)) {
6515
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6516
+ res.end((0, import_node_fs3.readFileSync)(sigFile, "utf-8"));
6517
+ } else {
6518
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6519
+ res.end(JSON.stringify({ signals: [], lastRun: null }));
6520
+ }
6521
+ return;
6522
+ }
5624
6523
  if (_req.method === "GET" && _req.url === "/api/templates") {
5625
6524
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
5626
6525
  res.end(JSON.stringify(TASK_TEMPLATES.map((t) => ({ id: t.id, name: t.name, description: t.description, prefix: t.prefix, fields: t.fields }))));
@@ -5631,17 +6530,288 @@ function startHttpServer() {
5631
6530
  res.end(JSON.stringify({ status: "ok", name: "hvip-hub", version: VERSION, wsPort, webPort, db: dbPath, registry: registry.all().length + " plugins" }));
5632
6531
  return;
5633
6532
  }
6533
+ if (_req.method === "GET" && _req.url === "/api/costs") {
6534
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6535
+ res.end(JSON.stringify(costTracker.summary()));
6536
+ return;
6537
+ }
6538
+ if (_req.method === "GET" && _req.url === "/api/costs/recent") {
6539
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6540
+ res.end(JSON.stringify(costTracker.recent(50)));
6541
+ return;
6542
+ }
6543
+ if (_req.method === "GET" && _req.url === "/api/health/pm2") {
6544
+ try {
6545
+ const raw = (0, import_node_child_process.execSync)("pm2 jlist", { encoding: "utf-8", timeout: 5e3, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] });
6546
+ const processes = JSON.parse(raw);
6547
+ const expected = ["hvip-hub", "hvip-mcp", "ai-trader", "worker-v2-01", "worker-v2-02", "chronos-dispatcher"];
6548
+ const health = processes.map((p) => ({
6549
+ name: p.name,
6550
+ status: p.pm2_env?.status || "unknown",
6551
+ pid: p.pid,
6552
+ uptime: p.pm2_env?.pm_uptime ? Math.round((Date.now() - p.pm2_env.pm_uptime) / 1e3) : 0,
6553
+ memory: Math.round((p.monit?.memory || 0) / 1024 / 1024),
6554
+ cpu: p.monit?.cpu || 0,
6555
+ restarts: p.pm2_env?.unstable_restarts || 0
6556
+ }));
6557
+ const missing = expected.filter((name) => !processes.some((p) => p.name === name && p.pm2_env?.status === "online"));
6558
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6559
+ res.end(JSON.stringify({
6560
+ processes: health,
6561
+ missing,
6562
+ totalExpected: expected.length,
6563
+ totalOnline: health.length - missing.length,
6564
+ healthy: missing.length === 0
6565
+ }));
6566
+ } catch (e) {
6567
+ res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
6568
+ res.end(JSON.stringify({ error: "PM2 \u67E5\u8BE2\u5931\u8D25", message: e.message }));
6569
+ }
6570
+ return;
6571
+ }
5634
6572
  if (_req.method === "GET" && _req.url === "/api/schedules") {
6573
+ const hubStatus = agentHub.status();
5635
6574
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
5636
- res.end(JSON.stringify(schedules.map((s) => ({ id: s.id, name: s.name, interval: s.interval, nextRun: s.lastRun + (s.count === 0 ? 3e4 : s.interval), count: s.count, failCount: s.failCount }))));
6575
+ res.end(JSON.stringify(schedules.map((s) => {
6576
+ const jobTasks = hubStatus.tasks.filter((t) => t.taskId.startsWith(s.id + "-"));
6577
+ const latest = jobTasks.sort((a, b) => (b.claimedAt || "").localeCompare(a.claimedAt || ""))[0];
6578
+ return {
6579
+ id: s.id,
6580
+ name: s.name,
6581
+ interval: s.interval,
6582
+ count: s.count,
6583
+ failCount: s.failCount,
6584
+ lastRun: s.lastRun || null,
6585
+ latestTask: latest ? {
6586
+ taskId: latest.taskId,
6587
+ status: latest.status,
6588
+ result: (latest.result || "").slice(0, 200)
6589
+ } : null
6590
+ };
6591
+ })));
6592
+ return;
6593
+ }
6594
+ if (_req.method === "GET" && _req.url === "/api/circuits") {
6595
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6596
+ const circuits = circuitBreaker.status();
6597
+ const openCount = circuits.filter((c) => c.state === "OPEN").length;
6598
+ res.end(JSON.stringify({ circuits, openCount, healthy: openCount === 0 }));
6599
+ return;
6600
+ }
6601
+ const apiCredits = getApiCredits();
6602
+ if (_req.method === "GET" && _req.url === "/api/credits/balance") {
6603
+ const key = _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") || "";
6604
+ const balance = apiCredits.getBalance(key);
6605
+ if (!balance) {
6606
+ res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
6607
+ res.end(JSON.stringify({ error: "\u65E0\u6548\u7684 API Key" }));
6608
+ } else {
6609
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6610
+ res.end(JSON.stringify(balance));
6611
+ }
6612
+ return;
6613
+ }
6614
+ if (_req.method === "GET" && _req.url === "/api/admin/keys") {
6615
+ if (token && _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") !== token) {
6616
+ res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
6617
+ res.end(JSON.stringify({ error: "\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650" }));
6618
+ return;
6619
+ }
6620
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6621
+ res.end(JSON.stringify(apiCredits.listAll()));
6622
+ return;
6623
+ }
6624
+ if (_req.method === "POST" && _req.url === "/api/admin/keys") {
6625
+ if (token && _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") !== token) {
6626
+ res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
6627
+ res.end(JSON.stringify({ error: "\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650" }));
6628
+ return;
6629
+ }
6630
+ const chunks = [];
6631
+ _req.on("data", (c) => chunks.push(c));
6632
+ _req.on("end", () => {
6633
+ try {
6634
+ const { clientName, initialCredits } = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
6635
+ if (!clientName) {
6636
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
6637
+ res.end(JSON.stringify({ error: "\u7F3A\u5C11 clientName" }));
6638
+ return;
6639
+ }
6640
+ const record = apiCredits.createKey(clientName, initialCredits || 1e3);
6641
+ res.writeHead(201, { "Content-Type": "application/json; charset=utf-8" });
6642
+ res.end(JSON.stringify(record));
6643
+ } catch {
6644
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
6645
+ res.end(JSON.stringify({ error: "JSON \u89E3\u6790\u5931\u8D25" }));
6646
+ }
6647
+ });
6648
+ return;
6649
+ }
6650
+ if (_req.method === "POST" && _req.url === "/api/admin/keys/topup") {
6651
+ if (token && _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") !== token) {
6652
+ res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
6653
+ res.end(JSON.stringify({ error: "\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650" }));
6654
+ return;
6655
+ }
6656
+ const chunks = [];
6657
+ _req.on("data", (c) => chunks.push(c));
6658
+ _req.on("end", () => {
6659
+ try {
6660
+ const { key, amount } = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
6661
+ if (!key || !amount) {
6662
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
6663
+ res.end(JSON.stringify({ error: "\u7F3A\u5C11 key \u6216 amount" }));
6664
+ return;
6665
+ }
6666
+ const result = apiCredits.topUp(key, amount);
6667
+ if (!result.ok) {
6668
+ res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
6669
+ res.end(JSON.stringify({ error: result.error }));
6670
+ return;
6671
+ }
6672
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6673
+ res.end(JSON.stringify({ ok: true, newBalance: result.newBalance }));
6674
+ } catch {
6675
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
6676
+ res.end(JSON.stringify({ error: "JSON \u89E3\u6790\u5931\u8D25" }));
6677
+ }
6678
+ });
6679
+ return;
6680
+ }
6681
+ if (_req.method === "POST" && _req.url === "/mcp") {
6682
+ const chunks = [];
6683
+ _req.on("data", (c) => chunks.push(c));
6684
+ _req.on("end", async () => {
6685
+ try {
6686
+ const bodyStr = Buffer.concat(chunks).toString("utf-8");
6687
+ const headers = {
6688
+ "Content-Type": "application/json",
6689
+ Accept: "application/json, text/event-stream"
6690
+ };
6691
+ if (mcpSessionId) headers["Mcp-Session-Id"] = mcpSessionId;
6692
+ const mcpRes = await fetch("http://127.0.0.1:9222/mcp", {
6693
+ method: "POST",
6694
+ headers,
6695
+ body: bodyStr
6696
+ });
6697
+ const body = await mcpRes.text();
6698
+ let result = body;
6699
+ const match = body.match(/data:\s*(\{[\s\S]*?\})\s*$/m);
6700
+ if (match) {
6701
+ try {
6702
+ const parsed = JSON.parse(match[1]);
6703
+ if (parsed.result?.content?.[0]?.text) {
6704
+ try {
6705
+ parsed.result.content[0].text = JSON.parse(parsed.result.content[0].text);
6706
+ } catch {
6707
+ }
6708
+ }
6709
+ result = JSON.stringify(parsed);
6710
+ } catch {
6711
+ log6.warn("MCP \u54CD\u5E94\u683C\u5F0F\u89E3\u6790\u5931\u8D25");
6712
+ }
6713
+ }
6714
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6715
+ res.end(result);
6716
+ } catch (e) {
6717
+ res.writeHead(502, { "Content-Type": "application/json; charset=utf-8" });
6718
+ res.end(JSON.stringify({ error: e.message || "MCP \u670D\u52A1\u672A\u542F\u52A8" }));
6719
+ }
6720
+ });
6721
+ return;
6722
+ }
6723
+ if (_req.method === "POST" && _req.url === "/api/chat") {
6724
+ if (chatProvider === "none") {
6725
+ res.writeHead(503, { "Content-Type": "application/json; charset=utf-8" });
6726
+ res.end(JSON.stringify({ error: "\u672A\u914D\u7F6E LLM Key" }));
6727
+ return;
6728
+ }
6729
+ const chunks = [];
6730
+ _req.on("data", (c) => chunks.push(c));
6731
+ _req.on("end", async () => {
6732
+ try {
6733
+ const body = Buffer.concat(chunks).toString("utf-8");
6734
+ const input = JSON.parse(body);
6735
+ if (chatProvider === "anthropic") {
6736
+ const anthroMsgs = [];
6737
+ for (const m of input.messages || []) {
6738
+ if (m.role === "system") {
6739
+ anthroMsgs.push({ role: "system", content: m.content });
6740
+ } else if (m.role === "user") {
6741
+ anthroMsgs.push({ role: "user", content: m.content });
6742
+ } else if (m.role === "assistant") {
6743
+ const c = [];
6744
+ if (m.content) c.push({ type: "text", text: m.content });
6745
+ if (m.tool_calls) for (const tc of m.tool_calls) {
6746
+ let args = {};
6747
+ try {
6748
+ args = JSON.parse(tc.function.arguments || "{}");
6749
+ } catch {
6750
+ }
6751
+ c.push({ type: "tool_use", id: tc.id, name: tc.function.name, input: args });
6752
+ }
6753
+ anthroMsgs.push({ role: "assistant", content: c.length ? c : m.content || "" });
6754
+ } else if (m.role === "tool") {
6755
+ anthroMsgs.push({ role: "user", content: [{ type: "tool_result", tool_use_id: m.tool_call_id, content: m.content }] });
6756
+ }
6757
+ }
6758
+ const anthroTools = (input.tools || []).map((t) => ({
6759
+ name: t.function.name,
6760
+ description: t.function.description,
6761
+ input_schema: t.function.parameters || {}
6762
+ }));
6763
+ const anthroRes = await fetch("https://api.anthropic.com/v1/messages", {
6764
+ method: "POST",
6765
+ headers: { "Content-Type": "application/json", "x-api-key": anthropicKey, "anthropic-version": "2023-06-01" },
6766
+ body: JSON.stringify({ model: llmModel, max_tokens: 2048, messages: anthroMsgs, tools: anthroTools })
6767
+ });
6768
+ const aJson = await anthroRes.json();
6769
+ if (aJson.type === "error") {
6770
+ res.writeHead(anthroRes.status, { "Content-Type": "application/json; charset=utf-8" });
6771
+ res.end(JSON.stringify({ error: aJson.error }));
6772
+ } else {
6773
+ const choice = { role: "assistant" };
6774
+ const content = [];
6775
+ if (aJson.content) for (const b of aJson.content) {
6776
+ if (b.type === "text") content.push(b.text);
6777
+ else if (b.type === "tool_use") {
6778
+ if (!choice.tool_calls) choice.tool_calls = [];
6779
+ choice.tool_calls.push({ id: b.id, type: "function", function: { name: b.name, arguments: JSON.stringify(b.input || {}) } });
6780
+ }
6781
+ }
6782
+ if (content.length) choice.content = content.join("\n");
6783
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6784
+ res.end(JSON.stringify({ id: aJson.id, model: aJson.model, choices: [{ message: choice, finish_reason: choice.tool_calls ? "tool_calls" : "stop" }] }));
6785
+ }
6786
+ } else {
6787
+ const oaiBody = JSON.stringify({ model: llmModel, max_tokens: 2048, messages: input.messages, tools: input.tools });
6788
+ const oaiRes = await fetch(openaiBase + "/chat/completions", {
6789
+ method: "POST",
6790
+ headers: { "Content-Type": "application/json", Authorization: "Bearer " + openaiKey },
6791
+ body: oaiBody
6792
+ });
6793
+ res.writeHead(oaiRes.status, { "Content-Type": "application/json; charset=utf-8" });
6794
+ res.end(await oaiRes.text());
6795
+ }
6796
+ } catch (e) {
6797
+ res.writeHead(502, { "Content-Type": "application/json; charset=utf-8" });
6798
+ res.end(JSON.stringify({ error: e.message }));
6799
+ }
6800
+ });
5637
6801
  return;
5638
6802
  }
5639
6803
  res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
5640
6804
  res.end(JSON.stringify({ error: "Not Found" }));
5641
6805
  });
6806
+ httpServer.on("error", (err) => {
6807
+ if (err.code === "EADDRINUSE") {
6808
+ log6.error(`\u7AEF\u53E3 ${webPort} \u88AB\u5360\u7528\uFF0C\u5C1D\u8BD5 ${webPort + 1}`);
6809
+ } else {
6810
+ log6.error(`HTTP Server \u9519\u8BEF: ${err.message}`);
6811
+ }
6812
+ });
5642
6813
  httpServer.listen(webPort, host, () => {
5643
- process.stderr.write(`[Hub] \u{1F310} \u4EEA\u8868\u76D8 \u2192 http://${host}:${webPort}
5644
- `);
6814
+ log6.info(`\u{1F310} \u4EEA\u8868\u76D8 \u2192 http://${host}:${webPort}`);
5645
6815
  });
5646
6816
  }
5647
6817
  var schedules = [
@@ -5688,25 +6858,146 @@ var schedules = [
5688
6858
  count: 0,
5689
6859
  failCount: 0,
5690
6860
  timer: null
6861
+ },
6862
+ {
6863
+ id: "sched-signals",
6864
+ name: "\u{1F6A6} VBT\u4FE1\u53F7\u5237\u65B0",
6865
+ interval: 4 * 36e5,
6866
+ template: "vbt-signal-refresh",
6867
+ params: { script: "scripts/refresh-signals.mjs" },
6868
+ lastRun: 0,
6869
+ count: 0,
6870
+ failCount: 0,
6871
+ timer: null
6872
+ },
6873
+ // ════════ 24x7 守护岗 ════════
6874
+ {
6875
+ id: "guard-dashboard",
6876
+ name: "\u{1F4CA} \u4EEA\u8868\u76D8\u5B88\u62A4",
6877
+ interval: 6 * 36e5,
6878
+ template: "guard-dashboard",
6879
+ params: { mission: "\u5168\u9762\u68C0\u67E5\u4EEA\u8868\u76D8KPI/\u56FE\u8868/Agent\u9762\u677F/\u4EFB\u52A1/\u52A8\u6001/\u521B\u5EFA\u5668" },
6880
+ lastRun: 0,
6881
+ count: 0,
6882
+ failCount: 0,
6883
+ timer: null
6884
+ },
6885
+ {
6886
+ id: "guard-debate",
6887
+ name: "\u2696\uFE0F \u8FA9\u8BBA\u5B88\u62A4",
6888
+ interval: 6 * 36e5,
6889
+ template: "guard-debate",
6890
+ params: { mission: "\u68C0\u67E5\u8FA9\u8BBA\u5361\u7247/\u804A\u5929\u6D41/\u7ACB\u573A\u8C31/\u5171\u8BC6\u73AF/\u8FD4\u56DE\u6309\u94AE" },
6891
+ lastRun: 0,
6892
+ count: 0,
6893
+ failCount: 0,
6894
+ timer: null
6895
+ },
6896
+ {
6897
+ id: "guard-terminal",
6898
+ name: "\u{1F5A5} \u7EC8\u7AEF\u5B88\u62A4",
6899
+ interval: 6 * 36e5,
6900
+ template: "guard-terminal",
6901
+ params: { mission: "\u68C0\u67E5\u6D41\u5F0F\u8F93\u51FA/\u7740\u8272/Pause/Clear/Worker\u8BA1\u6570" },
6902
+ lastRun: 0,
6903
+ count: 0,
6904
+ failCount: 0,
6905
+ timer: null
6906
+ },
6907
+ {
6908
+ id: "guard-signals",
6909
+ name: "\u{1F6A6} \u4FE1\u53F7\u5B88\u62A4",
6910
+ interval: 6 * 36e5,
6911
+ template: "guard-signals",
6912
+ params: { mission: "\u68C0\u67E5VBT\u4FE1\u53F7\u89E3\u6790/KPI/\u5361\u7247\u6E32\u67D3/\u4FE1\u53F7\u521B\u5EFA" },
6913
+ lastRun: 0,
6914
+ count: 0,
6915
+ failCount: 0,
6916
+ timer: null
6917
+ },
6918
+ {
6919
+ id: "guard-store",
6920
+ name: "\u{1F3EA} \u5546\u5E97\u5B88\u62A4",
6921
+ interval: 6 * 36e5,
6922
+ template: "guard-store",
6923
+ params: { mission: "\u68C0\u67E524\u4E2A\u63D2\u4EF6/\u5206\u7C7B/\u641C\u7D22/\u5361\u7247\u6E32\u67D3" },
6924
+ lastRun: 0,
6925
+ count: 0,
6926
+ failCount: 0,
6927
+ timer: null
6928
+ },
6929
+ {
6930
+ id: "guard-memory",
6931
+ name: "\u{1F9E0} \u8BB0\u5FC6\u5B88\u62A4",
6932
+ interval: 6 * 36e5,
6933
+ template: "guard-memory",
6934
+ params: { mission: "\u68C0\u67E5CRUD/\u641C\u7D22/\u7C7B\u578B\u8FC7\u6EE4/\u77E5\u8BC6\u6CE8\u5165/\u65B0\u5EFA\u8868\u5355" },
6935
+ lastRun: 0,
6936
+ count: 0,
6937
+ failCount: 0,
6938
+ timer: null
6939
+ },
6940
+ {
6941
+ id: "guard-scheduler",
6942
+ name: "\u23F0 \u8C03\u5EA6\u5B88\u62A4",
6943
+ interval: 3 * 36e5,
6944
+ template: "guard-scheduler",
6945
+ params: { mission: "\u68C0\u67E54\u5C97\u4F4D+7\u5B88\u62A4/\u8865\u53D1\u79EF\u538B/\u9632\u91CD\u590D" },
6946
+ lastRun: 0,
6947
+ count: 0,
6948
+ failCount: 0,
6949
+ timer: null
5691
6950
  }
5692
6951
  ];
5693
6952
  function runScheduledJob(job) {
6953
+ const status = agentHub.status();
6954
+ const prevTasks = status.tasks.filter(
6955
+ (t) => t.status === "assigned"
6956
+ ).filter((t) => t.taskId.startsWith(job.id + "-"));
6957
+ if (prevTasks.length > 0) {
6958
+ log6.warn(`Scheduler: \u23ED\uFE0F ${job.name} \u4E0A\u6B21\u4EFB\u52A1\u672A\u5B8C\u6210 (${prevTasks.map((t) => t.taskId).join(",")})\uFF0C\u8DF3\u8FC7`);
6959
+ scheduleNext(job);
6960
+ return;
6961
+ }
6962
+ const orphans = status.tasks.filter(
6963
+ (t) => t.status === "unassigned"
6964
+ ).filter((t) => t.taskId.startsWith(job.id + "-"));
6965
+ for (const o of orphans) {
6966
+ db?.saveTask({ taskId: o.taskId, status: "done", result: "[Scheduler] \u81EA\u52A8\u6E05\u7406\u5B64\u513F\u4EFB\u52A1" });
6967
+ log6.warn(`Scheduler: \u{1F9F9} \u6E05\u7406\u5B64\u513F: ${o.taskId}`);
6968
+ }
5694
6969
  const taskId = `${job.id}-${Date.now().toString(36)}`;
5695
6970
  const title = `[\u5B9A\u65F6] ${job.name} #${job.count + 1}`;
5696
- process.stderr.write(`[Scheduler] \u23F0 ${job.name} \u2192 ${taskId}
5697
- `);
5698
- agentHub.registerTask(taskId, title);
5699
- db?.saveTask({ taskId, status: "unassigned", title });
5700
- taskMeta.set(taskId, { templateId: job.template, params: job.params });
5701
- const result = spawnWorker(taskId);
5702
- if (result.ok) {
6971
+ if (job.template === "vbt-signal-refresh") {
6972
+ log6.info(`Scheduler: \u{1F504} ${job.name}`);
6973
+ try {
6974
+ const { execSync: execSync2 } = require("node:child_process");
6975
+ const out = execSync2(`node scripts/refresh-signals.mjs`, { cwd: process.cwd(), encoding: "utf-8", timeout: 6e4, windowsHide: true, maxBuffer: 2e5 });
6976
+ log6.info(`Scheduler: ${job.name} \u5B8C\u6210 \u2014 ${out.trim().split("\n").length} \u884C\u8F93\u51FA`);
6977
+ } catch (e) {
6978
+ log6.error(`Scheduler: ${job.name} \u5931\u8D25: ${e.message}`);
6979
+ job.failCount++;
6980
+ }
5703
6981
  job.count++;
5704
- job.failCount = 0;
5705
- } else {
5706
- job.failCount++;
5707
- process.stderr.write(`[Scheduler] \u274C ${job.name}: ${result.error}
5708
- `);
6982
+ job.lastRun = Date.now();
6983
+ scheduleNext(job);
6984
+ return;
6985
+ }
6986
+ log6.info(`Scheduler: \u23F0 ${job.name} \u2192 ${taskId}`);
6987
+ const tpl = TASK_TEMPLATES.find((t) => t.id === job.template);
6988
+ let promptB64 = "";
6989
+ if (tpl) {
6990
+ try {
6991
+ promptB64 = Buffer.from(tpl.buildPrompt(job.params), "utf-8").toString("base64");
6992
+ } catch {
6993
+ log6.warn(`promptB64 \u6784\u5EFA\u5931\u8D25: ${job.taskId}`);
6994
+ }
5709
6995
  }
6996
+ taskMeta.set(taskId, { templateId: job.template, params: job.params });
6997
+ agentHub.registerTask(taskId, title, promptB64 || void 0);
6998
+ db?.saveTask({ taskId, status: "unassigned", title });
6999
+ job.count++;
7000
+ job.failCount = 0;
5710
7001
  job.lastRun = Date.now();
5711
7002
  scheduleNext(job);
5712
7003
  }
@@ -5716,8 +7007,7 @@ function scheduleNext(job) {
5716
7007
  }
5717
7008
  function startScheduler() {
5718
7009
  for (const job of schedules) scheduleNext(job);
5719
- process.stderr.write(`[Scheduler] \u{1F4C5} ${schedules.length} \u4E2A\u5B9A\u65F6\u4EFB\u52A1 (\u9996\u6B2130s\u540E\u89E6\u53D1)
5720
- `);
7010
+ log6.info(`Scheduler: ${schedules.length} \u4E2A\u5B9A\u65F6\u4EFB\u52A1 (\u9996\u6B2130s\u540E\u89E6\u53D1\uFF0CChronos \u2192 V2 Worker \u96F6\u5F39\u7A97)`);
5721
7011
  }
5722
7012
  var banner = [
5723
7013
  `\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`,
@@ -5732,31 +7022,59 @@ var db = new HubDB(dbPath);
5732
7022
  if (db.open()) {
5733
7023
  agentHub.setDB(db);
5734
7024
  const stats = db.stats();
5735
- process.stderr.write(`[Hub] DB \u72B6\u6001: ${stats.taskCount} tasks, ${stats.messageCount} messages
5736
- `);
7025
+ log6.info(`DB \u72B6\u6001: ${stats.taskCount} tasks, ${stats.messageCount} messages`);
5737
7026
  }
5738
7027
  var memoryPath = flag("memory-db") || process.env.HUB_MEMORY_DB || ".hub/memory.db";
5739
7028
  var memory = new HubMemory(memoryPath);
5740
7029
  var memOk = memory.open();
5741
7030
  if (memOk) {
5742
7031
  const ms = memory.stats();
5743
- process.stderr.write(`[Hub] \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})
5744
- `);
7032
+ 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})`);
5745
7033
  }
5746
7034
  var registryPath = flag("registry-db") || process.env.HUB_REGISTRY_DB || ".hub/registry.db";
5747
7035
  var registry = new HubRegistry(registryPath);
5748
- registry.open();
7036
+ var regOk = registry.open();
7037
+ if (regOk) {
7038
+ agentHub.registryCount = registry.all().length;
7039
+ } else {
7040
+ log6.warn(`\u26A0\uFE0F Registry \u6253\u5F00\u5931\u8D25\uFF0C\u5546\u5E97\u529F\u80FD\u4E0D\u53EF\u7528`);
7041
+ }
7042
+ var costsPath = flag("costs-db") || process.env.HUB_COSTS_DB || ".hub/llm-costs.jsonl";
7043
+ var costTracker = new HubCosts(costsPath);
7044
+ agentHub.setCostTracker(costTracker);
7045
+ log6.info(`\u{1F4B0} \u6210\u672C\u8FFD\u8E2A: ${costsPath}`);
5749
7046
  startHttpServer();
5750
7047
  startScheduler();
7048
+ var mcpProcess = null;
7049
+ if (process.env.HUB_SPAWN_MCP === "1") {
7050
+ try {
7051
+ 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" } });
7052
+ mcpProcess.stderr?.on("data", (d) => process.stderr.write(d));
7053
+ log6.info("\u{1F6E0} MCP Server \u542F\u52A8\u4E2D \u2192 http://127.0.0.1:9222");
7054
+ } catch {
7055
+ log6.warn("\u26A0\uFE0F MCP Server \u542F\u52A8\u5931\u8D25");
7056
+ }
7057
+ } else {
7058
+ log6.info("\u{1F6E0} MCP Server \u7531 PM2 \u72EC\u7ACB\u7BA1\u7406 (hvip-mcp :9222)\uFF0C\u8DF3\u8FC7\u5B50\u8FDB\u7A0B\u6D3E\u751F");
7059
+ }
5751
7060
  agentHub.start(wsPort, host, VERSION, token);
7061
+ setTimeout(() => {
7062
+ wsPort = agentHub.port || wsPort;
7063
+ }, 2e3);
5752
7064
  function shutdown() {
5753
- process.stderr.write("\n[Hub] \u6B63\u5728\u5173\u95ED...\n");
7065
+ log6.info("\u6B63\u5728\u5173\u95ED...");
5754
7066
  for (const w of workers) {
5755
7067
  try {
5756
7068
  w.kill();
5757
7069
  } catch {
5758
7070
  }
5759
7071
  }
7072
+ if (mcpProcess) {
7073
+ try {
7074
+ process.kill(-mcpProcess.pid);
7075
+ } catch {
7076
+ }
7077
+ }
5760
7078
  agentHub.close();
5761
7079
  db.close();
5762
7080
  memory.close();