hvip-mcp-server 0.4.3 → 0.5.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 */]: "",
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();
@@ -3733,21 +3782,39 @@ var AgentHub = class {
3733
3782
  db = null;
3734
3783
  token = "";
3735
3784
  // PSK 鉴权令牌
3785
+ costTracker = null;
3786
+ // HubCosts 实例
3736
3787
  // ── 持久化绑定 ──
3788
+ setCostTracker(ct) {
3789
+ this.costTracker = ct;
3790
+ }
3737
3791
  setDB(db2) {
3738
3792
  this.db = db2;
3739
3793
  const rows = db2.loadTasks();
3794
+ let orphanCount = 0;
3740
3795
  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
- });
3796
+ if (r.status === "assigned" && r.assignedTo && !this.agents.has(r.assignedTo)) {
3797
+ this.tasks.set(r.taskId, {
3798
+ status: "unassigned",
3799
+ assignedTo: void 0,
3800
+ claimedAt: void 0,
3801
+ result: r.result || void 0,
3802
+ branch: r.branch || void 0
3803
+ });
3804
+ db2.saveTask({ taskId: r.taskId, status: "unassigned" });
3805
+ orphanCount++;
3806
+ } else {
3807
+ this.tasks.set(r.taskId, {
3808
+ status: r.status,
3809
+ assignedTo: r.assignedTo || void 0,
3810
+ claimedAt: r.claimedAt || void 0,
3811
+ result: r.result || void 0,
3812
+ branch: r.branch || void 0
3813
+ });
3814
+ }
3748
3815
  }
3749
3816
  if (rows.length > 0) {
3750
- console.log(`[AgentHub] \u4ECE DB \u6062\u590D ${rows.length} \u4E2A\u4EFB\u52A1`);
3817
+ log.info(`\u4ECE DB \u6062\u590D ${rows.length} \u4E2A\u4EFB\u52A1` + (orphanCount > 0 ? `\uFF0C\u91CA\u653E ${orphanCount} \u4E2A\u5B64\u513F\u4EFB\u52A1` : ""));
3751
3818
  }
3752
3819
  }
3753
3820
  // ── 启动 ──
@@ -3788,12 +3855,10 @@ var AgentHub = class {
3788
3855
  return ok === true ? true : false;
3789
3856
  }).then((ok) => {
3790
3857
  if (!ok) {
3791
- process.stderr.write(`[AgentHub] WS Hub \u8DF3\u8FC7\uFF08\u7AEF\u53E3 ${ports.join("/")} \u4E0D\u53EF\u7528\uFF09
3792
- `);
3858
+ log.warn(`WS Hub \u8DF3\u8FC7\uFF08\u7AEF\u53E3 ${ports.join("/")} \u4E0D\u53EF\u7528\uFF09`);
3793
3859
  return;
3794
3860
  }
3795
- process.stderr.write(`[AgentHub] WS Hub v${version} ws://${host2}:${this.port}
3796
- `);
3861
+ log.info(`WS Hub v${version} ws://${host2}:${this.port}`);
3797
3862
  this.setupHub();
3798
3863
  });
3799
3864
  }
@@ -3825,17 +3890,17 @@ var AgentHub = class {
3825
3890
  if (agentId) this.handleDisconnect(agentId);
3826
3891
  });
3827
3892
  ws.on("error", (e) => {
3828
- process.stderr.write(`[AgentHub] WS \u9519\u8BEF: ${e.message}
3829
- `);
3893
+ log.error(`WS \u9519\u8BEF: ${e.message}`);
3830
3894
  });
3831
3895
  });
3832
3896
  this.heartbeatTimer = setInterval(() => {
3833
3897
  const now = Date.now();
3834
3898
  for (const [id, a] of this.agents) {
3899
+ if (id.startsWith("term-") || id.startsWith("dashboard")) continue;
3835
3900
  if (now - a.lastSeen > 12e4) {
3836
3901
  a.ws.close();
3837
3902
  this.agents.delete(id);
3838
- console.log("[AgentHub] Agent \u5FC3\u8DF3\u8D85\u65F6: " + id);
3903
+ log.warn("Agent \u5FC3\u8DF3\u8D85\u65F6: " + id);
3839
3904
  }
3840
3905
  }
3841
3906
  }, 3e4);
@@ -3851,9 +3916,29 @@ var AgentHub = class {
3851
3916
  case "task:claim":
3852
3917
  this.handleClaim(msg);
3853
3918
  break;
3919
+ case "task:progress":
3920
+ this.broadcast(msg);
3921
+ break;
3922
+ // 流式文本转发
3923
+ case "task:tool":
3924
+ this.broadcast(msg);
3925
+ break;
3926
+ // 工具调用转发
3854
3927
  case "task:done":
3855
3928
  this.handleDone(msg);
3856
3929
  break;
3930
+ case "task:assign":
3931
+ this.handleAssign(msg);
3932
+ break;
3933
+ // Chronos AI 调度指令
3934
+ case "task:unassign":
3935
+ this.handleUnassign(msg);
3936
+ break;
3937
+ // Chronos 释放卡住任务
3938
+ case "task:reject":
3939
+ this.handleReject(msg);
3940
+ break;
3941
+ // Worker 忙时拒绝任务
3857
3942
  // ── Room ──
3858
3943
  case "room:join":
3859
3944
  this.handleRoomJoin(authAgentId, msg);
@@ -3892,7 +3977,7 @@ var AgentHub = class {
3892
3977
  lastSeen: Date.now()
3893
3978
  });
3894
3979
  this.joinRoom(agentId, "#lobby");
