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