@hirohsu/user-web-feedback 2.6.10 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +1718 -422
- package/dist/index.cjs +339 -2
- package/dist/static/settings.html +36 -0
- package/dist/static/settings.js +118 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -20661,6 +20661,7 @@ __export(database_exports, {
|
|
|
20661
20661
|
getPinnedPrompts: () => getPinnedPrompts,
|
|
20662
20662
|
getPromptById: () => getPromptById,
|
|
20663
20663
|
getRecentMCPServerErrors: () => getRecentMCPServerErrors,
|
|
20664
|
+
getSelfProbeSettings: () => getSelfProbeSettings,
|
|
20664
20665
|
getToolEnableConfigs: () => getToolEnableConfigs,
|
|
20665
20666
|
getUserPreferences: () => getUserPreferences,
|
|
20666
20667
|
initDatabase: () => initDatabase,
|
|
@@ -20676,6 +20677,7 @@ __export(database_exports, {
|
|
|
20676
20677
|
queryLogs: () => queryLogs,
|
|
20677
20678
|
queryMCPServerLogs: () => queryMCPServerLogs,
|
|
20678
20679
|
reorderPrompts: () => reorderPrompts,
|
|
20680
|
+
saveSelfProbeSettings: () => saveSelfProbeSettings,
|
|
20679
20681
|
setToolEnabled: () => setToolEnabled,
|
|
20680
20682
|
toggleMCPServerEnabled: () => toggleMCPServerEnabled,
|
|
20681
20683
|
togglePromptPin: () => togglePromptPin,
|
|
@@ -20974,6 +20976,14 @@ function createTables() {
|
|
|
20974
20976
|
}
|
|
20975
20977
|
} catch {
|
|
20976
20978
|
}
|
|
20979
|
+
db.exec(`
|
|
20980
|
+
CREATE TABLE IF NOT EXISTS self_probe_settings (
|
|
20981
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
20982
|
+
enabled INTEGER DEFAULT 0,
|
|
20983
|
+
interval_seconds INTEGER DEFAULT 300,
|
|
20984
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
20985
|
+
)
|
|
20986
|
+
`);
|
|
20977
20987
|
}
|
|
20978
20988
|
function initDefaultSettings() {
|
|
20979
20989
|
if (!db) throw new Error("Database not initialized");
|
|
@@ -22319,6 +22329,55 @@ function cleanupOldCLIExecutionLogs(daysToKeep = 7) {
|
|
|
22319
22329
|
`).run(cutoffDate.toISOString());
|
|
22320
22330
|
return result.changes;
|
|
22321
22331
|
}
|
|
22332
|
+
function getSelfProbeSettings() {
|
|
22333
|
+
const db2 = tryGetDb();
|
|
22334
|
+
if (!db2) return void 0;
|
|
22335
|
+
const row = db2.prepare(`
|
|
22336
|
+
SELECT
|
|
22337
|
+
id,
|
|
22338
|
+
enabled,
|
|
22339
|
+
interval_seconds as intervalSeconds,
|
|
22340
|
+
updated_at as updatedAt
|
|
22341
|
+
FROM self_probe_settings
|
|
22342
|
+
WHERE id = 1
|
|
22343
|
+
`).get();
|
|
22344
|
+
if (!row) return void 0;
|
|
22345
|
+
return {
|
|
22346
|
+
id: row.id,
|
|
22347
|
+
enabled: row.enabled === 1,
|
|
22348
|
+
intervalSeconds: row.intervalSeconds,
|
|
22349
|
+
updatedAt: row.updatedAt
|
|
22350
|
+
};
|
|
22351
|
+
}
|
|
22352
|
+
function saveSelfProbeSettings(settings) {
|
|
22353
|
+
const db2 = tryGetDb();
|
|
22354
|
+
if (!db2) throw new Error("Database not initialized");
|
|
22355
|
+
const existing = getSelfProbeSettings();
|
|
22356
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22357
|
+
if (existing) {
|
|
22358
|
+
db2.prepare(`
|
|
22359
|
+
UPDATE self_probe_settings
|
|
22360
|
+
SET enabled = COALESCE(?, enabled),
|
|
22361
|
+
interval_seconds = COALESCE(?, interval_seconds),
|
|
22362
|
+
updated_at = ?
|
|
22363
|
+
WHERE id = 1
|
|
22364
|
+
`).run(
|
|
22365
|
+
settings.enabled !== void 0 ? settings.enabled ? 1 : 0 : null,
|
|
22366
|
+
settings.intervalSeconds ?? null,
|
|
22367
|
+
now
|
|
22368
|
+
);
|
|
22369
|
+
} else {
|
|
22370
|
+
db2.prepare(`
|
|
22371
|
+
INSERT INTO self_probe_settings (id, enabled, interval_seconds, updated_at)
|
|
22372
|
+
VALUES (1, ?, ?, ?)
|
|
22373
|
+
`).run(
|
|
22374
|
+
settings.enabled ? 1 : 0,
|
|
22375
|
+
settings.intervalSeconds ?? 300,
|
|
22376
|
+
now
|
|
22377
|
+
);
|
|
22378
|
+
}
|
|
22379
|
+
return getSelfProbeSettings();
|
|
22380
|
+
}
|
|
22322
22381
|
var import_better_sqlite3, import_path2, import_fs2, import_crypto2, DB_DIR, DB_PATH, db, SYSTEM_PROMPT_VERSIONS, CURRENT_PROMPT_VERSION;
|
|
22323
22382
|
var init_database = __esm({
|
|
22324
22383
|
"src/utils/database.ts"() {
|
|
@@ -89361,6 +89420,127 @@ var InstanceLock = class {
|
|
|
89361
89420
|
|
|
89362
89421
|
// src/server/web-server.ts
|
|
89363
89422
|
init_database();
|
|
89423
|
+
|
|
89424
|
+
// src/utils/self-probe-service.ts
|
|
89425
|
+
init_cjs_shims();
|
|
89426
|
+
init_logger();
|
|
89427
|
+
init_database();
|
|
89428
|
+
var SelfProbeService = class {
|
|
89429
|
+
constructor(config2) {
|
|
89430
|
+
this.config = config2;
|
|
89431
|
+
this.enabled = config2.enableSelfProbe ?? false;
|
|
89432
|
+
this.intervalSeconds = config2.selfProbeIntervalSeconds ?? 300;
|
|
89433
|
+
}
|
|
89434
|
+
timer = null;
|
|
89435
|
+
lastProbeTime = null;
|
|
89436
|
+
probeCount = 0;
|
|
89437
|
+
enabled = false;
|
|
89438
|
+
intervalSeconds = 300;
|
|
89439
|
+
context = null;
|
|
89440
|
+
setContext(context) {
|
|
89441
|
+
this.context = context;
|
|
89442
|
+
}
|
|
89443
|
+
start() {
|
|
89444
|
+
if (this.timer) {
|
|
89445
|
+
this.stop();
|
|
89446
|
+
}
|
|
89447
|
+
const dbSettings = getSelfProbeSettings();
|
|
89448
|
+
if (dbSettings) {
|
|
89449
|
+
this.enabled = dbSettings.enabled;
|
|
89450
|
+
this.intervalSeconds = dbSettings.intervalSeconds;
|
|
89451
|
+
}
|
|
89452
|
+
if (!this.enabled) {
|
|
89453
|
+
logger.debug("Self-probe is disabled, not starting");
|
|
89454
|
+
return;
|
|
89455
|
+
}
|
|
89456
|
+
const intervalMs = this.intervalSeconds * 1e3;
|
|
89457
|
+
this.timer = setInterval(() => this.probe(), intervalMs);
|
|
89458
|
+
logger.info(`Self-probe started with interval: ${this.intervalSeconds}s`);
|
|
89459
|
+
}
|
|
89460
|
+
stop() {
|
|
89461
|
+
if (this.timer) {
|
|
89462
|
+
clearInterval(this.timer);
|
|
89463
|
+
this.timer = null;
|
|
89464
|
+
logger.info("Self-probe stopped");
|
|
89465
|
+
}
|
|
89466
|
+
}
|
|
89467
|
+
restart() {
|
|
89468
|
+
this.stop();
|
|
89469
|
+
this.start();
|
|
89470
|
+
}
|
|
89471
|
+
updateSettings(settings) {
|
|
89472
|
+
if (settings.enabled !== void 0) {
|
|
89473
|
+
this.enabled = settings.enabled;
|
|
89474
|
+
}
|
|
89475
|
+
if (settings.intervalSeconds !== void 0) {
|
|
89476
|
+
if (settings.intervalSeconds < 60 || settings.intervalSeconds > 600) {
|
|
89477
|
+
throw new Error("Interval must be between 60 and 600 seconds");
|
|
89478
|
+
}
|
|
89479
|
+
this.intervalSeconds = settings.intervalSeconds;
|
|
89480
|
+
}
|
|
89481
|
+
saveSelfProbeSettings(settings);
|
|
89482
|
+
if (this.enabled) {
|
|
89483
|
+
this.restart();
|
|
89484
|
+
} else {
|
|
89485
|
+
this.stop();
|
|
89486
|
+
}
|
|
89487
|
+
}
|
|
89488
|
+
async probe() {
|
|
89489
|
+
this.lastProbeTime = /* @__PURE__ */ new Date();
|
|
89490
|
+
this.probeCount++;
|
|
89491
|
+
try {
|
|
89492
|
+
if (this.context) {
|
|
89493
|
+
this.checkSocketIO();
|
|
89494
|
+
this.checkMCPStatus();
|
|
89495
|
+
this.triggerSessionCleanup();
|
|
89496
|
+
}
|
|
89497
|
+
logger.debug(`Self-probe #${this.probeCount} completed`);
|
|
89498
|
+
} catch (error2) {
|
|
89499
|
+
logger.warn("Self-probe encountered an issue:", error2);
|
|
89500
|
+
}
|
|
89501
|
+
}
|
|
89502
|
+
checkSocketIO() {
|
|
89503
|
+
if (!this.context) return;
|
|
89504
|
+
const connectedSockets = this.context.getSocketIOConnectionCount();
|
|
89505
|
+
logger.debug(`Self-probe: Socket.IO connected clients: ${connectedSockets}`);
|
|
89506
|
+
}
|
|
89507
|
+
checkMCPStatus() {
|
|
89508
|
+
if (!this.context) return;
|
|
89509
|
+
const mcpStatus = this.context.getMCPServerStatus();
|
|
89510
|
+
logger.debug(`Self-probe: MCP Server running: ${mcpStatus?.running ?? "N/A"}`);
|
|
89511
|
+
}
|
|
89512
|
+
triggerSessionCleanup() {
|
|
89513
|
+
if (!this.context) return;
|
|
89514
|
+
const sessionCount = this.context.getSessionCount();
|
|
89515
|
+
if (sessionCount > 0) {
|
|
89516
|
+
this.context.cleanupExpiredSessions();
|
|
89517
|
+
logger.debug(`Self-probe: Triggered session cleanup, active sessions: ${sessionCount}`);
|
|
89518
|
+
}
|
|
89519
|
+
}
|
|
89520
|
+
getStats() {
|
|
89521
|
+
return {
|
|
89522
|
+
enabled: this.enabled,
|
|
89523
|
+
intervalSeconds: this.intervalSeconds,
|
|
89524
|
+
lastProbeTime: this.lastProbeTime,
|
|
89525
|
+
probeCount: this.probeCount,
|
|
89526
|
+
isRunning: this.timer !== null
|
|
89527
|
+
};
|
|
89528
|
+
}
|
|
89529
|
+
isEnabled() {
|
|
89530
|
+
return this.enabled;
|
|
89531
|
+
}
|
|
89532
|
+
isRunning() {
|
|
89533
|
+
return this.timer !== null;
|
|
89534
|
+
}
|
|
89535
|
+
getProbeCount() {
|
|
89536
|
+
return this.probeCount;
|
|
89537
|
+
}
|
|
89538
|
+
getLastProbeTime() {
|
|
89539
|
+
return this.lastProbeTime;
|
|
89540
|
+
}
|
|
89541
|
+
};
|
|
89542
|
+
|
|
89543
|
+
// src/server/web-server.ts
|
|
89364
89544
|
init_crypto_helper();
|
|
89365
89545
|
init_ai_service();
|
|
89366
89546
|
init_mcp_client_manager();
|
|
@@ -89536,6 +89716,7 @@ var WebServer = class {
|
|
|
89536
89716
|
sseTransports = /* @__PURE__ */ new Map();
|
|
89537
89717
|
sseTransportsList = [];
|
|
89538
89718
|
dbInitialized = false;
|
|
89719
|
+
selfProbeService;
|
|
89539
89720
|
/**
|
|
89540
89721
|
* 延遲載入 ImageProcessor
|
|
89541
89722
|
*/
|
|
@@ -89577,6 +89758,7 @@ var WebServer = class {
|
|
|
89577
89758
|
this.config = config2;
|
|
89578
89759
|
this.portManager = new PortManager();
|
|
89579
89760
|
this.sessionStorage = new SessionStorage();
|
|
89761
|
+
this.selfProbeService = new SelfProbeService(config2);
|
|
89580
89762
|
this.app = (0, import_express.default)();
|
|
89581
89763
|
this.server = (0, import_http.createServer)(this.app);
|
|
89582
89764
|
this.io = new Server2(this.server, {
|
|
@@ -89841,6 +90023,7 @@ var WebServer = class {
|
|
|
89841
90023
|
});
|
|
89842
90024
|
});
|
|
89843
90025
|
this.app.get("/api/health", (req, res) => {
|
|
90026
|
+
const selfProbeStats = this.selfProbeService.getStats();
|
|
89844
90027
|
res.json({
|
|
89845
90028
|
status: "ok",
|
|
89846
90029
|
pid: process.pid,
|
|
@@ -89848,7 +90031,14 @@ var WebServer = class {
|
|
|
89848
90031
|
uptime: process.uptime(),
|
|
89849
90032
|
version: VERSION,
|
|
89850
90033
|
activeSessions: this.sessionStorage.getSessionCount(),
|
|
89851
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
90034
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
90035
|
+
selfProbe: {
|
|
90036
|
+
enabled: selfProbeStats.enabled,
|
|
90037
|
+
isRunning: selfProbeStats.isRunning,
|
|
90038
|
+
probeCount: selfProbeStats.probeCount,
|
|
90039
|
+
lastProbeTime: selfProbeStats.lastProbeTime?.toISOString() ?? null,
|
|
90040
|
+
intervalSeconds: selfProbeStats.intervalSeconds
|
|
90041
|
+
}
|
|
89852
90042
|
});
|
|
89853
90043
|
});
|
|
89854
90044
|
this.app.get("/api/dashboard/overview", (req, res) => {
|
|
@@ -90376,6 +90566,46 @@ var WebServer = class {
|
|
|
90376
90566
|
});
|
|
90377
90567
|
}
|
|
90378
90568
|
});
|
|
90569
|
+
this.app.get("/api/settings/self-probe", (req, res) => {
|
|
90570
|
+
try {
|
|
90571
|
+
const settings = getSelfProbeSettings();
|
|
90572
|
+
const stats = this.selfProbeService.getStats();
|
|
90573
|
+
res.json({
|
|
90574
|
+
success: true,
|
|
90575
|
+
settings: settings ?? { enabled: false, intervalSeconds: 300 },
|
|
90576
|
+
stats
|
|
90577
|
+
});
|
|
90578
|
+
} catch (error2) {
|
|
90579
|
+
logger.error("\u7372\u53D6 Self-Probe \u8A2D\u5B9A\u5931\u6557:", error2);
|
|
90580
|
+
res.status(500).json({
|
|
90581
|
+
success: false,
|
|
90582
|
+
error: error2 instanceof Error ? error2.message : "\u7372\u53D6 Self-Probe \u8A2D\u5B9A\u5931\u6557"
|
|
90583
|
+
});
|
|
90584
|
+
}
|
|
90585
|
+
});
|
|
90586
|
+
this.app.post("/api/settings/self-probe", (req, res) => {
|
|
90587
|
+
try {
|
|
90588
|
+
const { enabled, intervalSeconds } = req.body;
|
|
90589
|
+
if (intervalSeconds !== void 0 && (intervalSeconds < 60 || intervalSeconds > 600)) {
|
|
90590
|
+
res.status(400).json({
|
|
90591
|
+
success: false,
|
|
90592
|
+
error: "Interval must be between 60 and 600 seconds"
|
|
90593
|
+
});
|
|
90594
|
+
return;
|
|
90595
|
+
}
|
|
90596
|
+
this.selfProbeService.updateSettings({ enabled, intervalSeconds });
|
|
90597
|
+
const settings = getSelfProbeSettings();
|
|
90598
|
+
const stats = this.selfProbeService.getStats();
|
|
90599
|
+
logger.info(`Self-Probe \u8A2D\u5B9A\u5DF2\u66F4\u65B0: enabled=${enabled}, interval=${intervalSeconds}s`);
|
|
90600
|
+
res.json({ success: true, settings, stats });
|
|
90601
|
+
} catch (error2) {
|
|
90602
|
+
logger.error("\u66F4\u65B0 Self-Probe \u8A2D\u5B9A\u5931\u6557:", error2);
|
|
90603
|
+
res.status(500).json({
|
|
90604
|
+
success: false,
|
|
90605
|
+
error: error2 instanceof Error ? error2.message : "\u66F4\u65B0 Self-Probe \u8A2D\u5B9A\u5931\u6557"
|
|
90606
|
+
});
|
|
90607
|
+
}
|
|
90608
|
+
});
|
|
90379
90609
|
this.app.get("/api/logs", (req, res) => {
|
|
90380
90610
|
try {
|
|
90381
90611
|
const options = {};
|
|
@@ -91733,6 +91963,13 @@ var WebServer = class {
|
|
|
91733
91963
|
}
|
|
91734
91964
|
logger.mcpServerStarted(this.port, serverUrl);
|
|
91735
91965
|
await this.autoStartMCPServers();
|
|
91966
|
+
this.selfProbeService.setContext({
|
|
91967
|
+
getSocketIOConnectionCount: () => this.io.sockets.sockets.size,
|
|
91968
|
+
getMCPServerStatus: () => this.mcpServerRef?.getStatus(),
|
|
91969
|
+
getSessionCount: () => this.sessionStorage.getSessionCount(),
|
|
91970
|
+
cleanupExpiredSessions: () => this.sessionStorage.cleanupExpiredSessions()
|
|
91971
|
+
});
|
|
91972
|
+
this.selfProbeService.start();
|
|
91736
91973
|
} catch (error2) {
|
|
91737
91974
|
logger.error("Web\u4F3A\u670D\u5668\u555F\u52D5\u5931\u6557:", error2);
|
|
91738
91975
|
throw new MCPError(
|
|
@@ -91909,6 +92146,7 @@ var WebServer = class {
|
|
|
91909
92146
|
logger.warn("\u7B49\u5F85\u6D3B\u8E8D\u6703\u8A71\u5B8C\u6210\u6642\u767C\u751F\u932F\u8AA4\u6216\u8D85\u6642\uFF0C\u5C07\u7E7C\u7E8C\u505C\u6B62\u6D41\u7A0B", waitErr);
|
|
91910
92147
|
}
|
|
91911
92148
|
}
|
|
92149
|
+
this.selfProbeService.stop();
|
|
91912
92150
|
this.sessionStorage.clear();
|
|
91913
92151
|
this.sessionStorage.stopCleanupTimer();
|
|
91914
92152
|
this.io.disconnectSockets(true);
|
|
@@ -91952,6 +92190,24 @@ var WebServer = class {
|
|
|
91952
92190
|
getPort() {
|
|
91953
92191
|
return this.port;
|
|
91954
92192
|
}
|
|
92193
|
+
/**
|
|
92194
|
+
* 取得 Self-Probe 服務實例
|
|
92195
|
+
*/
|
|
92196
|
+
getSelfProbeService() {
|
|
92197
|
+
return this.selfProbeService;
|
|
92198
|
+
}
|
|
92199
|
+
/**
|
|
92200
|
+
* 取得 Socket.IO 伺服器實例
|
|
92201
|
+
*/
|
|
92202
|
+
getIO() {
|
|
92203
|
+
return this.io;
|
|
92204
|
+
}
|
|
92205
|
+
/**
|
|
92206
|
+
* 取得 Session Storage 實例
|
|
92207
|
+
*/
|
|
92208
|
+
getSessionStorage() {
|
|
92209
|
+
return this.sessionStorage;
|
|
92210
|
+
}
|
|
91955
92211
|
};
|
|
91956
92212
|
|
|
91957
92213
|
// src/server/mcp-server.ts
|
|
@@ -92393,6 +92649,26 @@ var MCPServer = class {
|
|
|
92393
92649
|
// src/config/index.ts
|
|
92394
92650
|
init_cjs_shims();
|
|
92395
92651
|
var import_dotenv = __toESM(require_main2(), 1);
|
|
92652
|
+
|
|
92653
|
+
// src/shared/ipc-constants.ts
|
|
92654
|
+
init_cjs_shims();
|
|
92655
|
+
var SUPERVISOR_DEFAULTS = {
|
|
92656
|
+
MAX_RESTART_ATTEMPTS: 5,
|
|
92657
|
+
RESTART_DELAY_MS: 2e3,
|
|
92658
|
+
HEALTH_CHECK_INTERVAL_MS: 3e4,
|
|
92659
|
+
HEALTH_CHECK_TIMEOUT_MS: 5e3,
|
|
92660
|
+
REQUEST_TIMEOUT_MS: 3e4,
|
|
92661
|
+
CONSECUTIVE_FAILURES_BEFORE_RESTART: 3
|
|
92662
|
+
};
|
|
92663
|
+
var SUPERVISOR_ENV_KEYS = {
|
|
92664
|
+
ENABLED: "SUPERVISOR_ENABLED",
|
|
92665
|
+
MAX_RESTART_ATTEMPTS: "SUPERVISOR_MAX_RESTART_ATTEMPTS",
|
|
92666
|
+
RESTART_DELAY_MS: "SUPERVISOR_RESTART_DELAY_MS",
|
|
92667
|
+
HEALTH_CHECK_INTERVAL_MS: "SUPERVISOR_HEALTH_CHECK_INTERVAL_MS",
|
|
92668
|
+
HEALTH_CHECK_TIMEOUT_MS: "SUPERVISOR_HEALTH_CHECK_TIMEOUT_MS"
|
|
92669
|
+
};
|
|
92670
|
+
|
|
92671
|
+
// src/config/index.ts
|
|
92396
92672
|
(0, import_dotenv.config)();
|
|
92397
92673
|
function getEnvVar(key, defaultValue) {
|
|
92398
92674
|
return process.env[key] || defaultValue;
|
|
@@ -92415,6 +92691,27 @@ function getEnvBoolean(key, defaultValue) {
|
|
|
92415
92691
|
if (!value) return defaultValue;
|
|
92416
92692
|
return value.toLowerCase() === "true";
|
|
92417
92693
|
}
|
|
92694
|
+
function createSupervisorConfig() {
|
|
92695
|
+
return {
|
|
92696
|
+
enabled: getEnvBoolean(SUPERVISOR_ENV_KEYS.ENABLED, false),
|
|
92697
|
+
maxRestartAttempts: getEnvNumber(
|
|
92698
|
+
SUPERVISOR_ENV_KEYS.MAX_RESTART_ATTEMPTS,
|
|
92699
|
+
SUPERVISOR_DEFAULTS.MAX_RESTART_ATTEMPTS
|
|
92700
|
+
),
|
|
92701
|
+
restartDelayMs: getEnvNumber(
|
|
92702
|
+
SUPERVISOR_ENV_KEYS.RESTART_DELAY_MS,
|
|
92703
|
+
SUPERVISOR_DEFAULTS.RESTART_DELAY_MS
|
|
92704
|
+
),
|
|
92705
|
+
healthCheckIntervalMs: getEnvNumber(
|
|
92706
|
+
SUPERVISOR_ENV_KEYS.HEALTH_CHECK_INTERVAL_MS,
|
|
92707
|
+
SUPERVISOR_DEFAULTS.HEALTH_CHECK_INTERVAL_MS
|
|
92708
|
+
),
|
|
92709
|
+
healthCheckTimeoutMs: getEnvNumber(
|
|
92710
|
+
SUPERVISOR_ENV_KEYS.HEALTH_CHECK_TIMEOUT_MS,
|
|
92711
|
+
SUPERVISOR_DEFAULTS.HEALTH_CHECK_TIMEOUT_MS
|
|
92712
|
+
)
|
|
92713
|
+
};
|
|
92714
|
+
}
|
|
92418
92715
|
function createDefaultConfig() {
|
|
92419
92716
|
return {
|
|
92420
92717
|
apiKey: process.env["MCP_API_KEY"],
|
|
@@ -92446,7 +92743,12 @@ function createDefaultConfig() {
|
|
|
92446
92743
|
healthCheckTimeout: getEnvNumber("MCP_HEALTH_CHECK_TIMEOUT", 3e3),
|
|
92447
92744
|
forceNewInstance: getEnvBoolean("MCP_FORCE_NEW_INSTANCE", false),
|
|
92448
92745
|
// MCP Server 傳輸模式
|
|
92449
|
-
mcpTransport: getEnvVar("MCP_TRANSPORT", "stdio")
|
|
92746
|
+
mcpTransport: getEnvVar("MCP_TRANSPORT", "stdio"),
|
|
92747
|
+
// Self-Probe (Keep-Alive) 設定
|
|
92748
|
+
enableSelfProbe: getEnvBoolean("MCP_ENABLE_SELF_PROBE", false),
|
|
92749
|
+
selfProbeIntervalSeconds: getEnvNumber("MCP_SELF_PROBE_INTERVAL", 300),
|
|
92750
|
+
// Supervisor 配置
|
|
92751
|
+
supervisor: createSupervisorConfig()
|
|
92450
92752
|
};
|
|
92451
92753
|
}
|
|
92452
92754
|
function validateConfig(config2) {
|
|
@@ -92483,6 +92785,41 @@ function validateConfig(config2) {
|
|
|
92483
92785
|
"INVALID_LOG_LEVEL"
|
|
92484
92786
|
);
|
|
92485
92787
|
}
|
|
92788
|
+
if (config2.selfProbeIntervalSeconds !== void 0) {
|
|
92789
|
+
if (config2.selfProbeIntervalSeconds < 60 || config2.selfProbeIntervalSeconds > 600) {
|
|
92790
|
+
throw new MCPError(
|
|
92791
|
+
`Invalid self-probe interval: ${config2.selfProbeIntervalSeconds}. Must be between 60 and 600 seconds.`,
|
|
92792
|
+
"INVALID_SELF_PROBE_INTERVAL"
|
|
92793
|
+
);
|
|
92794
|
+
}
|
|
92795
|
+
}
|
|
92796
|
+
if (config2.supervisor) {
|
|
92797
|
+
const sup = config2.supervisor;
|
|
92798
|
+
if (sup.maxRestartAttempts < 0 || sup.maxRestartAttempts > 20) {
|
|
92799
|
+
throw new MCPError(
|
|
92800
|
+
`Invalid supervisor max restart attempts: ${sup.maxRestartAttempts}. Must be between 0 and 20.`,
|
|
92801
|
+
"INVALID_SUPERVISOR_CONFIG"
|
|
92802
|
+
);
|
|
92803
|
+
}
|
|
92804
|
+
if (sup.restartDelayMs < 100 || sup.restartDelayMs > 3e4) {
|
|
92805
|
+
throw new MCPError(
|
|
92806
|
+
`Invalid supervisor restart delay: ${sup.restartDelayMs}. Must be between 100ms and 30000ms.`,
|
|
92807
|
+
"INVALID_SUPERVISOR_CONFIG"
|
|
92808
|
+
);
|
|
92809
|
+
}
|
|
92810
|
+
if (sup.healthCheckIntervalMs < 5e3 || sup.healthCheckIntervalMs > 3e5) {
|
|
92811
|
+
throw new MCPError(
|
|
92812
|
+
`Invalid supervisor health check interval: ${sup.healthCheckIntervalMs}. Must be between 5s and 300s.`,
|
|
92813
|
+
"INVALID_SUPERVISOR_CONFIG"
|
|
92814
|
+
);
|
|
92815
|
+
}
|
|
92816
|
+
if (sup.healthCheckTimeoutMs < 1e3 || sup.healthCheckTimeoutMs > 3e4) {
|
|
92817
|
+
throw new MCPError(
|
|
92818
|
+
`Invalid supervisor health check timeout: ${sup.healthCheckTimeoutMs}. Must be between 1s and 30s.`,
|
|
92819
|
+
"INVALID_SUPERVISOR_CONFIG"
|
|
92820
|
+
);
|
|
92821
|
+
}
|
|
92822
|
+
}
|
|
92486
92823
|
}
|
|
92487
92824
|
function getConfig() {
|
|
92488
92825
|
const config2 = createDefaultConfig();
|
|
@@ -342,6 +342,42 @@
|
|
|
342
342
|
<button id="savePreferencesBtn" class="btn btn-primary">儲存偏好設定</button>
|
|
343
343
|
</div>
|
|
344
344
|
</section>
|
|
345
|
+
|
|
346
|
+
<!-- Self-Probe 設定 -->
|
|
347
|
+
<section class="settings-section">
|
|
348
|
+
<h2 class="section-title">
|
|
349
|
+
<span class="icon">💓</span>
|
|
350
|
+
自我探查 (Keep-Alive)
|
|
351
|
+
</h2>
|
|
352
|
+
|
|
353
|
+
<div class="form-group">
|
|
354
|
+
<div class="checkbox-group">
|
|
355
|
+
<input type="checkbox" id="enableSelfProbe" />
|
|
356
|
+
<label for="enableSelfProbe">啟用自我探查</label>
|
|
357
|
+
</div>
|
|
358
|
+
<p class="form-help">定期檢查服務狀態,防止因閒置被系統回收</p>
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
<div class="form-group" id="selfProbeIntervalGroup">
|
|
362
|
+
<label class="form-label" for="selfProbeInterval">探查間隔(秒)</label>
|
|
363
|
+
<input type="number" id="selfProbeInterval" class="form-input"
|
|
364
|
+
min="60" max="600" step="30" value="300">
|
|
365
|
+
<p class="form-help">60-600 秒,預設 300 秒(5 分鐘)</p>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
<div class="form-group" id="selfProbeStatus" style="display: none;">
|
|
369
|
+
<label class="form-label">狀態資訊</label>
|
|
370
|
+
<div class="status-info" style="font-size: 13px; color: var(--text-muted); padding: 8px; background: var(--bg-secondary); border-radius: var(--radius-sm);">
|
|
371
|
+
<div id="selfProbeRunning">執行狀態: --</div>
|
|
372
|
+
<div id="selfProbeCount">探查次數: --</div>
|
|
373
|
+
<div id="selfProbeLastTime">上次探查: --</div>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<div class="form-actions">
|
|
378
|
+
<button id="saveSelfProbeBtn" class="btn btn-primary">儲存設定</button>
|
|
379
|
+
</div>
|
|
380
|
+
</section>
|
|
345
381
|
</div>
|
|
346
382
|
|
|
347
383
|
<!-- Toast Container -->
|
package/dist/static/settings.js
CHANGED
|
@@ -63,6 +63,15 @@
|
|
|
63
63
|
confirmBeforeSubmit: document.getElementById("confirmBeforeSubmit"),
|
|
64
64
|
defaultLanguage: document.getElementById("defaultLanguage"),
|
|
65
65
|
savePreferencesBtn: document.getElementById("savePreferencesBtn"),
|
|
66
|
+
// Self-Probe Settings
|
|
67
|
+
enableSelfProbe: document.getElementById("enableSelfProbe"),
|
|
68
|
+
selfProbeInterval: document.getElementById("selfProbeInterval"),
|
|
69
|
+
selfProbeIntervalGroup: document.getElementById("selfProbeIntervalGroup"),
|
|
70
|
+
selfProbeStatus: document.getElementById("selfProbeStatus"),
|
|
71
|
+
selfProbeRunning: document.getElementById("selfProbeRunning"),
|
|
72
|
+
selfProbeCount: document.getElementById("selfProbeCount"),
|
|
73
|
+
selfProbeLastTime: document.getElementById("selfProbeLastTime"),
|
|
74
|
+
saveSelfProbeBtn: document.getElementById("saveSelfProbeBtn"),
|
|
66
75
|
toastContainer: document.getElementById("toastContainer"),
|
|
67
76
|
};
|
|
68
77
|
|
|
@@ -76,6 +85,7 @@
|
|
|
76
85
|
loadAISettings();
|
|
77
86
|
loadCLISettings();
|
|
78
87
|
loadPreferences();
|
|
88
|
+
loadSelfProbeSettings();
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
function setupEventListeners() {
|
|
@@ -92,6 +102,21 @@
|
|
|
92
102
|
|
|
93
103
|
// User Preferences
|
|
94
104
|
elements.savePreferencesBtn.addEventListener("click", savePreferences);
|
|
105
|
+
|
|
106
|
+
// Self-Probe Settings
|
|
107
|
+
if (elements.enableSelfProbe) {
|
|
108
|
+
elements.enableSelfProbe.addEventListener("change", handleSelfProbeToggle);
|
|
109
|
+
}
|
|
110
|
+
if (elements.saveSelfProbeBtn) {
|
|
111
|
+
elements.saveSelfProbeBtn.addEventListener("click", saveSelfProbeSettings);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function handleSelfProbeToggle() {
|
|
116
|
+
const isEnabled = elements.enableSelfProbe.checked;
|
|
117
|
+
if (elements.selfProbeIntervalGroup) {
|
|
118
|
+
elements.selfProbeIntervalGroup.style.opacity = isEnabled ? "1" : "0.5";
|
|
119
|
+
}
|
|
95
120
|
}
|
|
96
121
|
|
|
97
122
|
function handleAIModeChange() {
|
|
@@ -397,6 +422,99 @@
|
|
|
397
422
|
}
|
|
398
423
|
}
|
|
399
424
|
|
|
425
|
+
// ============ Self-Probe Settings ============
|
|
426
|
+
|
|
427
|
+
async function loadSelfProbeSettings() {
|
|
428
|
+
try {
|
|
429
|
+
const response = await fetch(`${API_BASE}/api/settings/self-probe`);
|
|
430
|
+
const data = await response.json();
|
|
431
|
+
|
|
432
|
+
if (data.success) {
|
|
433
|
+
const settings = data.settings || {};
|
|
434
|
+
const stats = data.stats || {};
|
|
435
|
+
|
|
436
|
+
if (elements.enableSelfProbe) {
|
|
437
|
+
elements.enableSelfProbe.checked = settings.enabled || false;
|
|
438
|
+
}
|
|
439
|
+
if (elements.selfProbeInterval) {
|
|
440
|
+
elements.selfProbeInterval.value = settings.intervalSeconds || 300;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// 更新狀態資訊
|
|
444
|
+
updateSelfProbeStatus(stats);
|
|
445
|
+
handleSelfProbeToggle();
|
|
446
|
+
}
|
|
447
|
+
} catch (error) {
|
|
448
|
+
console.error("Failed to load Self-Probe settings:", error);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function updateSelfProbeStatus(stats) {
|
|
453
|
+
if (!elements.selfProbeStatus) return;
|
|
454
|
+
|
|
455
|
+
if (stats.enabled) {
|
|
456
|
+
elements.selfProbeStatus.style.display = "block";
|
|
457
|
+
|
|
458
|
+
if (elements.selfProbeRunning) {
|
|
459
|
+
elements.selfProbeRunning.textContent = `執行狀態: ${stats.isRunning ? "✅ 運行中" : "⏸️ 已停止"}`;
|
|
460
|
+
}
|
|
461
|
+
if (elements.selfProbeCount) {
|
|
462
|
+
elements.selfProbeCount.textContent = `探查次數: ${stats.probeCount || 0}`;
|
|
463
|
+
}
|
|
464
|
+
if (elements.selfProbeLastTime) {
|
|
465
|
+
const lastTime = stats.lastProbeTime
|
|
466
|
+
? new Date(stats.lastProbeTime).toLocaleString()
|
|
467
|
+
: "尚未執行";
|
|
468
|
+
elements.selfProbeLastTime.textContent = `上次探查: ${lastTime}`;
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
elements.selfProbeStatus.style.display = "none";
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function saveSelfProbeSettings() {
|
|
476
|
+
const settings = {
|
|
477
|
+
enabled: elements.enableSelfProbe?.checked || false,
|
|
478
|
+
intervalSeconds: parseInt(elements.selfProbeInterval?.value) || 300,
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// 驗證間隔
|
|
482
|
+
if (settings.intervalSeconds < 60 || settings.intervalSeconds > 600) {
|
|
483
|
+
showToast("探查間隔必須在 60-600 秒之間", "error");
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (elements.saveSelfProbeBtn) {
|
|
488
|
+
elements.saveSelfProbeBtn.disabled = true;
|
|
489
|
+
elements.saveSelfProbeBtn.textContent = "儲存中...";
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
const response = await fetch(`${API_BASE}/api/settings/self-probe`, {
|
|
494
|
+
method: "POST",
|
|
495
|
+
headers: { "Content-Type": "application/json" },
|
|
496
|
+
body: JSON.stringify(settings),
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const data = await response.json();
|
|
500
|
+
|
|
501
|
+
if (response.ok && data.success) {
|
|
502
|
+
showToast("Self-Probe 設定已儲存", "success");
|
|
503
|
+
updateSelfProbeStatus(data.stats);
|
|
504
|
+
} else {
|
|
505
|
+
showToast(`儲存失敗: ${data.error || "未知錯誤"}`, "error");
|
|
506
|
+
}
|
|
507
|
+
} catch (error) {
|
|
508
|
+
console.error("Save Self-Probe settings failed:", error);
|
|
509
|
+
showToast("儲存失敗", "error");
|
|
510
|
+
} finally {
|
|
511
|
+
if (elements.saveSelfProbeBtn) {
|
|
512
|
+
elements.saveSelfProbeBtn.disabled = false;
|
|
513
|
+
elements.saveSelfProbeBtn.textContent = "儲存設定";
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
400
518
|
function showToast(message, type = "info") {
|
|
401
519
|
const toast = document.createElement("div");
|
|
402
520
|
toast.className = `toast toast-${type}`;
|