hvip-mcp-server 0.5.0 → 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 +402 -72
- package/dist/hub-worker.js +2 -2
- package/dist/index.js +312 -87
- package/package.json +76 -73
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
|
|
@@ -5096,8 +5103,8 @@ var CircuitBreaker = class {
|
|
|
5096
5103
|
var circuitBreaker = new CircuitBreaker({ failureThreshold: 3, cooldownMs: 6e4 });
|
|
5097
5104
|
|
|
5098
5105
|
// src/hub-server.ts
|
|
5099
|
-
var
|
|
5100
|
-
var
|
|
5106
|
+
var import_node_fs3 = require("node:fs");
|
|
5107
|
+
var import_node_path3 = require("node:path");
|
|
5101
5108
|
|
|
5102
5109
|
// src/adapters/hub-templates.ts
|
|
5103
5110
|
var TASK_TEMPLATES = [
|
|
@@ -5934,10 +5941,150 @@ NEXT: <\u540E\u7EED\u8DDF\u8FDB\u5EFA\u8BAE>
|
|
|
5934
5941
|
}
|
|
5935
5942
|
];
|
|
5936
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
|
+
|
|
5937
6084
|
// src/hub-server.ts
|
|
5938
|
-
var envPath = (0,
|
|
5939
|
-
if ((0,
|
|
5940
|
-
(0,
|
|
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) => {
|
|
5941
6088
|
const hashIdx = line.indexOf("#");
|
|
5942
6089
|
if (hashIdx >= 0) line = line.substring(0, hashIdx);
|
|
5943
6090
|
const m = line.match(/^\s*([^#\s=]+)\s*=\s*(.*)$/);
|
|
@@ -5951,11 +6098,11 @@ if ((0, import_node_fs2.existsSync)(envPath)) {
|
|
|
5951
6098
|
}
|
|
5952
6099
|
});
|
|
5953
6100
|
}
|
|
5954
|
-
var VERSION = "0.
|
|
6101
|
+
var VERSION = "0.6.0";
|
|
5955
6102
|
function getDashboardHtml(host2, port) {
|
|
5956
|
-
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")];
|
|
5957
6104
|
for (const p of paths) {
|
|
5958
|
-
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);
|
|
5959
6106
|
}
|
|
5960
6107
|
return "<html><body><h2>dashboard.html not found</h2></body></html>";
|
|
5961
6108
|
}
|
|
@@ -5970,7 +6117,7 @@ var wsPort = parseInt(flag("port") || process.env.HUB_PORT || "9321", 10);
|
|
|
5970
6117
|
var host = flag("host") || process.env.HUB_HOST || "127.0.0.1";
|
|
5971
6118
|
var webPort = parseInt(flag("web-port") || process.env.HUB_WEB_PORT || "3000", 10);
|
|
5972
6119
|
var dbPath = flag("db") || process.env.HUB_DB_PATH || ".hub/hub.db";
|
|
5973
|
-
var
|
|
6120
|
+
var log6 = logger("Hub");
|
|
5974
6121
|
var anthropicKey = process.env.ANTHROPIC_API_KEY || "";
|
|
5975
6122
|
var openaiKey = process.env.OPENAI_API_KEY || "";
|
|
5976
6123
|
var openaiBase = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
|
|
@@ -5986,7 +6133,7 @@ var MAX_CONCURRENT_WORKERS = 3;
|
|
|
5986
6133
|
function spawnWorker(taskId) {
|
|
5987
6134
|
const activeWorkers = workers.filter((w) => w.exitCode === null && !w.killed).length;
|
|
5988
6135
|
if (activeWorkers >= MAX_CONCURRENT_WORKERS) {
|
|
5989
|
-
|
|
6136
|
+
log6.warn(`Worker \u5E76\u53D1\u4E0A\u9650 (${MAX_CONCURRENT_WORKERS})\uFF0C${taskId} \u7B49\u5F85\u4E0B\u6B21\u8C03\u5EA6`);
|
|
5990
6137
|
return { ok: false, error: `\u5E76\u53D1\u4E0A\u9650 ${MAX_CONCURRENT_WORKERS}\uFF0C\u5F53\u524D ${activeWorkers} \u6D3B\u8DC3` };
|
|
5991
6138
|
}
|
|
5992
6139
|
const hubUrl = `ws://127.0.0.1:${wsPort}`;
|
|
@@ -6000,23 +6147,23 @@ function spawnWorker(taskId) {
|
|
|
6000
6147
|
promptB64 = Buffer.from(p, "utf-8").toString("base64");
|
|
6001
6148
|
}
|
|
6002
6149
|
}
|
|
6003
|
-
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)];
|
|
6004
6151
|
if (promptB64) workerArgs.push("--prompt-b64", promptB64);
|
|
6005
6152
|
try {
|
|
6006
6153
|
const worker = (0, import_node_child_process.spawn)("node", workerArgs, { cwd: repoPath, stdio: "pipe", detached: true, windowsHide: true });
|
|
6007
6154
|
worker.stdout?.on("data", (d) => process.stderr.write(`[Worker-${taskId}] ${d}`));
|
|
6008
6155
|
worker.stderr?.on("data", (d) => process.stderr.write(`[Worker-${taskId}] ${d}`));
|
|
6009
|
-
worker.on("error", (e) =>
|
|
6156
|
+
worker.on("error", (e) => log6.error(`Worker \u542F\u52A8\u5931\u8D25: ${e.message}`));
|
|
6010
6157
|
worker.on("close", (code) => {
|
|
6011
|
-
|
|
6158
|
+
log6.info(`Worker-${taskId} \u9000\u51FA (${code})`);
|
|
6012
6159
|
const idx = workers.indexOf(worker);
|
|
6013
6160
|
if (idx >= 0) workers.splice(idx, 1);
|
|
6014
6161
|
});
|
|
6015
6162
|
workers.push(worker);
|
|
6016
|
-
|
|
6163
|
+
log6.info(`\u6D3B\u8DC3 Worker: ${workers.length}`);
|
|
6017
6164
|
return { ok: true, workerPid: worker.pid };
|
|
6018
6165
|
} catch (e) {
|
|
6019
|
-
|
|
6166
|
+
log6.error(`Worker \u542F\u52A8\u5F02\u5E38: ${e.message}`);
|
|
6020
6167
|
return { ok: false, error: e.message };
|
|
6021
6168
|
}
|
|
6022
6169
|
}
|
|
@@ -6049,21 +6196,21 @@ function startHttpServer() {
|
|
|
6049
6196
|
return;
|
|
6050
6197
|
}
|
|
6051
6198
|
if (_req.method === "GET" && _req.url?.startsWith("/v2/")) {
|
|
6052
|
-
const v2Dir = (0,
|
|
6199
|
+
const v2Dir = (0, import_node_path3.join)(__dirname, "..", "dashboard-v2", "dist");
|
|
6053
6200
|
let filePath;
|
|
6054
6201
|
if (_req.url === "/v2/") {
|
|
6055
|
-
filePath = (0,
|
|
6202
|
+
filePath = (0, import_node_path3.join)(v2Dir, "index.html");
|
|
6056
6203
|
} else {
|
|
6057
|
-
filePath = (0,
|
|
6204
|
+
filePath = (0, import_node_path3.join)(v2Dir, _req.url.replace(/^\/v2\//, ""));
|
|
6058
6205
|
}
|
|
6059
|
-
if (!(0,
|
|
6060
|
-
filePath = (0,
|
|
6206
|
+
if (!(0, import_node_fs3.existsSync)(filePath) || !filePath.includes(".")) {
|
|
6207
|
+
filePath = (0, import_node_path3.join)(v2Dir, "index.html");
|
|
6061
6208
|
}
|
|
6062
|
-
if ((0,
|
|
6209
|
+
if ((0, import_node_fs3.existsSync)(filePath)) {
|
|
6063
6210
|
const ext = filePath.split(".").pop() || "html";
|
|
6064
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" };
|
|
6065
6212
|
res.writeHead(200, { "Content-Type": mime[ext] || "application/octet-stream" });
|
|
6066
|
-
res.end((0,
|
|
6213
|
+
res.end((0, import_node_fs3.readFileSync)(filePath));
|
|
6067
6214
|
} else {
|
|
6068
6215
|
res.writeHead(404);
|
|
6069
6216
|
res.end("dashboard-v2 not found");
|
|
@@ -6071,11 +6218,11 @@ function startHttpServer() {
|
|
|
6071
6218
|
return;
|
|
6072
6219
|
}
|
|
6073
6220
|
if (_req.method === "GET" && _req.url === "/chat") {
|
|
6074
|
-
const paths = [(0,
|
|
6221
|
+
const paths = [(0, import_node_path3.join)(__dirname, "web", "index.html"), (0, import_node_path3.join)(__dirname, "..", "src", "web", "index.html")];
|
|
6075
6222
|
for (const p of paths) {
|
|
6076
|
-
if ((0,
|
|
6223
|
+
if ((0, import_node_fs3.existsSync)(p)) {
|
|
6077
6224
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
6078
|
-
res.end((0,
|
|
6225
|
+
res.end((0, import_node_fs3.readFileSync)(p, "utf-8"));
|
|
6079
6226
|
return;
|
|
6080
6227
|
}
|
|
6081
6228
|
}
|
|
@@ -6117,11 +6264,9 @@ function startHttpServer() {
|
|
|
6117
6264
|
}
|
|
6118
6265
|
agentHub.registerTask(taskId, title || taskId, promptB64 || void 0);
|
|
6119
6266
|
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" : ""}`);
|
|
6267
|
+
log6.info(`\u65B0\u4EFB\u52A1: ${taskId} "${title}"`);
|
|
6123
6268
|
res.writeHead(201, { "Content-Type": "application/json; charset=utf-8" });
|
|
6124
|
-
res.end(JSON.stringify({ ok: true, taskId, title
|
|
6269
|
+
res.end(JSON.stringify({ ok: true, taskId, title }));
|
|
6125
6270
|
} catch {
|
|
6126
6271
|
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
6127
6272
|
res.end(JSON.stringify({ error: "JSON \u89E3\u6790\u5931\u8D25" }));
|
|
@@ -6176,8 +6321,15 @@ function startHttpServer() {
|
|
|
6176
6321
|
res.end(JSON.stringify(entries));
|
|
6177
6322
|
return;
|
|
6178
6323
|
}
|
|
6179
|
-
if (_req.method === "GET" && _req.url === "/api/memory") {
|
|
6180
|
-
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
|
+
}
|
|
6181
6333
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6182
6334
|
res.end(JSON.stringify(entries));
|
|
6183
6335
|
return;
|
|
@@ -6268,10 +6420,10 @@ function startHttpServer() {
|
|
|
6268
6420
|
return;
|
|
6269
6421
|
}
|
|
6270
6422
|
if (_req.method === "GET" && _req.url === "/api/traders") {
|
|
6271
|
-
const stateFile = (0,
|
|
6272
|
-
if ((0,
|
|
6423
|
+
const stateFile = (0, import_node_path3.join)(process.cwd(), ".hub", "trader-state.json");
|
|
6424
|
+
if ((0, import_node_fs3.existsSync)(stateFile)) {
|
|
6273
6425
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6274
|
-
res.end((0,
|
|
6426
|
+
res.end((0, import_node_fs3.readFileSync)(stateFile, "utf-8"));
|
|
6275
6427
|
} else {
|
|
6276
6428
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6277
6429
|
res.end(JSON.stringify({ traders: {}, history: [], round: 0 }));
|
|
@@ -6279,22 +6431,39 @@ function startHttpServer() {
|
|
|
6279
6431
|
return;
|
|
6280
6432
|
}
|
|
6281
6433
|
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
|
-
|
|
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);
|
|
6298
6467
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6299
6468
|
res.end(JSON.stringify(rankings));
|
|
6300
6469
|
} else {
|
|
@@ -6303,11 +6472,48 @@ function startHttpServer() {
|
|
|
6303
6472
|
}
|
|
6304
6473
|
return;
|
|
6305
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
|
+
}
|
|
6306
6512
|
if (_req.method === "GET" && _req.url === "/api/signals") {
|
|
6307
|
-
const sigFile = (0,
|
|
6308
|
-
if ((0,
|
|
6513
|
+
const sigFile = (0, import_node_path3.join)(process.cwd(), ".hub", "signals.json");
|
|
6514
|
+
if ((0, import_node_fs3.existsSync)(sigFile)) {
|
|
6309
6515
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6310
|
-
res.end((0,
|
|
6516
|
+
res.end((0, import_node_fs3.readFileSync)(sigFile, "utf-8"));
|
|
6311
6517
|
} else {
|
|
6312
6518
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
6313
6519
|
res.end(JSON.stringify({ signals: [], lastRun: null }));
|
|
@@ -6392,6 +6598,86 @@ function startHttpServer() {
|
|
|
6392
6598
|
res.end(JSON.stringify({ circuits, openCount, healthy: openCount === 0 }));
|
|
6393
6599
|
return;
|
|
6394
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
|
+
}
|
|
6395
6681
|
if (_req.method === "POST" && _req.url === "/mcp") {
|
|
6396
6682
|
const chunks = [];
|
|
6397
6683
|
_req.on("data", (c) => chunks.push(c));
|
|
@@ -6422,6 +6708,7 @@ function startHttpServer() {
|
|
|
6422
6708
|
}
|
|
6423
6709
|
result = JSON.stringify(parsed);
|
|
6424
6710
|
} catch {
|
|
6711
|
+
log6.warn("MCP \u54CD\u5E94\u683C\u5F0F\u89E3\u6790\u5931\u8D25");
|
|
6425
6712
|
}
|
|
6426
6713
|
}
|
|
6427
6714
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
@@ -6516,8 +6803,15 @@ function startHttpServer() {
|
|
|
6516
6803
|
res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
|
|
6517
6804
|
res.end(JSON.stringify({ error: "Not Found" }));
|
|
6518
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
|
+
});
|
|
6519
6813
|
httpServer.listen(webPort, host, () => {
|
|
6520
|
-
|
|
6814
|
+
log6.info(`\u{1F310} \u4EEA\u8868\u76D8 \u2192 http://${host}:${webPort}`);
|
|
6521
6815
|
});
|
|
6522
6816
|
}
|
|
6523
6817
|
var schedules = [
|
|
@@ -6565,6 +6859,17 @@ var schedules = [
|
|
|
6565
6859
|
failCount: 0,
|
|
6566
6860
|
timer: null
|
|
6567
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
|
+
},
|
|
6568
6873
|
// ════════ 24x7 守护岗 ════════
|
|
6569
6874
|
{
|
|
6570
6875
|
id: "guard-dashboard",
|
|
@@ -6650,7 +6955,7 @@ function runScheduledJob(job) {
|
|
|
6650
6955
|
(t) => t.status === "assigned"
|
|
6651
6956
|
).filter((t) => t.taskId.startsWith(job.id + "-"));
|
|
6652
6957
|
if (prevTasks.length > 0) {
|
|
6653
|
-
|
|
6958
|
+
log6.warn(`Scheduler: \u23ED\uFE0F ${job.name} \u4E0A\u6B21\u4EFB\u52A1\u672A\u5B8C\u6210 (${prevTasks.map((t) => t.taskId).join(",")})\uFF0C\u8DF3\u8FC7`);
|
|
6654
6959
|
scheduleNext(job);
|
|
6655
6960
|
return;
|
|
6656
6961
|
}
|
|
@@ -6659,17 +6964,33 @@ function runScheduledJob(job) {
|
|
|
6659
6964
|
).filter((t) => t.taskId.startsWith(job.id + "-"));
|
|
6660
6965
|
for (const o of orphans) {
|
|
6661
6966
|
db?.saveTask({ taskId: o.taskId, status: "done", result: "[Scheduler] \u81EA\u52A8\u6E05\u7406\u5B64\u513F\u4EFB\u52A1" });
|
|
6662
|
-
|
|
6967
|
+
log6.warn(`Scheduler: \u{1F9F9} \u6E05\u7406\u5B64\u513F: ${o.taskId}`);
|
|
6663
6968
|
}
|
|
6664
6969
|
const taskId = `${job.id}-${Date.now().toString(36)}`;
|
|
6665
6970
|
const title = `[\u5B9A\u65F6] ${job.name} #${job.count + 1}`;
|
|
6666
|
-
|
|
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
|
+
}
|
|
6981
|
+
job.count++;
|
|
6982
|
+
job.lastRun = Date.now();
|
|
6983
|
+
scheduleNext(job);
|
|
6984
|
+
return;
|
|
6985
|
+
}
|
|
6986
|
+
log6.info(`Scheduler: \u23F0 ${job.name} \u2192 ${taskId}`);
|
|
6667
6987
|
const tpl = TASK_TEMPLATES.find((t) => t.id === job.template);
|
|
6668
6988
|
let promptB64 = "";
|
|
6669
6989
|
if (tpl) {
|
|
6670
6990
|
try {
|
|
6671
6991
|
promptB64 = Buffer.from(tpl.buildPrompt(job.params), "utf-8").toString("base64");
|
|
6672
6992
|
} catch {
|
|
6993
|
+
log6.warn(`promptB64 \u6784\u5EFA\u5931\u8D25: ${job.taskId}`);
|
|
6673
6994
|
}
|
|
6674
6995
|
}
|
|
6675
6996
|
taskMeta.set(taskId, { templateId: job.template, params: job.params });
|
|
@@ -6686,7 +7007,7 @@ function scheduleNext(job) {
|
|
|
6686
7007
|
}
|
|
6687
7008
|
function startScheduler() {
|
|
6688
7009
|
for (const job of schedules) scheduleNext(job);
|
|
6689
|
-
|
|
7010
|
+
log6.info(`Scheduler: ${schedules.length} \u4E2A\u5B9A\u65F6\u4EFB\u52A1 (\u9996\u6B2130s\u540E\u89E6\u53D1\uFF0CChronos \u2192 V2 Worker \u96F6\u5F39\u7A97)`);
|
|
6690
7011
|
}
|
|
6691
7012
|
var banner = [
|
|
6692
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`,
|
|
@@ -6701,38 +7022,47 @@ var db = new HubDB(dbPath);
|
|
|
6701
7022
|
if (db.open()) {
|
|
6702
7023
|
agentHub.setDB(db);
|
|
6703
7024
|
const stats = db.stats();
|
|
6704
|
-
|
|
7025
|
+
log6.info(`DB \u72B6\u6001: ${stats.taskCount} tasks, ${stats.messageCount} messages`);
|
|
6705
7026
|
}
|
|
6706
7027
|
var memoryPath = flag("memory-db") || process.env.HUB_MEMORY_DB || ".hub/memory.db";
|
|
6707
7028
|
var memory = new HubMemory(memoryPath);
|
|
6708
7029
|
var memOk = memory.open();
|
|
6709
7030
|
if (memOk) {
|
|
6710
7031
|
const ms = memory.stats();
|
|
6711
|
-
|
|
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})`);
|
|
6712
7033
|
}
|
|
6713
7034
|
var registryPath = flag("registry-db") || process.env.HUB_REGISTRY_DB || ".hub/registry.db";
|
|
6714
7035
|
var registry = new HubRegistry(registryPath);
|
|
6715
7036
|
var regOk = registry.open();
|
|
6716
|
-
if (
|
|
6717
|
-
|
|
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`);
|
|
6718
7041
|
}
|
|
6719
7042
|
var costsPath = flag("costs-db") || process.env.HUB_COSTS_DB || ".hub/llm-costs.jsonl";
|
|
6720
7043
|
var costTracker = new HubCosts(costsPath);
|
|
6721
7044
|
agentHub.setCostTracker(costTracker);
|
|
6722
|
-
|
|
7045
|
+
log6.info(`\u{1F4B0} \u6210\u672C\u8FFD\u8E2A: ${costsPath}`);
|
|
6723
7046
|
startHttpServer();
|
|
6724
7047
|
startScheduler();
|
|
6725
7048
|
var mcpProcess = null;
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
}
|
|
6731
|
-
|
|
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");
|
|
6732
7059
|
}
|
|
6733
7060
|
agentHub.start(wsPort, host, VERSION, token);
|
|
7061
|
+
setTimeout(() => {
|
|
7062
|
+
wsPort = agentHub.port || wsPort;
|
|
7063
|
+
}, 2e3);
|
|
6734
7064
|
function shutdown() {
|
|
6735
|
-
|
|
7065
|
+
log6.info("\u6B63\u5728\u5173\u95ED...");
|
|
6736
7066
|
for (const w of workers) {
|
|
6737
7067
|
try {
|
|
6738
7068
|
w.kill();
|