@hirohsu/user-web-feedback 2.6.0 → 2.7.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/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"() {
@@ -87562,7 +87621,7 @@ var helmet = Object.assign(
87562
87621
  var import_compression = __toESM(require_compression(), 1);
87563
87622
  var import_path6 = __toESM(require("path"), 1);
87564
87623
  var import_fs7 = __toESM(require("fs"), 1);
87565
- var import_url = require("url");
87624
+ var import_url2 = require("url");
87566
87625
  init_logger();
87567
87626
 
87568
87627
  // src/utils/port-manager.ts
@@ -89119,21 +89178,80 @@ var projectManager = ProjectManager.getInstance();
89119
89178
  init_cjs_shims();
89120
89179
  var import_fs4 = require("fs");
89121
89180
  var import_path4 = require("path");
89181
+ var import_url = require("url");
89182
+ var import_meta = {};
89183
+ function getDirname() {
89184
+ try {
89185
+ if (typeof import_meta !== "undefined" && importMetaUrl) {
89186
+ return (0, import_path4.dirname)((0, import_url.fileURLToPath)(importMetaUrl));
89187
+ }
89188
+ } catch {
89189
+ }
89190
+ if (typeof __dirname !== "undefined") {
89191
+ return __dirname;
89192
+ }
89193
+ return process.cwd();
89194
+ }
89195
+ function findPackageJson(startPath) {
89196
+ let currentPath = startPath;
89197
+ const root = process.platform === "win32" ? currentPath.split("\\")[0] + "\\" : "/";
89198
+ while (currentPath !== root) {
89199
+ const pkgPath = (0, import_path4.join)(currentPath, "package.json");
89200
+ if ((0, import_fs4.existsSync)(pkgPath)) {
89201
+ return pkgPath;
89202
+ }
89203
+ const parentPath = (0, import_path4.dirname)(currentPath);
89204
+ if (parentPath === currentPath) {
89205
+ break;
89206
+ }
89207
+ currentPath = parentPath;
89208
+ }
89209
+ return null;
89210
+ }
89122
89211
  function getPackageVersion() {
89123
89212
  try {
89124
- const possiblePaths = [
89125
- (0, import_path4.join)(__dirname, "..", "..", "package.json"),
89126
- (0, import_path4.join)(process.cwd(), "package.json")
89127
- ];
89128
- for (const pkgPath of possiblePaths) {
89213
+ const currentDir = getDirname();
89214
+ const pkgPath1 = findPackageJson(currentDir);
89215
+ if (pkgPath1) {
89129
89216
  try {
89130
- const pkgContent = (0, import_fs4.readFileSync)(pkgPath, "utf-8");
89217
+ const pkgContent = (0, import_fs4.readFileSync)(pkgPath1, "utf-8");
89131
89218
  const pkg = JSON.parse(pkgContent);
89132
89219
  if (pkg.version) {
89133
89220
  return pkg.version;
89134
89221
  }
89135
89222
  } catch {
89136
- continue;
89223
+ }
89224
+ }
89225
+ const cwd = process.cwd();
89226
+ if (cwd !== currentDir) {
89227
+ const pkgPath2 = findPackageJson(cwd);
89228
+ if (pkgPath2) {
89229
+ try {
89230
+ const pkgContent = (0, import_fs4.readFileSync)(pkgPath2, "utf-8");
89231
+ const pkg = JSON.parse(pkgContent);
89232
+ if (pkg.version) {
89233
+ return pkg.version;
89234
+ }
89235
+ } catch {
89236
+ }
89237
+ }
89238
+ }
89239
+ const possiblePaths = [
89240
+ (0, import_path4.join)(currentDir, "..", "..", "package.json"),
89241
+ (0, import_path4.join)(currentDir, "..", "package.json"),
89242
+ (0, import_path4.join)(process.cwd(), "package.json")
89243
+ ];
89244
+ for (const pkgPath of possiblePaths) {
89245
+ if ((0, import_fs4.existsSync)(pkgPath)) {
89246
+ try {
89247
+ const pkgContent = (0, import_fs4.readFileSync)(pkgPath, "utf-8");
89248
+ const pkg = JSON.parse(pkgContent);
89249
+ if (pkg.version) {
89250
+ return pkg.version;
89251
+ }
89252
+ } catch {
89253
+ continue;
89254
+ }
89137
89255
  }
89138
89256
  }
89139
89257
  return "0.0.0";
@@ -89302,6 +89420,127 @@ var InstanceLock = class {
89302
89420
 
89303
89421
  // src/server/web-server.ts
89304
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
89305
89544
  init_crypto_helper();
89306
89545
  init_ai_service();
89307
89546
  init_mcp_client_manager();
@@ -89477,6 +89716,7 @@ var WebServer = class {
89477
89716
  sseTransports = /* @__PURE__ */ new Map();
89478
89717
  sseTransportsList = [];
89479
89718
  dbInitialized = false;
89719
+ selfProbeService;
89480
89720
  /**
89481
89721
  * 延遲載入 ImageProcessor
89482
89722
  */
@@ -89518,6 +89758,7 @@ var WebServer = class {
89518
89758
  this.config = config2;
89519
89759
  this.portManager = new PortManager();
89520
89760
  this.sessionStorage = new SessionStorage();
89761
+ this.selfProbeService = new SelfProbeService(config2);
89521
89762
  this.app = (0, import_express.default)();
89522
89763
  this.server = (0, import_http.createServer)(this.app);
89523
89764
  this.io = new Server2(this.server, {
@@ -89558,7 +89799,7 @@ var WebServer = class {
89558
89799
  * 支援多種執行環境:開發模式、打包模式、npx 執行模式
89559
89800
  */
89560
89801
  getStaticAssetsPath() {
89561
- const __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
89802
+ const __filename2 = (0, import_url2.fileURLToPath)(importMetaUrl);
89562
89803
  const __dirname3 = import_path6.default.dirname(__filename2);
89563
89804
  const candidates = [];
89564
89805
  candidates.push(import_path6.default.resolve(__dirname3, "static"));
@@ -89782,6 +90023,7 @@ var WebServer = class {
89782
90023
  });
89783
90024
  });
89784
90025
  this.app.get("/api/health", (req, res) => {
90026
+ const selfProbeStats = this.selfProbeService.getStats();
89785
90027
  res.json({
89786
90028
  status: "ok",
89787
90029
  pid: process.pid,
@@ -89789,7 +90031,14 @@ var WebServer = class {
89789
90031
  uptime: process.uptime(),
89790
90032
  version: VERSION,
89791
90033
  activeSessions: this.sessionStorage.getSessionCount(),
89792
- 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
+ }
89793
90042
  });
89794
90043
  });
89795
90044
  this.app.get("/api/dashboard/overview", (req, res) => {
@@ -90317,6 +90566,46 @@ var WebServer = class {
90317
90566
  });
90318
90567
  }
90319
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
+ });
90320
90609
  this.app.get("/api/logs", (req, res) => {
90321
90610
  try {
90322
90611
  const options = {};
@@ -91674,6 +91963,13 @@ var WebServer = class {
91674
91963
  }
91675
91964
  logger.mcpServerStarted(this.port, serverUrl);
91676
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();
91677
91973
  } catch (error2) {
91678
91974
  logger.error("Web\u4F3A\u670D\u5668\u555F\u52D5\u5931\u6557:", error2);
91679
91975
  throw new MCPError(
@@ -91850,6 +92146,7 @@ var WebServer = class {
91850
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);
91851
92147
  }
91852
92148
  }
92149
+ this.selfProbeService.stop();
91853
92150
  this.sessionStorage.clear();
91854
92151
  this.sessionStorage.stopCleanupTimer();
91855
92152
  this.io.disconnectSockets(true);
@@ -91893,6 +92190,24 @@ var WebServer = class {
91893
92190
  getPort() {
91894
92191
  return this.port;
91895
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
+ }
91896
92211
  };
91897
92212
 
91898
92213
  // src/server/mcp-server.ts
@@ -92334,6 +92649,26 @@ var MCPServer = class {
92334
92649
  // src/config/index.ts
92335
92650
  init_cjs_shims();
92336
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
92337
92672
  (0, import_dotenv.config)();
92338
92673
  function getEnvVar(key, defaultValue) {
92339
92674
  return process.env[key] || defaultValue;
@@ -92356,6 +92691,27 @@ function getEnvBoolean(key, defaultValue) {
92356
92691
  if (!value) return defaultValue;
92357
92692
  return value.toLowerCase() === "true";
92358
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
+ }
92359
92715
  function createDefaultConfig() {
92360
92716
  return {
92361
92717
  apiKey: process.env["MCP_API_KEY"],
@@ -92387,7 +92743,12 @@ function createDefaultConfig() {
92387
92743
  healthCheckTimeout: getEnvNumber("MCP_HEALTH_CHECK_TIMEOUT", 3e3),
92388
92744
  forceNewInstance: getEnvBoolean("MCP_FORCE_NEW_INSTANCE", false),
92389
92745
  // MCP Server 傳輸模式
92390
- 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()
92391
92752
  };
92392
92753
  }
92393
92754
  function validateConfig(config2) {
@@ -92424,6 +92785,41 @@ function validateConfig(config2) {
92424
92785
  "INVALID_LOG_LEVEL"
92425
92786
  );
92426
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
+ }
92427
92823
  }
92428
92824
  function getConfig() {
92429
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 -->