@hirohsu/user-web-feedback 2.8.11 → 2.8.13

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 CHANGED
@@ -92987,6 +92987,9 @@ var WebServer = class {
92987
92987
  sseTransports = /* @__PURE__ */ new Map();
92988
92988
  sseTransportsList = [];
92989
92989
  pendingDeliveryCache = /* @__PURE__ */ new Map();
92990
+ streamableHttpTransports = /* @__PURE__ */ new Map();
92991
+ streamableHttpSseActive = /* @__PURE__ */ new Map();
92992
+ activeSessionPromises = /* @__PURE__ */ new Map();
92990
92993
  dbInitialized = false;
92991
92994
  selfProbeService;
92992
92995
  /**
@@ -94986,6 +94989,17 @@ var WebServer = class {
94986
94989
  /**
94987
94990
  * 處理回饋提交
94988
94991
  */
94992
+ async waitForActiveConnection(maxRounds = 5, intervalMs = 5e3) {
94993
+ for (let round = 1; round <= maxRounds; round++) {
94994
+ const hasStreamableSSE = [...this.streamableHttpSseActive.values()].some((v) => v);
94995
+ const hasSSETransport = this.sseTransportsList.length > 0;
94996
+ if (hasStreamableSSE || hasSSETransport) return true;
94997
+ logger.warn(`[\u9023\u7DDA\u7B49\u5F85] \u7B2C ${round}/${maxRounds} \u8F2A\uFF1A\u76EE\u524D\u7121\u6D3B\u8E8D MCP Client \u9023\u7DDA\uFF0C${intervalMs / 1e3}s \u5F8C\u91CD\u8A66...`);
94998
+ await new Promise((r) => setTimeout(r, intervalMs));
94999
+ }
95000
+ 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
+ return false;
95002
+ }
94989
95003
  async handleFeedbackSubmission(socket, feedbackData) {
94990
95004
  const session = this.sessionStorage.getSession(feedbackData.sessionId);
94991
95005
  if (!session) {
@@ -95044,6 +95058,10 @@ var WebServer = class {
95044
95058
  const projectId = session.projectId;
95045
95059
  setTimeout(() => this.pendingDeliveryCache.delete(projectId), 6e4);
95046
95060
  }
95061
+ const hasConnection = await this.waitForActiveConnection();
95062
+ if (hasConnection) {
95063
+ logger.info("[\u9023\u7DDA\u76E3\u63A7] \u9023\u7DDA\u78BA\u8A8D\uFF0C\u6B63\u5728\u9001\u51FA\u56DE\u8986\u81F3 MCP Client...");
95064
+ }
95047
95065
  session.resolve(session.feedback);
95048
95066
  const sessionIdToDelete = feedbackData.sessionId;
95049
95067
  setTimeout(() => this.sessionStorage.deleteSession(sessionIdToDelete), 5e3);
@@ -95131,8 +95149,11 @@ var WebServer = class {
95131
95149
  async collectFeedback(workSummary, timeoutSeconds, projectName, projectPath) {
95132
95150
  const sessionId = this.generateSessionId();
95133
95151
  const project = projectName ? projectManager.getOrCreateProject(projectName, projectPath) : projectManager.getDefaultProject();
95134
- logger.info(`\u5EFA\u7ACB\u56DE\u994B\u6703\u8A71: ${sessionId}, \u903E\u6642: ${timeoutSeconds}\u79D2, \u5C08\u6848: ${project.name} (${project.id})`);
95135
- const feedbackUrl = this.generateFeedbackUrl(sessionId);
95152
+ const existingPromise = this.activeSessionPromises.get(project.id);
95153
+ if (existingPromise) {
95154
+ logger.warn(`[\u91CD\u8907\u8ACB\u6C42] \u5C08\u6848 "${project.name}" \u5DF2\u6709\u9032\u884C\u4E2D\u7684\u56DE\u994B\u6703\u8A71\uFF0C\u91CD\u7528\u73FE\u6709\u8ACB\u6C42\uFF08\u9632\u6B62\u91CD\u8907\u5F48\u7A97\uFF09`);
95155
+ return existingPromise;
95156
+ }
95136
95157
  const pendingEntry = this.pendingDeliveryCache.get(project.id);
95137
95158
  if (pendingEntry && pendingEntry.expiresAt > Date.now()) {
95138
95159
  const prevSession = this.sessionStorage.getSession(pendingEntry.result.sessionId);
@@ -95142,7 +95163,9 @@ var WebServer = class {
95142
95163
  return pendingEntry.result;
95143
95164
  }
95144
95165
  }
95145
- return new Promise((resolve2, reject) => {
95166
+ logger.info(`\u5EFA\u7ACB\u56DE\u994B\u6703\u8A71: ${sessionId}, \u903E\u6642: ${timeoutSeconds}\u79D2, \u5C08\u6848: ${project.name} (${project.id})`);
95167
+ const feedbackUrl = this.generateFeedbackUrl(sessionId);
95168
+ const sessionPromise = new Promise((resolve2, reject) => {
95146
95169
  const session = {
95147
95170
  workSummary,
95148
95171
  feedback: [],
@@ -95172,6 +95195,11 @@ var WebServer = class {
95172
95195
  });
95173
95196
  }
95174
95197
  });
95198
+ this.activeSessionPromises.set(project.id, sessionPromise);
95199
+ void sessionPromise.finally(() => {
95200
+ this.activeSessionPromises.delete(project.id);
95201
+ });
95202
+ return sessionPromise;
95175
95203
  }
95176
95204
  emitDashboardSessionCreated(projectId, sessionId, projectName, workSummary) {
95177
95205
  if (this.io) {
@@ -95439,7 +95467,6 @@ var WebServer = class {
95439
95467
  */
95440
95468
  async setupStreamableHTTPEndpoints() {
95441
95469
  const { StreamableHTTPServerTransport: StreamableHTTPServerTransport2 } = await Promise.resolve().then(() => (init_streamableHttp2(), streamableHttp_exports2));
95442
- const streamableTransports = /* @__PURE__ */ new Map();
95443
95470
  this.app.post("/mcp", import_express.default.json(), async (req, res) => {
95444
95471
  logger.debug("\u6536\u5230 Streamable HTTP \u8ACB\u6C42:", JSON.stringify(req.body));
95445
95472
  if (!this.mcpServerRef) {
@@ -95450,7 +95477,7 @@ var WebServer = class {
95450
95477
  req.socket?.setKeepAlive(true, 3e4);
95451
95478
  req.on("close", () => {
95452
95479
  if (!res.writableEnded) {
95453
- logger.warn("[\u5BB9\u932F] MCP Client HTTP \u9023\u7DDA\u5728\u5DE5\u5177\u57F7\u884C\u671F\u9593\u63D0\u524D\u95DC\u9589\uFF0C\u56DE\u8986\u53EF\u80FD\u672A\u9001\u9054\uFF0C\u4E0B\u6B21\u547C\u53EB\u5C07\u5F9E\u5FEB\u53D6\u53D6\u56DE");
95480
+ logger.warn("[\u9023\u7DDA\u76E3\u63A7] MCP Client HTTP \u9023\u7DDA\u5728\u5DE5\u5177\u57F7\u884C\u671F\u9593\u63D0\u524D\u95DC\u9589\uFF0C\u56DE\u8986\u53EF\u80FD\u672A\u9001\u9054");
95454
95481
  }
95455
95482
  });
95456
95483
  const sessionId = req.headers["mcp-session-id"];
@@ -95462,23 +95489,30 @@ var WebServer = class {
95462
95489
  });
95463
95490
  transport.onclose = () => {
95464
95491
  const sid = transport.sessionId;
95465
- if (sid) streamableTransports.delete(sid);
95466
- logger.info(`Streamable HTTP Transport \u5DF2\u95DC\u9589, \u6703\u8A71: ${sid}`);
95492
+ if (sid) {
95493
+ this.streamableHttpTransports.delete(sid);
95494
+ this.streamableHttpSseActive.delete(sid);
95495
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Client \u5DF2\u65B7\u7DDA (sessionId=${sid})\uFF0C\u5269\u9918\u6D3B\u8E8D\u9023\u7DDA: ${this.streamableHttpTransports.size}`);
95496
+ }
95467
95497
  };
95468
95498
  try {
95469
95499
  await this.mcpServerRef.getMcpServerInstance().connect(transport);
95470
- logger.info("Streamable HTTP Transport \u5DF2\u9023\u63A5");
95500
+ const clientIp = req.headers["x-forwarded-for"] || req.socket?.remoteAddress || "unknown";
95501
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] \u2705 MCP Client \u5DF2\u9023\u7DDA \u2014 IP: ${clientIp}`);
95471
95502
  await transport.handleRequest(req, res, req.body);
95472
95503
  const sid = transport.sessionId;
95473
- if (sid) streamableTransports.set(sid, transport);
95504
+ if (sid) {
95505
+ this.streamableHttpTransports.set(sid, transport);
95506
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Session \u5DF2\u5EFA\u7ACB (sessionId=${sid})\uFF0C\u76EE\u524D\u6D3B\u8E8D\u9023\u7DDA: ${this.streamableHttpTransports.size}`);
95507
+ }
95474
95508
  } catch (error2) {
95475
95509
  logger.error("\u8655\u7406 Streamable HTTP \u521D\u59CB\u5316\u8ACB\u6C42\u5931\u6557:", error2);
95476
95510
  if (!res.headersSent) {
95477
95511
  res.status(500).json({ error: "Failed to initialize MCP session" });
95478
95512
  }
95479
95513
  }
95480
- } else if (sessionId && streamableTransports.has(sessionId)) {
95481
- const transport = streamableTransports.get(sessionId);
95514
+ } else if (sessionId && this.streamableHttpTransports.has(sessionId)) {
95515
+ const transport = this.streamableHttpTransports.get(sessionId);
95482
95516
  try {
95483
95517
  await transport.handleRequest(req, res, req.body);
95484
95518
  } catch (error2) {
@@ -95493,24 +95527,29 @@ var WebServer = class {
95493
95527
  });
95494
95528
  this.app.delete("/mcp", async (req, res) => {
95495
95529
  const sessionId = req.headers["mcp-session-id"];
95496
- if (sessionId && streamableTransports.has(sessionId)) {
95497
- const transport = streamableTransports.get(sessionId);
95530
+ if (sessionId && this.streamableHttpTransports.has(sessionId)) {
95531
+ const transport = this.streamableHttpTransports.get(sessionId);
95498
95532
  try {
95499
95533
  await transport.handleRequest(req, res, req.body);
95500
95534
  } catch {
95501
95535
  }
95502
- streamableTransports.delete(sessionId);
95536
+ this.streamableHttpTransports.delete(sessionId);
95537
+ this.streamableHttpSseActive.delete(sessionId);
95538
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Session \u5DF2\u522A\u9664 (sessionId=${sessionId})`);
95503
95539
  } else {
95504
95540
  res.status(404).json({ error: "Session not found" });
95505
95541
  }
95506
95542
  });
95507
95543
  this.app.get("/mcp", async (req, res) => {
95508
95544
  const sessionId = req.query["sessionId"];
95509
- if (!sessionId || !streamableTransports.has(sessionId)) {
95545
+ if (!sessionId || !this.streamableHttpTransports.has(sessionId)) {
95510
95546
  res.status(400).json({ error: "Invalid session" });
95511
95547
  return;
95512
95548
  }
95513
- const transport = streamableTransports.get(sessionId);
95549
+ this.streamableHttpSseActive.set(sessionId, true);
95550
+ const activeSseCount = [...this.streamableHttpSseActive.values()].filter(Boolean).length;
95551
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] \u{1F4E1} GET SSE \u4EA4\u4ED8\u901A\u9053\u5DF2\u958B\u555F (sessionId=${sessionId})\uFF0C\u6D3B\u8E8D SSE \u6578: ${activeSseCount}`);
95552
+ const transport = this.streamableHttpTransports.get(sessionId);
95514
95553
  const heartbeat = setInterval(() => {
95515
95554
  try {
95516
95555
  if (!res.writableEnded) res.write(": ping\n\n");
@@ -95519,7 +95558,12 @@ var WebServer = class {
95519
95558
  clearInterval(heartbeat);
95520
95559
  }
95521
95560
  }, 15e3);
95522
- req.on("close", () => clearInterval(heartbeat));
95561
+ req.on("close", () => {
95562
+ clearInterval(heartbeat);
95563
+ this.streamableHttpSseActive.set(sessionId, false);
95564
+ const remaining = [...this.streamableHttpSseActive.values()].filter(Boolean).length;
95565
+ logger.warn(`[\u9023\u7DDA\u76E3\u63A7] \u26A0\uFE0F GET SSE \u4EA4\u4ED8\u901A\u9053\u5DF2\u95DC\u9589 (sessionId=${sessionId})\uFF0C\u5269\u9918\u6D3B\u8E8D SSE \u6578: ${remaining}`);
95566
+ });
95523
95567
  try {
95524
95568
  await transport.handleRequest(req, res);
95525
95569
  } catch (error2) {
package/dist/index.cjs CHANGED
@@ -90002,6 +90002,9 @@ var WebServer = class {
90002
90002
  sseTransports = /* @__PURE__ */ new Map();
90003
90003
  sseTransportsList = [];
90004
90004
  pendingDeliveryCache = /* @__PURE__ */ new Map();
90005
+ streamableHttpTransports = /* @__PURE__ */ new Map();
90006
+ streamableHttpSseActive = /* @__PURE__ */ new Map();
90007
+ activeSessionPromises = /* @__PURE__ */ new Map();
90005
90008
  dbInitialized = false;
90006
90009
  selfProbeService;
90007
90010
  /**
@@ -92001,6 +92004,17 @@ var WebServer = class {
92001
92004
  /**
92002
92005
  * 處理回饋提交
92003
92006
  */
92007
+ async waitForActiveConnection(maxRounds = 5, intervalMs = 5e3) {
92008
+ for (let round = 1; round <= maxRounds; round++) {
92009
+ const hasStreamableSSE = [...this.streamableHttpSseActive.values()].some((v) => v);
92010
+ const hasSSETransport = this.sseTransportsList.length > 0;
92011
+ if (hasStreamableSSE || hasSSETransport) return true;
92012
+ logger.warn(`[\u9023\u7DDA\u7B49\u5F85] \u7B2C ${round}/${maxRounds} \u8F2A\uFF1A\u76EE\u524D\u7121\u6D3B\u8E8D MCP Client \u9023\u7DDA\uFF0C${intervalMs / 1e3}s \u5F8C\u91CD\u8A66...`);
92013
+ await new Promise((r) => setTimeout(r, intervalMs));
92014
+ }
92015
+ 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
+ return false;
92017
+ }
92004
92018
  async handleFeedbackSubmission(socket, feedbackData) {
92005
92019
  const session = this.sessionStorage.getSession(feedbackData.sessionId);
92006
92020
  if (!session) {
@@ -92059,6 +92073,10 @@ var WebServer = class {
92059
92073
  const projectId = session.projectId;
92060
92074
  setTimeout(() => this.pendingDeliveryCache.delete(projectId), 6e4);
92061
92075
  }
92076
+ const hasConnection = await this.waitForActiveConnection();
92077
+ if (hasConnection) {
92078
+ logger.info("[\u9023\u7DDA\u76E3\u63A7] \u9023\u7DDA\u78BA\u8A8D\uFF0C\u6B63\u5728\u9001\u51FA\u56DE\u8986\u81F3 MCP Client...");
92079
+ }
92062
92080
  session.resolve(session.feedback);
92063
92081
  const sessionIdToDelete = feedbackData.sessionId;
92064
92082
  setTimeout(() => this.sessionStorage.deleteSession(sessionIdToDelete), 5e3);
@@ -92146,8 +92164,11 @@ var WebServer = class {
92146
92164
  async collectFeedback(workSummary, timeoutSeconds, projectName, projectPath) {
92147
92165
  const sessionId = this.generateSessionId();
92148
92166
  const project = projectName ? projectManager.getOrCreateProject(projectName, projectPath) : projectManager.getDefaultProject();
92149
- logger.info(`\u5EFA\u7ACB\u56DE\u994B\u6703\u8A71: ${sessionId}, \u903E\u6642: ${timeoutSeconds}\u79D2, \u5C08\u6848: ${project.name} (${project.id})`);
92150
- const feedbackUrl = this.generateFeedbackUrl(sessionId);
92167
+ const existingPromise = this.activeSessionPromises.get(project.id);
92168
+ if (existingPromise) {
92169
+ logger.warn(`[\u91CD\u8907\u8ACB\u6C42] \u5C08\u6848 "${project.name}" \u5DF2\u6709\u9032\u884C\u4E2D\u7684\u56DE\u994B\u6703\u8A71\uFF0C\u91CD\u7528\u73FE\u6709\u8ACB\u6C42\uFF08\u9632\u6B62\u91CD\u8907\u5F48\u7A97\uFF09`);
92170
+ return existingPromise;
92171
+ }
92151
92172
  const pendingEntry = this.pendingDeliveryCache.get(project.id);
92152
92173
  if (pendingEntry && pendingEntry.expiresAt > Date.now()) {
92153
92174
  const prevSession = this.sessionStorage.getSession(pendingEntry.result.sessionId);
@@ -92157,7 +92178,9 @@ var WebServer = class {
92157
92178
  return pendingEntry.result;
92158
92179
  }
92159
92180
  }
92160
- return new Promise((resolve, reject) => {
92181
+ logger.info(`\u5EFA\u7ACB\u56DE\u994B\u6703\u8A71: ${sessionId}, \u903E\u6642: ${timeoutSeconds}\u79D2, \u5C08\u6848: ${project.name} (${project.id})`);
92182
+ const feedbackUrl = this.generateFeedbackUrl(sessionId);
92183
+ const sessionPromise = new Promise((resolve, reject) => {
92161
92184
  const session = {
92162
92185
  workSummary,
92163
92186
  feedback: [],
@@ -92187,6 +92210,11 @@ var WebServer = class {
92187
92210
  });
92188
92211
  }
92189
92212
  });
92213
+ this.activeSessionPromises.set(project.id, sessionPromise);
92214
+ void sessionPromise.finally(() => {
92215
+ this.activeSessionPromises.delete(project.id);
92216
+ });
92217
+ return sessionPromise;
92190
92218
  }
92191
92219
  emitDashboardSessionCreated(projectId, sessionId, projectName, workSummary) {
92192
92220
  if (this.io) {
@@ -92454,7 +92482,6 @@ var WebServer = class {
92454
92482
  */
92455
92483
  async setupStreamableHTTPEndpoints() {
92456
92484
  const { StreamableHTTPServerTransport: StreamableHTTPServerTransport2 } = await Promise.resolve().then(() => (init_streamableHttp2(), streamableHttp_exports2));
92457
- const streamableTransports = /* @__PURE__ */ new Map();
92458
92485
  this.app.post("/mcp", import_express.default.json(), async (req, res) => {
92459
92486
  logger.debug("\u6536\u5230 Streamable HTTP \u8ACB\u6C42:", JSON.stringify(req.body));
92460
92487
  if (!this.mcpServerRef) {
@@ -92465,7 +92492,7 @@ var WebServer = class {
92465
92492
  req.socket?.setKeepAlive(true, 3e4);
92466
92493
  req.on("close", () => {
92467
92494
  if (!res.writableEnded) {
92468
- logger.warn("[\u5BB9\u932F] MCP Client HTTP \u9023\u7DDA\u5728\u5DE5\u5177\u57F7\u884C\u671F\u9593\u63D0\u524D\u95DC\u9589\uFF0C\u56DE\u8986\u53EF\u80FD\u672A\u9001\u9054\uFF0C\u4E0B\u6B21\u547C\u53EB\u5C07\u5F9E\u5FEB\u53D6\u53D6\u56DE");
92495
+ logger.warn("[\u9023\u7DDA\u76E3\u63A7] MCP Client HTTP \u9023\u7DDA\u5728\u5DE5\u5177\u57F7\u884C\u671F\u9593\u63D0\u524D\u95DC\u9589\uFF0C\u56DE\u8986\u53EF\u80FD\u672A\u9001\u9054");
92469
92496
  }
92470
92497
  });
92471
92498
  const sessionId = req.headers["mcp-session-id"];
@@ -92477,23 +92504,30 @@ var WebServer = class {
92477
92504
  });
92478
92505
  transport.onclose = () => {
92479
92506
  const sid = transport.sessionId;
92480
- if (sid) streamableTransports.delete(sid);
92481
- logger.info(`Streamable HTTP Transport \u5DF2\u95DC\u9589, \u6703\u8A71: ${sid}`);
92507
+ if (sid) {
92508
+ this.streamableHttpTransports.delete(sid);
92509
+ this.streamableHttpSseActive.delete(sid);
92510
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Client \u5DF2\u65B7\u7DDA (sessionId=${sid})\uFF0C\u5269\u9918\u6D3B\u8E8D\u9023\u7DDA: ${this.streamableHttpTransports.size}`);
92511
+ }
92482
92512
  };
92483
92513
  try {
92484
92514
  await this.mcpServerRef.getMcpServerInstance().connect(transport);
92485
- logger.info("Streamable HTTP Transport \u5DF2\u9023\u63A5");
92515
+ const clientIp = req.headers["x-forwarded-for"] || req.socket?.remoteAddress || "unknown";
92516
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] \u2705 MCP Client \u5DF2\u9023\u7DDA \u2014 IP: ${clientIp}`);
92486
92517
  await transport.handleRequest(req, res, req.body);
92487
92518
  const sid = transport.sessionId;
92488
- if (sid) streamableTransports.set(sid, transport);
92519
+ if (sid) {
92520
+ this.streamableHttpTransports.set(sid, transport);
92521
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Session \u5DF2\u5EFA\u7ACB (sessionId=${sid})\uFF0C\u76EE\u524D\u6D3B\u8E8D\u9023\u7DDA: ${this.streamableHttpTransports.size}`);
92522
+ }
92489
92523
  } catch (error2) {
92490
92524
  logger.error("\u8655\u7406 Streamable HTTP \u521D\u59CB\u5316\u8ACB\u6C42\u5931\u6557:", error2);
92491
92525
  if (!res.headersSent) {
92492
92526
  res.status(500).json({ error: "Failed to initialize MCP session" });
92493
92527
  }
92494
92528
  }
92495
- } else if (sessionId && streamableTransports.has(sessionId)) {
92496
- const transport = streamableTransports.get(sessionId);
92529
+ } else if (sessionId && this.streamableHttpTransports.has(sessionId)) {
92530
+ const transport = this.streamableHttpTransports.get(sessionId);
92497
92531
  try {
92498
92532
  await transport.handleRequest(req, res, req.body);
92499
92533
  } catch (error2) {
@@ -92508,24 +92542,29 @@ var WebServer = class {
92508
92542
  });
92509
92543
  this.app.delete("/mcp", async (req, res) => {
92510
92544
  const sessionId = req.headers["mcp-session-id"];
92511
- if (sessionId && streamableTransports.has(sessionId)) {
92512
- const transport = streamableTransports.get(sessionId);
92545
+ if (sessionId && this.streamableHttpTransports.has(sessionId)) {
92546
+ const transport = this.streamableHttpTransports.get(sessionId);
92513
92547
  try {
92514
92548
  await transport.handleRequest(req, res, req.body);
92515
92549
  } catch {
92516
92550
  }
92517
- streamableTransports.delete(sessionId);
92551
+ this.streamableHttpTransports.delete(sessionId);
92552
+ this.streamableHttpSseActive.delete(sessionId);
92553
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Session \u5DF2\u522A\u9664 (sessionId=${sessionId})`);
92518
92554
  } else {
92519
92555
  res.status(404).json({ error: "Session not found" });
92520
92556
  }
92521
92557
  });
92522
92558
  this.app.get("/mcp", async (req, res) => {
92523
92559
  const sessionId = req.query["sessionId"];
92524
- if (!sessionId || !streamableTransports.has(sessionId)) {
92560
+ if (!sessionId || !this.streamableHttpTransports.has(sessionId)) {
92525
92561
  res.status(400).json({ error: "Invalid session" });
92526
92562
  return;
92527
92563
  }
92528
- const transport = streamableTransports.get(sessionId);
92564
+ this.streamableHttpSseActive.set(sessionId, true);
92565
+ const activeSseCount = [...this.streamableHttpSseActive.values()].filter(Boolean).length;
92566
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] \u{1F4E1} GET SSE \u4EA4\u4ED8\u901A\u9053\u5DF2\u958B\u555F (sessionId=${sessionId})\uFF0C\u6D3B\u8E8D SSE \u6578: ${activeSseCount}`);
92567
+ const transport = this.streamableHttpTransports.get(sessionId);
92529
92568
  const heartbeat = setInterval(() => {
92530
92569
  try {
92531
92570
  if (!res.writableEnded) res.write(": ping\n\n");
@@ -92534,7 +92573,12 @@ var WebServer = class {
92534
92573
  clearInterval(heartbeat);
92535
92574
  }
92536
92575
  }, 15e3);
92537
- req.on("close", () => clearInterval(heartbeat));
92576
+ req.on("close", () => {
92577
+ clearInterval(heartbeat);
92578
+ this.streamableHttpSseActive.set(sessionId, false);
92579
+ const remaining = [...this.streamableHttpSseActive.values()].filter(Boolean).length;
92580
+ logger.warn(`[\u9023\u7DDA\u76E3\u63A7] \u26A0\uFE0F GET SSE \u4EA4\u4ED8\u901A\u9053\u5DF2\u95DC\u9589 (sessionId=${sessionId})\uFF0C\u5269\u9918\u6D3B\u8E8D SSE \u6578: ${remaining}`);
92581
+ });
92538
92582
  try {
92539
92583
  await transport.handleRequest(req, res);
92540
92584
  } catch (error2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hirohsu/user-web-feedback",
3
- "version": "2.8.11",
3
+ "version": "2.8.13",
4
4
  "description": "基於Node.js的MCP回饋收集器 - 支持AI工作彙報和用戶回饋收集",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {