@hirohsu/user-web-feedback 2.8.10 → 2.8.12

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
@@ -105763,6 +105763,8 @@ var WebServer = class {
105763
105763
  sseTransports = /* @__PURE__ */ new Map();
105764
105764
  sseTransportsList = [];
105765
105765
  pendingDeliveryCache = /* @__PURE__ */ new Map();
105766
+ streamableHttpTransports = /* @__PURE__ */ new Map();
105767
+ streamableHttpSseActive = /* @__PURE__ */ new Map();
105766
105768
  dbInitialized = false;
105767
105769
  selfProbeService;
105768
105770
  /**
@@ -107762,6 +107764,17 @@ var WebServer = class {
107762
107764
  /**
107763
107765
  * 處理回饋提交
107764
107766
  */
107767
+ async waitForActiveConnection(maxRounds = 5, intervalMs = 5e3) {
107768
+ for (let round = 1; round <= maxRounds; round++) {
107769
+ const hasStreamableSSE = [...this.streamableHttpSseActive.values()].some((v) => v);
107770
+ const hasSSETransport = this.sseTransportsList.length > 0;
107771
+ if (hasStreamableSSE || hasSSETransport) return true;
107772
+ 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...`);
107773
+ await new Promise((r) => setTimeout(r, intervalMs));
107774
+ }
107775
+ 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`);
107776
+ return false;
107777
+ }
107765
107778
  async handleFeedbackSubmission(socket, feedbackData) {
107766
107779
  const session = this.sessionStorage.getSession(feedbackData.sessionId);
107767
107780
  if (!session) {
@@ -107804,6 +107817,7 @@ var WebServer = class {
107804
107817
  this.emitDashboardSessionUpdated(session.projectId, feedbackData.sessionId, "completed", session.workSummary);
107805
107818
  }
107806
107819
  if (session.resolve) {
107820
+ this.sessionStorage.updateSession(feedbackData.sessionId, { resolved: true });
107807
107821
  if (session.projectId) {
107808
107822
  const cachedResult2 = {
107809
107823
  feedback: session.feedback,
@@ -107819,6 +107833,10 @@ var WebServer = class {
107819
107833
  const projectId = session.projectId;
107820
107834
  setTimeout(() => this.pendingDeliveryCache.delete(projectId), 6e4);
107821
107835
  }
107836
+ const hasConnection = await this.waitForActiveConnection();
107837
+ if (hasConnection) {
107838
+ logger.info("[\u9023\u7DDA\u76E3\u63A7] \u9023\u7DDA\u78BA\u8A8D\uFF0C\u6B63\u5728\u9001\u51FA\u56DE\u8986\u81F3 MCP Client...");
107839
+ }
107822
107840
  session.resolve(session.feedback);
107823
107841
  const sessionIdToDelete = feedbackData.sessionId;
107824
107842
  setTimeout(() => this.sessionStorage.deleteSession(sessionIdToDelete), 5e3);
@@ -107909,10 +107927,13 @@ var WebServer = class {
107909
107927
  logger.info(`\u5EFA\u7ACB\u56DE\u994B\u6703\u8A71: ${sessionId}, \u903E\u6642: ${timeoutSeconds}\u79D2, \u5C08\u6848: ${project.name} (${project.id})`);
107910
107928
  const feedbackUrl = this.generateFeedbackUrl(sessionId);
107911
107929
  const pendingEntry = this.pendingDeliveryCache.get(project.id);
107912
- if (pendingEntry && pendingEntry.expiresAt > Date.now() && !this.sessionStorage.getSession(pendingEntry.result.sessionId)) {
107913
- logger.warn(`[\u91CD\u8A66\u88DC\u511F] \u5075\u6E2C\u5230\u5C08\u6848 "${project.name}" \u5B58\u5728\u672A\u78BA\u8A8D\u9001\u9054\u7684\u56DE\u8986 (sessionId: ${pendingEntry.result.sessionId})\uFF0C\u76F4\u63A5\u56DE\u50B3\u5FEB\u53D6\u7D50\u679C`);
107914
- this.pendingDeliveryCache.delete(project.id);
107915
- return pendingEntry.result;
107930
+ if (pendingEntry && pendingEntry.expiresAt > Date.now()) {
107931
+ const prevSession = this.sessionStorage.getSession(pendingEntry.result.sessionId);
107932
+ if (!prevSession || prevSession.resolved) {
107933
+ logger.warn(`[\u91CD\u8A66\u88DC\u511F] \u5075\u6E2C\u5230\u5C08\u6848 "${project.name}" \u5B58\u5728\u672A\u78BA\u8A8D\u9001\u9054\u7684\u56DE\u8986 (sessionId: ${pendingEntry.result.sessionId})\uFF0C\u76F4\u63A5\u56DE\u50B3\u5FEB\u53D6\u7D50\u679C`);
107934
+ this.pendingDeliveryCache.delete(project.id);
107935
+ return pendingEntry.result;
107936
+ }
107916
107937
  }
107917
107938
  return new Promise((resolve2, reject) => {
107918
107939
  const session = {
@@ -108211,7 +108232,6 @@ var WebServer = class {
108211
108232
  */
108212
108233
  async setupStreamableHTTPEndpoints() {
108213
108234
  const { StreamableHTTPServerTransport: StreamableHTTPServerTransport2 } = await Promise.resolve().then(() => (init_streamableHttp2(), streamableHttp_exports2));
108214
- const streamableTransports = /* @__PURE__ */ new Map();
108215
108235
  this.app.post("/mcp", import_express.default.json(), async (req, res) => {
108216
108236
  logger.debug("\u6536\u5230 Streamable HTTP \u8ACB\u6C42:", JSON.stringify(req.body));
108217
108237
  if (!this.mcpServerRef) {
@@ -108222,7 +108242,7 @@ var WebServer = class {
108222
108242
  req.socket?.setKeepAlive(true, 3e4);
108223
108243
  req.on("close", () => {
108224
108244
  if (!res.writableEnded) {
108225
- 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");
108245
+ 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");
108226
108246
  }
108227
108247
  });
108228
108248
  const sessionId = req.headers["mcp-session-id"];
@@ -108230,27 +108250,34 @@ var WebServer = class {
108230
108250
  if (isInitialize) {
108231
108251
  const transport = new StreamableHTTPServerTransport2({
108232
108252
  sessionIdGenerator: () => `http-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
108233
- enableJsonResponse: true
108253
+ enableJsonResponse: false
108234
108254
  });
108235
108255
  transport.onclose = () => {
108236
108256
  const sid = transport.sessionId;
108237
- if (sid) streamableTransports.delete(sid);
108238
- logger.info(`Streamable HTTP Transport \u5DF2\u95DC\u9589, \u6703\u8A71: ${sid}`);
108257
+ if (sid) {
108258
+ this.streamableHttpTransports.delete(sid);
108259
+ this.streamableHttpSseActive.delete(sid);
108260
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Client \u5DF2\u65B7\u7DDA (sessionId=${sid})\uFF0C\u5269\u9918\u6D3B\u8E8D\u9023\u7DDA: ${this.streamableHttpTransports.size}`);
108261
+ }
108239
108262
  };
108240
108263
  try {
108241
108264
  await this.mcpServerRef.getMcpServerInstance().connect(transport);
108242
- logger.info("Streamable HTTP Transport \u5DF2\u9023\u63A5");
108265
+ const clientIp = req.headers["x-forwarded-for"] || req.socket?.remoteAddress || "unknown";
108266
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] \u2705 MCP Client \u5DF2\u9023\u7DDA \u2014 IP: ${clientIp}`);
108243
108267
  await transport.handleRequest(req, res, req.body);
108244
108268
  const sid = transport.sessionId;
108245
- if (sid) streamableTransports.set(sid, transport);
108269
+ if (sid) {
108270
+ this.streamableHttpTransports.set(sid, transport);
108271
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Session \u5DF2\u5EFA\u7ACB (sessionId=${sid})\uFF0C\u76EE\u524D\u6D3B\u8E8D\u9023\u7DDA: ${this.streamableHttpTransports.size}`);
108272
+ }
108246
108273
  } catch (error2) {
108247
108274
  logger.error("\u8655\u7406 Streamable HTTP \u521D\u59CB\u5316\u8ACB\u6C42\u5931\u6557:", error2);
108248
108275
  if (!res.headersSent) {
108249
108276
  res.status(500).json({ error: "Failed to initialize MCP session" });
108250
108277
  }
108251
108278
  }
108252
- } else if (sessionId && streamableTransports.has(sessionId)) {
108253
- const transport = streamableTransports.get(sessionId);
108279
+ } else if (sessionId && this.streamableHttpTransports.has(sessionId)) {
108280
+ const transport = this.streamableHttpTransports.get(sessionId);
108254
108281
  try {
108255
108282
  await transport.handleRequest(req, res, req.body);
108256
108283
  } catch (error2) {
@@ -108265,31 +108292,52 @@ var WebServer = class {
108265
108292
  });
108266
108293
  this.app.delete("/mcp", async (req, res) => {
108267
108294
  const sessionId = req.headers["mcp-session-id"];
108268
- if (sessionId && streamableTransports.has(sessionId)) {
108269
- const transport = streamableTransports.get(sessionId);
108295
+ if (sessionId && this.streamableHttpTransports.has(sessionId)) {
108296
+ const transport = this.streamableHttpTransports.get(sessionId);
108270
108297
  try {
108271
108298
  await transport.handleRequest(req, res, req.body);
108272
108299
  } catch {
108273
108300
  }
108274
- streamableTransports.delete(sessionId);
108301
+ this.streamableHttpTransports.delete(sessionId);
108302
+ this.streamableHttpSseActive.delete(sessionId);
108303
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Session \u5DF2\u522A\u9664 (sessionId=${sessionId})`);
108275
108304
  } else {
108276
108305
  res.status(404).json({ error: "Session not found" });
108277
108306
  }
108278
108307
  });
108279
108308
  this.app.get("/mcp", async (req, res) => {
108280
108309
  const sessionId = req.query["sessionId"];
108281
- if (sessionId && streamableTransports.has(sessionId)) {
108282
- const transport = streamableTransports.get(sessionId);
108310
+ if (!sessionId || !this.streamableHttpTransports.has(sessionId)) {
108311
+ res.status(400).json({ error: "Invalid session" });
108312
+ return;
108313
+ }
108314
+ this.streamableHttpSseActive.set(sessionId, true);
108315
+ const activeSseCount = [...this.streamableHttpSseActive.values()].filter(Boolean).length;
108316
+ 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}`);
108317
+ const transport = this.streamableHttpTransports.get(sessionId);
108318
+ const heartbeat = setInterval(() => {
108283
108319
  try {
108284
- await transport.handleRequest(req, res);
108285
- } catch (error2) {
108286
- logger.error("\u8655\u7406 Streamable HTTP GET \u8ACB\u6C42\u5931\u6557:", error2);
108287
- if (!res.headersSent) {
108288
- res.status(500).json({ error: "Failed to process request" });
108289
- }
108320
+ if (!res.writableEnded) res.write(": ping\n\n");
108321
+ else clearInterval(heartbeat);
108322
+ } catch {
108323
+ clearInterval(heartbeat);
108290
108324
  }
108291
- } else {
108292
- res.status(400).json({ error: "Invalid session" });
108325
+ }, 15e3);
108326
+ req.on("close", () => {
108327
+ clearInterval(heartbeat);
108328
+ this.streamableHttpSseActive.set(sessionId, false);
108329
+ const remaining = [...this.streamableHttpSseActive.values()].filter(Boolean).length;
108330
+ 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}`);
108331
+ });
108332
+ try {
108333
+ await transport.handleRequest(req, res);
108334
+ } catch (error2) {
108335
+ logger.error("\u8655\u7406 Streamable HTTP GET \u8ACB\u6C42\u5931\u6557:", error2);
108336
+ if (!res.headersSent) {
108337
+ res.status(500).json({ error: "Failed to process request" });
108338
+ }
108339
+ } finally {
108340
+ clearInterval(heartbeat);
108293
108341
  }
108294
108342
  });
108295
108343
  logger.info("Streamable HTTP \u7AEF\u9EDE\u5DF2\u8A2D\u5B9A: GET/POST/DELETE /mcp");
package/dist/index.cjs CHANGED
@@ -102780,6 +102780,8 @@ var WebServer = class {
102780
102780
  sseTransports = /* @__PURE__ */ new Map();
102781
102781
  sseTransportsList = [];
102782
102782
  pendingDeliveryCache = /* @__PURE__ */ new Map();
102783
+ streamableHttpTransports = /* @__PURE__ */ new Map();
102784
+ streamableHttpSseActive = /* @__PURE__ */ new Map();
102783
102785
  dbInitialized = false;
102784
102786
  selfProbeService;
102785
102787
  /**
@@ -104779,6 +104781,17 @@ var WebServer = class {
104779
104781
  /**
104780
104782
  * 處理回饋提交
104781
104783
  */
104784
+ async waitForActiveConnection(maxRounds = 5, intervalMs = 5e3) {
104785
+ for (let round = 1; round <= maxRounds; round++) {
104786
+ const hasStreamableSSE = [...this.streamableHttpSseActive.values()].some((v) => v);
104787
+ const hasSSETransport = this.sseTransportsList.length > 0;
104788
+ if (hasStreamableSSE || hasSSETransport) return true;
104789
+ 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...`);
104790
+ await new Promise((r) => setTimeout(r, intervalMs));
104791
+ }
104792
+ 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`);
104793
+ return false;
104794
+ }
104782
104795
  async handleFeedbackSubmission(socket, feedbackData) {
104783
104796
  const session = this.sessionStorage.getSession(feedbackData.sessionId);
104784
104797
  if (!session) {
@@ -104821,6 +104834,7 @@ var WebServer = class {
104821
104834
  this.emitDashboardSessionUpdated(session.projectId, feedbackData.sessionId, "completed", session.workSummary);
104822
104835
  }
104823
104836
  if (session.resolve) {
104837
+ this.sessionStorage.updateSession(feedbackData.sessionId, { resolved: true });
104824
104838
  if (session.projectId) {
104825
104839
  const cachedResult2 = {
104826
104840
  feedback: session.feedback,
@@ -104836,6 +104850,10 @@ var WebServer = class {
104836
104850
  const projectId = session.projectId;
104837
104851
  setTimeout(() => this.pendingDeliveryCache.delete(projectId), 6e4);
104838
104852
  }
104853
+ const hasConnection = await this.waitForActiveConnection();
104854
+ if (hasConnection) {
104855
+ logger.info("[\u9023\u7DDA\u76E3\u63A7] \u9023\u7DDA\u78BA\u8A8D\uFF0C\u6B63\u5728\u9001\u51FA\u56DE\u8986\u81F3 MCP Client...");
104856
+ }
104839
104857
  session.resolve(session.feedback);
104840
104858
  const sessionIdToDelete = feedbackData.sessionId;
104841
104859
  setTimeout(() => this.sessionStorage.deleteSession(sessionIdToDelete), 5e3);
@@ -104926,10 +104944,13 @@ var WebServer = class {
104926
104944
  logger.info(`\u5EFA\u7ACB\u56DE\u994B\u6703\u8A71: ${sessionId}, \u903E\u6642: ${timeoutSeconds}\u79D2, \u5C08\u6848: ${project.name} (${project.id})`);
104927
104945
  const feedbackUrl = this.generateFeedbackUrl(sessionId);
104928
104946
  const pendingEntry = this.pendingDeliveryCache.get(project.id);
104929
- if (pendingEntry && pendingEntry.expiresAt > Date.now() && !this.sessionStorage.getSession(pendingEntry.result.sessionId)) {
104930
- logger.warn(`[\u91CD\u8A66\u88DC\u511F] \u5075\u6E2C\u5230\u5C08\u6848 "${project.name}" \u5B58\u5728\u672A\u78BA\u8A8D\u9001\u9054\u7684\u56DE\u8986 (sessionId: ${pendingEntry.result.sessionId})\uFF0C\u76F4\u63A5\u56DE\u50B3\u5FEB\u53D6\u7D50\u679C`);
104931
- this.pendingDeliveryCache.delete(project.id);
104932
- return pendingEntry.result;
104947
+ if (pendingEntry && pendingEntry.expiresAt > Date.now()) {
104948
+ const prevSession = this.sessionStorage.getSession(pendingEntry.result.sessionId);
104949
+ if (!prevSession || prevSession.resolved) {
104950
+ logger.warn(`[\u91CD\u8A66\u88DC\u511F] \u5075\u6E2C\u5230\u5C08\u6848 "${project.name}" \u5B58\u5728\u672A\u78BA\u8A8D\u9001\u9054\u7684\u56DE\u8986 (sessionId: ${pendingEntry.result.sessionId})\uFF0C\u76F4\u63A5\u56DE\u50B3\u5FEB\u53D6\u7D50\u679C`);
104951
+ this.pendingDeliveryCache.delete(project.id);
104952
+ return pendingEntry.result;
104953
+ }
104933
104954
  }
104934
104955
  return new Promise((resolve, reject) => {
104935
104956
  const session = {
@@ -105228,7 +105249,6 @@ var WebServer = class {
105228
105249
  */
105229
105250
  async setupStreamableHTTPEndpoints() {
105230
105251
  const { StreamableHTTPServerTransport: StreamableHTTPServerTransport2 } = await Promise.resolve().then(() => (init_streamableHttp2(), streamableHttp_exports2));
105231
- const streamableTransports = /* @__PURE__ */ new Map();
105232
105252
  this.app.post("/mcp", import_express.default.json(), async (req, res) => {
105233
105253
  logger.debug("\u6536\u5230 Streamable HTTP \u8ACB\u6C42:", JSON.stringify(req.body));
105234
105254
  if (!this.mcpServerRef) {
@@ -105239,7 +105259,7 @@ var WebServer = class {
105239
105259
  req.socket?.setKeepAlive(true, 3e4);
105240
105260
  req.on("close", () => {
105241
105261
  if (!res.writableEnded) {
105242
- 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");
105262
+ 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");
105243
105263
  }
105244
105264
  });
105245
105265
  const sessionId = req.headers["mcp-session-id"];
@@ -105247,27 +105267,34 @@ var WebServer = class {
105247
105267
  if (isInitialize) {
105248
105268
  const transport = new StreamableHTTPServerTransport2({
105249
105269
  sessionIdGenerator: () => `http-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
105250
- enableJsonResponse: true
105270
+ enableJsonResponse: false
105251
105271
  });
105252
105272
  transport.onclose = () => {
105253
105273
  const sid = transport.sessionId;
105254
- if (sid) streamableTransports.delete(sid);
105255
- logger.info(`Streamable HTTP Transport \u5DF2\u95DC\u9589, \u6703\u8A71: ${sid}`);
105274
+ if (sid) {
105275
+ this.streamableHttpTransports.delete(sid);
105276
+ this.streamableHttpSseActive.delete(sid);
105277
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Client \u5DF2\u65B7\u7DDA (sessionId=${sid})\uFF0C\u5269\u9918\u6D3B\u8E8D\u9023\u7DDA: ${this.streamableHttpTransports.size}`);
105278
+ }
105256
105279
  };
105257
105280
  try {
105258
105281
  await this.mcpServerRef.getMcpServerInstance().connect(transport);
105259
- logger.info("Streamable HTTP Transport \u5DF2\u9023\u63A5");
105282
+ const clientIp = req.headers["x-forwarded-for"] || req.socket?.remoteAddress || "unknown";
105283
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] \u2705 MCP Client \u5DF2\u9023\u7DDA \u2014 IP: ${clientIp}`);
105260
105284
  await transport.handleRequest(req, res, req.body);
105261
105285
  const sid = transport.sessionId;
105262
- if (sid) streamableTransports.set(sid, transport);
105286
+ if (sid) {
105287
+ this.streamableHttpTransports.set(sid, transport);
105288
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Session \u5DF2\u5EFA\u7ACB (sessionId=${sid})\uFF0C\u76EE\u524D\u6D3B\u8E8D\u9023\u7DDA: ${this.streamableHttpTransports.size}`);
105289
+ }
105263
105290
  } catch (error2) {
105264
105291
  logger.error("\u8655\u7406 Streamable HTTP \u521D\u59CB\u5316\u8ACB\u6C42\u5931\u6557:", error2);
105265
105292
  if (!res.headersSent) {
105266
105293
  res.status(500).json({ error: "Failed to initialize MCP session" });
105267
105294
  }
105268
105295
  }
105269
- } else if (sessionId && streamableTransports.has(sessionId)) {
105270
- const transport = streamableTransports.get(sessionId);
105296
+ } else if (sessionId && this.streamableHttpTransports.has(sessionId)) {
105297
+ const transport = this.streamableHttpTransports.get(sessionId);
105271
105298
  try {
105272
105299
  await transport.handleRequest(req, res, req.body);
105273
105300
  } catch (error2) {
@@ -105282,31 +105309,52 @@ var WebServer = class {
105282
105309
  });
105283
105310
  this.app.delete("/mcp", async (req, res) => {
105284
105311
  const sessionId = req.headers["mcp-session-id"];
105285
- if (sessionId && streamableTransports.has(sessionId)) {
105286
- const transport = streamableTransports.get(sessionId);
105312
+ if (sessionId && this.streamableHttpTransports.has(sessionId)) {
105313
+ const transport = this.streamableHttpTransports.get(sessionId);
105287
105314
  try {
105288
105315
  await transport.handleRequest(req, res, req.body);
105289
105316
  } catch {
105290
105317
  }
105291
- streamableTransports.delete(sessionId);
105318
+ this.streamableHttpTransports.delete(sessionId);
105319
+ this.streamableHttpSseActive.delete(sessionId);
105320
+ logger.info(`[\u9023\u7DDA\u76E3\u63A7] MCP Session \u5DF2\u522A\u9664 (sessionId=${sessionId})`);
105292
105321
  } else {
105293
105322
  res.status(404).json({ error: "Session not found" });
105294
105323
  }
105295
105324
  });
105296
105325
  this.app.get("/mcp", async (req, res) => {
105297
105326
  const sessionId = req.query["sessionId"];
105298
- if (sessionId && streamableTransports.has(sessionId)) {
105299
- const transport = streamableTransports.get(sessionId);
105327
+ if (!sessionId || !this.streamableHttpTransports.has(sessionId)) {
105328
+ res.status(400).json({ error: "Invalid session" });
105329
+ return;
105330
+ }
105331
+ this.streamableHttpSseActive.set(sessionId, true);
105332
+ const activeSseCount = [...this.streamableHttpSseActive.values()].filter(Boolean).length;
105333
+ 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}`);
105334
+ const transport = this.streamableHttpTransports.get(sessionId);
105335
+ const heartbeat = setInterval(() => {
105300
105336
  try {
105301
- await transport.handleRequest(req, res);
105302
- } catch (error2) {
105303
- logger.error("\u8655\u7406 Streamable HTTP GET \u8ACB\u6C42\u5931\u6557:", error2);
105304
- if (!res.headersSent) {
105305
- res.status(500).json({ error: "Failed to process request" });
105306
- }
105337
+ if (!res.writableEnded) res.write(": ping\n\n");
105338
+ else clearInterval(heartbeat);
105339
+ } catch {
105340
+ clearInterval(heartbeat);
105307
105341
  }
105308
- } else {
105309
- res.status(400).json({ error: "Invalid session" });
105342
+ }, 15e3);
105343
+ req.on("close", () => {
105344
+ clearInterval(heartbeat);
105345
+ this.streamableHttpSseActive.set(sessionId, false);
105346
+ const remaining = [...this.streamableHttpSseActive.values()].filter(Boolean).length;
105347
+ 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}`);
105348
+ });
105349
+ try {
105350
+ await transport.handleRequest(req, res);
105351
+ } catch (error2) {
105352
+ logger.error("\u8655\u7406 Streamable HTTP GET \u8ACB\u6C42\u5931\u6557:", error2);
105353
+ if (!res.headersSent) {
105354
+ res.status(500).json({ error: "Failed to process request" });
105355
+ }
105356
+ } finally {
105357
+ clearInterval(heartbeat);
105310
105358
  }
105311
105359
  });
105312
105360
  logger.info("Streamable HTTP \u7AEF\u9EDE\u5DF2\u8A2D\u5B9A: GET/POST/DELETE /mcp");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hirohsu/user-web-feedback",
3
- "version": "2.8.10",
3
+ "version": "2.8.12",
4
4
  "description": "基於Node.js的MCP回饋收集器 - 支持AI工作彙報和用戶回饋收集",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {