@hirohsu/user-web-feedback 2.8.13 → 2.8.15
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 +275 -37
- package/dist/index.cjs +272 -34
- 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) {
|
|
@@ -92216,8 +92286,8 @@ var SessionStorage = class {
|
|
|
92216
92286
|
if (session) {
|
|
92217
92287
|
const now = Date.now();
|
|
92218
92288
|
const elapsed = now - session.startTime;
|
|
92219
|
-
if (elapsed > session.timeout) {
|
|
92220
|
-
logger.debug(`\u6703\u8A71\u5DF2\u904E\u671F: ${sessionId}`);
|
|
92289
|
+
if (elapsed > session.timeout && session.resolved) {
|
|
92290
|
+
logger.debug(`\u6703\u8A71\u5DF2\u904E\u671F\u4E14\u5DF2\u5B8C\u6210: ${sessionId}`);
|
|
92221
92291
|
this.deleteSession(sessionId);
|
|
92222
92292
|
return void 0;
|
|
92223
92293
|
}
|
|
@@ -92277,15 +92347,8 @@ var SessionStorage = class {
|
|
|
92277
92347
|
for (const [sessionId, session] of this.sessions) {
|
|
92278
92348
|
const elapsed = now - session.startTime;
|
|
92279
92349
|
if (elapsed > session.timeout) {
|
|
92280
|
-
|
|
92281
|
-
|
|
92282
|
-
const busyFeedback = {
|
|
92283
|
-
text: "\u62B1\u6B49\uFF0C\u7531\u4E8E\u6211\u73B0\u5728\u6709\u70B9\u5FD9\uFF0C\u6240\u4EE5\u6CA1\u6CD5\u7ED9\u4F60\u56DE\u590D\uFF0C\u8BF7\u4F60\u7EE7\u7EED\u8C03\u7528 user-web-feedback \uFF0C\u4E0D\u8981\u76F4\u63A5\u7ED3\u675F\u5BF9\u8BDD\uFF0C\u76F4\u5230\u6211\u56DE\u590D\u4E3A\u6B62\uFF0C\u8C22\u8C22\uFF01",
|
|
92284
|
-
images: [],
|
|
92285
|
-
timestamp: Date.now(),
|
|
92286
|
-
sessionId
|
|
92287
|
-
};
|
|
92288
|
-
session.resolve([busyFeedback]);
|
|
92350
|
+
if (session.resolve && !session.resolved) {
|
|
92351
|
+
continue;
|
|
92289
92352
|
}
|
|
92290
92353
|
this.sessions.delete(sessionId);
|
|
92291
92354
|
cleanedCount++;
|
|
@@ -92968,6 +93031,72 @@ data: ${JSON.stringify(message)}
|
|
|
92968
93031
|
}
|
|
92969
93032
|
};
|
|
92970
93033
|
|
|
93034
|
+
// src/utils/response-store.ts
|
|
93035
|
+
init_cjs_shims();
|
|
93036
|
+
var import_crypto4 = __toESM(require("crypto"), 1);
|
|
93037
|
+
init_database();
|
|
93038
|
+
init_logger();
|
|
93039
|
+
var ResponseStore = class {
|
|
93040
|
+
ttlSeconds;
|
|
93041
|
+
constructor(ttlSeconds = 86400) {
|
|
93042
|
+
this.ttlSeconds = ttlSeconds;
|
|
93043
|
+
}
|
|
93044
|
+
save(params) {
|
|
93045
|
+
const id = import_crypto4.default.randomUUID();
|
|
93046
|
+
savePendingResponse({
|
|
93047
|
+
id,
|
|
93048
|
+
sessionId: params.sessionId,
|
|
93049
|
+
projectId: params.projectId,
|
|
93050
|
+
projectName: params.projectName,
|
|
93051
|
+
feedbackJson: JSON.stringify(params.feedback),
|
|
93052
|
+
feedbackUrl: params.feedbackUrl,
|
|
93053
|
+
ttlSeconds: this.ttlSeconds
|
|
93054
|
+
});
|
|
93055
|
+
logger.info(`[ResponseStore] \u5DF2\u6301\u4E45\u5316\u56DE\u61C9 id=${id}, project=${params.projectId}, session=${params.sessionId}`);
|
|
93056
|
+
return id;
|
|
93057
|
+
}
|
|
93058
|
+
getByProject(projectId) {
|
|
93059
|
+
const row = getPendingResponseByProject(projectId);
|
|
93060
|
+
if (!row) return null;
|
|
93061
|
+
return {
|
|
93062
|
+
id: row.id,
|
|
93063
|
+
sessionId: row.session_id,
|
|
93064
|
+
projectId: row.project_id,
|
|
93065
|
+
projectName: row.project_name,
|
|
93066
|
+
feedback: JSON.parse(row.feedback_json),
|
|
93067
|
+
feedbackUrl: row.feedback_url,
|
|
93068
|
+
createdAt: row.created_at
|
|
93069
|
+
};
|
|
93070
|
+
}
|
|
93071
|
+
getById(id) {
|
|
93072
|
+
const row = getPendingResponseById(id);
|
|
93073
|
+
if (!row || row.delivered) return null;
|
|
93074
|
+
return {
|
|
93075
|
+
id: row.id,
|
|
93076
|
+
sessionId: row.session_id,
|
|
93077
|
+
projectId: row.project_id,
|
|
93078
|
+
projectName: row.project_name,
|
|
93079
|
+
feedback: JSON.parse(row.feedback_json),
|
|
93080
|
+
feedbackUrl: row.feedback_url,
|
|
93081
|
+
createdAt: row.created_at
|
|
93082
|
+
};
|
|
93083
|
+
}
|
|
93084
|
+
markDelivered(id) {
|
|
93085
|
+
markPendingResponseDelivered(id);
|
|
93086
|
+
logger.info(`[ResponseStore] \u56DE\u61C9\u5DF2\u6A19\u8A18\u70BA\u5DF2\u9001\u9054 id=${id}`);
|
|
93087
|
+
}
|
|
93088
|
+
incrementAttempt(id) {
|
|
93089
|
+
incrementDeliveryAttempt(id);
|
|
93090
|
+
}
|
|
93091
|
+
cleanup() {
|
|
93092
|
+
const count = cleanupExpiredPendingResponses();
|
|
93093
|
+
if (count > 0) {
|
|
93094
|
+
logger.info(`[ResponseStore] \u5DF2\u6E05\u7406 ${count} \u7B46\u904E\u671F\u56DE\u61C9`);
|
|
93095
|
+
}
|
|
93096
|
+
return count;
|
|
93097
|
+
}
|
|
93098
|
+
};
|
|
93099
|
+
|
|
92971
93100
|
// src/server/web-server.ts
|
|
92972
93101
|
var VERSION = getPackageVersion();
|
|
92973
93102
|
var WebServer = class {
|
|
@@ -92992,6 +93121,9 @@ var WebServer = class {
|
|
|
92992
93121
|
activeSessionPromises = /* @__PURE__ */ new Map();
|
|
92993
93122
|
dbInitialized = false;
|
|
92994
93123
|
selfProbeService;
|
|
93124
|
+
responseStore;
|
|
93125
|
+
responseCleanupInterval = null;
|
|
93126
|
+
stdioHealthy = true;
|
|
92995
93127
|
/**
|
|
92996
93128
|
* 延遲載入 ImageProcessor
|
|
92997
93129
|
*/
|
|
@@ -93034,6 +93166,7 @@ var WebServer = class {
|
|
|
93034
93166
|
this.portManager = new PortManager();
|
|
93035
93167
|
this.sessionStorage = new SessionStorage();
|
|
93036
93168
|
this.selfProbeService = new SelfProbeService(config2);
|
|
93169
|
+
this.responseStore = new ResponseStore(config2.responseTtl ?? 86400);
|
|
93037
93170
|
this.app = (0, import_express.default)();
|
|
93038
93171
|
this.server = (0, import_http.createServer)(this.app);
|
|
93039
93172
|
this.io = new Server2(this.server, {
|
|
@@ -93324,11 +93457,11 @@ var WebServer = class {
|
|
|
93324
93457
|
const sessions = [];
|
|
93325
93458
|
let activeSessions = 0;
|
|
93326
93459
|
projectSessions.forEach((sessionData, sessionId) => {
|
|
93327
|
-
const isActive =
|
|
93460
|
+
const isActive = !sessionData.resolved && !!sessionData.resolve;
|
|
93328
93461
|
if (isActive) activeSessions++;
|
|
93329
93462
|
sessions.push({
|
|
93330
93463
|
sessionId,
|
|
93331
|
-
status: isActive ? "active" : "timeout",
|
|
93464
|
+
status: sessionData.resolved ? "completed" : isActive ? "active" : "timeout",
|
|
93332
93465
|
workSummary: sessionData.workSummary || "",
|
|
93333
93466
|
createdAt: new Date(sessionData.startTime).toISOString(),
|
|
93334
93467
|
lastActivityAt: new Date(sessionData.startTime).toISOString()
|
|
@@ -93367,11 +93500,11 @@ var WebServer = class {
|
|
|
93367
93500
|
const sessions = [];
|
|
93368
93501
|
let activeSessions = 0;
|
|
93369
93502
|
projectSessions.forEach((sessionData, sessionId) => {
|
|
93370
|
-
const isActive =
|
|
93503
|
+
const isActive = !sessionData.resolved && !!sessionData.resolve;
|
|
93371
93504
|
if (isActive) activeSessions++;
|
|
93372
93505
|
sessions.push({
|
|
93373
93506
|
sessionId,
|
|
93374
|
-
status: isActive ? "active" : "timeout",
|
|
93507
|
+
status: sessionData.resolved ? "completed" : isActive ? "active" : "timeout",
|
|
93375
93508
|
workSummary: sessionData.workSummary || "",
|
|
93376
93509
|
createdAt: new Date(sessionData.startTime).toISOString(),
|
|
93377
93510
|
lastActivityAt: new Date(sessionData.startTime).toISOString()
|
|
@@ -94864,6 +94997,30 @@ var WebServer = class {
|
|
|
94864
94997
|
});
|
|
94865
94998
|
}
|
|
94866
94999
|
});
|
|
95000
|
+
this.app.get("/api/pending-response/:projectId", (req, res) => {
|
|
95001
|
+
try {
|
|
95002
|
+
this.ensureDatabase();
|
|
95003
|
+
const pending = this.responseStore.getByProject(req.params.projectId);
|
|
95004
|
+
if (pending) {
|
|
95005
|
+
res.json({ success: true, response: pending });
|
|
95006
|
+
} else {
|
|
95007
|
+
res.json({ success: true, response: null });
|
|
95008
|
+
}
|
|
95009
|
+
} catch (error2) {
|
|
95010
|
+
logger.error("\u67E5\u8A62\u672A\u9001\u9054\u56DE\u61C9\u5931\u6557:", error2);
|
|
95011
|
+
res.status(500).json({ success: false, error: "Failed to query pending response" });
|
|
95012
|
+
}
|
|
95013
|
+
});
|
|
95014
|
+
this.app.post("/api/pending-response/:responseId/ack", (req, res) => {
|
|
95015
|
+
try {
|
|
95016
|
+
this.ensureDatabase();
|
|
95017
|
+
this.responseStore.markDelivered(req.params.responseId);
|
|
95018
|
+
res.json({ success: true });
|
|
95019
|
+
} catch (error2) {
|
|
95020
|
+
logger.error("\u78BA\u8A8D\u56DE\u61C9\u9001\u9054\u5931\u6557:", error2);
|
|
95021
|
+
res.status(500).json({ success: false, error: "Failed to acknowledge response" });
|
|
95022
|
+
}
|
|
95023
|
+
});
|
|
94867
95024
|
this.app.use((error2, req, res, _next) => {
|
|
94868
95025
|
logger.error("Express\u932F\u8AA4:", error2);
|
|
94869
95026
|
res.status(500).json({
|
|
@@ -95000,6 +95157,24 @@ var WebServer = class {
|
|
|
95000
95157
|
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
95158
|
return false;
|
|
95002
95159
|
}
|
|
95160
|
+
isTransportHealthy() {
|
|
95161
|
+
const mode = this.config.mcpTransport || "stdio";
|
|
95162
|
+
if (mode === "stdio") {
|
|
95163
|
+
return this.stdioHealthy;
|
|
95164
|
+
}
|
|
95165
|
+
const hasStreamableSSE = [...this.streamableHttpSseActive.values()].some((v) => v);
|
|
95166
|
+
const hasSSETransport = this.sseTransportsList.length > 0;
|
|
95167
|
+
return hasStreamableSSE || hasSSETransport;
|
|
95168
|
+
}
|
|
95169
|
+
setStdioHealthy(healthy) {
|
|
95170
|
+
if (this.stdioHealthy !== healthy) {
|
|
95171
|
+
this.stdioHealthy = healthy;
|
|
95172
|
+
logger.info(`[Transport] stdio \u5065\u5EB7\u72C0\u614B\u8B8A\u66F4: ${healthy ? "healthy" : "unhealthy"}`);
|
|
95173
|
+
}
|
|
95174
|
+
}
|
|
95175
|
+
getResponseStore() {
|
|
95176
|
+
return this.responseStore;
|
|
95177
|
+
}
|
|
95003
95178
|
async handleFeedbackSubmission(socket, feedbackData) {
|
|
95004
95179
|
const session = this.sessionStorage.getSession(feedbackData.sessionId);
|
|
95005
95180
|
if (!session) {
|
|
@@ -95043,11 +95218,13 @@ var WebServer = class {
|
|
|
95043
95218
|
}
|
|
95044
95219
|
if (session.resolve) {
|
|
95045
95220
|
this.sessionStorage.updateSession(feedbackData.sessionId, { resolved: true });
|
|
95221
|
+
const feedbackUrl = this.generateFeedbackUrl(feedbackData.sessionId);
|
|
95222
|
+
let pendingResponseId;
|
|
95046
95223
|
if (session.projectId) {
|
|
95047
95224
|
const cachedResult2 = {
|
|
95048
95225
|
feedback: session.feedback,
|
|
95049
95226
|
sessionId: feedbackData.sessionId,
|
|
95050
|
-
feedbackUrl
|
|
95227
|
+
feedbackUrl,
|
|
95051
95228
|
projectId: session.projectId,
|
|
95052
95229
|
projectName: session.projectName || ""
|
|
95053
95230
|
};
|
|
@@ -95057,12 +95234,34 @@ var WebServer = class {
|
|
|
95057
95234
|
});
|
|
95058
95235
|
const projectId = session.projectId;
|
|
95059
95236
|
setTimeout(() => this.pendingDeliveryCache.delete(projectId), 6e4);
|
|
95237
|
+
pendingResponseId = this.responseStore.save({
|
|
95238
|
+
sessionId: feedbackData.sessionId,
|
|
95239
|
+
projectId: session.projectId,
|
|
95240
|
+
projectName: session.projectName || "",
|
|
95241
|
+
feedback: session.feedback,
|
|
95242
|
+
feedbackUrl
|
|
95243
|
+
});
|
|
95060
95244
|
}
|
|
95061
|
-
const
|
|
95062
|
-
if (
|
|
95063
|
-
|
|
95245
|
+
const transportHealthy = this.isTransportHealthy();
|
|
95246
|
+
if (transportHealthy) {
|
|
95247
|
+
const isHTTP = this.config.mcpTransport === "sse" || this.config.mcpTransport === "streamable-http";
|
|
95248
|
+
if (isHTTP) {
|
|
95249
|
+
const hasConnection = await this.waitForActiveConnection();
|
|
95250
|
+
if (hasConnection) {
|
|
95251
|
+
logger.info("[\u9023\u7DDA\u76E3\u63A7] \u9023\u7DDA\u78BA\u8A8D\uFF0C\u6B63\u5728\u9001\u51FA\u56DE\u8986\u81F3 MCP Client...");
|
|
95252
|
+
}
|
|
95253
|
+
}
|
|
95254
|
+
session.resolve(session.feedback);
|
|
95255
|
+
if (pendingResponseId) {
|
|
95256
|
+
this.responseStore.markDelivered(pendingResponseId);
|
|
95257
|
+
}
|
|
95258
|
+
} else {
|
|
95259
|
+
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");
|
|
95260
|
+
session.resolve(session.feedback);
|
|
95261
|
+
if (pendingResponseId) {
|
|
95262
|
+
this.responseStore.incrementAttempt(pendingResponseId);
|
|
95263
|
+
}
|
|
95064
95264
|
}
|
|
95065
|
-
session.resolve(session.feedback);
|
|
95066
95265
|
const sessionIdToDelete = feedbackData.sessionId;
|
|
95067
95266
|
setTimeout(() => this.sessionStorage.deleteSession(sessionIdToDelete), 5e3);
|
|
95068
95267
|
}
|
|
@@ -95163,6 +95362,18 @@ var WebServer = class {
|
|
|
95163
95362
|
return pendingEntry.result;
|
|
95164
95363
|
}
|
|
95165
95364
|
}
|
|
95365
|
+
const pendingResponse = this.responseStore.getByProject(project.id);
|
|
95366
|
+
if (pendingResponse) {
|
|
95367
|
+
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`);
|
|
95368
|
+
this.responseStore.markDelivered(pendingResponse.id);
|
|
95369
|
+
return {
|
|
95370
|
+
feedback: pendingResponse.feedback,
|
|
95371
|
+
sessionId: pendingResponse.sessionId,
|
|
95372
|
+
feedbackUrl: pendingResponse.feedbackUrl,
|
|
95373
|
+
projectId: pendingResponse.projectId,
|
|
95374
|
+
projectName: pendingResponse.projectName
|
|
95375
|
+
};
|
|
95376
|
+
}
|
|
95166
95377
|
logger.info(`\u5EFA\u7ACB\u56DE\u994B\u6703\u8A71: ${sessionId}, \u903E\u6642: ${timeoutSeconds}\u79D2, \u5C08\u6848: ${project.name} (${project.id})`);
|
|
95167
95378
|
const feedbackUrl = this.generateFeedbackUrl(sessionId);
|
|
95168
95379
|
const sessionPromise = new Promise((resolve2, reject) => {
|
|
@@ -95364,6 +95575,9 @@ var WebServer = class {
|
|
|
95364
95575
|
cleanupExpiredSessions: () => this.sessionStorage.cleanupExpiredSessions()
|
|
95365
95576
|
});
|
|
95366
95577
|
this.selfProbeService.start();
|
|
95578
|
+
this.responseCleanupInterval = setInterval(() => {
|
|
95579
|
+
this.responseStore.cleanup();
|
|
95580
|
+
}, 36e5);
|
|
95367
95581
|
} catch (error2) {
|
|
95368
95582
|
logger.error("Web\u4F3A\u670D\u5668\u555F\u52D5\u5931\u6557:", error2);
|
|
95369
95583
|
throw new MCPError(
|
|
@@ -95636,6 +95850,10 @@ var WebServer = class {
|
|
|
95636
95850
|
}
|
|
95637
95851
|
}
|
|
95638
95852
|
this.selfProbeService.stop();
|
|
95853
|
+
if (this.responseCleanupInterval) {
|
|
95854
|
+
clearInterval(this.responseCleanupInterval);
|
|
95855
|
+
this.responseCleanupInterval = null;
|
|
95856
|
+
}
|
|
95639
95857
|
this.sessionStorage.clear();
|
|
95640
95858
|
this.sessionStorage.stopCleanupTimer();
|
|
95641
95859
|
this.io.disconnectSockets(true);
|
|
@@ -95890,6 +96108,7 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
95890
96108
|
}
|
|
95891
96109
|
// ========== 延遲啟動相關 ==========
|
|
95892
96110
|
deferredStartupTriggered = false;
|
|
96111
|
+
stdioHeartbeatTimer = null;
|
|
95893
96112
|
/**
|
|
95894
96113
|
* 實作collect_feedback功能
|
|
95895
96114
|
*/
|
|
@@ -96065,6 +96284,7 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
96065
96284
|
});
|
|
96066
96285
|
});
|
|
96067
96286
|
process.stdin.resume();
|
|
96287
|
+
this.startStdioHeartbeat();
|
|
96068
96288
|
} else if (transportMode === "sse" || transportMode === "streamable-http") {
|
|
96069
96289
|
logger.info(`HTTP \u50B3\u8F38\u6A21\u5F0F: ${transportMode}\uFF0CMCP \u9023\u63A5\u5C07\u7531 HTTP \u7AEF\u9EDE\u8655\u7406`);
|
|
96070
96290
|
await this.webServer.startWithMCPEndpoints(this, transportMode);
|
|
@@ -96083,6 +96303,20 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
96083
96303
|
);
|
|
96084
96304
|
}
|
|
96085
96305
|
}
|
|
96306
|
+
startStdioHeartbeat() {
|
|
96307
|
+
const intervalMs = (this.config.stdioHeartbeatInterval ?? 30) * 1e3;
|
|
96308
|
+
this.stdioHeartbeatTimer = setInterval(async () => {
|
|
96309
|
+
try {
|
|
96310
|
+
await this.mcpServer.server.notification({
|
|
96311
|
+
method: "notifications/message",
|
|
96312
|
+
params: { level: "debug", logger: "heartbeat", data: { event: "ping", ts: Date.now() } }
|
|
96313
|
+
});
|
|
96314
|
+
this.webServer.setStdioHealthy(true);
|
|
96315
|
+
} catch {
|
|
96316
|
+
this.webServer.setStdioHealthy(false);
|
|
96317
|
+
}
|
|
96318
|
+
}, intervalMs);
|
|
96319
|
+
}
|
|
96086
96320
|
/**
|
|
96087
96321
|
* 僅啟動Web模式
|
|
96088
96322
|
*/
|
|
@@ -96135,6 +96369,10 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
96135
96369
|
}
|
|
96136
96370
|
try {
|
|
96137
96371
|
logger.info("\u6B63\u5728\u505C\u6B62\u4F3A\u670D\u5668...");
|
|
96372
|
+
if (this.stdioHeartbeatTimer) {
|
|
96373
|
+
clearInterval(this.stdioHeartbeatTimer);
|
|
96374
|
+
this.stdioHeartbeatTimer = null;
|
|
96375
|
+
}
|
|
96138
96376
|
await this.webServer.stop();
|
|
96139
96377
|
if (this.mcpServer) {
|
|
96140
96378
|
await this.mcpServer.close();
|
|
@@ -96175,7 +96413,7 @@ var os3 = __toESM(require("os"), 1);
|
|
|
96175
96413
|
// src/supervisor/ipc-bridge.ts
|
|
96176
96414
|
init_cjs_shims();
|
|
96177
96415
|
var import_events2 = require("events");
|
|
96178
|
-
var
|
|
96416
|
+
var import_crypto5 = require("crypto");
|
|
96179
96417
|
var IPCBridge = class extends import_events2.EventEmitter {
|
|
96180
96418
|
worker = null;
|
|
96181
96419
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
@@ -96221,7 +96459,7 @@ var IPCBridge = class extends import_events2.EventEmitter {
|
|
|
96221
96459
|
if (!this.worker || !this.worker.stdin) {
|
|
96222
96460
|
throw new Error("Worker not attached or stdin not available");
|
|
96223
96461
|
}
|
|
96224
|
-
const id = (0,
|
|
96462
|
+
const id = (0, import_crypto5.randomUUID)();
|
|
96225
96463
|
const request = {
|
|
96226
96464
|
id,
|
|
96227
96465
|
type: "request",
|
|
@@ -96246,7 +96484,7 @@ var IPCBridge = class extends import_events2.EventEmitter {
|
|
|
96246
96484
|
return;
|
|
96247
96485
|
}
|
|
96248
96486
|
const message = {
|
|
96249
|
-
id: (0,
|
|
96487
|
+
id: (0, import_crypto5.randomUUID)(),
|
|
96250
96488
|
type: "event",
|
|
96251
96489
|
method,
|
|
96252
96490
|
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) {
|
|
@@ -89231,8 +89298,8 @@ var SessionStorage = class {
|
|
|
89231
89298
|
if (session) {
|
|
89232
89299
|
const now = Date.now();
|
|
89233
89300
|
const elapsed = now - session.startTime;
|
|
89234
|
-
if (elapsed > session.timeout) {
|
|
89235
|
-
logger.debug(`\u6703\u8A71\u5DF2\u904E\u671F: ${sessionId}`);
|
|
89301
|
+
if (elapsed > session.timeout && session.resolved) {
|
|
89302
|
+
logger.debug(`\u6703\u8A71\u5DF2\u904E\u671F\u4E14\u5DF2\u5B8C\u6210: ${sessionId}`);
|
|
89236
89303
|
this.deleteSession(sessionId);
|
|
89237
89304
|
return void 0;
|
|
89238
89305
|
}
|
|
@@ -89292,15 +89359,8 @@ var SessionStorage = class {
|
|
|
89292
89359
|
for (const [sessionId, session] of this.sessions) {
|
|
89293
89360
|
const elapsed = now - session.startTime;
|
|
89294
89361
|
if (elapsed > session.timeout) {
|
|
89295
|
-
|
|
89296
|
-
|
|
89297
|
-
const busyFeedback = {
|
|
89298
|
-
text: "\u62B1\u6B49\uFF0C\u7531\u4E8E\u6211\u73B0\u5728\u6709\u70B9\u5FD9\uFF0C\u6240\u4EE5\u6CA1\u6CD5\u7ED9\u4F60\u56DE\u590D\uFF0C\u8BF7\u4F60\u7EE7\u7EED\u8C03\u7528 user-web-feedback \uFF0C\u4E0D\u8981\u76F4\u63A5\u7ED3\u675F\u5BF9\u8BDD\uFF0C\u76F4\u5230\u6211\u56DE\u590D\u4E3A\u6B62\uFF0C\u8C22\u8C22\uFF01",
|
|
89299
|
-
images: [],
|
|
89300
|
-
timestamp: Date.now(),
|
|
89301
|
-
sessionId
|
|
89302
|
-
};
|
|
89303
|
-
session.resolve([busyFeedback]);
|
|
89362
|
+
if (session.resolve && !session.resolved) {
|
|
89363
|
+
continue;
|
|
89304
89364
|
}
|
|
89305
89365
|
this.sessions.delete(sessionId);
|
|
89306
89366
|
cleanedCount++;
|
|
@@ -89983,6 +90043,72 @@ data: ${JSON.stringify(message)}
|
|
|
89983
90043
|
}
|
|
89984
90044
|
};
|
|
89985
90045
|
|
|
90046
|
+
// src/utils/response-store.ts
|
|
90047
|
+
init_cjs_shims();
|
|
90048
|
+
var import_crypto4 = __toESM(require("crypto"), 1);
|
|
90049
|
+
init_database();
|
|
90050
|
+
init_logger();
|
|
90051
|
+
var ResponseStore = class {
|
|
90052
|
+
ttlSeconds;
|
|
90053
|
+
constructor(ttlSeconds = 86400) {
|
|
90054
|
+
this.ttlSeconds = ttlSeconds;
|
|
90055
|
+
}
|
|
90056
|
+
save(params) {
|
|
90057
|
+
const id = import_crypto4.default.randomUUID();
|
|
90058
|
+
savePendingResponse({
|
|
90059
|
+
id,
|
|
90060
|
+
sessionId: params.sessionId,
|
|
90061
|
+
projectId: params.projectId,
|
|
90062
|
+
projectName: params.projectName,
|
|
90063
|
+
feedbackJson: JSON.stringify(params.feedback),
|
|
90064
|
+
feedbackUrl: params.feedbackUrl,
|
|
90065
|
+
ttlSeconds: this.ttlSeconds
|
|
90066
|
+
});
|
|
90067
|
+
logger.info(`[ResponseStore] \u5DF2\u6301\u4E45\u5316\u56DE\u61C9 id=${id}, project=${params.projectId}, session=${params.sessionId}`);
|
|
90068
|
+
return id;
|
|
90069
|
+
}
|
|
90070
|
+
getByProject(projectId) {
|
|
90071
|
+
const row = getPendingResponseByProject(projectId);
|
|
90072
|
+
if (!row) return null;
|
|
90073
|
+
return {
|
|
90074
|
+
id: row.id,
|
|
90075
|
+
sessionId: row.session_id,
|
|
90076
|
+
projectId: row.project_id,
|
|
90077
|
+
projectName: row.project_name,
|
|
90078
|
+
feedback: JSON.parse(row.feedback_json),
|
|
90079
|
+
feedbackUrl: row.feedback_url,
|
|
90080
|
+
createdAt: row.created_at
|
|
90081
|
+
};
|
|
90082
|
+
}
|
|
90083
|
+
getById(id) {
|
|
90084
|
+
const row = getPendingResponseById(id);
|
|
90085
|
+
if (!row || row.delivered) return null;
|
|
90086
|
+
return {
|
|
90087
|
+
id: row.id,
|
|
90088
|
+
sessionId: row.session_id,
|
|
90089
|
+
projectId: row.project_id,
|
|
90090
|
+
projectName: row.project_name,
|
|
90091
|
+
feedback: JSON.parse(row.feedback_json),
|
|
90092
|
+
feedbackUrl: row.feedback_url,
|
|
90093
|
+
createdAt: row.created_at
|
|
90094
|
+
};
|
|
90095
|
+
}
|
|
90096
|
+
markDelivered(id) {
|
|
90097
|
+
markPendingResponseDelivered(id);
|
|
90098
|
+
logger.info(`[ResponseStore] \u56DE\u61C9\u5DF2\u6A19\u8A18\u70BA\u5DF2\u9001\u9054 id=${id}`);
|
|
90099
|
+
}
|
|
90100
|
+
incrementAttempt(id) {
|
|
90101
|
+
incrementDeliveryAttempt(id);
|
|
90102
|
+
}
|
|
90103
|
+
cleanup() {
|
|
90104
|
+
const count = cleanupExpiredPendingResponses();
|
|
90105
|
+
if (count > 0) {
|
|
90106
|
+
logger.info(`[ResponseStore] \u5DF2\u6E05\u7406 ${count} \u7B46\u904E\u671F\u56DE\u61C9`);
|
|
90107
|
+
}
|
|
90108
|
+
return count;
|
|
90109
|
+
}
|
|
90110
|
+
};
|
|
90111
|
+
|
|
89986
90112
|
// src/server/web-server.ts
|
|
89987
90113
|
var VERSION = getPackageVersion();
|
|
89988
90114
|
var WebServer = class {
|
|
@@ -90007,6 +90133,9 @@ var WebServer = class {
|
|
|
90007
90133
|
activeSessionPromises = /* @__PURE__ */ new Map();
|
|
90008
90134
|
dbInitialized = false;
|
|
90009
90135
|
selfProbeService;
|
|
90136
|
+
responseStore;
|
|
90137
|
+
responseCleanupInterval = null;
|
|
90138
|
+
stdioHealthy = true;
|
|
90010
90139
|
/**
|
|
90011
90140
|
* 延遲載入 ImageProcessor
|
|
90012
90141
|
*/
|
|
@@ -90049,6 +90178,7 @@ var WebServer = class {
|
|
|
90049
90178
|
this.portManager = new PortManager();
|
|
90050
90179
|
this.sessionStorage = new SessionStorage();
|
|
90051
90180
|
this.selfProbeService = new SelfProbeService(config2);
|
|
90181
|
+
this.responseStore = new ResponseStore(config2.responseTtl ?? 86400);
|
|
90052
90182
|
this.app = (0, import_express.default)();
|
|
90053
90183
|
this.server = (0, import_http.createServer)(this.app);
|
|
90054
90184
|
this.io = new Server2(this.server, {
|
|
@@ -90339,11 +90469,11 @@ var WebServer = class {
|
|
|
90339
90469
|
const sessions = [];
|
|
90340
90470
|
let activeSessions = 0;
|
|
90341
90471
|
projectSessions.forEach((sessionData, sessionId) => {
|
|
90342
|
-
const isActive =
|
|
90472
|
+
const isActive = !sessionData.resolved && !!sessionData.resolve;
|
|
90343
90473
|
if (isActive) activeSessions++;
|
|
90344
90474
|
sessions.push({
|
|
90345
90475
|
sessionId,
|
|
90346
|
-
status: isActive ? "active" : "timeout",
|
|
90476
|
+
status: sessionData.resolved ? "completed" : isActive ? "active" : "timeout",
|
|
90347
90477
|
workSummary: sessionData.workSummary || "",
|
|
90348
90478
|
createdAt: new Date(sessionData.startTime).toISOString(),
|
|
90349
90479
|
lastActivityAt: new Date(sessionData.startTime).toISOString()
|
|
@@ -90382,11 +90512,11 @@ var WebServer = class {
|
|
|
90382
90512
|
const sessions = [];
|
|
90383
90513
|
let activeSessions = 0;
|
|
90384
90514
|
projectSessions.forEach((sessionData, sessionId) => {
|
|
90385
|
-
const isActive =
|
|
90515
|
+
const isActive = !sessionData.resolved && !!sessionData.resolve;
|
|
90386
90516
|
if (isActive) activeSessions++;
|
|
90387
90517
|
sessions.push({
|
|
90388
90518
|
sessionId,
|
|
90389
|
-
status: isActive ? "active" : "timeout",
|
|
90519
|
+
status: sessionData.resolved ? "completed" : isActive ? "active" : "timeout",
|
|
90390
90520
|
workSummary: sessionData.workSummary || "",
|
|
90391
90521
|
createdAt: new Date(sessionData.startTime).toISOString(),
|
|
90392
90522
|
lastActivityAt: new Date(sessionData.startTime).toISOString()
|
|
@@ -91879,6 +92009,30 @@ var WebServer = class {
|
|
|
91879
92009
|
});
|
|
91880
92010
|
}
|
|
91881
92011
|
});
|
|
92012
|
+
this.app.get("/api/pending-response/:projectId", (req, res) => {
|
|
92013
|
+
try {
|
|
92014
|
+
this.ensureDatabase();
|
|
92015
|
+
const pending = this.responseStore.getByProject(req.params.projectId);
|
|
92016
|
+
if (pending) {
|
|
92017
|
+
res.json({ success: true, response: pending });
|
|
92018
|
+
} else {
|
|
92019
|
+
res.json({ success: true, response: null });
|
|
92020
|
+
}
|
|
92021
|
+
} catch (error2) {
|
|
92022
|
+
logger.error("\u67E5\u8A62\u672A\u9001\u9054\u56DE\u61C9\u5931\u6557:", error2);
|
|
92023
|
+
res.status(500).json({ success: false, error: "Failed to query pending response" });
|
|
92024
|
+
}
|
|
92025
|
+
});
|
|
92026
|
+
this.app.post("/api/pending-response/:responseId/ack", (req, res) => {
|
|
92027
|
+
try {
|
|
92028
|
+
this.ensureDatabase();
|
|
92029
|
+
this.responseStore.markDelivered(req.params.responseId);
|
|
92030
|
+
res.json({ success: true });
|
|
92031
|
+
} catch (error2) {
|
|
92032
|
+
logger.error("\u78BA\u8A8D\u56DE\u61C9\u9001\u9054\u5931\u6557:", error2);
|
|
92033
|
+
res.status(500).json({ success: false, error: "Failed to acknowledge response" });
|
|
92034
|
+
}
|
|
92035
|
+
});
|
|
91882
92036
|
this.app.use((error2, req, res, _next) => {
|
|
91883
92037
|
logger.error("Express\u932F\u8AA4:", error2);
|
|
91884
92038
|
res.status(500).json({
|
|
@@ -92015,6 +92169,24 @@ var WebServer = class {
|
|
|
92015
92169
|
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
92170
|
return false;
|
|
92017
92171
|
}
|
|
92172
|
+
isTransportHealthy() {
|
|
92173
|
+
const mode = this.config.mcpTransport || "stdio";
|
|
92174
|
+
if (mode === "stdio") {
|
|
92175
|
+
return this.stdioHealthy;
|
|
92176
|
+
}
|
|
92177
|
+
const hasStreamableSSE = [...this.streamableHttpSseActive.values()].some((v) => v);
|
|
92178
|
+
const hasSSETransport = this.sseTransportsList.length > 0;
|
|
92179
|
+
return hasStreamableSSE || hasSSETransport;
|
|
92180
|
+
}
|
|
92181
|
+
setStdioHealthy(healthy) {
|
|
92182
|
+
if (this.stdioHealthy !== healthy) {
|
|
92183
|
+
this.stdioHealthy = healthy;
|
|
92184
|
+
logger.info(`[Transport] stdio \u5065\u5EB7\u72C0\u614B\u8B8A\u66F4: ${healthy ? "healthy" : "unhealthy"}`);
|
|
92185
|
+
}
|
|
92186
|
+
}
|
|
92187
|
+
getResponseStore() {
|
|
92188
|
+
return this.responseStore;
|
|
92189
|
+
}
|
|
92018
92190
|
async handleFeedbackSubmission(socket, feedbackData) {
|
|
92019
92191
|
const session = this.sessionStorage.getSession(feedbackData.sessionId);
|
|
92020
92192
|
if (!session) {
|
|
@@ -92058,11 +92230,13 @@ var WebServer = class {
|
|
|
92058
92230
|
}
|
|
92059
92231
|
if (session.resolve) {
|
|
92060
92232
|
this.sessionStorage.updateSession(feedbackData.sessionId, { resolved: true });
|
|
92233
|
+
const feedbackUrl = this.generateFeedbackUrl(feedbackData.sessionId);
|
|
92234
|
+
let pendingResponseId;
|
|
92061
92235
|
if (session.projectId) {
|
|
92062
92236
|
const cachedResult2 = {
|
|
92063
92237
|
feedback: session.feedback,
|
|
92064
92238
|
sessionId: feedbackData.sessionId,
|
|
92065
|
-
feedbackUrl
|
|
92239
|
+
feedbackUrl,
|
|
92066
92240
|
projectId: session.projectId,
|
|
92067
92241
|
projectName: session.projectName || ""
|
|
92068
92242
|
};
|
|
@@ -92072,12 +92246,34 @@ var WebServer = class {
|
|
|
92072
92246
|
});
|
|
92073
92247
|
const projectId = session.projectId;
|
|
92074
92248
|
setTimeout(() => this.pendingDeliveryCache.delete(projectId), 6e4);
|
|
92249
|
+
pendingResponseId = this.responseStore.save({
|
|
92250
|
+
sessionId: feedbackData.sessionId,
|
|
92251
|
+
projectId: session.projectId,
|
|
92252
|
+
projectName: session.projectName || "",
|
|
92253
|
+
feedback: session.feedback,
|
|
92254
|
+
feedbackUrl
|
|
92255
|
+
});
|
|
92075
92256
|
}
|
|
92076
|
-
const
|
|
92077
|
-
if (
|
|
92078
|
-
|
|
92257
|
+
const transportHealthy = this.isTransportHealthy();
|
|
92258
|
+
if (transportHealthy) {
|
|
92259
|
+
const isHTTP = this.config.mcpTransport === "sse" || this.config.mcpTransport === "streamable-http";
|
|
92260
|
+
if (isHTTP) {
|
|
92261
|
+
const hasConnection = await this.waitForActiveConnection();
|
|
92262
|
+
if (hasConnection) {
|
|
92263
|
+
logger.info("[\u9023\u7DDA\u76E3\u63A7] \u9023\u7DDA\u78BA\u8A8D\uFF0C\u6B63\u5728\u9001\u51FA\u56DE\u8986\u81F3 MCP Client...");
|
|
92264
|
+
}
|
|
92265
|
+
}
|
|
92266
|
+
session.resolve(session.feedback);
|
|
92267
|
+
if (pendingResponseId) {
|
|
92268
|
+
this.responseStore.markDelivered(pendingResponseId);
|
|
92269
|
+
}
|
|
92270
|
+
} else {
|
|
92271
|
+
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");
|
|
92272
|
+
session.resolve(session.feedback);
|
|
92273
|
+
if (pendingResponseId) {
|
|
92274
|
+
this.responseStore.incrementAttempt(pendingResponseId);
|
|
92275
|
+
}
|
|
92079
92276
|
}
|
|
92080
|
-
session.resolve(session.feedback);
|
|
92081
92277
|
const sessionIdToDelete = feedbackData.sessionId;
|
|
92082
92278
|
setTimeout(() => this.sessionStorage.deleteSession(sessionIdToDelete), 5e3);
|
|
92083
92279
|
}
|
|
@@ -92178,6 +92374,18 @@ var WebServer = class {
|
|
|
92178
92374
|
return pendingEntry.result;
|
|
92179
92375
|
}
|
|
92180
92376
|
}
|
|
92377
|
+
const pendingResponse = this.responseStore.getByProject(project.id);
|
|
92378
|
+
if (pendingResponse) {
|
|
92379
|
+
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`);
|
|
92380
|
+
this.responseStore.markDelivered(pendingResponse.id);
|
|
92381
|
+
return {
|
|
92382
|
+
feedback: pendingResponse.feedback,
|
|
92383
|
+
sessionId: pendingResponse.sessionId,
|
|
92384
|
+
feedbackUrl: pendingResponse.feedbackUrl,
|
|
92385
|
+
projectId: pendingResponse.projectId,
|
|
92386
|
+
projectName: pendingResponse.projectName
|
|
92387
|
+
};
|
|
92388
|
+
}
|
|
92181
92389
|
logger.info(`\u5EFA\u7ACB\u56DE\u994B\u6703\u8A71: ${sessionId}, \u903E\u6642: ${timeoutSeconds}\u79D2, \u5C08\u6848: ${project.name} (${project.id})`);
|
|
92182
92390
|
const feedbackUrl = this.generateFeedbackUrl(sessionId);
|
|
92183
92391
|
const sessionPromise = new Promise((resolve, reject) => {
|
|
@@ -92379,6 +92587,9 @@ var WebServer = class {
|
|
|
92379
92587
|
cleanupExpiredSessions: () => this.sessionStorage.cleanupExpiredSessions()
|
|
92380
92588
|
});
|
|
92381
92589
|
this.selfProbeService.start();
|
|
92590
|
+
this.responseCleanupInterval = setInterval(() => {
|
|
92591
|
+
this.responseStore.cleanup();
|
|
92592
|
+
}, 36e5);
|
|
92382
92593
|
} catch (error2) {
|
|
92383
92594
|
logger.error("Web\u4F3A\u670D\u5668\u555F\u52D5\u5931\u6557:", error2);
|
|
92384
92595
|
throw new MCPError(
|
|
@@ -92651,6 +92862,10 @@ var WebServer = class {
|
|
|
92651
92862
|
}
|
|
92652
92863
|
}
|
|
92653
92864
|
this.selfProbeService.stop();
|
|
92865
|
+
if (this.responseCleanupInterval) {
|
|
92866
|
+
clearInterval(this.responseCleanupInterval);
|
|
92867
|
+
this.responseCleanupInterval = null;
|
|
92868
|
+
}
|
|
92654
92869
|
this.sessionStorage.clear();
|
|
92655
92870
|
this.sessionStorage.stopCleanupTimer();
|
|
92656
92871
|
this.io.disconnectSockets(true);
|
|
@@ -92905,6 +93120,7 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
92905
93120
|
}
|
|
92906
93121
|
// ========== 延遲啟動相關 ==========
|
|
92907
93122
|
deferredStartupTriggered = false;
|
|
93123
|
+
stdioHeartbeatTimer = null;
|
|
92908
93124
|
/**
|
|
92909
93125
|
* 實作collect_feedback功能
|
|
92910
93126
|
*/
|
|
@@ -93080,6 +93296,7 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
93080
93296
|
});
|
|
93081
93297
|
});
|
|
93082
93298
|
process.stdin.resume();
|
|
93299
|
+
this.startStdioHeartbeat();
|
|
93083
93300
|
} else if (transportMode === "sse" || transportMode === "streamable-http") {
|
|
93084
93301
|
logger.info(`HTTP \u50B3\u8F38\u6A21\u5F0F: ${transportMode}\uFF0CMCP \u9023\u63A5\u5C07\u7531 HTTP \u7AEF\u9EDE\u8655\u7406`);
|
|
93085
93302
|
await this.webServer.startWithMCPEndpoints(this, transportMode);
|
|
@@ -93098,6 +93315,20 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
93098
93315
|
);
|
|
93099
93316
|
}
|
|
93100
93317
|
}
|
|
93318
|
+
startStdioHeartbeat() {
|
|
93319
|
+
const intervalMs = (this.config.stdioHeartbeatInterval ?? 30) * 1e3;
|
|
93320
|
+
this.stdioHeartbeatTimer = setInterval(async () => {
|
|
93321
|
+
try {
|
|
93322
|
+
await this.mcpServer.server.notification({
|
|
93323
|
+
method: "notifications/message",
|
|
93324
|
+
params: { level: "debug", logger: "heartbeat", data: { event: "ping", ts: Date.now() } }
|
|
93325
|
+
});
|
|
93326
|
+
this.webServer.setStdioHealthy(true);
|
|
93327
|
+
} catch {
|
|
93328
|
+
this.webServer.setStdioHealthy(false);
|
|
93329
|
+
}
|
|
93330
|
+
}, intervalMs);
|
|
93331
|
+
}
|
|
93101
93332
|
/**
|
|
93102
93333
|
* 僅啟動Web模式
|
|
93103
93334
|
*/
|
|
@@ -93150,6 +93381,10 @@ Include a COMPLETE Markdown report with ALL of the following sections:
|
|
|
93150
93381
|
}
|
|
93151
93382
|
try {
|
|
93152
93383
|
logger.info("\u6B63\u5728\u505C\u6B62\u4F3A\u670D\u5668...");
|
|
93384
|
+
if (this.stdioHeartbeatTimer) {
|
|
93385
|
+
clearInterval(this.stdioHeartbeatTimer);
|
|
93386
|
+
this.stdioHeartbeatTimer = null;
|
|
93387
|
+
}
|
|
93153
93388
|
await this.webServer.stop();
|
|
93154
93389
|
if (this.mcpServer) {
|
|
93155
93390
|
await this.mcpServer.close();
|
|
@@ -93278,7 +93513,10 @@ function createDefaultConfig() {
|
|
|
93278
93513
|
enableSelfProbe: getEnvBoolean("MCP_ENABLE_SELF_PROBE", false),
|
|
93279
93514
|
selfProbeIntervalSeconds: getEnvNumber("MCP_SELF_PROBE_INTERVAL", 300),
|
|
93280
93515
|
// Supervisor 配置
|
|
93281
|
-
supervisor: createSupervisorConfig()
|
|
93516
|
+
supervisor: createSupervisorConfig(),
|
|
93517
|
+
// Response Delivery 可靠性設定
|
|
93518
|
+
responseTtl: getEnvNumber("MCP_RESPONSE_TTL", 86400),
|
|
93519
|
+
stdioHeartbeatInterval: getEnvNumber("MCP_STDIO_HEARTBEAT_INTERVAL", 30)
|
|
93282
93520
|
};
|
|
93283
93521
|
}
|
|
93284
93522
|
function validateConfig(config2) {
|