@hirohsu/user-web-feedback 2.8.13 → 2.8.14
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 +267 -22
- package/dist/index.cjs +264 -19
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -2872,7 +2872,7 @@ var require_main = __commonJS({
|
|
|
2872
2872
|
var fs9 = require("fs");
|
|
2873
2873
|
var path10 = require("path");
|
|
2874
2874
|
var os4 = require("os");
|
|
2875
|
-
var
|
|
2875
|
+
var crypto5 = require("crypto");
|
|
2876
2876
|
var packageJson = require_package();
|
|
2877
2877
|
var version2 = packageJson.version;
|
|
2878
2878
|
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
@@ -3091,7 +3091,7 @@ var require_main = __commonJS({
|
|
|
3091
3091
|
const authTag = ciphertext.subarray(-16);
|
|
3092
3092
|
ciphertext = ciphertext.subarray(12, -16);
|
|
3093
3093
|
try {
|
|
3094
|
-
const aesgcm =
|
|
3094
|
+
const aesgcm = crypto5.createDecipheriv("aes-256-gcm", key, nonce);
|
|
3095
3095
|
aesgcm.setAuthTag(authTag);
|
|
3096
3096
|
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
3097
3097
|
} catch (error2) {
|
|
@@ -3272,6 +3272,7 @@ var init_crypto_helper = __esm({
|
|
|
3272
3272
|
var database_exports = {};
|
|
3273
3273
|
__export(database_exports, {
|
|
3274
3274
|
batchSetToolEnabled: () => batchSetToolEnabled,
|
|
3275
|
+
cleanupExpiredPendingResponses: () => cleanupExpiredPendingResponses,
|
|
3275
3276
|
cleanupOldAPIErrorLogs: () => cleanupOldAPIErrorLogs,
|
|
3276
3277
|
cleanupOldAPILogs: () => cleanupOldAPILogs,
|
|
3277
3278
|
cleanupOldCLIExecutionLogs: () => cleanupOldCLIExecutionLogs,
|
|
@@ -3300,6 +3301,8 @@ __export(database_exports, {
|
|
|
3300
3301
|
getEnabledNonDeferredMCPServers: () => getEnabledNonDeferredMCPServers,
|
|
3301
3302
|
getLogSources: () => getLogSources,
|
|
3302
3303
|
getMCPServerById: () => getMCPServerById,
|
|
3304
|
+
getPendingResponseById: () => getPendingResponseById,
|
|
3305
|
+
getPendingResponseByProject: () => getPendingResponseByProject,
|
|
3303
3306
|
getPinnedPrompts: () => getPinnedPrompts,
|
|
3304
3307
|
getPromptById: () => getPromptById,
|
|
3305
3308
|
getPromptConfigs: () => getPromptConfigs,
|
|
@@ -3307,6 +3310,7 @@ __export(database_exports, {
|
|
|
3307
3310
|
getSelfProbeSettings: () => getSelfProbeSettings,
|
|
3308
3311
|
getToolEnableConfigs: () => getToolEnableConfigs,
|
|
3309
3312
|
getUserPreferences: () => getUserPreferences,
|
|
3313
|
+
incrementDeliveryAttempt: () => incrementDeliveryAttempt,
|
|
3310
3314
|
initDatabase: () => initDatabase,
|
|
3311
3315
|
insertCLIExecutionLog: () => insertCLIExecutionLog,
|
|
3312
3316
|
insertLog: () => insertLog,
|
|
@@ -3315,12 +3319,14 @@ __export(database_exports, {
|
|
|
3315
3319
|
isToolEnabled: () => isToolEnabled,
|
|
3316
3320
|
logAPIError: () => logAPIError,
|
|
3317
3321
|
logAPIRequest: () => logAPIRequest,
|
|
3322
|
+
markPendingResponseDelivered: () => markPendingResponseDelivered,
|
|
3318
3323
|
queryAPIErrorLogs: () => queryAPIErrorLogs,
|
|
3319
3324
|
queryAPILogs: () => queryAPILogs,
|
|
3320
3325
|
queryLogs: () => queryLogs,
|
|
3321
3326
|
queryMCPServerLogs: () => queryMCPServerLogs,
|
|
3322
3327
|
reorderPrompts: () => reorderPrompts,
|
|
3323
3328
|
resetPromptConfigs: () => resetPromptConfigs,
|
|
3329
|
+
savePendingResponse: () => savePendingResponse,
|
|
3324
3330
|
saveSelfProbeSettings: () => saveSelfProbeSettings,
|
|
3325
3331
|
setToolEnabled: () => setToolEnabled,
|
|
3326
3332
|
toggleMCPServerEnabled: () => toggleMCPServerEnabled,
|
|
@@ -3644,6 +3650,23 @@ function createTables() {
|
|
|
3644
3650
|
)
|
|
3645
3651
|
`);
|
|
3646
3652
|
initDefaultPromptConfigs();
|
|
3653
|
+
db.exec(`
|
|
3654
|
+
CREATE TABLE IF NOT EXISTS pending_responses (
|
|
3655
|
+
id TEXT PRIMARY KEY,
|
|
3656
|
+
session_id TEXT NOT NULL,
|
|
3657
|
+
project_id TEXT NOT NULL,
|
|
3658
|
+
project_name TEXT DEFAULT '',
|
|
3659
|
+
feedback_json TEXT NOT NULL,
|
|
3660
|
+
feedback_url TEXT DEFAULT '',
|
|
3661
|
+
created_at INTEGER NOT NULL,
|
|
3662
|
+
delivered INTEGER DEFAULT 0,
|
|
3663
|
+
delivery_attempts INTEGER DEFAULT 0,
|
|
3664
|
+
last_attempt_at INTEGER,
|
|
3665
|
+
expires_at INTEGER NOT NULL
|
|
3666
|
+
)
|
|
3667
|
+
`);
|
|
3668
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_pending_responses_project ON pending_responses(project_id, delivered)`);
|
|
3669
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_pending_responses_expires ON pending_responses(expires_at)`);
|
|
3647
3670
|
}
|
|
3648
3671
|
function initDefaultSettings() {
|
|
3649
3672
|
if (!db) throw new Error("Database not initialized");
|
|
@@ -5135,6 +5158,50 @@ function resetPromptConfigs() {
|
|
|
5135
5158
|
initDefaultPromptConfigs();
|
|
5136
5159
|
return getPromptConfigs();
|
|
5137
5160
|
}
|
|
5161
|
+
function savePendingResponse(data) {
|
|
5162
|
+
const db2 = tryGetDb();
|
|
5163
|
+
if (!db2) return;
|
|
5164
|
+
const now = Date.now();
|
|
5165
|
+
db2.prepare(`
|
|
5166
|
+
INSERT OR REPLACE INTO pending_responses (id, session_id, project_id, project_name, feedback_json, feedback_url, created_at, delivered, delivery_attempts, last_attempt_at, expires_at)
|
|
5167
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?)
|
|
5168
|
+
`).run(data.id, data.sessionId, data.projectId, data.projectName, data.feedbackJson, data.feedbackUrl, now, now + data.ttlSeconds * 1e3);
|
|
5169
|
+
}
|
|
5170
|
+
function getPendingResponseByProject(projectId) {
|
|
5171
|
+
const db2 = tryGetDb();
|
|
5172
|
+
if (!db2) return null;
|
|
5173
|
+
const now = Date.now();
|
|
5174
|
+
return db2.prepare(`
|
|
5175
|
+
SELECT * FROM pending_responses
|
|
5176
|
+
WHERE project_id = ? AND delivered = 0 AND expires_at > ?
|
|
5177
|
+
ORDER BY created_at DESC LIMIT 1
|
|
5178
|
+
`).get(projectId, now) ?? null;
|
|
5179
|
+
}
|
|
5180
|
+
function getPendingResponseById(id) {
|
|
5181
|
+
const db2 = tryGetDb();
|
|
5182
|
+
if (!db2) return null;
|
|
5183
|
+
return db2.prepare("SELECT * FROM pending_responses WHERE id = ?").get(id) ?? null;
|
|
5184
|
+
}
|
|
5185
|
+
function markPendingResponseDelivered(id) {
|
|
5186
|
+
const db2 = tryGetDb();
|
|
5187
|
+
if (!db2) return;
|
|
5188
|
+
db2.prepare("UPDATE pending_responses SET delivered = 1, delivery_attempts = delivery_attempts + 1, last_attempt_at = ? WHERE id = ?").run(Date.now(), id);
|
|
5189
|
+
}
|
|
5190
|
+
function incrementDeliveryAttempt(id) {
|
|
5191
|
+
const db2 = tryGetDb();
|
|
5192
|
+
if (!db2) return;
|
|
5193
|
+
db2.prepare("UPDATE pending_responses SET delivery_attempts = delivery_attempts + 1, last_attempt_at = ? WHERE id = ?").run(Date.now(), id);
|
|
5194
|
+
}
|
|
5195
|
+
function cleanupExpiredPendingResponses() {
|
|
5196
|
+
const db2 = tryGetDb();
|
|
5197
|
+
if (!db2) return 0;
|
|
5198
|
+
const now = Date.now();
|
|
5199
|
+
const deliveredCutoff = now - 36e5;
|
|
5200
|
+
const result = db2.prepare(`
|
|
5201
|
+
DELETE FROM pending_responses WHERE expires_at < ? OR (delivered = 1 AND last_attempt_at < ?)
|
|
5202
|
+
`).run(now, deliveredCutoff);
|
|
5203
|
+
return result.changes;
|
|
5204
|
+
}
|
|
5138
5205
|
var import_better_sqlite3, import_path2, import_fs2, import_crypto2, DB_DIR, DB_PATH, db, SYSTEM_PROMPT_VERSIONS, CURRENT_PROMPT_VERSION, DEFAULT_PROMPT_CONFIGS;
|
|
5139
5206
|
var init_database = __esm({
|
|
5140
5207
|
"src/utils/database.ts"() {
|
|
@@ -45141,14 +45208,14 @@ var require_etag = __commonJS({
|
|
|
45141
45208
|
"use strict";
|
|
45142
45209
|
init_cjs_shims();
|
|
45143
45210
|
module2.exports = etag;
|
|
45144
|
-
var
|
|
45211
|
+
var crypto5 = require("crypto");
|
|
45145
45212
|
var Stats = require("fs").Stats;
|
|
45146
45213
|
var toString = Object.prototype.toString;
|
|
45147
45214
|
function entitytag(entity) {
|
|
45148
45215
|
if (entity.length === 0) {
|
|
45149
45216
|
return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
|
|
45150
45217
|
}
|
|
45151
|
-
var hash =
|
|
45218
|
+
var hash = crypto5.createHash("sha1").update(entity, "utf8").digest("base64").substring(0, 27);
|
|
45152
45219
|
var len = typeof entity === "string" ? Buffer.byteLength(entity, "utf8") : entity.length;
|
|
45153
45220
|
return '"' + len.toString(16) + "-" + hash + '"';
|
|
45154
45221
|
}
|
|
@@ -48062,11 +48129,11 @@ var require_cookie_signature = __commonJS({
|
|
|
48062
48129
|
"node_modules/.pnpm/cookie-signature@1.0.7/node_modules/cookie-signature/index.js"(exports2) {
|
|
48063
48130
|
"use strict";
|
|
48064
48131
|
init_cjs_shims();
|
|
48065
|
-
var
|
|
48132
|
+
var crypto5 = require("crypto");
|
|
48066
48133
|
exports2.sign = function(val, secret) {
|
|
48067
48134
|
if ("string" !== typeof val) throw new TypeError("Cookie value must be provided as a string.");
|
|
48068
48135
|
if (null == secret) throw new TypeError("Secret key must be provided.");
|
|
48069
|
-
return val + "." +
|
|
48136
|
+
return val + "." + crypto5.createHmac("sha256", secret).update(val).digest("base64").replace(/\=+$/, "");
|
|
48070
48137
|
};
|
|
48071
48138
|
exports2.unsign = function(val, secret) {
|
|
48072
48139
|
if ("string" !== typeof val) throw new TypeError("Signed cookie string must be provided.");
|
|
@@ -48075,7 +48142,7 @@ var require_cookie_signature = __commonJS({
|
|
|
48075
48142
|
return sha1(mac) == sha1(val) ? str : false;
|
|
48076
48143
|
};
|
|
48077
48144
|
function sha1(str) {
|
|
48078
|
-
return
|
|
48145
|
+
return crypto5.createHash("sha1").update(str).digest("hex");
|
|
48079
48146
|
}
|
|
48080
48147
|
}
|
|
48081
48148
|
});
|
|
@@ -49615,7 +49682,7 @@ var require_base64id = __commonJS({
|
|
|
49615
49682
|
"node_modules/.pnpm/base64id@2.0.0/node_modules/base64id/lib/base64id.js"(exports2, module2) {
|
|
49616
49683
|
"use strict";
|
|
49617
49684
|
init_cjs_shims();
|
|
49618
|
-
var
|
|
49685
|
+
var crypto5 = require("crypto");
|
|
49619
49686
|
var Base64Id = function() {
|
|
49620
49687
|
};
|
|
49621
49688
|
Base64Id.prototype.getRandomBytes = function(bytes) {
|
|
@@ -49623,12 +49690,12 @@ var require_base64id = __commonJS({
|
|
|
49623
49690
|
var self = this;
|
|
49624
49691
|
bytes = bytes || 12;
|
|
49625
49692
|
if (bytes > BUFFER_SIZE) {
|
|
49626
|
-
return
|
|
49693
|
+
return crypto5.randomBytes(bytes);
|
|
49627
49694
|
}
|
|
49628
49695
|
var bytesInBuffer = parseInt(BUFFER_SIZE / bytes);
|
|
49629
49696
|
var threshold = parseInt(bytesInBuffer * 0.85);
|
|
49630
49697
|
if (!threshold) {
|
|
49631
|
-
return
|
|
49698
|
+
return crypto5.randomBytes(bytes);
|
|
49632
49699
|
}
|
|
49633
49700
|
if (this.bytesBufferIndex == null) {
|
|
49634
49701
|
this.bytesBufferIndex = -1;
|
|
@@ -49640,14 +49707,14 @@ var require_base64id = __commonJS({
|
|
|
49640
49707
|
if (this.bytesBufferIndex == -1 || this.bytesBufferIndex > threshold) {
|
|
49641
49708
|
if (!this.isGeneratingBytes) {
|
|
49642
49709
|
this.isGeneratingBytes = true;
|
|
49643
|
-
|
|
49710
|
+
crypto5.randomBytes(BUFFER_SIZE, function(err, bytes2) {
|
|
49644
49711
|
self.bytesBuffer = bytes2;
|
|
49645
49712
|
self.bytesBufferIndex = 0;
|
|
49646
49713
|
self.isGeneratingBytes = false;
|
|
49647
49714
|
});
|
|
49648
49715
|
}
|
|
49649
49716
|
if (this.bytesBufferIndex == -1) {
|
|
49650
|
-
return
|
|
49717
|
+
return crypto5.randomBytes(bytes);
|
|
49651
49718
|
}
|
|
49652
49719
|
}
|
|
49653
49720
|
var result = this.bytesBuffer.slice(bytes * this.bytesBufferIndex, bytes * (this.bytesBufferIndex + 1));
|
|
@@ -49661,7 +49728,7 @@ var require_base64id = __commonJS({
|
|
|
49661
49728
|
}
|
|
49662
49729
|
this.sequenceNumber = this.sequenceNumber + 1 | 0;
|
|
49663
49730
|
rand.writeInt32BE(this.sequenceNumber, 11);
|
|
49664
|
-
if (
|
|
49731
|
+
if (crypto5.randomBytes) {
|
|
49665
49732
|
this.getRandomBytes(12).copy(rand);
|
|
49666
49733
|
} else {
|
|
49667
49734
|
[0, 4, 8].forEach(function(i) {
|
|
@@ -88780,7 +88847,10 @@ function createDefaultConfig() {
|
|
|
88780
88847
|
enableSelfProbe: getEnvBoolean("MCP_ENABLE_SELF_PROBE", false),
|
|
88781
88848
|
selfProbeIntervalSeconds: getEnvNumber("MCP_SELF_PROBE_INTERVAL", 300),
|
|
88782
88849
|
// Supervisor 配置
|
|
88783
|
-
supervisor: createSupervisorConfig()
|
|
88850
|
+
supervisor: createSupervisorConfig(),
|
|
88851
|
+
// Response Delivery 可靠性設定
|
|
88852
|
+
responseTtl: getEnvNumber("MCP_RESPONSE_TTL", 86400),
|
|
88853
|
+
stdioHeartbeatInterval: getEnvNumber("MCP_STDIO_HEARTBEAT_INTERVAL", 30)
|
|
88784
88854
|
};
|
|
88785
88855
|
}
|
|
88786
88856
|
function validateConfig(config2) {
|
|
@@ -92968,6 +93038,72 @@ data: ${JSON.stringify(message)}
|
|
|
92968
93038
|
}
|
|
92969
93039
|
};
|
|
92970
93040
|
|
|
93041
|
+
// src/utils/response-store.ts
|
|
93042
|
+
init_cjs_shims();
|
|
93043
|
+
var import_crypto4 = __toESM(require("crypto"), 1);
|
|
93044
|
+
init_database();
|
|
93045
|
+
init_logger();
|
|
93046
|
+
var ResponseStore = class {
|
|
93047
|
+
ttlSeconds;
|
|
93048
|
+
constructor(ttlSeconds = 86400) {
|
|
93049
|
+
this.ttlSeconds = ttlSeconds;
|
|
93050
|
+
}
|
|
93051
|
+
save(params) {
|
|
93052
|
+
const id = import_crypto4.default.randomUUID();
|
|
93053
|
+
savePendingResponse({
|
|
93054
|
+
id,
|
|
93055
|
+
sessionId: params.sessionId,
|
|
93056
|
+
projectId: params.projectId,
|
|
93057
|
+
projectName: params.projectName,
|
|
93058
|
+
feedbackJson: JSON.stringify(params.feedback),
|
|
93059
|
+
feedbackUrl: params.feedbackUrl,
|
|
93060
|
+
ttlSeconds: this.ttlSeconds
|
|
93061
|
+
});
|
|
93062
|
+
logger.info(`[ResponseStore] \u5DF2\u6301\u4E45\u5316\u56DE\u61C9 id=${id}, project=${params.projectId}, session=${params.sessionId}`);
|
|
93063
|
+
return id;
|
|
93064
|
+
}
|
|
93065
|
+
getByProject(projectId) {
|
|
93066
|
+
const row = getPendingResponseByProject(projectId);
|
|
93067
|
+
if (!row) return null;
|
|
93068
|
+
return {
|
|
93069
|
+
id: row.id,
|
|
93070
|
+
sessionId: row.session_id,
|
|
93071
|
+
projectId: row.project_id,
|
|
93072
|
+
projectName: row.project_name,
|
|
93073
|
+
feedback: JSON.parse(row.feedback_json),
|
|
93074
|
+
feedbackUrl: row.feedback_url,
|
|
93075
|
+
createdAt: row.created_at
|
|
93076
|
+
};
|
|
93077
|
+
}
|
|
93078
|
+
getById(id) {
|
|
93079
|
+
const row = getPendingResponseById(id);
|
|
93080
|
+
if (!row || row.delivered) return null;
|
|
93081
|
+
return {
|
|
93082
|
+
id: row.id,
|
|
93083
|
+
sessionId: row.session_id,
|
|
93084
|
+
projectId: row.project_id,
|
|
93085
|
+
projectName: row.project_name,
|
|
93086
|
+
feedback: JSON.parse(row.feedback_json),
|
|
93087
|
+
feedbackUrl: row.feedback_url,
|
|
93088
|
+
createdAt: row.created_at
|
|
93089
|
+
};
|
|
93090
|
+
}
|
|
93091
|
+
markDelivered(id) {
|
|
93092
|
+
markPendingResponseDelivered(id);
|
|
93093
|
+
logger.info(`[ResponseStore] \u56DE\u61C9\u5DF2\u6A19\u8A18\u70BA\u5DF2\u9001\u9054 id=${id}`);
|
|
93094
|
+
}
|
|
93095
|
+
incrementAttempt(id) {
|
|
93096
|
+
incrementDeliveryAttempt(id);
|
|
93097
|
+
}
|
|
93098
|
+
cleanup() {
|
|
93099
|
+
const count = cleanupExpiredPendingResponses();
|
|
93100
|
+
if (count > 0) {
|
|
93101
|
+
logger.info(`[ResponseStore] \u5DF2\u6E05\u7406 ${count} \u7B46\u904E\u671F\u56DE\u61C9`);
|
|
93102
|
+
}
|
|
93103
|
+
return count;
|
|
93104
|
+
}
|
|
93105
|
+
};
|
|
93106
|
+
|
|
92971
93107
|
// src/server/web-server.ts
|
|
92972
93108
|
var VERSION = getPackageVersion();
|
|
92973
93109
|
var WebServer = class {
|
|
@@ -92992,6 +93128,9 @@ var WebServer = class {
|
|
|
92992
93128
|
activeSessionPromises = /* @__PURE__ */ new Map();
|
|
92993
93129
|
dbInitialized = false;
|
|
92994
93130
|
selfProbeService;
|
|
93131
|
+
responseStore;
|
|
93132
|
+
responseCleanupInterval = null;
|
|
93133
|
+
stdioHealthy = true;
|
|
92995
93134
|
/**
|
|
92996
93135
|
* 延遲載入 ImageProcessor
|
|
92997
93136
|
*/
|
|
@@ -93034,6 +93173,7 @@ var WebServer = class {
|
|
|
93034
93173
|
this.portManager = new PortManager();
|
|
93035
93174
|
this.sessionStorage = new SessionStorage();
|
|
93036
93175
|
this.selfProbeService = new SelfProbeService(config2);
|
|
93176
|
+
this.responseStore = new ResponseStore(config2.responseTtl ?? 86400);
|
|
93037
93177
|
this.app = (0, import_express.default)();
|
|
93038
93178
|
this.server = (0, import_http.createServer)(this.app);
|
|
93039
93179
|
this.io = new Server2(this.server, {
|
|
@@ -94864,6 +95004,30 @@ var WebServer = class {
|
|
|
94864
95004
|
});
|
|
94865
95005
|
}
|
|
94866
95006
|
});
|
|
95007
|
+
this.app.get("/api/pending-response/:projectId", (req, res) => {
|
|
95008
|
+
try {
|
|
95009
|
+
this.ensureDatabase();
|
|
95010
|
+
const pending = this.responseStore.getByProject(req.params.projectId);
|
|
95011
|
+
if (pending) {
|
|
95012
|
+
res.json({ success: true, response: pending });
|
|
95013
|
+
} else {
|
|
95014
|
+
res.json({ success: true, response: null });
|
|
95015
|
+
}
|
|
95016
|
+
} catch (error2) {
|
|
95017
|
+
logger.error("\u67E5\u8A62\u672A\u9001\u9054\u56DE\u61C9\u5931\u6557:", error2);
|
|
95018
|
+
res.status(500).json({ success: false, error: "Failed to query pending response" });
|
|
95019
|
+
}
|
|
95020
|
+
});
|
|
95021
|
+
this.app.post("/api/pending-response/:responseId/ack", (req, res) => {
|
|
95022
|
+
try {
|
|
95023
|
+
this.ensureDatabase();
|
|
95024
|
+
this.responseStore.markDelivered(req.params.responseId);
|
|
95025
|
+
res.json({ success: true });
|
|
95026
|
+
} catch (error2) {
|
|
95027
|
+
logger.error("\u78BA\u8A8D\u56DE\u61C9\u9001\u9054\u5931\u6557:", error2);
|
|
95028
|
+
res.status(500).json({ success: false, error: "Failed to acknowledge response" });
|
|
95029
|
+
}
|
|
95030
|
+
});
|
|
94867
95031
|
this.app.use((error2, req, res, _next) => {
|
|
94868
95032
|
logger.error("Express\u932F\u8AA4:", error2);
|
|
94869
95033
|
res.status(500).json({
|
|
@@ -95000,6 +95164,24 @@ var WebServer = class {
|
|
|
95000
95164
|
logger.error(`[\u9023\u7DDA\u7B49\u5F85] \u5DF2\u5617\u8A66 ${maxRounds} \u8F2A\u4ECD\u7121\u6D3B\u8E8D\u9023\u7DDA\uFF0C\u5C07\u76F4\u63A5\u9001\u51FA\uFF08\u5FEB\u53D6\u6A5F\u5236\u4F5C\u70BA\u4FDD\u8B77\uFF09`);
|
|
95001
95165
|
return false;
|
|
95002
95166
|
}
|
|
95167
|
+
isTransportHealthy() {
|
|
95168
|
+
const mode = this.config.mcpTransport || "stdio";
|
|
95169
|
+
if (mode === "stdio") {
|
|
95170
|
+
return this.stdioHealthy;
|
|
95171
|
+
}
|
|
95172
|
+
const hasStreamableSSE = [...this.streamableHttpSseActive.values()].some((v) => v);
|
|
95173
|
+
const hasSSETransport = this.sseTransportsList.length > 0;
|
|
95174
|
+
return hasStreamableSSE || hasSSETransport;
|
|
95175
|
+
}
|
|
95176
|
+
setStdioHealthy(healthy) {
|
|
95177
|
+
if (this.stdioHealthy !== healthy) {
|
|
95178
|
+
this.stdioHealthy = healthy;
|
|
95179
|
+
logger.info(`[Transport] stdio \u5065\u5EB7\u72C0\u614B\u8B8A\u66F4: ${healthy ? "healthy" : "unhealthy"}`);
|
|
95180
|
+
}
|
|
95181
|
+
}
|
|
95182
|
+
getResponseStore() {
|
|
95183
|
+
return this.responseStore;
|
|
95184
|
+
}
|
|
95003
95185
|
async handleFeedbackSubmission(socket, feedbackData) {
|
|
95004
95186
|
const session = this.sessionStorage.getSession(feedbackData.sessionId);
|
|
95005
95187
|
if (!session) {
|
|
@@ -95043,11 +95225,13 @@ var WebServer = class {
|
|
|
95043
95225
|
}
|
|
95044
95226
|
if (session.resolve) {
|
|
95045
95227
|
this.sessionStorage.updateSession(feedbackData.sessionId, { resolved: true });
|
|
95228
|
+
const feedbackUrl = this.generateFeedbackUrl(feedbackData.sessionId);
|
|
95229
|
+
let pendingResponseId;
|
|
95046
95230
|
if (session.projectId) {
|
|
95047
95231
|
const cachedResult2 = {
|
|
95048
95232
|
feedback: session.feedback,
|
|
95049
95233
|
sessionId: feedbackData.sessionId,
|
|
95050
|
-
feedbackUrl
|
|
95234
|
+
feedbackUrl,
|
|
95051
95235
|
projectId: session.projectId,
|
|
95052
95236
|
projectName: session.projectName || ""
|
|
95053
95237
|
};
|
|
@@ -95057,12 +95241,34 @@ var WebServer = class {
|
|
|
95057
95241
|
});
|
|
95058
95242
|
const projectId = session.projectId;
|
|
95059
95243
|
setTimeout(() => this.pendingDeliveryCache.delete(projectId), 6e4);
|
|
95244
|
+
pendingResponseId = this.responseStore.save({
|
|
95245
|
+
sessionId: feedbackData.sessionId,
|
|
95246
|
+
projectId: session.projectId,
|
|
95247
|
+
projectName: session.projectName || "",
|
|
95248
|
+
feedback: session.feedback,
|
|
95249
|
+
feedbackUrl
|
|
95250
|
+
});
|
|
95060
95251
|
}
|
|
95061
|
-
const
|
|
95062
|
-
if (
|
|
95063
|
-
|
|
95252
|
+
const transportHealthy = this.isTransportHealthy();
|
|
95253
|
+
if (transportHealthy) {
|
|
95254
|
+
const isHTTP = this.config.mcpTransport === "sse" || this.config.mcpTransport === "streamable-http";
|
|
95255
|
+
if (isHTTP) {
|
|
95256
|
+
const hasConnection = await this.waitForActiveConnection();
|
|
95257
|
+
if (hasConnection) {
|
|
95258
|
+
logger.info("[\u9023\u7DDA\u76E3\u63A7] \u9023\u7DDA\u78BA\u8A8D\uFF0C\u6B63\u5728\u9001\u51FA\u56DE\u8986\u81F3 MCP Client...");
|
|
95259
|
+
}
|
|
95260
|
+
}
|
|
95261
|
+
session.resolve(session.feedback);
|
|
95262
|
+
if (pendingResponseId) {
|
|
95263
|
+
this.responseStore.markDelivered(pendingResponseId);
|
|
95264
|
+
}
|
|
95265
|
+
} else {
|
|
95266
|
+
logger.warn("[Transport] Transport \u4E0D\u5065\u5EB7\uFF0C\u56DE\u61C9\u5DF2\u6301\u4E45\u5316\u81F3 DB\uFF0C\u7B49\u5F85 MCP Client \u91CD\u9023\u53D6\u56DE");
|
|
95267
|
+
session.resolve(session.feedback);
|
|
95268
|
+
if (pendingResponseId) {
|
|
95269
|
+
this.responseStore.incrementAttempt(pendingResponseId);
|
|
95270
|
+
}
|
|
95064
95271
|
}
|
|
95065
|
-
session.resolve(session.feedback);
|
|
95066
95272
|
const sessionIdToDelete = feedbackData.sessionId;
|
|
95067
95273
|
setTimeout(() => this.sessionStorage.deleteSession(sessionIdToDelete), 5e3);
|
|
95068
95274
|
}
|
|
@@ -95163,6 +95369,18 @@ var WebServer = class {
|
|
|
95163
95369
|
return pendingEntry.result;
|
|
95164
95370
|
}
|
|
95165
95371
|
}
|
|
95372
|
+
const pendingResponse = this.responseStore.getByProject(project.id);
|
|
95373
|
+
if (pendingResponse) {
|
|
95374
|
+
logger.warn(`[ResponseStore \u5FA9\u539F] \u5075\u6E2C\u5230\u5C08\u6848 "${project.name}" \u6709\u672A\u9001\u9054\u7684\u6301\u4E45\u5316\u56DE\u61C9 (id: ${pendingResponse.id})\uFF0C\u76F4\u63A5\u56DE\u50B3`);
|
|
95375
|
+
this.responseStore.markDelivered(pendingResponse.id);
|
|
95376
|
+
return {
|
|
95377
|
+
feedback: pendingResponse.feedback,
|
|
95378
|
+
sessionId: pendingResponse.sessionId,
|
|
95379
|
+
feedbackUrl: pendingResponse.feedbackUrl,
|
|
95380
|
+
projectId: pendingResponse.projectId,
|
|
95381
|
+
projectName: pendingResponse.projectName
|
|
95382
|
+
};
|
|
95383
|
+
}
|
|
95166
95384
|
logger.info(`\u5EFA\u7ACB\u56DE\u994B\u6703\u8A71: ${sessionId}, \u903E\u6642: ${timeoutSeconds}\u79D2, \u5C08\u6848: ${project.name} (${project.id})`);
|
|
95167
95385
|
const feedbackUrl = this.generateFeedbackUrl(sessionId);
|
|
95168
95386
|
const sessionPromise = new Promise((resolve2, reject) => {
|
|
@@ -95364,6 +95582,9 @@ var WebServer = class {
|
|
|
95364
95582
|
cleanupExpiredSessions: () => this.sessionStorage.cleanupExpiredSessions()
|
|
95365
95583
|
});
|
|
95366
95584
|
this.selfProbeService.start();
|
|
95585
|
+
this.responseCleanupInterval = setInterval(() => {
|
|
95586
|
+
this.responseStore.cleanup();
|
|
95587
|
+
}, 36e5);
|
|
95367
95588
|
} catch (error2) {
|
|
95368
95589
|
logger.error("Web\u4F3A\u670D\u5668\u555F\u52D5\u5931\u6557:", error2);
|
|
95369
95590
|
throw new MCPError(
|
|
@@ -95636,6 +95857,10 @@ var WebServer = class {
|
|
|
95636
95857
|
}
|
|
95637
95858
|
}
|
|
95638
95859
|
this.selfProbeService.stop();
|
|
95860
|
+
if (this.responseCleanupInterval) {
|
|
95861
|
+
clearInterval(this.responseCleanupInterval);
|
|
95862
|
+
this.responseCleanupInterval = null;
|
|
95863
|
+
}
|
|
95639
95864
|
this.sessionStorage.clear();
|
|
95640
95865
|
this.sessionStorage.stopCleanupTimer();
|
|
95641
95866
|
this.io.disconnectSockets(true);
|
|
@@ -95890,6 +96115,7 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
95890
96115
|
}
|
|
95891
96116
|
// ========== 延遲啟動相關 ==========
|
|
95892
96117
|
deferredStartupTriggered = false;
|
|
96118
|
+
stdioHeartbeatTimer = null;
|
|
95893
96119
|
/**
|
|
95894
96120
|
* 實作collect_feedback功能
|
|
95895
96121
|
*/
|
|
@@ -96065,6 +96291,7 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
96065
96291
|
});
|
|
96066
96292
|
});
|
|
96067
96293
|
process.stdin.resume();
|
|
96294
|
+
this.startStdioHeartbeat();
|
|
96068
96295
|
} else if (transportMode === "sse" || transportMode === "streamable-http") {
|
|
96069
96296
|
logger.info(`HTTP \u50B3\u8F38\u6A21\u5F0F: ${transportMode}\uFF0CMCP \u9023\u63A5\u5C07\u7531 HTTP \u7AEF\u9EDE\u8655\u7406`);
|
|
96070
96297
|
await this.webServer.startWithMCPEndpoints(this, transportMode);
|
|
@@ -96083,6 +96310,20 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
96083
96310
|
);
|
|
96084
96311
|
}
|
|
96085
96312
|
}
|
|
96313
|
+
startStdioHeartbeat() {
|
|
96314
|
+
const intervalMs = (this.config.stdioHeartbeatInterval ?? 30) * 1e3;
|
|
96315
|
+
this.stdioHeartbeatTimer = setInterval(async () => {
|
|
96316
|
+
try {
|
|
96317
|
+
await this.mcpServer.server.notification({
|
|
96318
|
+
method: "notifications/message",
|
|
96319
|
+
params: { level: "debug", logger: "heartbeat", data: { event: "ping", ts: Date.now() } }
|
|
96320
|
+
});
|
|
96321
|
+
this.webServer.setStdioHealthy(true);
|
|
96322
|
+
} catch {
|
|
96323
|
+
this.webServer.setStdioHealthy(false);
|
|
96324
|
+
}
|
|
96325
|
+
}, intervalMs);
|
|
96326
|
+
}
|
|
96086
96327
|
/**
|
|
96087
96328
|
* 僅啟動Web模式
|
|
96088
96329
|
*/
|
|
@@ -96135,6 +96376,10 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
96135
96376
|
}
|
|
96136
96377
|
try {
|
|
96137
96378
|
logger.info("\u6B63\u5728\u505C\u6B62\u4F3A\u670D\u5668...");
|
|
96379
|
+
if (this.stdioHeartbeatTimer) {
|
|
96380
|
+
clearInterval(this.stdioHeartbeatTimer);
|
|
96381
|
+
this.stdioHeartbeatTimer = null;
|
|
96382
|
+
}
|
|
96138
96383
|
await this.webServer.stop();
|
|
96139
96384
|
if (this.mcpServer) {
|
|
96140
96385
|
await this.mcpServer.close();
|
|
@@ -96175,7 +96420,7 @@ var os3 = __toESM(require("os"), 1);
|
|
|
96175
96420
|
// src/supervisor/ipc-bridge.ts
|
|
96176
96421
|
init_cjs_shims();
|
|
96177
96422
|
var import_events2 = require("events");
|
|
96178
|
-
var
|
|
96423
|
+
var import_crypto5 = require("crypto");
|
|
96179
96424
|
var IPCBridge = class extends import_events2.EventEmitter {
|
|
96180
96425
|
worker = null;
|
|
96181
96426
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
@@ -96221,7 +96466,7 @@ var IPCBridge = class extends import_events2.EventEmitter {
|
|
|
96221
96466
|
if (!this.worker || !this.worker.stdin) {
|
|
96222
96467
|
throw new Error("Worker not attached or stdin not available");
|
|
96223
96468
|
}
|
|
96224
|
-
const id = (0,
|
|
96469
|
+
const id = (0, import_crypto5.randomUUID)();
|
|
96225
96470
|
const request = {
|
|
96226
96471
|
id,
|
|
96227
96472
|
type: "request",
|
|
@@ -96246,7 +96491,7 @@ var IPCBridge = class extends import_events2.EventEmitter {
|
|
|
96246
96491
|
return;
|
|
96247
96492
|
}
|
|
96248
96493
|
const message = {
|
|
96249
|
-
id: (0,
|
|
96494
|
+
id: (0, import_crypto5.randomUUID)(),
|
|
96250
96495
|
type: "event",
|
|
96251
96496
|
method,
|
|
96252
96497
|
params,
|
package/dist/index.cjs
CHANGED
|
@@ -20630,6 +20630,7 @@ var init_crypto_helper = __esm({
|
|
|
20630
20630
|
var database_exports = {};
|
|
20631
20631
|
__export(database_exports, {
|
|
20632
20632
|
batchSetToolEnabled: () => batchSetToolEnabled,
|
|
20633
|
+
cleanupExpiredPendingResponses: () => cleanupExpiredPendingResponses,
|
|
20633
20634
|
cleanupOldAPIErrorLogs: () => cleanupOldAPIErrorLogs,
|
|
20634
20635
|
cleanupOldAPILogs: () => cleanupOldAPILogs,
|
|
20635
20636
|
cleanupOldCLIExecutionLogs: () => cleanupOldCLIExecutionLogs,
|
|
@@ -20658,6 +20659,8 @@ __export(database_exports, {
|
|
|
20658
20659
|
getEnabledNonDeferredMCPServers: () => getEnabledNonDeferredMCPServers,
|
|
20659
20660
|
getLogSources: () => getLogSources,
|
|
20660
20661
|
getMCPServerById: () => getMCPServerById,
|
|
20662
|
+
getPendingResponseById: () => getPendingResponseById,
|
|
20663
|
+
getPendingResponseByProject: () => getPendingResponseByProject,
|
|
20661
20664
|
getPinnedPrompts: () => getPinnedPrompts,
|
|
20662
20665
|
getPromptById: () => getPromptById,
|
|
20663
20666
|
getPromptConfigs: () => getPromptConfigs,
|
|
@@ -20665,6 +20668,7 @@ __export(database_exports, {
|
|
|
20665
20668
|
getSelfProbeSettings: () => getSelfProbeSettings,
|
|
20666
20669
|
getToolEnableConfigs: () => getToolEnableConfigs,
|
|
20667
20670
|
getUserPreferences: () => getUserPreferences,
|
|
20671
|
+
incrementDeliveryAttempt: () => incrementDeliveryAttempt,
|
|
20668
20672
|
initDatabase: () => initDatabase,
|
|
20669
20673
|
insertCLIExecutionLog: () => insertCLIExecutionLog,
|
|
20670
20674
|
insertLog: () => insertLog,
|
|
@@ -20673,12 +20677,14 @@ __export(database_exports, {
|
|
|
20673
20677
|
isToolEnabled: () => isToolEnabled,
|
|
20674
20678
|
logAPIError: () => logAPIError,
|
|
20675
20679
|
logAPIRequest: () => logAPIRequest,
|
|
20680
|
+
markPendingResponseDelivered: () => markPendingResponseDelivered,
|
|
20676
20681
|
queryAPIErrorLogs: () => queryAPIErrorLogs,
|
|
20677
20682
|
queryAPILogs: () => queryAPILogs,
|
|
20678
20683
|
queryLogs: () => queryLogs,
|
|
20679
20684
|
queryMCPServerLogs: () => queryMCPServerLogs,
|
|
20680
20685
|
reorderPrompts: () => reorderPrompts,
|
|
20681
20686
|
resetPromptConfigs: () => resetPromptConfigs,
|
|
20687
|
+
savePendingResponse: () => savePendingResponse,
|
|
20682
20688
|
saveSelfProbeSettings: () => saveSelfProbeSettings,
|
|
20683
20689
|
setToolEnabled: () => setToolEnabled,
|
|
20684
20690
|
toggleMCPServerEnabled: () => toggleMCPServerEnabled,
|
|
@@ -21002,6 +21008,23 @@ function createTables() {
|
|
|
21002
21008
|
)
|
|
21003
21009
|
`);
|
|
21004
21010
|
initDefaultPromptConfigs();
|
|
21011
|
+
db.exec(`
|
|
21012
|
+
CREATE TABLE IF NOT EXISTS pending_responses (
|
|
21013
|
+
id TEXT PRIMARY KEY,
|
|
21014
|
+
session_id TEXT NOT NULL,
|
|
21015
|
+
project_id TEXT NOT NULL,
|
|
21016
|
+
project_name TEXT DEFAULT '',
|
|
21017
|
+
feedback_json TEXT NOT NULL,
|
|
21018
|
+
feedback_url TEXT DEFAULT '',
|
|
21019
|
+
created_at INTEGER NOT NULL,
|
|
21020
|
+
delivered INTEGER DEFAULT 0,
|
|
21021
|
+
delivery_attempts INTEGER DEFAULT 0,
|
|
21022
|
+
last_attempt_at INTEGER,
|
|
21023
|
+
expires_at INTEGER NOT NULL
|
|
21024
|
+
)
|
|
21025
|
+
`);
|
|
21026
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_pending_responses_project ON pending_responses(project_id, delivered)`);
|
|
21027
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_pending_responses_expires ON pending_responses(expires_at)`);
|
|
21005
21028
|
}
|
|
21006
21029
|
function initDefaultSettings() {
|
|
21007
21030
|
if (!db) throw new Error("Database not initialized");
|
|
@@ -22493,6 +22516,50 @@ function resetPromptConfigs() {
|
|
|
22493
22516
|
initDefaultPromptConfigs();
|
|
22494
22517
|
return getPromptConfigs();
|
|
22495
22518
|
}
|
|
22519
|
+
function savePendingResponse(data) {
|
|
22520
|
+
const db2 = tryGetDb();
|
|
22521
|
+
if (!db2) return;
|
|
22522
|
+
const now = Date.now();
|
|
22523
|
+
db2.prepare(`
|
|
22524
|
+
INSERT OR REPLACE INTO pending_responses (id, session_id, project_id, project_name, feedback_json, feedback_url, created_at, delivered, delivery_attempts, last_attempt_at, expires_at)
|
|
22525
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?)
|
|
22526
|
+
`).run(data.id, data.sessionId, data.projectId, data.projectName, data.feedbackJson, data.feedbackUrl, now, now + data.ttlSeconds * 1e3);
|
|
22527
|
+
}
|
|
22528
|
+
function getPendingResponseByProject(projectId) {
|
|
22529
|
+
const db2 = tryGetDb();
|
|
22530
|
+
if (!db2) return null;
|
|
22531
|
+
const now = Date.now();
|
|
22532
|
+
return db2.prepare(`
|
|
22533
|
+
SELECT * FROM pending_responses
|
|
22534
|
+
WHERE project_id = ? AND delivered = 0 AND expires_at > ?
|
|
22535
|
+
ORDER BY created_at DESC LIMIT 1
|
|
22536
|
+
`).get(projectId, now) ?? null;
|
|
22537
|
+
}
|
|
22538
|
+
function getPendingResponseById(id) {
|
|
22539
|
+
const db2 = tryGetDb();
|
|
22540
|
+
if (!db2) return null;
|
|
22541
|
+
return db2.prepare("SELECT * FROM pending_responses WHERE id = ?").get(id) ?? null;
|
|
22542
|
+
}
|
|
22543
|
+
function markPendingResponseDelivered(id) {
|
|
22544
|
+
const db2 = tryGetDb();
|
|
22545
|
+
if (!db2) return;
|
|
22546
|
+
db2.prepare("UPDATE pending_responses SET delivered = 1, delivery_attempts = delivery_attempts + 1, last_attempt_at = ? WHERE id = ?").run(Date.now(), id);
|
|
22547
|
+
}
|
|
22548
|
+
function incrementDeliveryAttempt(id) {
|
|
22549
|
+
const db2 = tryGetDb();
|
|
22550
|
+
if (!db2) return;
|
|
22551
|
+
db2.prepare("UPDATE pending_responses SET delivery_attempts = delivery_attempts + 1, last_attempt_at = ? WHERE id = ?").run(Date.now(), id);
|
|
22552
|
+
}
|
|
22553
|
+
function cleanupExpiredPendingResponses() {
|
|
22554
|
+
const db2 = tryGetDb();
|
|
22555
|
+
if (!db2) return 0;
|
|
22556
|
+
const now = Date.now();
|
|
22557
|
+
const deliveredCutoff = now - 36e5;
|
|
22558
|
+
const result = db2.prepare(`
|
|
22559
|
+
DELETE FROM pending_responses WHERE expires_at < ? OR (delivered = 1 AND last_attempt_at < ?)
|
|
22560
|
+
`).run(now, deliveredCutoff);
|
|
22561
|
+
return result.changes;
|
|
22562
|
+
}
|
|
22496
22563
|
var import_better_sqlite3, import_path2, import_fs2, import_crypto2, DB_DIR, DB_PATH, db, SYSTEM_PROMPT_VERSIONS, CURRENT_PROMPT_VERSION, DEFAULT_PROMPT_CONFIGS;
|
|
22497
22564
|
var init_database = __esm({
|
|
22498
22565
|
"src/utils/database.ts"() {
|
|
@@ -42028,14 +42095,14 @@ var require_etag = __commonJS({
|
|
|
42028
42095
|
"use strict";
|
|
42029
42096
|
init_cjs_shims();
|
|
42030
42097
|
module2.exports = etag;
|
|
42031
|
-
var
|
|
42098
|
+
var crypto5 = require("crypto");
|
|
42032
42099
|
var Stats = require("fs").Stats;
|
|
42033
42100
|
var toString = Object.prototype.toString;
|
|
42034
42101
|
function entitytag(entity) {
|
|
42035
42102
|
if (entity.length === 0) {
|
|
42036
42103
|
return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
|
|
42037
42104
|
}
|
|
42038
|
-
var hash =
|
|
42105
|
+
var hash = crypto5.createHash("sha1").update(entity, "utf8").digest("base64").substring(0, 27);
|
|
42039
42106
|
var len = typeof entity === "string" ? Buffer.byteLength(entity, "utf8") : entity.length;
|
|
42040
42107
|
return '"' + len.toString(16) + "-" + hash + '"';
|
|
42041
42108
|
}
|
|
@@ -44949,11 +45016,11 @@ var require_cookie_signature = __commonJS({
|
|
|
44949
45016
|
"node_modules/.pnpm/cookie-signature@1.0.7/node_modules/cookie-signature/index.js"(exports2) {
|
|
44950
45017
|
"use strict";
|
|
44951
45018
|
init_cjs_shims();
|
|
44952
|
-
var
|
|
45019
|
+
var crypto5 = require("crypto");
|
|
44953
45020
|
exports2.sign = function(val, secret) {
|
|
44954
45021
|
if ("string" !== typeof val) throw new TypeError("Cookie value must be provided as a string.");
|
|
44955
45022
|
if (null == secret) throw new TypeError("Secret key must be provided.");
|
|
44956
|
-
return val + "." +
|
|
45023
|
+
return val + "." + crypto5.createHmac("sha256", secret).update(val).digest("base64").replace(/\=+$/, "");
|
|
44957
45024
|
};
|
|
44958
45025
|
exports2.unsign = function(val, secret) {
|
|
44959
45026
|
if ("string" !== typeof val) throw new TypeError("Signed cookie string must be provided.");
|
|
@@ -44962,7 +45029,7 @@ var require_cookie_signature = __commonJS({
|
|
|
44962
45029
|
return sha1(mac) == sha1(val) ? str : false;
|
|
44963
45030
|
};
|
|
44964
45031
|
function sha1(str) {
|
|
44965
|
-
return
|
|
45032
|
+
return crypto5.createHash("sha1").update(str).digest("hex");
|
|
44966
45033
|
}
|
|
44967
45034
|
}
|
|
44968
45035
|
});
|
|
@@ -46502,7 +46569,7 @@ var require_base64id = __commonJS({
|
|
|
46502
46569
|
"node_modules/.pnpm/base64id@2.0.0/node_modules/base64id/lib/base64id.js"(exports2, module2) {
|
|
46503
46570
|
"use strict";
|
|
46504
46571
|
init_cjs_shims();
|
|
46505
|
-
var
|
|
46572
|
+
var crypto5 = require("crypto");
|
|
46506
46573
|
var Base64Id = function() {
|
|
46507
46574
|
};
|
|
46508
46575
|
Base64Id.prototype.getRandomBytes = function(bytes) {
|
|
@@ -46510,12 +46577,12 @@ var require_base64id = __commonJS({
|
|
|
46510
46577
|
var self = this;
|
|
46511
46578
|
bytes = bytes || 12;
|
|
46512
46579
|
if (bytes > BUFFER_SIZE) {
|
|
46513
|
-
return
|
|
46580
|
+
return crypto5.randomBytes(bytes);
|
|
46514
46581
|
}
|
|
46515
46582
|
var bytesInBuffer = parseInt(BUFFER_SIZE / bytes);
|
|
46516
46583
|
var threshold = parseInt(bytesInBuffer * 0.85);
|
|
46517
46584
|
if (!threshold) {
|
|
46518
|
-
return
|
|
46585
|
+
return crypto5.randomBytes(bytes);
|
|
46519
46586
|
}
|
|
46520
46587
|
if (this.bytesBufferIndex == null) {
|
|
46521
46588
|
this.bytesBufferIndex = -1;
|
|
@@ -46527,14 +46594,14 @@ var require_base64id = __commonJS({
|
|
|
46527
46594
|
if (this.bytesBufferIndex == -1 || this.bytesBufferIndex > threshold) {
|
|
46528
46595
|
if (!this.isGeneratingBytes) {
|
|
46529
46596
|
this.isGeneratingBytes = true;
|
|
46530
|
-
|
|
46597
|
+
crypto5.randomBytes(BUFFER_SIZE, function(err, bytes2) {
|
|
46531
46598
|
self.bytesBuffer = bytes2;
|
|
46532
46599
|
self.bytesBufferIndex = 0;
|
|
46533
46600
|
self.isGeneratingBytes = false;
|
|
46534
46601
|
});
|
|
46535
46602
|
}
|
|
46536
46603
|
if (this.bytesBufferIndex == -1) {
|
|
46537
|
-
return
|
|
46604
|
+
return crypto5.randomBytes(bytes);
|
|
46538
46605
|
}
|
|
46539
46606
|
}
|
|
46540
46607
|
var result = this.bytesBuffer.slice(bytes * this.bytesBufferIndex, bytes * (this.bytesBufferIndex + 1));
|
|
@@ -46548,7 +46615,7 @@ var require_base64id = __commonJS({
|
|
|
46548
46615
|
}
|
|
46549
46616
|
this.sequenceNumber = this.sequenceNumber + 1 | 0;
|
|
46550
46617
|
rand.writeInt32BE(this.sequenceNumber, 11);
|
|
46551
|
-
if (
|
|
46618
|
+
if (crypto5.randomBytes) {
|
|
46552
46619
|
this.getRandomBytes(12).copy(rand);
|
|
46553
46620
|
} else {
|
|
46554
46621
|
[0, 4, 8].forEach(function(i) {
|
|
@@ -85588,7 +85655,7 @@ var require_main2 = __commonJS({
|
|
|
85588
85655
|
var fs9 = require("fs");
|
|
85589
85656
|
var path9 = require("path");
|
|
85590
85657
|
var os3 = require("os");
|
|
85591
|
-
var
|
|
85658
|
+
var crypto5 = require("crypto");
|
|
85592
85659
|
var packageJson = require_package2();
|
|
85593
85660
|
var version2 = packageJson.version;
|
|
85594
85661
|
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
@@ -85807,7 +85874,7 @@ var require_main2 = __commonJS({
|
|
|
85807
85874
|
const authTag = ciphertext.subarray(-16);
|
|
85808
85875
|
ciphertext = ciphertext.subarray(12, -16);
|
|
85809
85876
|
try {
|
|
85810
|
-
const aesgcm =
|
|
85877
|
+
const aesgcm = crypto5.createDecipheriv("aes-256-gcm", key, nonce);
|
|
85811
85878
|
aesgcm.setAuthTag(authTag);
|
|
85812
85879
|
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
85813
85880
|
} catch (error2) {
|
|
@@ -89983,6 +90050,72 @@ data: ${JSON.stringify(message)}
|
|
|
89983
90050
|
}
|
|
89984
90051
|
};
|
|
89985
90052
|
|
|
90053
|
+
// src/utils/response-store.ts
|
|
90054
|
+
init_cjs_shims();
|
|
90055
|
+
var import_crypto4 = __toESM(require("crypto"), 1);
|
|
90056
|
+
init_database();
|
|
90057
|
+
init_logger();
|
|
90058
|
+
var ResponseStore = class {
|
|
90059
|
+
ttlSeconds;
|
|
90060
|
+
constructor(ttlSeconds = 86400) {
|
|
90061
|
+
this.ttlSeconds = ttlSeconds;
|
|
90062
|
+
}
|
|
90063
|
+
save(params) {
|
|
90064
|
+
const id = import_crypto4.default.randomUUID();
|
|
90065
|
+
savePendingResponse({
|
|
90066
|
+
id,
|
|
90067
|
+
sessionId: params.sessionId,
|
|
90068
|
+
projectId: params.projectId,
|
|
90069
|
+
projectName: params.projectName,
|
|
90070
|
+
feedbackJson: JSON.stringify(params.feedback),
|
|
90071
|
+
feedbackUrl: params.feedbackUrl,
|
|
90072
|
+
ttlSeconds: this.ttlSeconds
|
|
90073
|
+
});
|
|
90074
|
+
logger.info(`[ResponseStore] \u5DF2\u6301\u4E45\u5316\u56DE\u61C9 id=${id}, project=${params.projectId}, session=${params.sessionId}`);
|
|
90075
|
+
return id;
|
|
90076
|
+
}
|
|
90077
|
+
getByProject(projectId) {
|
|
90078
|
+
const row = getPendingResponseByProject(projectId);
|
|
90079
|
+
if (!row) return null;
|
|
90080
|
+
return {
|
|
90081
|
+
id: row.id,
|
|
90082
|
+
sessionId: row.session_id,
|
|
90083
|
+
projectId: row.project_id,
|
|
90084
|
+
projectName: row.project_name,
|
|
90085
|
+
feedback: JSON.parse(row.feedback_json),
|
|
90086
|
+
feedbackUrl: row.feedback_url,
|
|
90087
|
+
createdAt: row.created_at
|
|
90088
|
+
};
|
|
90089
|
+
}
|
|
90090
|
+
getById(id) {
|
|
90091
|
+
const row = getPendingResponseById(id);
|
|
90092
|
+
if (!row || row.delivered) return null;
|
|
90093
|
+
return {
|
|
90094
|
+
id: row.id,
|
|
90095
|
+
sessionId: row.session_id,
|
|
90096
|
+
projectId: row.project_id,
|
|
90097
|
+
projectName: row.project_name,
|
|
90098
|
+
feedback: JSON.parse(row.feedback_json),
|
|
90099
|
+
feedbackUrl: row.feedback_url,
|
|
90100
|
+
createdAt: row.created_at
|
|
90101
|
+
};
|
|
90102
|
+
}
|
|
90103
|
+
markDelivered(id) {
|
|
90104
|
+
markPendingResponseDelivered(id);
|
|
90105
|
+
logger.info(`[ResponseStore] \u56DE\u61C9\u5DF2\u6A19\u8A18\u70BA\u5DF2\u9001\u9054 id=${id}`);
|
|
90106
|
+
}
|
|
90107
|
+
incrementAttempt(id) {
|
|
90108
|
+
incrementDeliveryAttempt(id);
|
|
90109
|
+
}
|
|
90110
|
+
cleanup() {
|
|
90111
|
+
const count = cleanupExpiredPendingResponses();
|
|
90112
|
+
if (count > 0) {
|
|
90113
|
+
logger.info(`[ResponseStore] \u5DF2\u6E05\u7406 ${count} \u7B46\u904E\u671F\u56DE\u61C9`);
|
|
90114
|
+
}
|
|
90115
|
+
return count;
|
|
90116
|
+
}
|
|
90117
|
+
};
|
|
90118
|
+
|
|
89986
90119
|
// src/server/web-server.ts
|
|
89987
90120
|
var VERSION = getPackageVersion();
|
|
89988
90121
|
var WebServer = class {
|
|
@@ -90007,6 +90140,9 @@ var WebServer = class {
|
|
|
90007
90140
|
activeSessionPromises = /* @__PURE__ */ new Map();
|
|
90008
90141
|
dbInitialized = false;
|
|
90009
90142
|
selfProbeService;
|
|
90143
|
+
responseStore;
|
|
90144
|
+
responseCleanupInterval = null;
|
|
90145
|
+
stdioHealthy = true;
|
|
90010
90146
|
/**
|
|
90011
90147
|
* 延遲載入 ImageProcessor
|
|
90012
90148
|
*/
|
|
@@ -90049,6 +90185,7 @@ var WebServer = class {
|
|
|
90049
90185
|
this.portManager = new PortManager();
|
|
90050
90186
|
this.sessionStorage = new SessionStorage();
|
|
90051
90187
|
this.selfProbeService = new SelfProbeService(config2);
|
|
90188
|
+
this.responseStore = new ResponseStore(config2.responseTtl ?? 86400);
|
|
90052
90189
|
this.app = (0, import_express.default)();
|
|
90053
90190
|
this.server = (0, import_http.createServer)(this.app);
|
|
90054
90191
|
this.io = new Server2(this.server, {
|
|
@@ -91879,6 +92016,30 @@ var WebServer = class {
|
|
|
91879
92016
|
});
|
|
91880
92017
|
}
|
|
91881
92018
|
});
|
|
92019
|
+
this.app.get("/api/pending-response/:projectId", (req, res) => {
|
|
92020
|
+
try {
|
|
92021
|
+
this.ensureDatabase();
|
|
92022
|
+
const pending = this.responseStore.getByProject(req.params.projectId);
|
|
92023
|
+
if (pending) {
|
|
92024
|
+
res.json({ success: true, response: pending });
|
|
92025
|
+
} else {
|
|
92026
|
+
res.json({ success: true, response: null });
|
|
92027
|
+
}
|
|
92028
|
+
} catch (error2) {
|
|
92029
|
+
logger.error("\u67E5\u8A62\u672A\u9001\u9054\u56DE\u61C9\u5931\u6557:", error2);
|
|
92030
|
+
res.status(500).json({ success: false, error: "Failed to query pending response" });
|
|
92031
|
+
}
|
|
92032
|
+
});
|
|
92033
|
+
this.app.post("/api/pending-response/:responseId/ack", (req, res) => {
|
|
92034
|
+
try {
|
|
92035
|
+
this.ensureDatabase();
|
|
92036
|
+
this.responseStore.markDelivered(req.params.responseId);
|
|
92037
|
+
res.json({ success: true });
|
|
92038
|
+
} catch (error2) {
|
|
92039
|
+
logger.error("\u78BA\u8A8D\u56DE\u61C9\u9001\u9054\u5931\u6557:", error2);
|
|
92040
|
+
res.status(500).json({ success: false, error: "Failed to acknowledge response" });
|
|
92041
|
+
}
|
|
92042
|
+
});
|
|
91882
92043
|
this.app.use((error2, req, res, _next) => {
|
|
91883
92044
|
logger.error("Express\u932F\u8AA4:", error2);
|
|
91884
92045
|
res.status(500).json({
|
|
@@ -92015,6 +92176,24 @@ var WebServer = class {
|
|
|
92015
92176
|
logger.error(`[\u9023\u7DDA\u7B49\u5F85] \u5DF2\u5617\u8A66 ${maxRounds} \u8F2A\u4ECD\u7121\u6D3B\u8E8D\u9023\u7DDA\uFF0C\u5C07\u76F4\u63A5\u9001\u51FA\uFF08\u5FEB\u53D6\u6A5F\u5236\u4F5C\u70BA\u4FDD\u8B77\uFF09`);
|
|
92016
92177
|
return false;
|
|
92017
92178
|
}
|
|
92179
|
+
isTransportHealthy() {
|
|
92180
|
+
const mode = this.config.mcpTransport || "stdio";
|
|
92181
|
+
if (mode === "stdio") {
|
|
92182
|
+
return this.stdioHealthy;
|
|
92183
|
+
}
|
|
92184
|
+
const hasStreamableSSE = [...this.streamableHttpSseActive.values()].some((v) => v);
|
|
92185
|
+
const hasSSETransport = this.sseTransportsList.length > 0;
|
|
92186
|
+
return hasStreamableSSE || hasSSETransport;
|
|
92187
|
+
}
|
|
92188
|
+
setStdioHealthy(healthy) {
|
|
92189
|
+
if (this.stdioHealthy !== healthy) {
|
|
92190
|
+
this.stdioHealthy = healthy;
|
|
92191
|
+
logger.info(`[Transport] stdio \u5065\u5EB7\u72C0\u614B\u8B8A\u66F4: ${healthy ? "healthy" : "unhealthy"}`);
|
|
92192
|
+
}
|
|
92193
|
+
}
|
|
92194
|
+
getResponseStore() {
|
|
92195
|
+
return this.responseStore;
|
|
92196
|
+
}
|
|
92018
92197
|
async handleFeedbackSubmission(socket, feedbackData) {
|
|
92019
92198
|
const session = this.sessionStorage.getSession(feedbackData.sessionId);
|
|
92020
92199
|
if (!session) {
|
|
@@ -92058,11 +92237,13 @@ var WebServer = class {
|
|
|
92058
92237
|
}
|
|
92059
92238
|
if (session.resolve) {
|
|
92060
92239
|
this.sessionStorage.updateSession(feedbackData.sessionId, { resolved: true });
|
|
92240
|
+
const feedbackUrl = this.generateFeedbackUrl(feedbackData.sessionId);
|
|
92241
|
+
let pendingResponseId;
|
|
92061
92242
|
if (session.projectId) {
|
|
92062
92243
|
const cachedResult2 = {
|
|
92063
92244
|
feedback: session.feedback,
|
|
92064
92245
|
sessionId: feedbackData.sessionId,
|
|
92065
|
-
feedbackUrl
|
|
92246
|
+
feedbackUrl,
|
|
92066
92247
|
projectId: session.projectId,
|
|
92067
92248
|
projectName: session.projectName || ""
|
|
92068
92249
|
};
|
|
@@ -92072,12 +92253,34 @@ var WebServer = class {
|
|
|
92072
92253
|
});
|
|
92073
92254
|
const projectId = session.projectId;
|
|
92074
92255
|
setTimeout(() => this.pendingDeliveryCache.delete(projectId), 6e4);
|
|
92256
|
+
pendingResponseId = this.responseStore.save({
|
|
92257
|
+
sessionId: feedbackData.sessionId,
|
|
92258
|
+
projectId: session.projectId,
|
|
92259
|
+
projectName: session.projectName || "",
|
|
92260
|
+
feedback: session.feedback,
|
|
92261
|
+
feedbackUrl
|
|
92262
|
+
});
|
|
92075
92263
|
}
|
|
92076
|
-
const
|
|
92077
|
-
if (
|
|
92078
|
-
|
|
92264
|
+
const transportHealthy = this.isTransportHealthy();
|
|
92265
|
+
if (transportHealthy) {
|
|
92266
|
+
const isHTTP = this.config.mcpTransport === "sse" || this.config.mcpTransport === "streamable-http";
|
|
92267
|
+
if (isHTTP) {
|
|
92268
|
+
const hasConnection = await this.waitForActiveConnection();
|
|
92269
|
+
if (hasConnection) {
|
|
92270
|
+
logger.info("[\u9023\u7DDA\u76E3\u63A7] \u9023\u7DDA\u78BA\u8A8D\uFF0C\u6B63\u5728\u9001\u51FA\u56DE\u8986\u81F3 MCP Client...");
|
|
92271
|
+
}
|
|
92272
|
+
}
|
|
92273
|
+
session.resolve(session.feedback);
|
|
92274
|
+
if (pendingResponseId) {
|
|
92275
|
+
this.responseStore.markDelivered(pendingResponseId);
|
|
92276
|
+
}
|
|
92277
|
+
} else {
|
|
92278
|
+
logger.warn("[Transport] Transport \u4E0D\u5065\u5EB7\uFF0C\u56DE\u61C9\u5DF2\u6301\u4E45\u5316\u81F3 DB\uFF0C\u7B49\u5F85 MCP Client \u91CD\u9023\u53D6\u56DE");
|
|
92279
|
+
session.resolve(session.feedback);
|
|
92280
|
+
if (pendingResponseId) {
|
|
92281
|
+
this.responseStore.incrementAttempt(pendingResponseId);
|
|
92282
|
+
}
|
|
92079
92283
|
}
|
|
92080
|
-
session.resolve(session.feedback);
|
|
92081
92284
|
const sessionIdToDelete = feedbackData.sessionId;
|
|
92082
92285
|
setTimeout(() => this.sessionStorage.deleteSession(sessionIdToDelete), 5e3);
|
|
92083
92286
|
}
|
|
@@ -92178,6 +92381,18 @@ var WebServer = class {
|
|
|
92178
92381
|
return pendingEntry.result;
|
|
92179
92382
|
}
|
|
92180
92383
|
}
|
|
92384
|
+
const pendingResponse = this.responseStore.getByProject(project.id);
|
|
92385
|
+
if (pendingResponse) {
|
|
92386
|
+
logger.warn(`[ResponseStore \u5FA9\u539F] \u5075\u6E2C\u5230\u5C08\u6848 "${project.name}" \u6709\u672A\u9001\u9054\u7684\u6301\u4E45\u5316\u56DE\u61C9 (id: ${pendingResponse.id})\uFF0C\u76F4\u63A5\u56DE\u50B3`);
|
|
92387
|
+
this.responseStore.markDelivered(pendingResponse.id);
|
|
92388
|
+
return {
|
|
92389
|
+
feedback: pendingResponse.feedback,
|
|
92390
|
+
sessionId: pendingResponse.sessionId,
|
|
92391
|
+
feedbackUrl: pendingResponse.feedbackUrl,
|
|
92392
|
+
projectId: pendingResponse.projectId,
|
|
92393
|
+
projectName: pendingResponse.projectName
|
|
92394
|
+
};
|
|
92395
|
+
}
|
|
92181
92396
|
logger.info(`\u5EFA\u7ACB\u56DE\u994B\u6703\u8A71: ${sessionId}, \u903E\u6642: ${timeoutSeconds}\u79D2, \u5C08\u6848: ${project.name} (${project.id})`);
|
|
92182
92397
|
const feedbackUrl = this.generateFeedbackUrl(sessionId);
|
|
92183
92398
|
const sessionPromise = new Promise((resolve, reject) => {
|
|
@@ -92379,6 +92594,9 @@ var WebServer = class {
|
|
|
92379
92594
|
cleanupExpiredSessions: () => this.sessionStorage.cleanupExpiredSessions()
|
|
92380
92595
|
});
|
|
92381
92596
|
this.selfProbeService.start();
|
|
92597
|
+
this.responseCleanupInterval = setInterval(() => {
|
|
92598
|
+
this.responseStore.cleanup();
|
|
92599
|
+
}, 36e5);
|
|
92382
92600
|
} catch (error2) {
|
|
92383
92601
|
logger.error("Web\u4F3A\u670D\u5668\u555F\u52D5\u5931\u6557:", error2);
|
|
92384
92602
|
throw new MCPError(
|
|
@@ -92651,6 +92869,10 @@ var WebServer = class {
|
|
|
92651
92869
|
}
|
|
92652
92870
|
}
|
|
92653
92871
|
this.selfProbeService.stop();
|
|
92872
|
+
if (this.responseCleanupInterval) {
|
|
92873
|
+
clearInterval(this.responseCleanupInterval);
|
|
92874
|
+
this.responseCleanupInterval = null;
|
|
92875
|
+
}
|
|
92654
92876
|
this.sessionStorage.clear();
|
|
92655
92877
|
this.sessionStorage.stopCleanupTimer();
|
|
92656
92878
|
this.io.disconnectSockets(true);
|
|
@@ -92905,6 +93127,7 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
92905
93127
|
}
|
|
92906
93128
|
// ========== 延遲啟動相關 ==========
|
|
92907
93129
|
deferredStartupTriggered = false;
|
|
93130
|
+
stdioHeartbeatTimer = null;
|
|
92908
93131
|
/**
|
|
92909
93132
|
* 實作collect_feedback功能
|
|
92910
93133
|
*/
|
|
@@ -93080,6 +93303,7 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
93080
93303
|
});
|
|
93081
93304
|
});
|
|
93082
93305
|
process.stdin.resume();
|
|
93306
|
+
this.startStdioHeartbeat();
|
|
93083
93307
|
} else if (transportMode === "sse" || transportMode === "streamable-http") {
|
|
93084
93308
|
logger.info(`HTTP \u50B3\u8F38\u6A21\u5F0F: ${transportMode}\uFF0CMCP \u9023\u63A5\u5C07\u7531 HTTP \u7AEF\u9EDE\u8655\u7406`);
|
|
93085
93309
|
await this.webServer.startWithMCPEndpoints(this, transportMode);
|
|
@@ -93098,6 +93322,20 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
93098
93322
|
);
|
|
93099
93323
|
}
|
|
93100
93324
|
}
|
|
93325
|
+
startStdioHeartbeat() {
|
|
93326
|
+
const intervalMs = (this.config.stdioHeartbeatInterval ?? 30) * 1e3;
|
|
93327
|
+
this.stdioHeartbeatTimer = setInterval(async () => {
|
|
93328
|
+
try {
|
|
93329
|
+
await this.mcpServer.server.notification({
|
|
93330
|
+
method: "notifications/message",
|
|
93331
|
+
params: { level: "debug", logger: "heartbeat", data: { event: "ping", ts: Date.now() } }
|
|
93332
|
+
});
|
|
93333
|
+
this.webServer.setStdioHealthy(true);
|
|
93334
|
+
} catch {
|
|
93335
|
+
this.webServer.setStdioHealthy(false);
|
|
93336
|
+
}
|
|
93337
|
+
}, intervalMs);
|
|
93338
|
+
}
|
|
93101
93339
|
/**
|
|
93102
93340
|
* 僅啟動Web模式
|
|
93103
93341
|
*/
|
|
@@ -93150,6 +93388,10 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
93150
93388
|
}
|
|
93151
93389
|
try {
|
|
93152
93390
|
logger.info("\u6B63\u5728\u505C\u6B62\u4F3A\u670D\u5668...");
|
|
93391
|
+
if (this.stdioHeartbeatTimer) {
|
|
93392
|
+
clearInterval(this.stdioHeartbeatTimer);
|
|
93393
|
+
this.stdioHeartbeatTimer = null;
|
|
93394
|
+
}
|
|
93153
93395
|
await this.webServer.stop();
|
|
93154
93396
|
if (this.mcpServer) {
|
|
93155
93397
|
await this.mcpServer.close();
|
|
@@ -93278,7 +93520,10 @@ function createDefaultConfig() {
|
|
|
93278
93520
|
enableSelfProbe: getEnvBoolean("MCP_ENABLE_SELF_PROBE", false),
|
|
93279
93521
|
selfProbeIntervalSeconds: getEnvNumber("MCP_SELF_PROBE_INTERVAL", 300),
|
|
93280
93522
|
// Supervisor 配置
|
|
93281
|
-
supervisor: createSupervisorConfig()
|
|
93523
|
+
supervisor: createSupervisorConfig(),
|
|
93524
|
+
// Response Delivery 可靠性設定
|
|
93525
|
+
responseTtl: getEnvNumber("MCP_RESPONSE_TTL", 86400),
|
|
93526
|
+
stdioHeartbeatInterval: getEnvNumber("MCP_STDIO_HEARTBEAT_INTERVAL", 30)
|
|
93282
93527
|
};
|
|
93283
93528
|
}
|
|
93284
93529
|
function validateConfig(config2) {
|