3895
- console.log(`[AgentHub] Agent \u6CE8\u518C: ${agentId} (${name}) skills: [${capabilities.join(", ")}]`);
3980
+ log.info(`Agent \u6CE8\u518C: ${agentId} (${name}) skills: [${capabilities.join(", ")}]`);
3896
3981
  this.send(ws, {
3897
3982
  type: "agent:registered",
3898
3983
  agentId,
@@ -3900,6 +3985,13 @@ var AgentHub = class {
3900
3985
  message: `\u5DF2\u6CE8\u518C\u3002\u53EF\u7528\u4EFB\u52A1: ${this.getUnassignedTasks().join(", ") || "\u65E0"}`,
3901
3986
  pendingTasks: this.getUnassignedTasks()
3902
3987
  });
3988
+ this.broadcast({
3989
+ type: "agent:update",
3990
+ agentId,
3991
+ name,
3992
+ capabilities,
3993
+ status: "idle"
3994
+ });
3903
3995
  const agentVersion = String(msg.version || "");
3904
3996
  if (agentVersion && agentVersion !== this.version) {
3905
3997
  this.send(ws, {
@@ -3921,7 +4013,8 @@ var AgentHub = class {
3921
4013
  }
3922
4014
  handleDisconnect(agentId) {
3923
4015
  const info = this.agents.get(agentId);
3924
- console.log(`[AgentHub] Agent \u79BB\u7EBF: ${agentId} (${info?.name || "?"})`);
4016
+ log.info(`Agent \u79BB\u7EBF: ${agentId} (${info?.name || "?"})`);
4017
+ this.broadcast({ type: "agent:offline", agentId });
3925
4018
  for (const [roomId, room] of this.rooms) {
3926
4019
  if (room.members.has(agentId)) {
3927
4020
  room.members.delete(agentId);
@@ -3961,8 +4054,12 @@ var AgentHub = class {
3961
4054
  return;
3962
4055
  }
3963
4056
  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;
4057
+ const assignedWorker = task.assignedTo ? this.agents.get(task.assignedTo) : null;
4058
+ if (assignedWorker) {
4059
+ this.sendTo(agentId, { type: "error", message: `\u4EFB\u52A1 ${taskId} \u5DF2\u88AB ${task.assignedTo} \u8BA4\u9886` });
4060
+ return;
4061
+ }
4062
+ log.info(`${agentId} \u63A5\u7BA1\u5B64\u513F\u4EFB\u52A1 ${taskId}\uFF08\u539F ${task.assignedTo} \u5DF2\u79BB\u7EBF\uFF09`);
3966
4063
  }
3967
4064
  task.status = "assigned";
3968
4065
  task.assignedTo = agentId;
@@ -3970,7 +4067,7 @@ var AgentHub = class {
3970
4067
  const a = this.agents.get(agentId);
3971
4068
  if (a) a.status = "working";
3972
4069
  this.joinRoom(agentId, `#task-${taskId}`);
3973
- console.log(`[AgentHub] ${agentId} \u8BA4\u9886 ${taskId}`);
4070
+ log.info(`${agentId} \u8BA4\u9886 ${taskId}`);
3974
4071
  this.db?.saveTask({ taskId, status: "assigned", title: this.getTaskTitle(taskId), assignedTo: agentId, claimedAt: task.claimedAt });
3975
4072
  this.sendTo(agentId, {
3976
4073
  type: "task:assigned",
@@ -3986,18 +4083,44 @@ var AgentHub = class {
3986
4083
  const agentId = String(msg.agentId || "");
3987
4084
  const result = String(msg.result || "");
3988
4085
  const branch = String(msg.branch || "");
4086
+ const error = msg.error ? String(msg.error) : "";
4087
+ const usage = msg.usage;
4088
+ const steps = Number(msg.steps || 0);
4089
+ if (this.costTracker && usage?.inputTokens) {
4090
+ this.costTracker.record({
4091
+ agentId,
4092
+ taskId,
4093
+ model: usage.model || "claude-sonnet-4-6",
4094
+ inputTokens: usage.inputTokens || 0,
4095
+ outputTokens: usage.outputTokens || 0,
4096
+ purpose: "task"
4097
+ });
4098
+ }
3989
4099
  const task = this.tasks.get(taskId);
3990
4100
  if (!task) {
3991
4101
  this.sendTo(agentId, { type: "error", message: `\u4EFB\u52A1 ${taskId} \u4E0D\u5B58\u5728` });
3992
4102
  return;
3993
4103
  }
4104
+ if (error || /^❌|^执行失败/i.test(result)) {
4105
+ task.status = "unassigned";
4106
+ task.assignedTo = void 0;
4107
+ task.claimedAt = void 0;
4108
+ const a2 = this.agents.get(agentId);
4109
+ if (a2) a2.status = "idle";
4110
+ this.db?.saveTask({ taskId, status: "unassigned" });
4111
+ log.info(`\u4EFB\u52A1\u6267\u884C\u5931\u8D25\uFF0C\u91CA\u653E\u91CD\u8BD5: ${taskId} \u2014 ${error || result.slice(0, 80)}`);
4112
+ this.broadcast({ type: "agent:update", agentId, status: "idle" });
4113
+ this.broadcast({ type: "task:released", taskId, reason: `\u6267\u884C\u5931\u8D25: ${error || result.slice(0, 60)}` });
4114
+ return;
4115
+ }
3994
4116
  task.status = "done";
3995
4117
  task.result = result;
3996
4118
  task.branch = branch;
3997
4119
  const a = this.agents.get(agentId);
3998
4120
  if (a) a.status = "idle";
3999
- console.log(`[AgentHub] ${agentId} \u5B8C\u6210 ${taskId}: ${result}`);
4121
+ log.info(`${agentId} \u5B8C\u6210 ${taskId}: ${result}`);
4000
4122
  this.db?.saveTask({ taskId, status: "done", title: this.getTaskTitle(taskId), assignedTo: agentId, result, branch });
4123
+ this.broadcast({ type: "agent:update", agentId, status: "idle" });
4001
4124
  this.sendToRoom(`#task-${taskId}`, agentId, `\u5DF2\u5B8C\u6210 ${taskId}: ${result} (branch: ${branch})\uFF0C\u7B49\u5F85\u5BA1\u6838\u3002`);
4002
4125
  this.sendToRoom("#review", agentId, `${taskId} \u63D0\u4EA4\u5B8C\u6210\uFF0Cbranch: ${branch}`);
4003
4126
  this.broadcast({
@@ -4009,41 +4132,125 @@ var AgentHub = class {
4009
4132
  message: `${agentId} \u5DF2\u5B8C\u6210 ${taskId}\uFF0C\u7B49\u5F85\u5BA1\u6838`
4010
4133
  });
4011
4134
  }
4135
+ // ── Chronos 释放卡住任务 ──
4136
+ handleUnassign(msg) {
4137
+ const taskId = String(msg.taskId || "");
4138
+ const reason = String(msg.reason || "Chronos \u5DE1\u68C0\u91CA\u653E");
4139
+ const task = this.tasks.get(taskId);
4140
+ if (!task) {
4141
+ log.warn(`Chronos \u91CA\u653E\u5931\u8D25: \u4EFB\u52A1 ${taskId} \u4E0D\u5B58\u5728`);
4142
+ return;
4143
+ }
4144
+ if (task.status !== "assigned") {
4145
+ return;
4146
+ }
4147
+ const oldAgentId = task.assignedTo;
4148
+ task.status = "unassigned";
4149
+ task.assignedTo = void 0;
4150
+ task.claimedAt = void 0;
4151
+ if (oldAgentId) {
4152
+ const oldWorker = this.agents.get(oldAgentId);
4153
+ if (oldWorker && oldWorker.status === "working") {
4154
+ oldWorker.status = "idle";
4155
+ }
4156
+ }
4157
+ this.db?.saveTask({ taskId, status: "unassigned" });
4158
+ log.warn(`Chronos \u91CA\u653E\u5361\u4F4F\u4EFB\u52A1: ${taskId} (\u539F ${oldAgentId || "?"}) \u2014 ${reason}`);
4159
+ this.broadcast({
4160
+ type: "task:released",
4161
+ taskId,
4162
+ reason: `Chronos \u91CA\u653E: ${reason}`
4163
+ });
4164
+ }
4165
+ // ── Worker 拒绝任务(忙碌)──
4166
+ handleReject(msg) {
4167
+ const taskId = String(msg.taskId || "");
4168
+ const agentId = String(msg.agentId || "");
4169
+ const reason = String(msg.reason || "Worker busy");
4170
+ const task = this.tasks.get(taskId);
4171
+ if (!task) return;
4172
+ if (task.status !== "assigned") return;
4173
+ task.status = "unassigned";
4174
+ task.assignedTo = void 0;
4175
+ task.claimedAt = void 0;
4176
+ const a = this.agents.get(agentId);
4177
+ if (a && a.status === "working") a.status = "idle";
4178
+ this.db?.saveTask({ taskId, status: "unassigned" });
4179
+ log.info(`\u4EFB\u52A1\u88AB\u62D2\u7EDD: ${taskId} by ${agentId} \u2014 ${reason}`);
4180
+ this.broadcast({ type: "task:released", taskId, reason: `Worker \u62D2\u7EDD: ${reason}` });
4181
+ }
4012
4182
  // ── 注册任务(自动派发给空闲 Agent) ──
4013
- registerTask(taskId, title) {
4183
+ /** 检查是否有空闲的 WS Worker(非 dashboard、非 CLI spawn) */
4184
+ hasIdleWorker() {
4185
+ for (const [id, a] of this.agents) {
4186
+ if (a.status === "idle" && !id.startsWith("dashboard") && !id.startsWith("term-")) {
4187
+ return true;
4188
+ }
4189
+ }
4190
+ return false;
4191
+ }
4192
+ registerTask(taskId, title, promptB64) {
4014
4193
  if (!this.tasks.has(taskId)) {
4015
4194
  this.tasks.set(taskId, { status: "unassigned" });
4016
4195
  }
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}"`);
4196
+ const task = this.tasks.get(taskId);
4197
+ if (title) task.title = title;
4198
+ if (promptB64) task.promptB64 = promptB64;
4199
+ log.info(`\u4EFB\u52A1\u6CE8\u518C: ${taskId} "${title || taskId}"`);
4022
4200
  this.broadcast({ type: "task:announced", taskId, title: title || taskId });
4023
- const idleAgents = [...this.agents.entries()].filter(([, a]) => a.status === "idle" && !a.agentId.startsWith("dashboard"));
4201
+ const hasChronos = [...this.agents.values()].some(
4202
+ (a) => a.agentId === "chronos-dispatcher" && a.status !== "offline"
4203
+ );
4204
+ if (hasChronos) {
4205
+ log.info(`Chronos \u5728\u7EBF\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85 AI \u8C03\u5EA6`);
4206
+ return;
4207
+ }
4208
+ const idleAgents = [...this.agents.entries()].filter(([, a]) => a.status === "idle" && !a.agentId.startsWith("dashboard") && !a.agentId.startsWith("term-"));
4024
4209
  if (idleAgents.length === 0) {
4025
- console.log(`[AgentHub] \u6CA1\u6709\u7A7A\u95F2 Agent\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85\u624B\u52A8 spawn`);
4210
+ log.warn(`\u6CA1\u6709\u7A7A\u95F2 Agent\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85\u624B\u52A8 spawn`);
4026
4211
  return;
4027
4212
  }
4028
4213
  const match = idleAgents.find(([, a]) => a.capabilities.includes(taskId)) || idleAgents[0];
4029
4214
  if (match) {
4030
- console.log(`[AgentHub] \u81EA\u52A8\u6D3E\u53D1 ${taskId} \u2192 ${match[1].name} (${match[0]})`);
4031
- this.dispatchTaskTo(taskId, match[0]);
4215
+ log.info(`\u81EA\u52A8\u6D3E\u53D1 ${taskId} \u2192 ${match[1].name} (${match[0]})`);
4216
+ this.dispatchTaskTo(taskId, match[0], promptB64);
4032
4217
  }
4033
4218
  }
4034
4219
  // ── 派发任务 ──
4035
- dispatchTaskTo(taskId, agentId) {
4220
+ dispatchTaskTo(taskId, agentId, promptB64) {
4036
4221
  if (!this.tasks.has(taskId)) {
4037
4222
  this.tasks.set(taskId, { status: "unassigned" });
4038
4223
  }
4039
4224
  const task = this.tasks.get(taskId);
4040
4225
  if (task.status === "assigned") return;
4041
- this.sendTo(agentId, {
4226
+ const msg = {
4042
4227
  type: "task:dispatch",
4043
4228
  taskId,
4044
- title: this.getTaskTitle(taskId),
4045
- url: `https://github.com/okx-wallet-H/hvip-mcp/blob/master/tasks/${taskId}.md`
4046
- });
4229
+ title: this.getTaskTitle(taskId)
4230
+ };
4231
+ if (promptB64) msg.promptB64 = promptB64;
4232
+ else if (task.promptB64) msg.promptB64 = task.promptB64;
4233
+ this.sendTo(agentId, msg);
4234
+ }
4235
+ // ── Chronos 调度指令 ──
4236
+ handleAssign(msg) {
4237
+ const taskId = String(msg.taskId || "");
4238
+ const targetAgentId = String(msg.agentId || "");
4239
+ const assignedBy = String(msg.assignedBy || "chronos");
4240
+ if (!taskId || !targetAgentId) return;
4241
+ const targetWorker = this.agents.get(targetAgentId);
4242
+ if (!targetWorker) {
4243
+ log.warn(`Chronos \u6307\u6D3E\u5931\u8D25: Worker ${targetAgentId} \u4E0D\u5728\u7EBF`);
4244
+ return;
4245
+ }
4246
+ if (targetWorker.status !== "idle") {
4247
+ log.warn(`Chronos \u6307\u6D3E\u5931\u8D25: Worker ${targetAgentId} \u5FD9\u788C\u4E2D`);
4248
+ return;
4249
+ }
4250
+ log.info(`Chronos \u8C03\u5EA6: ${taskId} \u2192 ${targetWorker.name}`);
4251
+ const task = this.tasks.get(taskId);
4252
+ const promptB64 = task?.promptB64;
4253
+ this.dispatchTaskTo(taskId, targetAgentId, promptB64);
4047
4254
  }
4048
4255
  // ── 审核 + 自动通知房间 ──
4049
4256
  reviewTask(taskId, verdict, feedback) {
@@ -4091,7 +4298,7 @@ var AgentHub = class {
4091
4298
  });
4092
4299
  }
4093
4300
  this.sendToRoomMembers(room, { type: "room:member_joined", roomId, agentId });
4094
- console.log(`[AgentHub] ${agentId} \u2192 ${roomId}`);
4301
+ log.info(`${agentId} \u2192 ${roomId}`);
4095
4302
  }
4096
4303
  leaveRoom(agentId, roomId) {
4097
4304
  const room = this.rooms.get(roomId);
@@ -4254,6 +4461,7 @@ function ensureDir(dbPath2) {
4254
4461
  }
4255
4462
 
4256
4463
  // src/adapters/hub-persistence.ts
4464
+ var log2 = logger("HubDB");
4257
4465
  var HubDB = class {
4258
4466
  db = null;
4259
4467
  dbPath;
@@ -4263,19 +4471,17 @@ var HubDB = class {
4263
4471
  // ── 初始化 ──
4264
4472
  open() {
4265
4473
  if (!isSqliteAvailable()) {
4266
- process.stderr.write("[HubDB] node:sqlite \u4E0D\u53EF\u7528\uFF0C\u8DF3\u8FC7\u6301\u4E45\u5316\n");
4474
+ log2.info("node:sqlite \u4E0D\u53EF\u7528\uFF0C\u8DF3\u8FC7\u6301\u4E45\u5316");
4267
4475
  return false;
4268
4476
  }
4269
4477
  try {
4270
4478
  ensureDir(this.dbPath);
4271
4479
  this.db = openDB(this.dbPath, { create: true });
4272
4480
  this.migrate();
4273
- process.stderr.write(`[HubDB] \u5DF2\u6253\u5F00 ${this.dbPath}
4274
- `);
4481
+ log2.info(`\u5DF2\u6253\u5F00 ${this.dbPath}`);
4275
4482
  return true;
4276
4483
  } catch (e) {
4277
- process.stderr.write(`[HubDB] \u6253\u5F00\u5931\u8D25: ${String(e)}
4278
- `);
4484
+ log2.error(`\u6253\u5F00\u5931\u8D25: ${String(e)}`);
4279
4485
  return false;
4280
4486
  }
4281
4487
  }
@@ -4331,8 +4537,7 @@ var HubDB = class {
4331
4537
  task.reviewedAt || null
4332
4538
  );
4333
4539
  } catch (e) {
4334
- process.stderr.write(`[HubDB] saveTask(${task.taskId}) \u5931\u8D25: ${String(e)}
4335
- `);
4540
+ log2.error(`saveTask(${task.taskId}) \u5931\u8D25: ${String(e)}`);
4336
4541
  }
4337
4542
  }
4338
4543
  loadTasks() {
@@ -4340,8 +4545,7 @@ var HubDB = class {
4340
4545
  try {
4341
4546
  return this.db.prepare("SELECT * FROM hub_tasks ORDER BY taskId").all();
4342
4547
  } catch (e) {
4343
- process.stderr.write(`[HubDB] loadTasks \u5931\u8D25: ${String(e)}
4344
- `);
4548
+ log2.error(`loadTasks \u5931\u8D25: ${String(e)}`);
4345
4549
  return [];
4346
4550
  }
4347
4551
  }
@@ -4356,8 +4560,7 @@ var HubDB = class {
4356
4560
  )
4357
4561
  `).run(roomId, roomId);
4358
4562
  } catch (e) {
4359
- process.stderr.write(`[HubDB] saveMessage(${roomId}) \u5931\u8D25: ${String(e)}
4360
- `);
4563
+ log2.error(`saveMessage(${roomId}) \u5931\u8D25: ${String(e)}`);
4361
4564
  }
4362
4565
  }
4363
4566
  loadMessages(roomId, limit = 50) {
@@ -4367,8 +4570,7 @@ var HubDB = class {
4367
4570
  `SELECT * FROM hub_messages WHERE roomId = ? ORDER BY id DESC LIMIT ?`
4368
4571
  ).all(roomId, limit).reverse();
4369
4572
  } catch (e) {
4370
- process.stderr.write(`[HubDB] loadMessages(${roomId}) \u5931\u8D25: ${String(e)}
4371
- `);
4573
+ log2.error(`loadMessages(${roomId}) \u5931\u8D25: ${String(e)}`);
4372
4574
  return [];
4373
4575
  }
4374
4576
  }
@@ -4386,11 +4588,12 @@ var HubDB = class {
4386
4588
  };
4387
4589
 
4388
4590
  // src/adapters/hub-memory.ts
4591
+ var log3 = logger("Memory");
4389
4592
  var HubMemory = class _HubMemory {
4390
4593
  db = null;
4391
4594
  dbPath;
4392
4595
  decayTimer = null;
4393
- /** 4 种记忆类型的衰减参数 */
4596
+ /** 5 种记忆类型的衰减参数 */
4394
4597
  static DECAY = {
4395
4598
  memory: { rate: 0.94, windowDays: 7 },
4396
4599
  // 7 天后开始衰减
@@ -4398,8 +4601,10 @@ var HubMemory = class _HubMemory {
4398
4601
  // 永不衰减
4399
4602
  directive: { rate: 1, windowDays: 0 },
4400
4603
  // 永不衰减
4401
- skill: { rate: 0.9, windowDays: 14 }
4604
+ skill: { rate: 0.9, windowDays: 14 },
4402
4605
  // 14 天后开始衰减
4606
+ strategy: { rate: 0.95, windowDays: 7 }
4607
+ // 7 天后开始衰减,速度慢于 memory
4403
4608
  };
4404
4609
  static HEAT_THRESHOLD = 3;
4405
4610
  // 衰减周期内被读 >= 次则跳过衰减
@@ -4411,20 +4616,18 @@ var HubMemory = class _HubMemory {
4411
4616
  // ── 生命周期 ─────────────────────────────────────────────────────────
4412
4617
  open() {
4413
4618
  if (!isSqliteAvailable()) {
4414
- process.stderr.write("[Memory] node:sqlite \u4E0D\u53EF\u7528\n");
4619
+ log3.info("node:sqlite \u4E0D\u53EF\u7528");
4415
4620
  return false;
4416
4621
  }
4417
4622
  try {
4418
4623
  ensureDir(this.dbPath);
4419
4624
  this.db = openDB(this.dbPath, { create: true });
4420
4625
  this.migrate();
4421
- process.stderr.write(`[Memory] \u5DF2\u6253\u5F00 ${this.dbPath}
4422
- `);
4626
+ log3.info(`\u5DF2\u6253\u5F00 ${this.dbPath}`);
4423
4627
  this.startDecay();
4424
4628
  return true;
4425
4629
  } catch (e) {
4426
- process.stderr.write(`[Memory] \u6253\u5F00\u5931\u8D25: ${String(e)}
4427
- `);
4630
+ log3.error(`\u6253\u5F00\u5931\u8D25: ${String(e)}`);
4428
4631
  return false;
4429
4632
  }
4430
4633
  }
@@ -4463,10 +4666,18 @@ var HubMemory = class _HubMemory {
4463
4666
  `);
4464
4667
  }
4465
4668
  // ── CRUD ─────────────────────────────────────────────────────────────
4669
+ /** 标准化 tags:接受 string[] 或逗号分隔的字符串,统一返回逗号分隔字符串 */
4670
+ normalizeTags(raw) {
4671
+ if (!raw) return "";
4672
+ if (Array.isArray(raw)) {
4673
+ return raw.map((t) => t.replace(/,/g, "")).filter(Boolean).join(",");
4674
+ }
4675
+ return raw.split(",").map((t) => t.trim()).filter(Boolean).join(",");
4676
+ }
4466
4677
  store(opts) {
4467
4678
  const id = `mem-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
4468
4679
  const type = opts.type || "memory";
4469
- const tags = (opts.tags || []).map((t) => t.replace(/,/g, "")).join(",");
4680
+ const tags = this.normalizeTags(opts.tags);
4470
4681
  const confidence = Math.min(1, Math.max(0, opts.confidence ?? 1));
4471
4682
  const now = (/* @__PURE__ */ new Date()).toISOString();
4472
4683
  this.db.prepare(`
@@ -4590,6 +4801,7 @@ var SEED = [
4590
4801
  { 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
4802
  { 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
4803
  ];
4804
+ var log4 = logger("Registry");
4593
4805
  var HubRegistry = class {
4594
4806
  constructor(dbPath2) {
4595
4807
  this.dbPath = dbPath2;
@@ -4606,14 +4818,11 @@ var HubRegistry = class {
4606
4818
  const n = this.db.prepare("SELECT COUNT(*) as n FROM registry").get()?.n || 0;
4607
4819
  if (n === 0) {
4608
4820
  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
- `);
4821
+ log4.info(`\u9884\u7F6E ${SEED.length} \u4E2AMCP\u63D2\u4EF6`);
4822
+ } else log4.info(`\u5DF2\u52A0\u8F7D ${n} \u4E2AMCP\u63D2\u4EF6`);
4613
4823
  return true;
4614
4824
  } catch (e) {
4615
- process.stderr.write(`[Registry] ${String(e)}
4616
- `);
4825
+ log4.error(`\u6253\u5F00\u5931\u8D25: ${String(e)}`);
4617
4826
  return false;
4618
4827
  }
4619
4828
  }
@@ -4628,12 +4837,14 @@ var HubRegistry = class {
4628
4837
  }
4629
4838
  }
4630
4839
  add(p) {
4840
+ if (!this.db) throw new Error("Registry \u672A\u6253\u5F00");
4631
4841
  const id = "mcp-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 5);
4632
4842
  const now = (/* @__PURE__ */ new Date()).toISOString();
4633
4843
  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
4844
  return { id, ...p, verified: p.verified || false, createdAt: now };
4635
4845
  }
4636
4846
  search(q, category, limit = 30) {
4847
+ if (!this.db) return [];
4637
4848
  let sql = "SELECT * FROM registry WHERE 1=1";
4638
4849
  const params = [];
4639
4850
  if (q) {
@@ -4653,6 +4864,7 @@ var HubRegistry = class {
4653
4864
  return (Array.isArray(rows) ? rows : []).map((r) => this.rowToPlugin(r));
4654
4865
  }
4655
4866
  byCategory() {
4867
+ if (!this.db) return {};
4656
4868
  const rows = this.db.prepare("SELECT * FROM registry ORDER BY category, stars DESC").all();
4657
4869
  const map = {};
4658
4870
  for (const r of Array.isArray(rows) ? rows : []) {
@@ -4663,9 +4875,11 @@ var HubRegistry = class {
4663
4875
  return map;
4664
4876
  }
4665
4877
  all(limit = 100) {
4878
+ if (!this.db) return [];
4666
4879
  return this.db.prepare("SELECT * FROM registry ORDER BY stars DESC LIMIT ?").all(limit).map((r) => this.rowToPlugin(r));
4667
4880
  }
4668
4881
  get(id) {
4882
+ if (!this.db) return null;
4669
4883
  const r = this.db.prepare("SELECT * FROM registry WHERE id=?").get(id);
4670
4884
  return r ? this.rowToPlugin(r) : null;
4671
4885
  }
@@ -4674,9 +4888,216 @@ var HubRegistry = class {
4674
4888
  }
4675
4889
  };
4676
4890
 
4677
- // src/hub-server.ts
4891
+ // src/adapters/hub-costs.ts
4678
4892
  var import_node_fs = require("node:fs");
4679
4893
  var import_node_path = require("node:path");
4894
+ var PRICING = {
4895
+ "claude-sonnet-4-6": { input: 3, output: 15 },
4896
+ "claude-sonnet-4-5": { input: 3, output: 15 },
4897
+ "claude-haiku-4-5": { input: 1, output: 5 },
4898
+ "claude-opus-4-8": { input: 15, output: 75 },
4899
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
4900
+ "gpt-4o": { input: 2.5, output: 10 },
4901
+ "deepseek-v4-pro": { input: 0.55, output: 2.19 }
4902
+ };
4903
+ function getCost(model, inputTokens, outputTokens) {
4904
+ const p = PRICING[model];
4905
+ if (!p) return 0;
4906
+ return inputTokens / 1e6 * p.input + outputTokens / 1e6 * p.output;
4907
+ }
4908
+ var HubCosts = class {
4909
+ filePath;
4910
+ cache = [];
4911
+ cacheLoaded = false;
4912
+ constructor(dbPath2 = ".hub/llm-costs.jsonl") {
4913
+ this.filePath = dbPath2;
4914
+ }
4915
+ /** 记录一次 LLM 调用 */
4916
+ record(entry) {
4917
+ const cost = entry.cost ?? getCost(entry.model, entry.inputTokens, entry.outputTokens);
4918
+ const rec = {
4919
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4920
+ agentId: entry.agentId,
4921
+ taskId: entry.taskId,
4922
+ model: entry.model,
4923
+ inputTokens: entry.inputTokens,
4924
+ outputTokens: entry.outputTokens,
4925
+ cost: Math.round(cost * 1e4) / 1e4,
4926
+ // 4 decimal places
4927
+ purpose: entry.purpose
4928
+ };
4929
+ try {
4930
+ const dir = (0, import_node_path.dirname)(this.filePath);
4931
+ if (!(0, import_node_fs.existsSync)(dir)) (0, import_node_fs.mkdirSync)(dir, { recursive: true });
4932
+ (0, import_node_fs.appendFileSync)(this.filePath, JSON.stringify(rec) + "\n", "utf-8");
4933
+ } catch {
4934
+ }
4935
+ this.cache.push(rec);
4936
+ }
4937
+ /** 加载全部记录 */
4938
+ loadAll() {
4939
+ if (this.cacheLoaded) return this.cache;
4940
+ try {
4941
+ if ((0, import_node_fs.existsSync)(this.filePath)) {
4942
+ const raw = (0, import_node_fs.readFileSync)(this.filePath, "utf-8");
4943
+ this.cache = raw.split("\n").filter(Boolean).map((line) => {
4944
+ try {
4945
+ return JSON.parse(line);
4946
+ } catch {
4947
+ return null;
4948
+ }
4949
+ }).filter(Boolean);
4950
+ }
4951
+ } catch {
4952
+ }
4953
+ this.cacheLoaded = true;
4954
+ return this.cache;
4955
+ }
4956
+ /** 汇总统计 */
4957
+ summary() {
4958
+ const all = this.loadAll();
4959
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4960
+ const todayEntries = all.filter((e) => e.timestamp.startsWith(today));
4961
+ const sum = (entries) => ({
4962
+ calls: entries.length,
4963
+ inputTokens: entries.reduce((s, e) => s + e.inputTokens, 0),
4964
+ outputTokens: entries.reduce((s, e) => s + e.outputTokens, 0),
4965
+ cost: entries.reduce((s, e) => s + e.cost, 0)
4966
+ });
4967
+ const byModel = {};
4968
+ for (const e of all) {
4969
+ if (!byModel[e.model]) byModel[e.model] = { calls: 0, inputTokens: 0, outputTokens: 0, cost: 0 };
4970
+ byModel[e.model].calls++;
4971
+ byModel[e.model].inputTokens += e.inputTokens;
4972
+ byModel[e.model].outputTokens += e.outputTokens;
4973
+ byModel[e.model].cost += e.cost;
4974
+ }
4975
+ const byAgent = {};
4976
+ for (const e of all) {
4977
+ if (!byAgent[e.agentId]) byAgent[e.agentId] = { calls: 0, inputTokens: 0, outputTokens: 0, cost: 0 };
4978
+ byAgent[e.agentId].calls++;
4979
+ byAgent[e.agentId].inputTokens += e.inputTokens;
4980
+ byAgent[e.agentId].outputTokens += e.outputTokens;
4981
+ byAgent[e.agentId].cost += e.cost;
4982
+ }
4983
+ const hourly = [];
4984
+ for (let h = 23; h >= 0; h--) {
4985
+ const slot = new Date(Date.now() - h * 36e5).toISOString().slice(0, 13);
4986
+ const entries = all.filter((e) => e.timestamp.startsWith(slot));
4987
+ hourly.push({ hour: slot, calls: entries.length, cost: entries.reduce((s, e) => s + e.cost, 0) });
4988
+ }
4989
+ return {
4990
+ today: sum(todayEntries),
4991
+ total: sum(all),
4992
+ byModel,
4993
+ byAgent,
4994
+ hourly
4995
+ };
4996
+ }
4997
+ /** 最近 N 条记录 */
4998
+ recent(n = 50) {
4999
+ const all = this.loadAll();
5000
+ return all.slice(-n);
5001
+ }
5002
+ };
5003
+
5004
+ // src/adapters/circuit-breaker.ts
5005
+ var CircuitBreaker = class {
5006
+ circuits = /* @__PURE__ */ new Map();
5007
+ failureThreshold = 3;
5008
+ cooldownMs = 6e4;
5009
+ // 1 minute default
5010
+ constructor(opts) {
5011
+ if (opts?.failureThreshold) this.failureThreshold = opts.failureThreshold;
5012
+ if (opts?.cooldownMs) this.cooldownMs = opts.cooldownMs;
5013
+ }
5014
+ /** 获取或创建熔断器 */
5015
+ get(name) {
5016
+ let c = this.circuits.get(name);
5017
+ if (!c) {
5018
+ c = {
5019
+ name,
5020
+ state: "CLOSED",
5021
+ failures: 0,
5022
+ successCount: 0,
5023
+ failureCount: 0,
5024
+ lastFailure: null,
5025
+ lastSuccess: null,
5026
+ openedAt: null,
5027
+ cooldownMs: this.cooldownMs
5028
+ };
5029
+ this.circuits.set(name, c);
5030
+ }
5031
+ return c;
5032
+ }
5033
+ /** 调用前检查 — 返回 true 表示可以执行 */
5034
+ canCall(name) {
5035
+ const c = this.get(name);
5036
+ if (c.state === "CLOSED") return true;
5037
+ if (c.state === "HALF_OPEN") return true;
5038
+ if (c.openedAt) {
5039
+ const elapsed = Date.now() - new Date(c.openedAt).getTime();
5040
+ if (elapsed >= c.cooldownMs) {
5041
+ c.state = "HALF_OPEN";
5042
+ return true;
5043
+ }
5044
+ }
5045
+ return false;
5046
+ }
5047
+ /** 调用成功 */
5048
+ onSuccess(name) {
5049
+ const c = this.get(name);
5050
+ c.successCount++;
5051
+ c.lastSuccess = (/* @__PURE__ */ new Date()).toISOString();
5052
+ if (c.state === "HALF_OPEN") {
5053
+ c.state = "CLOSED";
5054
+ c.failures = 0;
5055
+ }
5056
+ if (c.state === "CLOSED") {
5057
+ c.failures = 0;
5058
+ }
5059
+ }
5060
+ /** 调用失败 */
5061
+ onFailure(name) {
5062
+ const c = this.get(name);
5063
+ c.failures++;
5064
+ c.failureCount++;
5065
+ c.lastFailure = (/* @__PURE__ */ new Date()).toISOString();
5066
+ if (c.failures >= this.failureThreshold) {
5067
+ c.state = "OPEN";
5068
+ c.openedAt = (/* @__PURE__ */ new Date()).toISOString();
5069
+ }
5070
+ }
5071
+ /** 包裹一个异步函数 */
5072
+ async wrap(name, fn, fallback) {
5073
+ if (!this.canCall(name)) {
5074
+ if (fallback) return fallback();
5075
+ throw new Error(`Circuit OPEN: ${name}`);
5076
+ }
5077
+ try {
5078
+ const result = await fn();
5079
+ this.onSuccess(name);
5080
+ return result;
5081
+ } catch (e) {
5082
+ this.onFailure(name);
5083
+ if (fallback) return fallback();
5084
+ throw e;
5085
+ }
5086
+ }
5087
+ /** 获取所有熔断器状态 */
5088
+ status() {
5089
+ return [...this.circuits.values()];
5090
+ }
5091
+ /** 重置指定熔断器 */
5092
+ reset(name) {
5093
+ this.circuits.delete(name);
5094
+ }
5095
+ };
5096
+ var circuitBreaker = new CircuitBreaker({ failureThreshold: 3, cooldownMs: 6e4 });
5097
+
5098
+ // src/hub-server.ts
5099
+ var import_node_fs2 = require("node:fs");
5100
+ var import_node_path2 = require("node:path");
4680
5101
 
4681
5102
  // src/adapters/hub-templates.ts
4682
5103
  var TASK_TEMPLATES = [
@@ -5353,16 +5774,188 @@ ACTION: <\u521B\u5EFA\u7684\u4EFB\u52A1ID>
5353
5774
  STATUS: <\u5DF2\u5206\u6D3E/\u5DF2\u4FEE\u590D/\u5DF2\u9A8C\u8BC1>
5354
5775
  NEXT: <\u540E\u7EED\u8DDF\u8FDB\u5EFA\u8BAE>
5355
5776
 
5777
+ \u8F93\u51FA TASK_COMPLETE`
5778
+ },
5779
+ // ════════════ 24x7 守护岗 ════════════
5780
+ {
5781
+ id: "guard-dashboard",
5782
+ name: "\u{1F4CA} \u4EEA\u8868\u76D8\u5B88\u62A4",
5783
+ 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",
5784
+ prefix: "G",
5785
+ fields: [
5786
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5787
+ ],
5788
+ 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
5789
+
5790
+ ## \u68C0\u67E5\u6E05\u5355
5791
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5792
+ |------|--------|
5793
+ | 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 |
5794
+ | Chart.js\u56FE\u8868 | \u997C\u56FE/\u6298\u7EBF\u56FE\u80FD\u6E32\u67D3\uFF1F\u6570\u636E\u70B9\u6709\u7D2F\u79EF\uFF1F |
5795
+ | Agent\u9762\u677F | working/idle \u6B63\u786E\u5206\u7EC4\uFF1F\u5361\u7247\u5E76\u6392\u663E\u793A\uFF1F |
5796
+ | \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 |
5797
+ | \u5B9E\u65F6\u52A8\u6001 | WS\u6D88\u606F\u80FD\u63A8\u9001\uFF1Ffeed\u8BA1\u6570\u6B63\u786E\uFF1F |
5798
+ | \u4EFB\u52A1\u521B\u5EFA\u5668 | \u6A21\u677F\u5361\u7247\u80FD\u9009\uFF1F\u8868\u5355\u80FD\u586B\uFF1F\u521B\u5EFA\u6309\u94AE\u751F\u6548\uFF1F |
5799
+ | \u26A1\u5FEB\u6377\u6309\u94AE | BTC\u884C\u60C5\u5F39\u7A97\uFF1F\u65B0\u5EFA\u4EFB\u52A1\uFF1F |
5800
+
5801
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5802
+ \u8F93\u51FA TASK_COMPLETE`
5803
+ },
5804
+ {
5805
+ id: "guard-debate",
5806
+ name: "\u2696\uFE0F \u8FA9\u8BBA\u5B88\u62A4",
5807
+ description: "7x24\u7EF4\u62A4\u8FA9\u8BBA\u9762\u677F: \u5361\u7247\u5217\u8868/\u804A\u5929\u6D41/\u7ACB\u573A\u8C31/\u5171\u8BC6\u73AF",
5808
+ prefix: "G",
5809
+ fields: [
5810
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5811
+ ],
5812
+ buildPrompt: (p) => `\u4F60\u662F\u8FA9\u8BBA\u9762\u677F\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u8FA9\u8BBA\u529F\u80FD\u4E00\u5207\u6B63\u5E38\u3002
5813
+
5814
+ ## \u68C0\u67E5\u6E05\u5355
5815
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5816
+ |------|--------|
5817
+ | \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 |
5818
+ | \u804A\u5929\u6D41 | \u70B9\u51FB\u5361\u7247\u8FDB\u5165\u804A\u5929\uFF1FAgent1\u5DE6\u6C14\u6CE1/Agent2\u53F3\u6C14\u6CE1\uFF1F |
5819
+ | \u8BBA\u70B9\u63D0\u53D6 | parseKeyPoints\u80FD\u63D0\u53D6\u6570\u636E\uFF1F\u8868\u683C\u884C\u8BC6\u522B\uFF1F |
5820
+ | \u7EFC\u5408\u7ED3\u8BBA | CONCLUSION\u63D0\u53D6\uFF1F\u7EFC\u5408\u6846\u663E\u793A\uFF1F |
5821
+ | \u8FD4\u56DE\u6309\u94AE | closeDebateDetail\u6B63\u5E38\u5DE5\u4F5C\uFF1F\u5361\u7247\u5217\u8868\u6062\u590D\uFF1F |
5822
+
5823
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5824
+ \u8F93\u51FA TASK_COMPLETE`
5825
+ },
5826
+ {
5827
+ id: "guard-terminal",
5828
+ name: "\u{1F5A5} \u7EC8\u7AEF\u5B88\u62A4",
5829
+ description: "7x24\u7EF4\u62A4\u5B9E\u65F6\u7EC8\u7AEF: \u6D41\u5F0F\u8F93\u51FA/Pause/Clear/\u7740\u8272",
5830
+ prefix: "G",
5831
+ fields: [
5832
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5833
+ ],
5834
+ buildPrompt: (p) => `\u4F60\u662F\u5B9E\u65F6\u7EC8\u7AEF\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u7EC8\u7AEF\u529F\u80FD\u4E00\u5207\u6B63\u5E38\u3002
5835
+
5836
+ ## \u68C0\u67E5\u6E05\u5355
5837
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5838
+ |------|--------|
5839
+ | \u6D41\u5F0F\u8F93\u51FA | WS\u6D88\u606F\u80FD\u5B9E\u65F6\u63A8\u9001\u5230\u7EC8\u7AEF\uFF1F\u591AWorker\u8F93\u51FA\u6C47\u805A\uFF1F |
5840
+ | \u7740\u8272 | success/warn/err/info/dim \u5206\u7C7B\u6B63\u786E\uFF1F |
5841
+ | Pause/Clear | \u6682\u505C\u81EA\u52A8\u6EDA\u52A8\uFF1F\u6E05\u7A7A\u7EC8\u7AEF\uFF1F |
5842
+ | Worker\u8BA1\u6570 | \u5728\u7EBFWorker\u6570\u663E\u793A\u6B63\u786E\uFF1F\u25CF LIVE/\u25CB idle\u5207\u6362\uFF1F |
5843
+ | \u65F6\u95F4\u6233 | \u6BCF\u6761\u6D88\u606F\u5E26 HH:MM:SS\uFF1F\u6807\u7B7E\u7740\u8272\uFF1F|
5844
+
5845
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5846
+ \u8F93\u51FA TASK_COMPLETE`
5847
+ },
5848
+ {
5849
+ id: "guard-signals",
5850
+ name: "\u{1F6A6} \u4FE1\u53F7\u5B88\u62A4",
5851
+ description: "7x24\u7EF4\u62A4\u4FE1\u53F7\u9762\u677F: VBT\u4FE1\u53F7\u89E3\u6790/\u65B9\u5411KPI/\u7F6E\u4FE1\u5EA6\u6761",
5852
+ prefix: "G",
5853
+ fields: [
5854
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5855
+ ],
5856
+ buildPrompt: (p) => `\u4F60\u662F\u4FE1\u53F7\u9762\u677F\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u4FE1\u53F7\u529F\u80FD\u4E00\u5207\u6B63\u5E38\u3002
5857
+
5858
+ ## \u68C0\u67E5\u6E05\u5355
5859
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5860
+ |------|--------|
5861
+ | \u4FE1\u53F7\u89E3\u6790 | V-\u524D\u7F00\u4EFB\u52A1\u6B63\u786E\u63D0\u53D6CURRENT_SIGNAL/SHARPE/WIN_RATE\uFF1F |
5862
+ | KPI | \u505A\u591A/\u4E2D\u6027/\u505A\u7A7A\u8BA1\u6570\u6B63\u786E\uFF1F\u5E73\u5747\u80DC\u7387\u8BA1\u7B97\uFF1F |
5863
+ | \u4FE1\u53F7\u5361\u7247 | \u65B9\u5411\u7740\u8272(\u7EFF\u591A/\u7EA2\u7A7A)\uFF1FSharpe/MDD/\u80DC\u7387\u663E\u793A\uFF1F |
5864
+ | \u6700\u8FD1\u4FE1\u53F7 | LAST_5_SIGNALS\u89E3\u6790\uFF1F\u4FE1\u53F7\u5386\u53F2\u663E\u793A\uFF1F |
5865
+ | \u521B\u5EFA\u4FE1\u53F7\u4EFB\u52A1 | vbt-signal\u6A21\u677F\u53EF\u7528\uFF1F\u80FD\u521B\u5EFA\u65B0\u4FE1\u53F7\uFF1F |
5866
+
5867
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5868
+ \u8F93\u51FA TASK_COMPLETE`
5869
+ },
5870
+ {
5871
+ id: "guard-store",
5872
+ name: "\u{1F3EA} \u5546\u5E97\u5B88\u62A4",
5873
+ description: "7x24\u7EF4\u62A4MCP\u63D2\u4EF6\u5546\u5E97: \u641C\u7D22/\u5206\u7C7B/\u5361\u7247\u6E32\u67D3",
5874
+ prefix: "G",
5875
+ fields: [
5876
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5877
+ ],
5878
+ buildPrompt: (p) => `\u4F60\u662FMCP\u5546\u5E97\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u5546\u5E97\u529F\u80FD\u4E00\u5207\u6B63\u5E38\u3002
5879
+
5880
+ ## \u68C0\u67E5\u6E05\u5355
5881
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5882
+ |------|--------|
5883
+ | \u63D2\u4EF6\u52A0\u8F7D | /api/store \u8FD4\u56DE24\u4E2A\u63D2\u4EF6\uFF1F22\u4E2A\u5206\u7C7B\uFF1F |
5884
+ | \u641C\u7D22\u8FC7\u6EE4 | \u8F93\u5165\u5173\u952E\u8BCD\u5B9E\u65F6\u8FC7\u6EE4\uFF1F\u7A7A\u7ED3\u679C\u63D0\u793A\uFF1F |
5885
+ | \u5361\u7247\u6E32\u67D3 | \u540D\u79F0/\u63CF\u8FF0/\u4ED3\u5E93/\u661F\u6807/\u5DF2\u9A8C\u8BC1\u6B63\u786E\u663E\u793A\uFF1F |
5886
+ | \u5206\u7C7B\u5206\u7EC4 | \u6309category\u5206\u7EC4\u663E\u793A\uFF1F\u6807\u9898\u6B63\u786E\uFF1F |
5887
+
5888
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5889
+ \u8F93\u51FA TASK_COMPLETE`
5890
+ },
5891
+ {
5892
+ id: "guard-memory",
5893
+ name: "\u{1F9E0} \u8BB0\u5FC6\u5B88\u62A4",
5894
+ description: "7x24\u7EF4\u62A4\u8BB0\u5FC6\u5E93: CRUD/\u641C\u7D22/\u7C7B\u578B\u8FC7\u6EE4/\u77E5\u8BC6\u6CE8\u5165",
5895
+ prefix: "G",
5896
+ fields: [
5897
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5898
+ ],
5899
+ buildPrompt: (p) => `\u4F60\u662F\u8BB0\u5FC6\u5E93\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u8BB0\u5FC6\u529F\u80FD\u4E00\u5207\u6B63\u5E38\u3002
5900
+
5901
+ ## \u68C0\u67E5\u6E05\u5355
5902
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5903
+ |------|--------|
5904
+ | CRUD | POST\u521B\u5EFA/GET\u5217\u8868/DELETE\u5220\u9664\u6B63\u5E38\uFF1F |
5905
+ | \u641C\u7D22 | /api/memory/search \u5173\u952E\u8BCD\u641C\u7D22\uFF1F |
5906
+ | \u7C7B\u578B\u8FC7\u6EE4 | all/memory/directive/skill/doc/strategy\u7B5B\u9009\uFF1F |
5907
+ | \u77E5\u8BC6\u6CE8\u5165 | Worker\u6267\u884C\u524D\u80FD\u81EA\u52A8\u641C\u8BB0\u5FC6\uFF1F |
5908
+ | \u65B0\u5EFA\u8868\u5355 | \u7C7B\u578B\u4E0B\u62C9/\u6587\u672C/\u6807\u7B7E/\u7F6E\u4FE1\u5EA6\u6ED1\u5757\uFF1F |
5909
+
5910
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5911
+ \u8F93\u51FA TASK_COMPLETE`
5912
+ },
5913
+ {
5914
+ id: "guard-scheduler",
5915
+ name: "\u23F0 \u8C03\u5EA6\u5B88\u62A4",
5916
+ description: "7x24\u5B88\u62A4\u5B9A\u65F6\u4EFB\u52A1: \u68C0\u67E5\u79EF\u538B/\u8865\u53D1Worker/\u76D1\u63A74\u4E2A\u5C97\u4F4D",
5917
+ prefix: "G",
5918
+ fields: [
5919
+ { key: "mission", label: "\u7EF4\u62A4\u4EFB\u52A1", placeholder: "\u5168\u9762\u68C0\u67E5", required: true }
5920
+ ],
5921
+ buildPrompt: (p) => `\u4F60\u662F\u8C03\u5EA6\u7CFB\u7EDF\u5B88\u62A4\u8005\u3002\u68C0\u67E5\u5B9A\u65F6\u4EFB\u52A1\u4E00\u5207\u6B63\u5E38\u3002
5922
+
5923
+ ## \u68C0\u67E5\u6E05\u5355
5924
+ | \u7EC4\u4EF6 | \u68C0\u67E5\u9879 |
5925
+ |------|--------|
5926
+ | 4\u4E2A\u5C97\u4F4D | \u5206\u6790\u5E08/\u7814\u7A76\u5458/\u7B56\u5C55\u4EBA/\u5DE5\u7A0B\u5E08\u90FD\u5728\u8DD1\uFF1F |
5927
+ | \u79EF\u538B\u68C0\u67E5 | \u6709 unassigned \u7684\u5B9A\u65F6\u4EFB\u52A1\uFF1F\u6709\u5C31\u8865\u53D1Worker |
5928
+ | \u9632\u91CD\u590D | scheduler\u8DF3\u8FC7\u672A\u5B8C\u6210\u7684\u4EFB\u52A1\uFF1F |
5929
+ | \u95F4\u9694 | 4h/8h/12h/24h \u95F4\u9694\u6B63\u786E\uFF1F |
5930
+ | \u4EA7\u51FA\u8D28\u91CF | \u6700\u8FD1\u5B8C\u6210\u7684\u5B9A\u65F6\u4EFB\u52A1\u7ED3\u679C\u6709\u610F\u4E49\uFF1F |
5931
+
5932
+ \u53D1\u73B0\u95EE\u9898\u2192\u76F4\u63A5\u4FEE\u4EE3\u7801\u2192build\u2192commit\u3002
5356
5933
  \u8F93\u51FA TASK_COMPLETE`
5357
5934
  }
5358
5935
  ];
5359
5936
 
5360
5937
  // src/hub-server.ts
5938
+ var envPath = (0, import_node_path2.join)(process.cwd(), ".env");
5939
+ if ((0, import_node_fs2.existsSync)(envPath)) {
5940
+ (0, import_node_fs2.readFileSync)(envPath, "utf-8").split(/\r?\n/).forEach((line) => {
5941
+ const hashIdx = line.indexOf("#");
5942
+ if (hashIdx >= 0) line = line.substring(0, hashIdx);
5943
+ const m = line.match(/^\s*([^#\s=]+)\s*=\s*(.*)$/);
5944
+ if (m) {
5945
+ const val = m[2].trim();
5946
+ if (!val) {
5947
+ delete process.env[m[1]];
5948
+ } else if (!process.env[m[1]]) {
5949
+ process.env[m[1]] = val;
5950
+ }
5951
+ }
5952
+ });
5953
+ }
5361
5954
  var VERSION = "0.4.3";
5362
5955
  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")];
5956
+ const paths = [(0, import_node_path2.join)(__dirname, "web", "dashboard.html"), (0, import_node_path2.join)(__dirname, "..", "src", "web", "dashboard.html")];
5364
5957
  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);
5958
+ if ((0, import_node_fs2.existsSync)(p)) return (0, import_node_fs2.readFileSync)(p, "utf-8").replace("HUB_HOST", host2).replace("WS_PORT = 0", "WS_PORT = " + port);
5366
5959
  }
5367
5960
  return "<html><body><h2>dashboard.html not found</h2></body></html>";
5368
5961
  }
@@ -5377,12 +5970,25 @@ var wsPort = parseInt(flag("port") || process.env.HUB_PORT || "9321", 10);
5377
5970
  var host = flag("host") || process.env.HUB_HOST || "127.0.0.1";
5378
5971
  var webPort = parseInt(flag("web-port") || process.env.HUB_WEB_PORT || "3000", 10);
5379
5972
  var dbPath = flag("db") || process.env.HUB_DB_PATH || ".hub/hub.db";
5973
+ var log5 = logger("Hub");
5974
+ var anthropicKey = process.env.ANTHROPIC_API_KEY || "";
5975
+ var openaiKey = process.env.OPENAI_API_KEY || "";
5976
+ var openaiBase = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
5977
+ var llmModel = process.env.LLM_MODEL || "gpt-4o-mini";
5978
+ var chatProvider = openaiKey ? "openai" : anthropicKey ? "anthropic" : "none";
5380
5979
  var token = process.env.HUB_AUTH_TOKEN || "";
5381
5980
  var workers = [];
5981
+ var mcpSessionId = "";
5382
5982
  function validateTaskId(id) {
5383
5983
  return /^[A-Za-z0-9_-]+$/.test(id) && !id.includes("..") && id.length <= 64;
5384
5984
  }
5985
+ var MAX_CONCURRENT_WORKERS = 3;
5385
5986
  function spawnWorker(taskId) {
5987
+ const activeWorkers = workers.filter((w) => w.exitCode === null && !w.killed).length;
5988
+ if (activeWorkers >= MAX_CONCURRENT_WORKERS) {
5989
+ log5.warn(`Worker \u5E76\u53D1\u4E0A\u9650 (${MAX_CONCURRENT_WORKERS})\uFF0C${taskId} \u7B49\u5F85\u4E0B\u6B21\u8C03\u5EA6`);
5990
+ return { ok: false, error: `\u5E76\u53D1\u4E0A\u9650 ${MAX_CONCURRENT_WORKERS}\uFF0C\u5F53\u524D ${activeWorkers} \u6D3B\u8DC3` };
5991
+ }
5386
5992
  const hubUrl = `ws://127.0.0.1:${wsPort}`;
5387
5993
  const repoPath = process.cwd();
5388
5994
  const meta = taskMeta.get(taskId);
@@ -5397,24 +6003,20 @@ function spawnWorker(taskId) {
5397
6003
  const workerArgs = ["dist/hub-worker.js", "--task", taskId, "--hub", hubUrl, "--repo", repoPath, "--web-port", String(webPort)];
5398
6004
  if (promptB64) workerArgs.push("--prompt-b64", promptB64);
5399
6005
  try {
5400
- const worker = (0, import_node_child_process.spawn)("node", workerArgs, { cwd: repoPath, stdio: "pipe", detached: true });
6006
+ const worker = (0, import_node_child_process.spawn)("node", workerArgs, { cwd: repoPath, stdio: "pipe", detached: true, windowsHide: true });
5401
6007
  worker.stdout?.on("data", (d) => process.stderr.write(`[Worker-${taskId}] ${d}`));
5402
6008
  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
- `));
6009
+ worker.on("error", (e) => log5.error(`Worker \u542F\u52A8\u5931\u8D25: ${e.message}`));
5405
6010
  worker.on("close", (code) => {
5406
- process.stderr.write(`[Hub] Worker-${taskId} \u9000\u51FA (${code})
5407
- `);
6011
+ log5.info(`Worker-${taskId} \u9000\u51FA (${code})`);
5408
6012
  const idx = workers.indexOf(worker);
5409
6013
  if (idx >= 0) workers.splice(idx, 1);
5410
6014
  });
5411
6015
  workers.push(worker);
5412
- process.stderr.write(`[Hub] \u6D3B\u8DC3 Worker: ${workers.length}
5413
- `);
6016
+ log5.info(`\u6D3B\u8DC3 Worker: ${workers.length}`);
5414
6017
  return { ok: true, workerPid: worker.pid };
5415
6018
  } catch (e) {
5416
- process.stderr.write(`[Hub] Worker \u542F\u52A8\u5F02\u5E38: ${e.message}
5417
- `);
6019
+ log5.error(`Worker \u542F\u52A8\u5F02\u5E38: ${e.message}`);
5418
6020
  return { ok: false, error: e.message };
5419
6021
  }
5420
6022
  }
@@ -5441,6 +6043,46 @@ function startHttpServer() {
5441
6043
  res.end(getDashboardHtml(host, wsPort));
5442
6044
  return;
5443
6045
  }
6046
+ if (_req.method === "GET" && _req.url === "/v2") {
6047
+ res.writeHead(301, { "Location": "/v2/" });
6048
+ res.end();
6049
+ return;
6050
+ }
6051
+ if (_req.method === "GET" && _req.url?.startsWith("/v2/")) {
6052
+ const v2Dir = (0, import_node_path2.join)(__dirname, "..", "dashboard-v2", "dist");
6053
+ let filePath;
6054
+ if (_req.url === "/v2/") {
6055
+ filePath = (0, import_node_path2.join)(v2Dir, "index.html");
6056
+ } else {
6057
+ filePath = (0, import_node_path2.join)(v2Dir, _req.url.replace(/^\/v2\//, ""));
6058
+ }
6059
+ if (!(0, import_node_fs2.existsSync)(filePath) || !filePath.includes(".")) {
6060
+ filePath = (0, import_node_path2.join)(v2Dir, "index.html");
6061
+ }
6062
+ if ((0, import_node_fs2.existsSync)(filePath)) {
6063
+ const ext = filePath.split(".").pop() || "html";
6064
+ const mime = { html: "text/html; charset=utf-8", js: "application/javascript", css: "text/css", svg: "image/svg+xml", png: "image/png", ico: "image/x-icon" };
6065
+ res.writeHead(200, { "Content-Type": mime[ext] || "application/octet-stream" });
6066
+ res.end((0, import_node_fs2.readFileSync)(filePath));
6067
+ } else {
6068
+ res.writeHead(404);
6069
+ res.end("dashboard-v2 not found");
6070
+ }
6071
+ return;
6072
+ }
6073
+ if (_req.method === "GET" && _req.url === "/chat") {
6074
+ const paths = [(0, import_node_path2.join)(__dirname, "web", "index.html"), (0, import_node_path2.join)(__dirname, "..", "src", "web", "index.html")];
6075
+ for (const p of paths) {
6076
+ if ((0, import_node_fs2.existsSync)(p)) {
6077
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
6078
+ res.end((0, import_node_fs2.readFileSync)(p, "utf-8"));
6079
+ return;
6080
+ }
6081
+ }
6082
+ res.writeHead(404);
6083
+ res.end("chat UI not found");
6084
+ return;
6085
+ }
5444
6086
  if (_req.method === "GET" && _req.url === "/api/status") {
5445
6087
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
5446
6088
  res.end(JSON.stringify(agentHub.status()));
@@ -5462,18 +6104,22 @@ function startHttpServer() {
5462
6104
  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
6105
  return;
5464
6106
  }
5465
- agentHub.registerTask(taskId, title || taskId);
5466
- db?.saveTask({ taskId, status: "unassigned", title: title || taskId });
5467
6107
  if (template && params) taskMeta.set(taskId, { templateId: template, params });
6108
+ let promptB64 = "";
6109
+ if (template && params) {
6110
+ const tpl = TASK_TEMPLATES.find((t) => t.id === template);
6111
+ if (tpl) {
6112
+ try {
6113
+ promptB64 = Buffer.from(tpl.buildPrompt(params), "utf-8").toString("base64");
6114
+ } catch {
6115
+ }
6116
+ }
6117
+ }
6118
+ agentHub.registerTask(taskId, title || taskId, promptB64 || void 0);
6119
+ db?.saveTask({ taskId, status: "unassigned", title: title || taskId });
5468
6120
  let spawned = false;
5469
6121
  let workerPid = 0;
5470
- if (template) {
5471
- const result = spawnWorker(taskId);
5472
- spawned = result.ok;
5473
- workerPid = result.workerPid || 0;
5474
- }
5475
- process.stderr.write(`[Hub] \u65B0\u4EFB\u52A1: ${taskId} "${title}"${spawned ? " \u{1F916} \u5DF2\u62C9\u8D77" : ""}
5476
- `);
6122
+ log5.info(`\u65B0\u4EFB\u52A1: ${taskId} "${title}"${spawned ? " \u{1F916} \u5DF2\u62C9\u8D77" : ""}`);
5477
6123
  res.writeHead(201, { "Content-Type": "application/json; charset=utf-8" });
5478
6124
  res.end(JSON.stringify({ ok: true, taskId, title, spawned, workerPid }));
5479
6125
  } catch {
@@ -5621,6 +6267,53 @@ function startHttpServer() {
5621
6267
  });
5622
6268
  return;
5623
6269
  }
6270
+ if (_req.method === "GET" && _req.url === "/api/traders") {
6271
+ const stateFile = (0, import_node_path2.join)(process.cwd(), ".hub", "trader-state.json");
6272
+ if ((0, import_node_fs2.existsSync)(stateFile)) {
6273
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6274
+ res.end((0, import_node_fs2.readFileSync)(stateFile, "utf-8"));
6275
+ } else {
6276
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6277
+ res.end(JSON.stringify({ traders: {}, history: [], round: 0 }));
6278
+ }
6279
+ return;
6280
+ }
6281
+ if (_req.method === "GET" && _req.url === "/api/traders/leaderboard") {
6282
+ const stateFile = (0, import_node_path2.join)(process.cwd(), ".hub", "trader-state.json");
6283
+ if ((0, import_node_fs2.existsSync)(stateFile)) {
6284
+ const state = JSON.parse((0, import_node_fs2.readFileSync)(stateFile, "utf-8"));
6285
+ const rankings = Object.values(state.traders || {}).map((t) => ({
6286
+ id: t.id,
6287
+ name: t.name,
6288
+ emoji: t.emoji,
6289
+ title: t.title,
6290
+ style: t.style,
6291
+ capital: t.capital,
6292
+ totalPnl: t.totalPnl,
6293
+ totalPnlPct: t.totalPnlPct,
6294
+ tradeCount: t.tradeCount || 0,
6295
+ winCount: t.winCount || 0,
6296
+ openPositions: (t.openPositions || []).filter((p) => !p.closed).length
6297
+ })).sort((a, b) => b.totalPnlPct - a.totalPnlPct);
6298
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6299
+ res.end(JSON.stringify(rankings));
6300
+ } else {
6301
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6302
+ res.end(JSON.stringify([]));
6303
+ }
6304
+ return;
6305
+ }
6306
+ if (_req.method === "GET" && _req.url === "/api/signals") {
6307
+ const sigFile = (0, import_node_path2.join)(process.cwd(), ".hub", "signals.json");
6308
+ if ((0, import_node_fs2.existsSync)(sigFile)) {
6309
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6310
+ res.end((0, import_node_fs2.readFileSync)(sigFile, "utf-8"));
6311
+ } else {
6312
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6313
+ res.end(JSON.stringify({ signals: [], lastRun: null }));
6314
+ }
6315
+ return;
6316
+ }
5624
6317
  if (_req.method === "GET" && _req.url === "/api/templates") {
5625
6318
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
5626
6319
  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 +6324,200 @@ function startHttpServer() {
5631
6324
  res.end(JSON.stringify({ status: "ok", name: "hvip-hub", version: VERSION, wsPort, webPort, db: dbPath, registry: registry.all().length + " plugins" }));
5632
6325
  return;
5633
6326
  }
6327
+ if (_req.method === "GET" && _req.url === "/api/costs") {
6328
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6329
+ res.end(JSON.stringify(costTracker.summary()));
6330
+ return;
6331
+ }
6332
+ if (_req.method === "GET" && _req.url === "/api/costs/recent") {
6333
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6334
+ res.end(JSON.stringify(costTracker.recent(50)));
6335
+ return;
6336
+ }
6337
+ if (_req.method === "GET" && _req.url === "/api/health/pm2") {
6338
+ try {
6339
+ const raw = (0, import_node_child_process.execSync)("pm2 jlist", { encoding: "utf-8", timeout: 5e3, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] });
6340
+ const processes = JSON.parse(raw);
6341
+ const expected = ["hvip-hub", "hvip-mcp", "ai-trader", "worker-v2-01", "worker-v2-02", "chronos-dispatcher"];
6342
+ const health = processes.map((p) => ({
6343
+ name: p.name,
6344
+ status: p.pm2_env?.status || "unknown",
6345
+ pid: p.pid,
6346
+ uptime: p.pm2_env?.pm_uptime ? Math.round((Date.now() - p.pm2_env.pm_uptime) / 1e3) : 0,
6347
+ memory: Math.round((p.monit?.memory || 0) / 1024 / 1024),
6348
+ cpu: p.monit?.cpu || 0,
6349
+ restarts: p.pm2_env?.unstable_restarts || 0
6350
+ }));
6351
+ const missing = expected.filter((name) => !processes.some((p) => p.name === name && p.pm2_env?.status === "online"));
6352
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6353
+ res.end(JSON.stringify({
6354
+ processes: health,
6355
+ missing,
6356
+ totalExpected: expected.length,
6357
+ totalOnline: health.length - missing.length,
6358
+ healthy: missing.length === 0
6359
+ }));
6360
+ } catch (e) {
6361
+ res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
6362
+ res.end(JSON.stringify({ error: "PM2 \u67E5\u8BE2\u5931\u8D25", message: e.message }));
6363
+ }
6364
+ return;
6365
+ }
5634
6366
  if (_req.method === "GET" && _req.url === "/api/schedules") {
6367
+ const hubStatus = agentHub.status();
6368
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6369
+ res.end(JSON.stringify(schedules.map((s) => {
6370
+ const jobTasks = hubStatus.tasks.filter((t) => t.taskId.startsWith(s.id + "-"));
6371
+ const latest = jobTasks.sort((a, b) => (b.claimedAt || "").localeCompare(a.claimedAt || ""))[0];
6372
+ return {
6373
+ id: s.id,
6374
+ name: s.name,
6375
+ interval: s.interval,
6376
+ count: s.count,
6377
+ failCount: s.failCount,
6378
+ lastRun: s.lastRun || null,
6379
+ latestTask: latest ? {
6380
+ taskId: latest.taskId,
6381
+ status: latest.status,
6382
+ result: (latest.result || "").slice(0, 200)
6383
+ } : null
6384
+ };
6385
+ })));
6386
+ return;
6387
+ }
6388
+ if (_req.method === "GET" && _req.url === "/api/circuits") {
5635
6389
  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 }))));
6390
+ const circuits = circuitBreaker.status();
6391
+ const openCount = circuits.filter((c) => c.state === "OPEN").length;
6392
+ res.end(JSON.stringify({ circuits, openCount, healthy: openCount === 0 }));
6393
+ return;
6394
+ }
6395
+ if (_req.method === "POST" && _req.url === "/mcp") {
6396
+ const chunks = [];
6397
+ _req.on("data", (c) => chunks.push(c));
6398
+ _req.on("end", async () => {
6399
+ try {
6400
+ const bodyStr = Buffer.concat(chunks).toString("utf-8");
6401
+ const headers = {
6402
+ "Content-Type": "application/json",
6403
+ Accept: "application/json, text/event-stream"
6404
+ };
6405
+ if (mcpSessionId) headers["Mcp-Session-Id"] = mcpSessionId;
6406
+ const mcpRes = await fetch("http://127.0.0.1:9222/mcp", {
6407
+ method: "POST",
6408
+ headers,
6409
+ body: bodyStr
6410
+ });
6411
+ const body = await mcpRes.text();
6412
+ let result = body;
6413
+ const match = body.match(/data:\s*(\{[\s\S]*?\})\s*$/m);
6414
+ if (match) {
6415
+ try {
6416
+ const parsed = JSON.parse(match[1]);
6417
+ if (parsed.result?.content?.[0]?.text) {
6418
+ try {
6419
+ parsed.result.content[0].text = JSON.parse(parsed.result.content[0].text);
6420
+ } catch {
6421
+ }
6422
+ }
6423
+ result = JSON.stringify(parsed);
6424
+ } catch {
6425
+ }
6426
+ }
6427
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6428
+ res.end(result);
6429
+ } catch (e) {
6430
+ res.writeHead(502, { "Content-Type": "application/json; charset=utf-8" });
6431
+ res.end(JSON.stringify({ error: e.message || "MCP \u670D\u52A1\u672A\u542F\u52A8" }));
6432
+ }
6433
+ });
6434
+ return;
6435
+ }
6436
+ if (_req.method === "POST" && _req.url === "/api/chat") {
6437
+ if (chatProvider === "none") {
6438
+ res.writeHead(503, { "Content-Type": "application/json; charset=utf-8" });
6439
+ res.end(JSON.stringify({ error: "\u672A\u914D\u7F6E LLM Key" }));
6440
+ return;
6441
+ }
6442
+ const chunks = [];
6443
+ _req.on("data", (c) => chunks.push(c));
6444
+ _req.on("end", async () => {
6445
+ try {
6446
+ const body = Buffer.concat(chunks).toString("utf-8");
6447
+ const input = JSON.parse(body);
6448
+ if (chatProvider === "anthropic") {
6449
+ const anthroMsgs = [];
6450
+ for (const m of input.messages || []) {
6451
+ if (m.role === "system") {
6452
+ anthroMsgs.push({ role: "system", content: m.content });
6453
+ } else if (m.role === "user") {
6454
+ anthroMsgs.push({ role: "user", content: m.content });
6455
+ } else if (m.role === "assistant") {
6456
+ const c = [];
6457
+ if (m.content) c.push({ type: "text", text: m.content });
6458
+ if (m.tool_calls) for (const tc of m.tool_calls) {
6459
+ let args = {};
6460
+ try {
6461
+ args = JSON.parse(tc.function.arguments || "{}");
6462
+ } catch {
6463
+ }
6464
+ c.push({ type: "tool_use", id: tc.id, name: tc.function.name, input: args });
6465
+ }
6466
+ anthroMsgs.push({ role: "assistant", content: c.length ? c : m.content || "" });
6467
+ } else if (m.role === "tool") {
6468
+ anthroMsgs.push({ role: "user", content: [{ type: "tool_result", tool_use_id: m.tool_call_id, content: m.content }] });
6469
+ }
6470
+ }
6471
+ const anthroTools = (input.tools || []).map((t) => ({
6472
+ name: t.function.name,
6473
+ description: t.function.description,
6474
+ input_schema: t.function.parameters || {}
6475
+ }));
6476
+ const anthroRes = await fetch("https://api.anthropic.com/v1/messages", {
6477
+ method: "POST",
6478
+ headers: { "Content-Type": "application/json", "x-api-key": anthropicKey, "anthropic-version": "2023-06-01" },
6479
+ body: JSON.stringify({ model: llmModel, max_tokens: 2048, messages: anthroMsgs, tools: anthroTools })
6480
+ });
6481
+ const aJson = await anthroRes.json();
6482
+ if (aJson.type === "error") {
6483
+ res.writeHead(anthroRes.status, { "Content-Type": "application/json; charset=utf-8" });
6484
+ res.end(JSON.stringify({ error: aJson.error }));
6485
+ } else {
6486
+ const choice = { role: "assistant" };
6487
+ const content = [];
6488
+ if (aJson.content) for (const b of aJson.content) {
6489
+ if (b.type === "text") content.push(b.text);
6490
+ else if (b.type === "tool_use") {
6491
+ if (!choice.tool_calls) choice.tool_calls = [];
6492
+ choice.tool_calls.push({ id: b.id, type: "function", function: { name: b.name, arguments: JSON.stringify(b.input || {}) } });
6493
+ }
6494
+ }
6495
+ if (content.length) choice.content = content.join("\n");
6496
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
6497
+ res.end(JSON.stringify({ id: aJson.id, model: aJson.model, choices: [{ message: choice, finish_reason: choice.tool_calls ? "tool_calls" : "stop" }] }));
6498
+ }
6499
+ } else {
6500
+ const oaiBody = JSON.stringify({ model: llmModel, max_tokens: 2048, messages: input.messages, tools: input.tools });
6501
+ const oaiRes = await fetch(openaiBase + "/chat/completions", {
6502
+ method: "POST",
6503
+ headers: { "Content-Type": "application/json", Authorization: "Bearer " + openaiKey },
6504
+ body: oaiBody
6505
+ });
6506
+ res.writeHead(oaiRes.status, { "Content-Type": "application/json; charset=utf-8" });
6507
+ res.end(await oaiRes.text());
6508
+ }
6509
+ } catch (e) {
6510
+ res.writeHead(502, { "Content-Type": "application/json; charset=utf-8" });
6511
+ res.end(JSON.stringify({ error: e.message }));
6512
+ }
6513
+ });
5637
6514
  return;
5638
6515
  }
5639
6516
  res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
5640
6517
  res.end(JSON.stringify({ error: "Not Found" }));
5641
6518
  });
5642
6519
  httpServer.listen(webPort, host, () => {
5643
- process.stderr.write(`[Hub] \u{1F310} \u4EEA\u8868\u76D8 \u2192 http://${host}:${webPort}
5644
- `);
6520
+ log5.info(`\u{1F310} \u4EEA\u8868\u76D8 \u2192 http://${host}:${webPort}`);
5645
6521
  });
5646
6522
  }
5647
6523
  var schedules = [
@@ -5688,25 +6564,119 @@ var schedules = [
5688
6564
  count: 0,
5689
6565
  failCount: 0,
5690
6566
  timer: null
6567
+ },
6568
+ // ════════ 24x7 守护岗 ════════
6569
+ {
6570
+ id: "guard-dashboard",
6571
+ name: "\u{1F4CA} \u4EEA\u8868\u76D8\u5B88\u62A4",
6572
+ interval: 6 * 36e5,
6573
+ template: "guard-dashboard",
6574
+ params: { mission: "\u5168\u9762\u68C0\u67E5\u4EEA\u8868\u76D8KPI/\u56FE\u8868/Agent\u9762\u677F/\u4EFB\u52A1/\u52A8\u6001/\u521B\u5EFA\u5668" },
6575
+ lastRun: 0,
6576
+ count: 0,
6577
+ failCount: 0,
6578
+ timer: null
6579
+ },
6580
+ {
6581
+ id: "guard-debate",
6582
+ name: "\u2696\uFE0F \u8FA9\u8BBA\u5B88\u62A4",
6583
+ interval: 6 * 36e5,
6584
+ template: "guard-debate",
6585
+ params: { mission: "\u68C0\u67E5\u8FA9\u8BBA\u5361\u7247/\u804A\u5929\u6D41/\u7ACB\u573A\u8C31/\u5171\u8BC6\u73AF/\u8FD4\u56DE\u6309\u94AE" },
6586
+ lastRun: 0,
6587
+ count: 0,
6588
+ failCount: 0,
6589
+ timer: null
6590
+ },
6591
+ {
6592
+ id: "guard-terminal",
6593
+ name: "\u{1F5A5} \u7EC8\u7AEF\u5B88\u62A4",
6594
+ interval: 6 * 36e5,
6595
+ template: "guard-terminal",
6596
+ params: { mission: "\u68C0\u67E5\u6D41\u5F0F\u8F93\u51FA/\u7740\u8272/Pause/Clear/Worker\u8BA1\u6570" },
6597
+ lastRun: 0,
6598
+ count: 0,
6599
+ failCount: 0,
6600
+ timer: null
6601
+ },
6602
+ {
6603
+ id: "guard-signals",
6604
+ name: "\u{1F6A6} \u4FE1\u53F7\u5B88\u62A4",
6605
+ interval: 6 * 36e5,
6606
+ template: "guard-signals",
6607
+ params: { mission: "\u68C0\u67E5VBT\u4FE1\u53F7\u89E3\u6790/KPI/\u5361\u7247\u6E32\u67D3/\u4FE1\u53F7\u521B\u5EFA" },
6608
+ lastRun: 0,
6609
+ count: 0,
6610
+ failCount: 0,
6611
+ timer: null
6612
+ },
6613
+ {
6614
+ id: "guard-store",
6615
+ name: "\u{1F3EA} \u5546\u5E97\u5B88\u62A4",
6616
+ interval: 6 * 36e5,
6617
+ template: "guard-store",
6618
+ params: { mission: "\u68C0\u67E524\u4E2A\u63D2\u4EF6/\u5206\u7C7B/\u641C\u7D22/\u5361\u7247\u6E32\u67D3" },
6619
+ lastRun: 0,
6620
+ count: 0,
6621
+ failCount: 0,
6622
+ timer: null
6623
+ },
6624
+ {
6625
+ id: "guard-memory",
6626
+ name: "\u{1F9E0} \u8BB0\u5FC6\u5B88\u62A4",
6627
+ interval: 6 * 36e5,
6628
+ template: "guard-memory",
6629
+ params: { mission: "\u68C0\u67E5CRUD/\u641C\u7D22/\u7C7B\u578B\u8FC7\u6EE4/\u77E5\u8BC6\u6CE8\u5165/\u65B0\u5EFA\u8868\u5355" },
6630
+ lastRun: 0,
6631
+ count: 0,
6632
+ failCount: 0,
6633
+ timer: null
6634
+ },
6635
+ {
6636
+ id: "guard-scheduler",
6637
+ name: "\u23F0 \u8C03\u5EA6\u5B88\u62A4",
6638
+ interval: 3 * 36e5,
6639
+ template: "guard-scheduler",
6640
+ params: { mission: "\u68C0\u67E54\u5C97\u4F4D+7\u5B88\u62A4/\u8865\u53D1\u79EF\u538B/\u9632\u91CD\u590D" },
6641
+ lastRun: 0,
6642
+ count: 0,
6643
+ failCount: 0,
6644
+ timer: null
5691
6645
  }
5692
6646
  ];
5693
6647
  function runScheduledJob(job) {
6648
+ const status = agentHub.status();
6649
+ const prevTasks = status.tasks.filter(
6650
+ (t) => t.status === "assigned"
6651
+ ).filter((t) => t.taskId.startsWith(job.id + "-"));
6652
+ if (prevTasks.length > 0) {
6653
+ log5.warn(`Scheduler: \u23ED\uFE0F ${job.name} \u4E0A\u6B21\u4EFB\u52A1\u672A\u5B8C\u6210 (${prevTasks.map((t) => t.taskId).join(",")})\uFF0C\u8DF3\u8FC7`);
6654
+ scheduleNext(job);
6655
+ return;
6656
+ }
6657
+ const orphans = status.tasks.filter(
6658
+ (t) => t.status === "unassigned"
6659
+ ).filter((t) => t.taskId.startsWith(job.id + "-"));
6660
+ for (const o of orphans) {
6661
+ db?.saveTask({ taskId: o.taskId, status: "done", result: "[Scheduler] \u81EA\u52A8\u6E05\u7406\u5B64\u513F\u4EFB\u52A1" });
6662
+ log5.warn(`Scheduler: \u{1F9F9} \u6E05\u7406\u5B64\u513F: ${o.taskId}`);
6663
+ }
5694
6664
  const taskId = `${job.id}-${Date.now().toString(36)}`;
5695
6665
  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) {
5703
- job.count++;
5704
- job.failCount = 0;
5705
- } else {
5706
- job.failCount++;
5707
- process.stderr.write(`[Scheduler] \u274C ${job.name}: ${result.error}
5708
- `);
6666
+ log5.info(`Scheduler: \u23F0 ${job.name} \u2192 ${taskId}`);
6667
+ const tpl = TASK_TEMPLATES.find((t) => t.id === job.template);
6668
+ let promptB64 = "";
6669
+ if (tpl) {
6670
+ try {
6671
+ promptB64 = Buffer.from(tpl.buildPrompt(job.params), "utf-8").toString("base64");
6672
+ } catch {
6673
+ }
5709
6674
  }
6675
+ taskMeta.set(taskId, { templateId: job.template, params: job.params });
6676
+ agentHub.registerTask(taskId, title, promptB64 || void 0);
6677
+ db?.saveTask({ taskId, status: "unassigned", title });
6678
+ job.count++;
6679
+ job.failCount = 0;
5710
6680
  job.lastRun = Date.now();
5711
6681
  scheduleNext(job);
5712
6682
  }
@@ -5716,8 +6686,7 @@ function scheduleNext(job) {
5716
6686
  }
5717
6687
  function startScheduler() {
5718
6688
  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
- `);
6689
+ log5.info(`Scheduler: ${schedules.length} \u4E2A\u5B9A\u65F6\u4EFB\u52A1 (\u9996\u6B2130s\u540E\u89E6\u53D1\uFF0CChronos \u2192 V2 Worker \u96F6\u5F39\u7A97)`);
5721
6690
  }
5722
6691
  var banner = [
5723
6692
  `\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 +6701,50 @@ var db = new HubDB(dbPath);
5732
6701
  if (db.open()) {
5733
6702
  agentHub.setDB(db);
5734
6703
  const stats = db.stats();
5735
- process.stderr.write(`[Hub] DB \u72B6\u6001: ${stats.taskCount} tasks, ${stats.messageCount} messages
5736
- `);
6704
+ log5.info(`DB \u72B6\u6001: ${stats.taskCount} tasks, ${stats.messageCount} messages`);
5737
6705
  }
5738
6706
  var memoryPath = flag("memory-db") || process.env.HUB_MEMORY_DB || ".hub/memory.db";
5739
6707
  var memory = new HubMemory(memoryPath);
5740
6708
  var memOk = memory.open();
5741
6709
  if (memOk) {
5742
6710
  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
- `);
6711
+ log5.info(`\u{1F9E0} \u8BB0\u5FC6: ${ms.total} \u6761 (doc:${ms.byType.doc || 0} directive:${ms.byType.directive || 0} memory:${ms.byType.memory || 0} skill:${ms.byType.skill || 0})`);
5745
6712
  }
5746
6713
  var registryPath = flag("registry-db") || process.env.HUB_REGISTRY_DB || ".hub/registry.db";
5747
6714
  var registry = new HubRegistry(registryPath);
5748
- registry.open();
6715
+ var regOk = registry.open();
6716
+ if (!regOk) {
6717
+ log5.warn(`\u26A0\uFE0F Registry \u6253\u5F00\u5931\u8D25\uFF0C\u5546\u5E97\u529F\u80FD\u4E0D\u53EF\u7528`);
6718
+ }
6719
+ var costsPath = flag("costs-db") || process.env.HUB_COSTS_DB || ".hub/llm-costs.jsonl";
6720
+ var costTracker = new HubCosts(costsPath);
6721
+ agentHub.setCostTracker(costTracker);
6722
+ log5.info(`\u{1F4B0} \u6210\u672C\u8FFD\u8E2A: ${costsPath}`);
5749
6723
  startHttpServer();
5750
6724
  startScheduler();
6725
+ var mcpProcess = null;
6726
+ try {
6727
+ mcpProcess = (0, import_node_child_process.spawn)("node", ["dist/index.js", "start:http"], { cwd: process.cwd(), stdio: "pipe", detached: true, env: { ...process.env, PORT: "9222" } });
6728
+ mcpProcess.stderr?.on("data", (d) => process.stderr.write(d));
6729
+ log5.info("\u{1F6E0} MCP Server \u542F\u52A8\u4E2D \u2192 http://127.0.0.1:9222");
6730
+ } catch {
6731
+ log5.warn("\u26A0\uFE0F MCP Server \u542F\u52A8\u5931\u8D25");
6732
+ }
5751
6733
  agentHub.start(wsPort, host, VERSION, token);
5752
6734
  function shutdown() {
5753
- process.stderr.write("\n[Hub] \u6B63\u5728\u5173\u95ED...\n");
6735
+ log5.info("\u6B63\u5728\u5173\u95ED...");
5754
6736
  for (const w of workers) {
5755
6737
  try {
5756
6738
  w.kill();
5757
6739
  } catch {
5758
6740
  }
5759
6741
  }
6742
+ if (mcpProcess) {
6743
+ try {
6744
+ process.kill(-mcpProcess.pid);
6745
+ } catch {
6746
+ }
6747
+ }
5760
6748
  agentHub.close();
5761
6749
  db.close();
5762
6750
  memory.close();