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.
- package/dist/hub-server.js +1097 -109
- package/dist/hub-worker.js +96 -21
- package/dist/index.js +257 -49
- package/package.json +7 -2
package/dist/hub-server.js
CHANGED
|
@@ -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.
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3792
|
-
`);
|
|
3858
|
+
log.warn(`WS Hub \u8DF3\u8FC7\uFF08\u7AEF\u53E3 ${ports.join("/")} \u4E0D\u53EF\u7528\uFF09`);
|
|
3793
3859
|
return;
|
|
3794
3860
|
}
|
|
3795
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3965
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4226
|
+
const msg = {
|
|
4042
4227
|
type: "task:dispatch",
|
|
4043
4228
|
taskId,
|
|
4044
|
-
title: this.getTaskTitle(taskId)
|
|
4045
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4274
|
-
`);
|
|
4481
|
+
log2.info(`\u5DF2\u6253\u5F00 ${this.dbPath}`);
|
|
4275
4482
|
return true;
|
|
4276
4483
|
} catch (e) {
|
|
4277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
4422
|
-
`);
|
|
4626
|
+
log3.info(`\u5DF2\u6253\u5F00 ${this.dbPath}`);
|
|
4423
4627
|
this.startDecay();
|
|
4424
4628
|
return true;
|
|
4425
4629
|
} catch (e) {
|
|
4426
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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,
|
|
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,
|
|
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) =>
|
|
5404
|
-
`));
|
|
6009
|
+
worker.on("error", (e) => log5.error(`Worker \u542F\u52A8\u5931\u8D25: ${e.message}`));
|
|
5405
6010
|
worker.on("close", (code) => {
|
|
5406
|
-
|
|
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
|
-
|
|
5413
|
-
`);
|
|
6016
|
+
log5.info(`\u6D3B\u8DC3 Worker: ${workers.length}`);
|
|
5414
6017
|
return { ok: true, workerPid: worker.pid };
|
|
5415
6018
|
} catch (e) {
|
|
5416
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|