hvip-mcp-server 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hub-server.js +462 -83
- package/dist/hub-worker.js +2 -2
- package/dist/index.js +493 -38680
- package/package.json +5 -2
package/dist/hub-server.js
CHANGED
|
@@ -3723,7 +3723,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
3723
3723
|
// src/utils/logger.ts
|
|
3724
3724
|
var LEVEL_EMOJI = {
|
|
3725
3725
|
[0 /* DEBUG */]: "\u{1F50D}",
|
|
3726
|
-
[1 /* INFO */]: "",
|
|
3726
|
+
[1 /* INFO */]: "\u2139\uFE0F",
|
|
3727
3727
|
[2 /* WARN */]: "\u26A0\uFE0F",
|
|
3728
3728
|
[3 /* ERROR */]: "\u274C",
|
|
3729
3729
|
[4 /* SILENT */]: ""
|
|
@@ -3778,7 +3778,11 @@ var AgentHub = class {
|
|
|
3778
3778
|
rooms = /* @__PURE__ */ new Map();
|
|
3779
3779
|
heartbeatTimer = null;
|
|
3780
3780
|
version = "0.0.0";
|
|
3781
|
+
startTime = 0;
|
|
3781
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
|
|
3782
3786
|
db = null;
|
|
3783
3787
|
token = "";
|
|
3784
3788
|
// PSK 鉴权令牌
|
|
@@ -3820,6 +3824,7 @@ var AgentHub = class {
|
|
|
3820
3824
|
// ── 启动 ──
|
|
3821
3825
|
start(port, host2 = "0.0.0.0", version = "0.0.0", token2 = "") {
|
|
3822
3826
|
this.version = version;
|
|
3827
|
+
this.startTime = Date.now();
|
|
3823
3828
|
this.token = token2;
|
|
3824
3829
|
const startWss = (p) => {
|
|
3825
3830
|
return new Promise((resolve) => {
|
|
@@ -4415,7 +4420,7 @@ var AgentHub = class {
|
|
|
4415
4420
|
branch: t.branch
|
|
4416
4421
|
}));
|
|
4417
4422
|
const rooms = this.getRooms();
|
|
4418
|
-
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` };
|
|
4419
4424
|
}
|
|
4420
4425
|
// ── 关闭 ──
|
|
4421
4426
|
close() {
|
|
@@ -4986,9 +4991,11 @@ var HubCosts = class {
|
|
|
4986
4991
|
const entries = all.filter((e) => e.timestamp.startsWith(slot));
|
|
4987
4992
|
hourly.push({ hour: slot, calls: entries.length, cost: entries.reduce((s, e) => s + e.cost, 0) });
|
|
4988
4993
|
}
|
|
4994
|
+
const budget = parseFloat(process.env.HUB_DAILY_BUDGET || "5.00");
|
|
4989
4995
|
return {
|
|
4990
4996
|
today: sum(todayEntries),
|
|
4991
4997
|
total: sum(all),
|
|
4998
|
+
budget,
|
|
4992
4999
|
byModel,
|
|
4993
5000
|
byAgent,
|
|
4994
5001
|
hourly
|
|
@@ -5005,11 +5012,29 @@ var HubCosts = class {
|
|
|
5005
5012
|
var CircuitBreaker = class {
|
|
5006
5013
|
circuits = /* @__PURE__ */ new Map();
|
|
5007
5014
|
failureThreshold = 3;
|
|
5008
|
-
|
|
5009
|
-
// 1 minute
|
|
5015
|
+
baseCooldownMs = 6e4;
|
|
5016
|
+
// 1 minute base
|
|
5017
|
+
maxCooldownMs = 3e5;
|
|
5018
|
+
// 5 minutes max
|
|
5019
|
+
listeners = /* @__PURE__ */ new Set();
|
|
5010
5020
|
constructor(opts) {
|
|
5011
5021
|
if (opts?.failureThreshold) this.failureThreshold = opts.failureThreshold;
|
|
5012
|
-
if (opts?.cooldownMs) this.
|
|
5022
|
+
if (opts?.cooldownMs) this.baseCooldownMs = opts.cooldownMs;
|
|
5023
|
+
if (opts?.maxCooldownMs) this.maxCooldownMs = opts.maxCooldownMs;
|
|
5024
|
+
}
|
|
5025
|
+
/** 注册状态变更监听 */
|
|
5026
|
+
onStateChange(handler) {
|
|
5027
|
+
this.listeners.add(handler);
|
|
5028
|
+
return () => this.listeners.delete(handler);
|
|
5029
|
+
}
|
|
5030
|
+
/** 内部通知 */
|
|
5031
|
+
notify(event, name, entry) {
|
|
5032
|
+
for (const h of this.listeners) {
|
|
5033
|
+
try {
|
|
5034
|
+
h(event, name, entry);
|
|
5035
|
+
} catch {
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5013
5038
|
}
|
|
5014
5039
|
/** 获取或创建熔断器 */
|
|
5015
5040
|
get(name) {
|
|
@@ -5024,7 +5049,8 @@ var CircuitBreaker = class {
|
|
|
5024
5049
|
lastFailure: null,
|
|
5025
5050
|
lastSuccess: null,
|
|
5026
5051
|
openedAt: null,
|
|
5027
|
-
cooldownMs: this.
|
|
5052
|
+
cooldownMs: this.baseCooldownMs,
|
|
5053
|
+
backoffMultiplier: 1
|
|
5028
5054
|
};
|
|
5029
5055
|
this.circuits.set(name, c);
|
|
5030
5056
|
}
|
|
@@ -5039,6 +5065,7 @@ var CircuitBreaker = class {
|
|
|
5039
5065
|
const elapsed = Date.now() - new Date(c.openedAt).getTime();
|
|
5040
5066
|
if (elapsed >= c.cooldownMs) {
|
|
5041
5067
|
c.state = "HALF_OPEN";
|
|
5068
|
+
this.notify("half_open", name, c);
|
|
5042
5069
|
return true;
|
|
5043
5070
|
}
|
|
5044
5071
|
}
|
|
@@ -5052,27 +5079,36 @@ var CircuitBreaker = class {
|
|
|
5052
5079
|
if (c.state === "HALF_OPEN") {
|
|
5053
5080
|
c.state = "CLOSED";
|
|
5054
5081
|
c.failures = 0;
|
|
5082
|
+
c.backoffMultiplier = 1;
|
|
5083
|
+
this.notify("closed", name, c);
|
|
5055
5084
|
}
|
|
5056
5085
|
if (c.state === "CLOSED") {
|
|
5057
5086
|
c.failures = 0;
|
|
5058
5087
|
}
|
|
5059
5088
|
}
|
|
5060
|
-
/** 调用失败 */
|
|
5089
|
+
/** 调用失败 — 带指数退避 */
|
|
5061
5090
|
onFailure(name) {
|
|
5062
5091
|
const c = this.get(name);
|
|
5063
5092
|
c.failures++;
|
|
5064
5093
|
c.failureCount++;
|
|
5065
5094
|
c.lastFailure = (/* @__PURE__ */ new Date()).toISOString();
|
|
5066
5095
|
if (c.failures >= this.failureThreshold) {
|
|
5096
|
+
const backoffMs = Math.min(
|
|
5097
|
+
this.baseCooldownMs * Math.pow(2, c.backoffMultiplier - 1),
|
|
5098
|
+
this.maxCooldownMs
|
|
5099
|
+
);
|
|
5100
|
+
c.cooldownMs = backoffMs;
|
|
5101
|
+
c.backoffMultiplier++;
|
|
5067
5102
|
c.state = "OPEN";
|
|
5068
5103
|
c.openedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5104
|
+
this.notify("opened", name, c);
|
|
5069
5105
|
}
|
|
5070
5106
|
}
|
|
5071
|
-
/**
|
|
5072
|
-
async wrap(name, fn, fallback) {
|
|
5107
|
+
/** 包裹一个异步函数,支持重试和 fallback */
|
|
5108
|
+
async wrap(name, fn, fallback, retries = 0) {
|
|
5073
5109
|
if (!this.canCall(name)) {
|
|
5074
5110
|
if (fallback) return fallback();
|
|
5075
|
-
throw new Error(`Circuit OPEN: ${name}`);
|
|
5111
|
+
throw new Error(`Circuit OPEN: ${name} (\u51B7\u5374\u4E2D, ${this.get(name).cooldownMs}ms)`);
|
|
5076
5112
|
}
|
|
5077
5113
|
try {
|
|
5078
5114
|
const result = await fn();
|
|
@@ -5080,6 +5116,11 @@ var CircuitBreaker = class {
|
|
|
5080
5116
|
return result;
|
|
5081
5117
|
} catch (e) {
|
|
5082
5118
|
this.onFailure(name);
|
|
5119
|
+
if (retries > 0) {
|
|
5120
|
+
const wait = Math.min(1e3 * Math.pow(2, 3 - retries), 9e3);
|
|
5121
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
5122
|
+
return this.wrap(name, fn, fallback, retries - 1);
|
|
5123
|
+
}
|
|
5083
5124
|
if (fallback) return fallback();
|
|
5084
5125
|
throw e;
|
|
5085
5126
|
}
|
|
@@ -5088,16 +5129,31 @@ var CircuitBreaker = class {
|
|
|
5088
5129
|
status() {
|
|
5089
5130
|
return [...this.circuits.values()];
|
|
5090
5131
|
}
|
|
5091
|
-
/** 重置指定熔断器 */
|
|
5132
|
+
/** 重置指定熔断器 — 立即清除状态 */
|
|
5092
5133
|
reset(name) {
|
|
5093
|
-
this.circuits.
|
|
5134
|
+
const c = this.circuits.get(name);
|
|
5135
|
+
if (c) {
|
|
5136
|
+
this.notify("reset", name, c);
|
|
5137
|
+
this.circuits.delete(name);
|
|
5138
|
+
}
|
|
5139
|
+
}
|
|
5140
|
+
/** 重置所有熔断器 */
|
|
5141
|
+
resetAll() {
|
|
5142
|
+
for (const [name, c] of this.circuits) {
|
|
5143
|
+
this.notify("reset", name, c);
|
|
5144
|
+
}
|
|
5145
|
+
this.circuits.clear();
|
|
5094
5146
|
}
|
|
5095
5147
|
};
|
|
5096
|
-
var circuitBreaker = new CircuitBreaker({
|
|
5148
|
+
var circuitBreaker = new CircuitBreaker({
|
|
5149
|
+
failureThreshold: 3,
|
|
5150
|
+
cooldownMs: 6e4,
|
|
5151
|
+
maxCooldownMs: 3e5
|
|
5152
|
+
});
|
|
5097
5153
|
|
|
5098
5154
|
// src/hub-server.ts
|
|
5099
|
-
var
|
|
5100
|
-
var
|
|
5155
|
+
var import_node_fs3 = require("node:fs");
|
|
5156
|
+
var import_node_path3 = require("node:path");
|
|
5101
5157
|
|
|
5102
5158
|
// src/adapters/hub-templates.ts
|
|
5103
5159
|
var TASK_TEMPLATES = [
|
|
@@ -5934,10 +5990,150 @@ NEXT: <\u540E\u7EED\u8DDF\u8FDB\u5EFA\u8BAE>
|
|
|
5934
5990
|
}
|
|
5935
5991
|
];
|
|
5936
5992
|
|
|
5993
|
+
// src/adapters/api-credits.ts
|
|
5994
|
+
var import_node_sqlite = require("node:sqlite");
|
|
5995
|
+
var import_node_fs2 = require("node:fs");
|
|
5996
|
+
var import_node_path2 = require("node:path");
|
|
5997
|
+
var log5 = logger("Credits");
|
|
5998
|
+
var CREDIT_COST = {
|
|
5999
|
+
READ: 1,
|
|
6000
|
+
WRITE: 5,
|
|
6001
|
+
FUND_TRANSFER: 10,
|
|
6002
|
+
ADMIN: 20
|
|
6003
|
+
};
|
|
6004
|
+
var ApiCredits = class {
|
|
6005
|
+
db;
|
|
6006
|
+
cache = /* @__PURE__ */ new Map();
|
|
6007
|
+
cacheLoaded = false;
|
|
6008
|
+
constructor(dbPath2 = ".hub/api-credits.db") {
|
|
6009
|
+
const dir = (0, import_node_path2.dirname)(dbPath2);
|
|
6010
|
+
if (!(0, import_node_fs2.existsSync)(dir)) (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
6011
|
+
this.db = new import_node_sqlite.DatabaseSync(dbPath2);
|
|
6012
|
+
this.db.exec("PRAGMA journal_mode=WAL");
|
|
6013
|
+
this.init();
|
|
6014
|
+
}
|
|
6015
|
+
init() {
|
|
6016
|
+
this.db.exec(`
|
|
6017
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
6018
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6019
|
+
key TEXT UNIQUE NOT NULL,
|
|
6020
|
+
client_name TEXT NOT NULL,
|
|
6021
|
+
credits REAL NOT NULL DEFAULT 0,
|
|
6022
|
+
total_used INTEGER NOT NULL DEFAULT 0,
|
|
6023
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
6024
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
6025
|
+
last_used_at TEXT
|
|
6026
|
+
);
|
|
6027
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(key);
|
|
6028
|
+
`);
|
|
6029
|
+
this.loadCache();
|
|
6030
|
+
}
|
|
6031
|
+
loadCache() {
|
|
6032
|
+
const rows = this.db.prepare("SELECT * FROM api_keys").all();
|
|
6033
|
+
for (const r of rows) this.cache.set(r.key, r);
|
|
6034
|
+
this.cacheLoaded = true;
|
|
6035
|
+
log5.info(`\u5DF2\u52A0\u8F7D ${rows.length} \u4E2A API Key`);
|
|
6036
|
+
}
|
|
6037
|
+
/** 查找 Key — 缓存命中或直接查 DB */
|
|
6038
|
+
lookup(key) {
|
|
6039
|
+
if (!this.cacheLoaded) this.loadCache();
|
|
6040
|
+
const cached = this.cache.get(key);
|
|
6041
|
+
if (cached) return cached;
|
|
6042
|
+
const row = this.db.prepare("SELECT * FROM api_keys WHERE key = ?").get(key);
|
|
6043
|
+
if (row) {
|
|
6044
|
+
this.cache.set(key, row);
|
|
6045
|
+
return row;
|
|
6046
|
+
}
|
|
6047
|
+
return null;
|
|
6048
|
+
}
|
|
6049
|
+
/** 校验并扣费 */
|
|
6050
|
+
deduct(key, riskLevel) {
|
|
6051
|
+
const record = this.lookup(key);
|
|
6052
|
+
if (!record) return { ok: false, error: "\u65E0\u6548\u7684 API Key" };
|
|
6053
|
+
if (!record.enabled) return { ok: false, error: "API Key \u5DF2\u7981\u7528" };
|
|
6054
|
+
const cost = CREDIT_COST[riskLevel] || 1;
|
|
6055
|
+
if (record.credits < cost) return { ok: false, error: `\u79EF\u5206\u4E0D\u8DB3 (\u9700\u8981 ${cost}\uFF0C\u5269\u4F59 ${record.credits})` };
|
|
6056
|
+
record.credits -= cost;
|
|
6057
|
+
record.totalUsed++;
|
|
6058
|
+
record.lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6059
|
+
this.db.prepare("UPDATE api_keys SET credits = ?, total_used = ?, last_used_at = ? WHERE id = ?").run(record.credits, record.totalUsed, record.lastUsedAt, record.id);
|
|
6060
|
+
return { ok: true, remaining: record.credits };
|
|
6061
|
+
}
|
|
6062
|
+
/** 管理:创建 Key */
|
|
6063
|
+
createKey(clientName, initialCredits = 1e3) {
|
|
6064
|
+
const key = "hvip-" + Array.from(
|
|
6065
|
+
{ length: 32 },
|
|
6066
|
+
() => "abcdefghijklmnopqrstuvwxyz0123456789"[Math.floor(Math.random() * 36)]
|
|
6067
|
+
).join("");
|
|
6068
|
+
const result = this.db.prepare(
|
|
6069
|
+
"INSERT INTO api_keys (key, client_name, credits) VALUES (?, ?, ?)"
|
|
6070
|
+
).run(key, clientName, initialCredits);
|
|
6071
|
+
const record = {
|
|
6072
|
+
id: result.lastInsertRowid,
|
|
6073
|
+
key,
|
|
6074
|
+
clientName,
|
|
6075
|
+
credits: initialCredits,
|
|
6076
|
+
totalUsed: 0,
|
|
6077
|
+
enabled: true,
|
|
6078
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6079
|
+
lastUsedAt: null
|
|
6080
|
+
};
|
|
6081
|
+
this.cache.set(key, record);
|
|
6082
|
+
return record;
|
|
6083
|
+
}
|
|
6084
|
+
/** 管理:充值 */
|
|
6085
|
+
topUp(key, amount) {
|
|
6086
|
+
const record = this.lookup(key);
|
|
6087
|
+
if (!record) return { ok: false, error: "Key \u4E0D\u5B58\u5728" };
|
|
6088
|
+
if (amount <= 0) return { ok: false, error: "\u5145\u503C\u91D1\u989D\u5FC5\u987B > 0" };
|
|
6089
|
+
record.credits += amount;
|
|
6090
|
+
this.db.prepare("UPDATE api_keys SET credits = ? WHERE id = ?").run(record.credits, record.id);
|
|
6091
|
+
return { ok: true, newBalance: record.credits };
|
|
6092
|
+
}
|
|
6093
|
+
/** 管理:启用/禁用 Key */
|
|
6094
|
+
setEnabled(key, enabled) {
|
|
6095
|
+
const record = this.lookup(key);
|
|
6096
|
+
if (!record) return false;
|
|
6097
|
+
record.enabled = enabled;
|
|
6098
|
+
this.db.prepare("UPDATE api_keys SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, record.id);
|
|
6099
|
+
return true;
|
|
6100
|
+
}
|
|
6101
|
+
/** 管理:列出所有 Key (含 Key 前缀,完整 Key 仅创建时返回) */
|
|
6102
|
+
listAll() {
|
|
6103
|
+
return [...this.cache.values()].map((r) => ({
|
|
6104
|
+
id: r.id,
|
|
6105
|
+
clientName: r.clientName,
|
|
6106
|
+
credits: r.credits,
|
|
6107
|
+
totalUsed: r.totalUsed,
|
|
6108
|
+
enabled: r.enabled,
|
|
6109
|
+
createdAt: r.createdAt,
|
|
6110
|
+
lastUsedAt: r.lastUsedAt,
|
|
6111
|
+
keyPrefix: r.key.slice(0, 12) + "..." + r.key.slice(-8)
|
|
6112
|
+
}));
|
|
6113
|
+
}
|
|
6114
|
+
/** 客户端:查询余额 */
|
|
6115
|
+
getBalance(key) {
|
|
6116
|
+
const record = this.lookup(key);
|
|
6117
|
+
if (!record) return null;
|
|
6118
|
+
return { clientName: record.clientName, credits: record.credits, totalUsed: record.totalUsed };
|
|
6119
|
+
}
|
|
6120
|
+
close() {
|
|
6121
|
+
try {
|
|
6122
|
+
this.db.close();
|
|
6123
|
+
} catch {
|
|
6124
|
+
}
|
|
6125
|
+
}
|
|
6126
|
+
};
|
|
6127
|
+
var instance = null;
|
|
6128
|
+
function getApiCredits(dbPath2) {
|
|
6129
|
+
if (!instance) instance = new ApiCredits(dbPath2);
|
|
6130
|
+
return instance;
|
|
6131
|
+
}
|
|
6132
|
+
|
|
5937
6133
|
// src/hub-server.ts
|
|
5938
|
-
var envPath = (0,
|
|
5939
|
-
if ((0,
|
|
5940
|
-
(0,
|
|
6134
|
+
var envPath = (0, import_node_path3.join)(process.cwd(), ".env");
|
|
6135
|
+
if ((0, import_node_fs3.existsSync)(envPath)) {
|
|
6136
|
+
(0, import_node_fs3.readFileSync)(envPath, "utf-8").split(/\r?\n/).forEach((line) => {
|
|
5941
6137
|
const hashIdx = line.indexOf("#");
|
|
5942
6138
|
if (hashIdx >= 0) line = line.substring(0, hashIdx);
|
|
5943
6139
|
const m = line.match(/^\s*([^#\s=]+)\s*=\s*(.*)$/);
|
|
@@ -5951,11 +6147,11 @@ if ((0, import_node_fs2.existsSync)(envPath)) {
|
|
|
5951
6147
|
}
|
|
5952
6148
|
});
|
|
5953
6149
|
}
|
|
5954
|
-
var VERSION = "0.
|
|
6150
|
+
var VERSION = "0.6.1";
|
|
5955
6151
|
function getDashboardHtml(host2, port) {
|
|
5956
|
-
const paths = [(0,
|
|
6152
|
+
const paths = [(0, import_node_path3.join)(__dirname, "web", "dashboard.html"), (0, import_node_path3.join)(__dirname, "..", "src", "web", "dashboard.html")];
|
|
5957
6153
|
for (const p of paths) {
|
|
5958
|
-
if ((0,
|
|
6154
|
+
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);
|
|
5959
6155
|
}
|
|
5960
6156
|
return "<html><body><h2>dashboard.html not found</h2></body></html>";
|
|
5961
6157
|
}
|
|
@@ -5970,7 +6166,7 @@ var wsPort = parseInt(flag("port") || process.env.HUB_PORT || "9321", 10);
|
|
|
5970
6166
|
var host = flag("host") || process.env.HUB_HOST || "127.0.0.1";
|
|
5971
6167
|
var webPort = parseInt(flag("web-port") || process.env.HUB_WEB_PORT || "3000", 10);
|
|
5972
6168
|
var dbPath = flag("db") || process.env.HUB_DB_PATH || ".hub/hub.db";
|
|
5973
|
-
var
|
|
6169
|
+
var log6 = logger("Hub");
|
|
5974
6170
|
var anthropicKey = process.env.ANTHROPIC_API_KEY || "";
|
|
5975
6171
|
var openaiKey = process.env.OPENAI_API_KEY || "";
|
|
5976
6172
|
var openaiBase = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
|
|
@@ -5986,7 +6182,7 @@ var MAX_CONCURRENT_WORKERS = 3;
|
|
|
5986
6182
|
function spawnWorker(taskId) {
|
|
5987
6183
|
const activeWorkers = workers.filter((w) => w.exitCode === null && !w.killed).length;
|
|
5988
6184
|
if (activeWorkers >= MAX_CONCURRENT_WORKERS) {
|
|
5989
|
-
|
|
6185
|
+
log6.warn(`Worker \u5E76\u53D1\u4E0A\u9650 (${MAX_CONCURRENT_WORKERS})\uFF0C${taskId} \u7B49\u5F85\u4E0B\u6B21\u8C03\u5EA6`);
|
|
5990
6186
|
return { ok: false, error: `\u5E76\u53D1\u4E0A\u9650 ${MAX_CONCURRENT_WORKERS}\uFF0C\u5F53\u524D ${activeWorkers} \u6D3B\u8DC3` };
|
|
5991
6187
|
}
|
|
5992
6188
|
const hubUrl = `ws://127.0.0.1:${wsPort}`;
|
|
@@ -6000,23 +6196,23 @@ function spawnWorker(taskId) {
|
|
|
6000
6196
|
promptB64 = Buffer.from(p, "utf-8").toString("base64");
|
|
6001
6197
|
}
|
|
6002
6198
|
}
|
|
6003
|
-
const workerArgs = ["dist/hub-worker.js", "--task", taskId, "--hub", hubUrl, "--repo", repoPath, "--web-port", String(webPort)];
|
|
6199
|
+
const workerArgs = ["dist/hub-worker-v2.js", "--task", taskId, "--hub", hubUrl, "--repo", repoPath, "--web-port", String(webPort)];
|
|
6004
6200
|
if (promptB64) workerArgs.push("--prompt-b64", promptB64);
|
|
6005
6201
|
try {
|
|
6006
6202
|
const worker = (0, import_node_child_process.spawn)("node", workerArgs, { cwd: repoPath, stdio: "pipe", detached: true, windowsHide: true });
|
|
6007
6203
|
worker.stdout?.on("data", (d) => process.stderr.write(`[Worker-${taskId}] ${d}`));
|
|
6008
6204
|
worker.stderr?.on("data", (d) => process.stderr.write(`[Worker-${taskId}] ${d}`));
|
|
6009
|
-
worker.on("error", (e) =>
|
|
6205
|
+
worker.on("error", (e) => log6.error(`Worker \u542F\u52A8\u5931\u8D25: ${e.message}`));
|
|
6010
6206
|
worker.on("close", (code) => {
|
|
6011
|
-
|
|
6207
|
+
log6.info(`Worker-${taskId} \u9000\u51FA (${code})`);
|
|
6012
6208
|
const idx = workers.indexOf(worker);
|
|
6013
6209
|
if (idx >= 0) workers.splice(idx, 1);
|
|
6014
6210
|
});
|
|
6015
6211
|
workers.push(worker);
|
|
6016
|
-
|
|
6212
|
+
log6.info(`\u6D3B\u8DC3 Worker: ${workers.length}`);
|
|
6017
6213
|
return { ok: true, workerPid: worker.pid };
|
|
6018
6214
|
} catch (e) {
|
|
6019
|
-
|
|
6215
|
+
log6.error(`Worker \u542F\u52A8\u5F02\u5E38: ${e.message}`);
|
|
6020
6216
|
return { ok: false, error: e.message };
|
|
6021
6217
|
}
|
|
6022
6218
|
}
|
|
@@ -6049,21 +6245,21 @@ function startHttpServer() {
|
|
|
6049
6245
|
return;
|
|
6050
6246
|
}
|
|
6051
6247
|
if (_req.method === "GET" && _req.url?.startsWith("/v2/")) {
|
|
6052
|
-
const v2Dir = (0,
|
|
6248
|
+
const v2Dir = (0, import_node_path3.join)(__dirname, "..", "dashboard-v2", "dist");
|
|
6053
6249
|
let filePath;
|
|
6054
6250
|
if (_req.url === "/v2/") {
|
|
6055
|
-
filePath = (0,
|
|
6251
|
+
filePath = (0, import_node_path3.join)(v2Dir, "index.html");
|
|
6056
6252
|
} else {
|
|
6057
|
-
filePath = (0,
|
|
6253
|
+
filePath = (0, import_node_path3.join)(v2Dir, _req.url.replace(/^\/v2\//, ""));
|
|
6058
6254
|
}
|
|
6059
|
-
if (!(0,
|
|
6060
|
-
filePath = (0,
|
|
6255
|
+
if (!(0, import_node_fs3.existsSync)(filePath) || !filePath.includes(".")) {
|
|
6256
|
+
filePath = (0, import_node_path3.join)(v2Dir, "index.html");
|
|
6061
6257
|
}
|
|
6062
|
-
if ((0,
|
|
6258
|
+
if ((0, import_node_fs3.existsSync)(filePath)) {
|
|
6063
6259
|
const ext = filePath.split(".").pop() || "html";
|
|
6064
6260
|
const mime = { html: "text/html; charset=utf-8", js: "application/javascript", css: "text/css", svg: "image/svg+xml", png: "image/png", ico: "image/x-icon" };
|
|
6065
6261
|
res.writeHead(200, { "Content-Type": mime[ext] || "application/octet-stream" });
|
|
6066
|
-
res.end((0,
|
|
6262
|
+
res.end((0, import_node_fs3.readFileSync)(filePath));
|
|
6067
6263
|
} else {
|
|
6068
6264
|
res.writeHead(404);
|
|
6069
6265
|
res.end("dashboard-v2 not found");
|
|
@@ -6071,11 +6267,11 @@ function startHttpServer() {
|
|
|
6071
6267
|
return;
|
|
6072
6268
|
}
|
|
6073
6269
|
if (_req.method === "GET" && _req.url === "/chat") {
|
|
6074
|
-
const paths = [(0,
|
|
6270
|
+
const paths = [(0, import_node_path3.join)(__dirname, "web", "index.html"), (0, import_node_path3.join)(__dirname, "..", "src", "web", "index.html")];
|
|
6075
6271
|
for (const p of paths) {
|
|
6076
|
-
if ((0,
|
|
6272
|
+
if ((0, import_node_fs3.existsSync)(p)) {
|
|
6077
6273
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
6078
|
-
res.end((0,
|
|
6274
|
+
res.end((0, import_node_fs3.readFileSync)(p, "utf-8"));
|
|
6079
6275
|
return;
|
|
6080
6276
|
}
|
|
6081
6277
|
}
|
|
@@ -6117,11 +6313,9 @@ function startHttpServer() {
|
|
|
6117
6313
|
}
|
|
6118
6314
|
agentHub.registerTask(taskId, title || taskId, promptB64 || void 0);
|
|
6119
6315
|
db?.saveTask({ taskId, status: "unassigned", title: title || taskId });
|
|
6120
|
-
|
|
6121
|
-
let workerPid = 0;
|
|
6122
|
-
log5.info(`\u65B0\u4EFB\u52A1: ${taskId} "${title}"${spawned ? " \u{1F916} \u5DF2\u62C9\u8D77" : ""}`);
|
|
6316
|
+
log6.info(`\u65B0\u4EFB\u52A1: ${taskId} "${title}"`);
|
|
6123
6317
|
res.writeHead(201, { "Content-Type": "application/json; charset=utf-8" });
|
|
6124
|
-
res.end(JSON.stringify({ ok: true, taskId, title
|
|
6318
|
+
res.end(JSON.stringify({ ok: true, taskId, title }));
|
|
6125
6319
|
} catch {
|
|
6126
6320
|
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
6127
6321
|
res.end(JSON.stringify({ error: "JSON \u89E3\u6790\u5931\u8D25" }));
|
|
@@ -6176,8 +6370,15 @@ function startHttpServer() {
|
|
|
6176
6370
|
res.end(JSON.stringify(entries));
|
|
6177
6371
|
return;
|
|
6178
6372
|
}
|
|
6179
|
-
if (_req.method === "GET" && _req.url === "/api/memory") {
|
|
6180
|
-
const
|
|
6373
|
+
if (_req.method === "GET" && (_req.url === "/api/memory" || _req.url?.startsWith("/api/memory?"))) {
|
|
6374
|
+
const url = _req.url.startsWith("/api/memory?") ? new import_node_url.URL(_req.url, `http://${host}:${webPort}`) : null;
|
|
6375
|
+
const type = url?.searchParams.get("type") || "";
|
|
6376
|
+
let entries;
|
|
6377
|
+
if (type && type !== "all" && ["memory", "doc", "directive", "skill", "strategy"].includes(type)) {
|
|
6378
|
+
entries = memory.byType(type, 30);
|
|
6379
|
+
} else {
|
|
6380
|
+
entries = memory.recent(30);
|
|
6381
|
+
}
|
|
6181
6382
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6182
6383
|
res.end(JSON.stringify(entries));
|
|
6183
6384
|
return;
|
|
@@ -6268,10 +6469,10 @@ function startHttpServer() {
|
|
|
6268
6469
|
return;
|
|
6269
6470
|
}
|
|
6270
6471
|
if (_req.method === "GET" && _req.url === "/api/traders") {
|
|
6271
|
-
const stateFile = (0,
|
|
6272
|
-
if ((0,
|
|
6472
|
+
const stateFile = (0, import_node_path3.join)(process.cwd(), ".hub", "trader-state.json");
|
|
6473
|
+
if ((0, import_node_fs3.existsSync)(stateFile)) {
|
|
6273
6474
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6274
|
-
res.end((0,
|
|
6475
|
+
res.end((0, import_node_fs3.readFileSync)(stateFile, "utf-8"));
|
|
6275
6476
|
} else {
|
|
6276
6477
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6277
6478
|
res.end(JSON.stringify({ traders: {}, history: [], round: 0 }));
|
|
@@ -6279,22 +6480,39 @@ function startHttpServer() {
|
|
|
6279
6480
|
return;
|
|
6280
6481
|
}
|
|
6281
6482
|
if (_req.method === "GET" && _req.url === "/api/traders/leaderboard") {
|
|
6282
|
-
const stateFile = (0,
|
|
6283
|
-
if ((0,
|
|
6284
|
-
const state = JSON.parse((0,
|
|
6285
|
-
const rankings = Object.values(state.traders || {}).map((t) =>
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6483
|
+
const stateFile = (0, import_node_path3.join)(process.cwd(), ".hub", "trader-state.json");
|
|
6484
|
+
if ((0, import_node_fs3.existsSync)(stateFile)) {
|
|
6485
|
+
const state = JSON.parse((0, import_node_fs3.readFileSync)(stateFile, "utf-8"));
|
|
6486
|
+
const rankings = Object.values(state.traders || {}).map((t) => {
|
|
6487
|
+
const positions = (t.openPositions || []).filter((p) => !p.closed);
|
|
6488
|
+
const initialCapital = 1e4;
|
|
6489
|
+
return {
|
|
6490
|
+
id: t.id,
|
|
6491
|
+
name: t.name,
|
|
6492
|
+
emoji: t.emoji,
|
|
6493
|
+
capital: t.capital,
|
|
6494
|
+
equity: t.equity || t.capital,
|
|
6495
|
+
totalPnl: t.totalPnl,
|
|
6496
|
+
totalPnlPct: t.totalPnlPct,
|
|
6497
|
+
unrealizedPnl: t.unrealizedPnl || 0,
|
|
6498
|
+
tradeCount: t.tradeCount || 0,
|
|
6499
|
+
winCount: t.winCount || 0,
|
|
6500
|
+
totalFees: t.totalFees || 0,
|
|
6501
|
+
totalFunding: t.totalFunding || 0,
|
|
6502
|
+
openPositions: positions.length,
|
|
6503
|
+
positions: positions.map((p) => ({
|
|
6504
|
+
symbol: p.symbol,
|
|
6505
|
+
direction: p.direction,
|
|
6506
|
+
leverage: p.leverage,
|
|
6507
|
+
entryPrice: p.entryPrice,
|
|
6508
|
+
currentPrice: p.currentPrice,
|
|
6509
|
+
margin: p.margin,
|
|
6510
|
+
liquidationPrice: p.liquidationPrice,
|
|
6511
|
+
unrealizedPnl: p.unrealizedPnl,
|
|
6512
|
+
unrealizedPnlPct: p.unrealizedPnlPct
|
|
6513
|
+
}))
|
|
6514
|
+
};
|
|
6515
|
+
}).sort((a, b) => b.totalPnlPct - a.totalPnlPct);
|
|
6298
6516
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6299
6517
|
res.end(JSON.stringify(rankings));
|
|
6300
6518
|
} else {
|
|
@@ -6303,11 +6521,48 @@ function startHttpServer() {
|
|
|
6303
6521
|
}
|
|
6304
6522
|
return;
|
|
6305
6523
|
}
|
|
6524
|
+
if (_req.method === "GET" && _req.url?.startsWith("/api/traders/history")) {
|
|
6525
|
+
const url = new import_node_url.URL(_req.url, `http://${host}:${webPort}`);
|
|
6526
|
+
const traderId = url.searchParams.get("trader") || "";
|
|
6527
|
+
const limit = parseInt(url.searchParams.get("limit") || "50");
|
|
6528
|
+
const histFile = (0, import_node_path3.join)(process.cwd(), ".hub", "trade-history.jsonl");
|
|
6529
|
+
if ((0, import_node_fs3.existsSync)(histFile)) {
|
|
6530
|
+
try {
|
|
6531
|
+
const lines = (0, import_node_fs3.readFileSync)(histFile, "utf-8").trim().split("\n");
|
|
6532
|
+
let entries = lines.map((l) => {
|
|
6533
|
+
try {
|
|
6534
|
+
return JSON.parse(l);
|
|
6535
|
+
} catch {
|
|
6536
|
+
return null;
|
|
6537
|
+
}
|
|
6538
|
+
}).filter(Boolean);
|
|
6539
|
+
if (traderId) entries = entries.filter((e) => e.traderId === traderId);
|
|
6540
|
+
entries = entries.slice(-limit);
|
|
6541
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6542
|
+
res.end(JSON.stringify(entries));
|
|
6543
|
+
} catch {
|
|
6544
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6545
|
+
res.end(JSON.stringify([]));
|
|
6546
|
+
}
|
|
6547
|
+
} else {
|
|
6548
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6549
|
+
res.end(JSON.stringify([]));
|
|
6550
|
+
}
|
|
6551
|
+
return;
|
|
6552
|
+
}
|
|
6553
|
+
if (_req.method === "GET" && _req.url === "/api/traders/risk") {
|
|
6554
|
+
const mode = process.env.AI_TRADER_MODE || "simulate";
|
|
6555
|
+
const maxOrder = parseFloat(process.env.TRADER_MAX_ORDER_USD || "100");
|
|
6556
|
+
const dailyLimit = parseFloat(process.env.TRADER_DAILY_LOSS_LIMIT || "500");
|
|
6557
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6558
|
+
res.end(JSON.stringify({ mode, maxOrderUsd: maxOrder, dailyLossLimit: dailyLimit }));
|
|
6559
|
+
return;
|
|
6560
|
+
}
|
|
6306
6561
|
if (_req.method === "GET" && _req.url === "/api/signals") {
|
|
6307
|
-
const sigFile = (0,
|
|
6308
|
-
if ((0,
|
|
6562
|
+
const sigFile = (0, import_node_path3.join)(process.cwd(), ".hub", "signals.json");
|
|
6563
|
+
if ((0, import_node_fs3.existsSync)(sigFile)) {
|
|
6309
6564
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6310
|
-
res.end((0,
|
|
6565
|
+
res.end((0, import_node_fs3.readFileSync)(sigFile, "utf-8"));
|
|
6311
6566
|
} else {
|
|
6312
6567
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6313
6568
|
res.end(JSON.stringify({ signals: [], lastRun: null }));
|
|
@@ -6392,6 +6647,86 @@ function startHttpServer() {
|
|
|
6392
6647
|
res.end(JSON.stringify({ circuits, openCount, healthy: openCount === 0 }));
|
|
6393
6648
|
return;
|
|
6394
6649
|
}
|
|
6650
|
+
const apiCredits = getApiCredits();
|
|
6651
|
+
if (_req.method === "GET" && _req.url === "/api/credits/balance") {
|
|
6652
|
+
const key = _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") || "";
|
|
6653
|
+
const balance = apiCredits.getBalance(key);
|
|
6654
|
+
if (!balance) {
|
|
6655
|
+
res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
|
|
6656
|
+
res.end(JSON.stringify({ error: "\u65E0\u6548\u7684 API Key" }));
|
|
6657
|
+
} else {
|
|
6658
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6659
|
+
res.end(JSON.stringify(balance));
|
|
6660
|
+
}
|
|
6661
|
+
return;
|
|
6662
|
+
}
|
|
6663
|
+
if (_req.method === "GET" && _req.url === "/api/admin/keys") {
|
|
6664
|
+
if (token && _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") !== token) {
|
|
6665
|
+
res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
|
|
6666
|
+
res.end(JSON.stringify({ error: "\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650" }));
|
|
6667
|
+
return;
|
|
6668
|
+
}
|
|
6669
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6670
|
+
res.end(JSON.stringify(apiCredits.listAll()));
|
|
6671
|
+
return;
|
|
6672
|
+
}
|
|
6673
|
+
if (_req.method === "POST" && _req.url === "/api/admin/keys") {
|
|
6674
|
+
if (token && _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") !== token) {
|
|
6675
|
+
res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
|
|
6676
|
+
res.end(JSON.stringify({ error: "\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650" }));
|
|
6677
|
+
return;
|
|
6678
|
+
}
|
|
6679
|
+
const chunks = [];
|
|
6680
|
+
_req.on("data", (c) => chunks.push(c));
|
|
6681
|
+
_req.on("end", () => {
|
|
6682
|
+
try {
|
|
6683
|
+
const { clientName, initialCredits } = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
|
|
6684
|
+
if (!clientName) {
|
|
6685
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
6686
|
+
res.end(JSON.stringify({ error: "\u7F3A\u5C11 clientName" }));
|
|
6687
|
+
return;
|
|
6688
|
+
}
|
|
6689
|
+
const record = apiCredits.createKey(clientName, initialCredits || 1e3);
|
|
6690
|
+
res.writeHead(201, { "Content-Type": "application/json; charset=utf-8" });
|
|
6691
|
+
res.end(JSON.stringify(record));
|
|
6692
|
+
} catch {
|
|
6693
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
6694
|
+
res.end(JSON.stringify({ error: "JSON \u89E3\u6790\u5931\u8D25" }));
|
|
6695
|
+
}
|
|
6696
|
+
});
|
|
6697
|
+
return;
|
|
6698
|
+
}
|
|
6699
|
+
if (_req.method === "POST" && _req.url === "/api/admin/keys/topup") {
|
|
6700
|
+
if (token && _req.headers["authorization"]?.replace(/^Bearer\s+/i, "") !== token) {
|
|
6701
|
+
res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
|
|
6702
|
+
res.end(JSON.stringify({ error: "\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650" }));
|
|
6703
|
+
return;
|
|
6704
|
+
}
|
|
6705
|
+
const chunks = [];
|
|
6706
|
+
_req.on("data", (c) => chunks.push(c));
|
|
6707
|
+
_req.on("end", () => {
|
|
6708
|
+
try {
|
|
6709
|
+
const { key, amount } = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
|
|
6710
|
+
if (!key || !amount) {
|
|
6711
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
6712
|
+
res.end(JSON.stringify({ error: "\u7F3A\u5C11 key \u6216 amount" }));
|
|
6713
|
+
return;
|
|
6714
|
+
}
|
|
6715
|
+
const result = apiCredits.topUp(key, amount);
|
|
6716
|
+
if (!result.ok) {
|
|
6717
|
+
res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
|
|
6718
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
6719
|
+
return;
|
|
6720
|
+
}
|
|
6721
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6722
|
+
res.end(JSON.stringify({ ok: true, newBalance: result.newBalance }));
|
|
6723
|
+
} catch {
|
|
6724
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
6725
|
+
res.end(JSON.stringify({ error: "JSON \u89E3\u6790\u5931\u8D25" }));
|
|
6726
|
+
}
|
|
6727
|
+
});
|
|
6728
|
+
return;
|
|
6729
|
+
}
|
|
6395
6730
|
if (_req.method === "POST" && _req.url === "/mcp") {
|
|
6396
6731
|
const chunks = [];
|
|
6397
6732
|
_req.on("data", (c) => chunks.push(c));
|
|
@@ -6422,6 +6757,7 @@ function startHttpServer() {
|
|
|
6422
6757
|
}
|
|
6423
6758
|
result = JSON.stringify(parsed);
|
|
6424
6759
|
} catch {
|
|
6760
|
+
log6.warn("MCP \u54CD\u5E94\u683C\u5F0F\u89E3\u6790\u5931\u8D25");
|
|
6425
6761
|
}
|
|
6426
6762
|
}
|
|
6427
6763
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
@@ -6516,8 +6852,15 @@ function startHttpServer() {
|
|
|
6516
6852
|
res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
|
|
6517
6853
|
res.end(JSON.stringify({ error: "Not Found" }));
|
|
6518
6854
|
});
|
|
6855
|
+
httpServer.on("error", (err) => {
|
|
6856
|
+
if (err.code === "EADDRINUSE") {
|
|
6857
|
+
log6.error(`\u7AEF\u53E3 ${webPort} \u88AB\u5360\u7528\uFF0C\u5C1D\u8BD5 ${webPort + 1}`);
|
|
6858
|
+
} else {
|
|
6859
|
+
log6.error(`HTTP Server \u9519\u8BEF: ${err.message}`);
|
|
6860
|
+
}
|
|
6861
|
+
});
|
|
6519
6862
|
httpServer.listen(webPort, host, () => {
|
|
6520
|
-
|
|
6863
|
+
log6.info(`\u{1F310} \u4EEA\u8868\u76D8 \u2192 http://${host}:${webPort}`);
|
|
6521
6864
|
});
|
|
6522
6865
|
}
|
|
6523
6866
|
var schedules = [
|
|
@@ -6565,6 +6908,17 @@ var schedules = [
|
|
|
6565
6908
|
failCount: 0,
|
|
6566
6909
|
timer: null
|
|
6567
6910
|
},
|
|
6911
|
+
{
|
|
6912
|
+
id: "sched-signals",
|
|
6913
|
+
name: "\u{1F6A6} VBT\u4FE1\u53F7\u5237\u65B0",
|
|
6914
|
+
interval: 4 * 36e5,
|
|
6915
|
+
template: "vbt-signal-refresh",
|
|
6916
|
+
params: { script: "scripts/refresh-signals.mjs" },
|
|
6917
|
+
lastRun: 0,
|
|
6918
|
+
count: 0,
|
|
6919
|
+
failCount: 0,
|
|
6920
|
+
timer: null
|
|
6921
|
+
},
|
|
6568
6922
|
// ════════ 24x7 守护岗 ════════
|
|
6569
6923
|
{
|
|
6570
6924
|
id: "guard-dashboard",
|
|
@@ -6650,7 +7004,7 @@ function runScheduledJob(job) {
|
|
|
6650
7004
|
(t) => t.status === "assigned"
|
|
6651
7005
|
).filter((t) => t.taskId.startsWith(job.id + "-"));
|
|
6652
7006
|
if (prevTasks.length > 0) {
|
|
6653
|
-
|
|
7007
|
+
log6.warn(`Scheduler: \u23ED\uFE0F ${job.name} \u4E0A\u6B21\u4EFB\u52A1\u672A\u5B8C\u6210 (${prevTasks.map((t) => t.taskId).join(",")})\uFF0C\u8DF3\u8FC7`);
|
|
6654
7008
|
scheduleNext(job);
|
|
6655
7009
|
return;
|
|
6656
7010
|
}
|
|
@@ -6659,17 +7013,33 @@ function runScheduledJob(job) {
|
|
|
6659
7013
|
).filter((t) => t.taskId.startsWith(job.id + "-"));
|
|
6660
7014
|
for (const o of orphans) {
|
|
6661
7015
|
db?.saveTask({ taskId: o.taskId, status: "done", result: "[Scheduler] \u81EA\u52A8\u6E05\u7406\u5B64\u513F\u4EFB\u52A1" });
|
|
6662
|
-
|
|
7016
|
+
log6.warn(`Scheduler: \u{1F9F9} \u6E05\u7406\u5B64\u513F: ${o.taskId}`);
|
|
6663
7017
|
}
|
|
6664
7018
|
const taskId = `${job.id}-${Date.now().toString(36)}`;
|
|
6665
7019
|
const title = `[\u5B9A\u65F6] ${job.name} #${job.count + 1}`;
|
|
6666
|
-
|
|
7020
|
+
if (job.template === "vbt-signal-refresh") {
|
|
7021
|
+
log6.info(`Scheduler: \u{1F504} ${job.name}`);
|
|
7022
|
+
try {
|
|
7023
|
+
const { execSync: execSync2 } = require("node:child_process");
|
|
7024
|
+
const out = execSync2(`node scripts/refresh-signals.mjs`, { cwd: process.cwd(), encoding: "utf-8", timeout: 6e4, windowsHide: true, maxBuffer: 2e5 });
|
|
7025
|
+
log6.info(`Scheduler: ${job.name} \u5B8C\u6210 \u2014 ${out.trim().split("\n").length} \u884C\u8F93\u51FA`);
|
|
7026
|
+
} catch (e) {
|
|
7027
|
+
log6.error(`Scheduler: ${job.name} \u5931\u8D25: ${e.message}`);
|
|
7028
|
+
job.failCount++;
|
|
7029
|
+
}
|
|
7030
|
+
job.count++;
|
|
7031
|
+
job.lastRun = Date.now();
|
|
7032
|
+
scheduleNext(job);
|
|
7033
|
+
return;
|
|
7034
|
+
}
|
|
7035
|
+
log6.info(`Scheduler: \u23F0 ${job.name} \u2192 ${taskId}`);
|
|
6667
7036
|
const tpl = TASK_TEMPLATES.find((t) => t.id === job.template);
|
|
6668
7037
|
let promptB64 = "";
|
|
6669
7038
|
if (tpl) {
|
|
6670
7039
|
try {
|
|
6671
7040
|
promptB64 = Buffer.from(tpl.buildPrompt(job.params), "utf-8").toString("base64");
|
|
6672
7041
|
} catch {
|
|
7042
|
+
log6.warn(`promptB64 \u6784\u5EFA\u5931\u8D25: ${job.taskId}`);
|
|
6673
7043
|
}
|
|
6674
7044
|
}
|
|
6675
7045
|
taskMeta.set(taskId, { templateId: job.template, params: job.params });
|
|
@@ -6686,7 +7056,7 @@ function scheduleNext(job) {
|
|
|
6686
7056
|
}
|
|
6687
7057
|
function startScheduler() {
|
|
6688
7058
|
for (const job of schedules) scheduleNext(job);
|
|
6689
|
-
|
|
7059
|
+
log6.info(`Scheduler: ${schedules.length} \u4E2A\u5B9A\u65F6\u4EFB\u52A1 (\u9996\u6B2130s\u540E\u89E6\u53D1\uFF0CChronos \u2192 V2 Worker \u96F6\u5F39\u7A97)`);
|
|
6690
7060
|
}
|
|
6691
7061
|
var banner = [
|
|
6692
7062
|
`\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`,
|
|
@@ -6701,38 +7071,47 @@ var db = new HubDB(dbPath);
|
|
|
6701
7071
|
if (db.open()) {
|
|
6702
7072
|
agentHub.setDB(db);
|
|
6703
7073
|
const stats = db.stats();
|
|
6704
|
-
|
|
7074
|
+
log6.info(`DB \u72B6\u6001: ${stats.taskCount} tasks, ${stats.messageCount} messages`);
|
|
6705
7075
|
}
|
|
6706
7076
|
var memoryPath = flag("memory-db") || process.env.HUB_MEMORY_DB || ".hub/memory.db";
|
|
6707
7077
|
var memory = new HubMemory(memoryPath);
|
|
6708
7078
|
var memOk = memory.open();
|
|
6709
7079
|
if (memOk) {
|
|
6710
7080
|
const ms = memory.stats();
|
|
6711
|
-
|
|
7081
|
+
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})`);
|
|
6712
7082
|
}
|
|
6713
7083
|
var registryPath = flag("registry-db") || process.env.HUB_REGISTRY_DB || ".hub/registry.db";
|
|
6714
7084
|
var registry = new HubRegistry(registryPath);
|
|
6715
7085
|
var regOk = registry.open();
|
|
6716
|
-
if (
|
|
6717
|
-
|
|
7086
|
+
if (regOk) {
|
|
7087
|
+
agentHub.registryCount = registry.all().length;
|
|
7088
|
+
} else {
|
|
7089
|
+
log6.warn(`\u26A0\uFE0F Registry \u6253\u5F00\u5931\u8D25\uFF0C\u5546\u5E97\u529F\u80FD\u4E0D\u53EF\u7528`);
|
|
6718
7090
|
}
|
|
6719
7091
|
var costsPath = flag("costs-db") || process.env.HUB_COSTS_DB || ".hub/llm-costs.jsonl";
|
|
6720
7092
|
var costTracker = new HubCosts(costsPath);
|
|
6721
7093
|
agentHub.setCostTracker(costTracker);
|
|
6722
|
-
|
|
7094
|
+
log6.info(`\u{1F4B0} \u6210\u672C\u8FFD\u8E2A: ${costsPath}`);
|
|
6723
7095
|
startHttpServer();
|
|
6724
7096
|
startScheduler();
|
|
6725
7097
|
var mcpProcess = null;
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
}
|
|
6731
|
-
|
|
7098
|
+
if (process.env.HUB_SPAWN_MCP === "1") {
|
|
7099
|
+
try {
|
|
7100
|
+
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" } });
|
|
7101
|
+
mcpProcess.stderr?.on("data", (d) => process.stderr.write(d));
|
|
7102
|
+
log6.info("\u{1F6E0} MCP Server \u542F\u52A8\u4E2D \u2192 http://127.0.0.1:9222");
|
|
7103
|
+
} catch {
|
|
7104
|
+
log6.warn("\u26A0\uFE0F MCP Server \u542F\u52A8\u5931\u8D25");
|
|
7105
|
+
}
|
|
7106
|
+
} else {
|
|
7107
|
+
log6.info("\u{1F6E0} MCP Server \u7531 PM2 \u72EC\u7ACB\u7BA1\u7406 (hvip-mcp :9222)\uFF0C\u8DF3\u8FC7\u5B50\u8FDB\u7A0B\u6D3E\u751F");
|
|
6732
7108
|
}
|
|
6733
7109
|
agentHub.start(wsPort, host, VERSION, token);
|
|
7110
|
+
setTimeout(() => {
|
|
7111
|
+
wsPort = agentHub.port || wsPort;
|
|
7112
|
+
}, 2e3);
|
|
6734
7113
|
function shutdown() {
|
|
6735
|
-
|
|
7114
|
+
log6.info("\u6B63\u5728\u5173\u95ED...");
|
|
6736
7115
|
for (const w of workers) {
|
|
6737
7116
|
try {
|
|
6738
7117
|
w.kill();
|