mcp-feedback-enhanced 0.1.64 → 0.1.67

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.
Files changed (2) hide show
  1. package/dist/index.js +571 -32
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16,7 +16,9 @@ import * as os from 'os';
16
16
  import * as fs from 'fs';
17
17
  import * as path from 'path';
18
18
  import * as net from 'net';
19
+ import * as http from 'http';
19
20
  import { createRequire } from 'module';
21
+ import { exec } from 'child_process';
20
22
  // Read version from package.json (ES module compatible)
21
23
  const require = createRequire(import.meta.url);
22
24
  const packageJson = require('../package.json');
@@ -167,7 +169,7 @@ function getLiveServers() {
167
169
  * Now async to support port connectivity checks
168
170
  */
169
171
  async function findExtensionForProjectAsync(projectPath) {
170
- const servers = await getLiveServersAsync();
172
+ let servers = await getLiveServersAsync();
171
173
  debug(`Looking for Extension for project: ${projectPath}`);
172
174
  debug(`Found ${servers.length} live Extension server(s) with valid ports`);
173
175
  if (servers.length === 0) {
@@ -176,15 +178,40 @@ async function findExtensionForProjectAsync(projectPath) {
176
178
  // Get MCP server's CURSOR_TRACE_ID (if running in Cursor)
177
179
  const myTraceId = process.env['CURSOR_TRACE_ID'] || '';
178
180
  debug(`My CURSOR_TRACE_ID: ${myTraceId || '(not set)'}`);
179
- // Strategy 0: CURSOR_TRACE_ID match (HIGHEST PRIORITY - same Cursor window)
181
+ // Strategy 0: CURSOR_TRACE_ID match - but only if UNIQUE match
182
+ // (Multiple windows can share the same trace ID, so we need workspace matching as tiebreaker)
180
183
  if (myTraceId) {
181
- const traceMatch = servers.find(s => s.cursorTraceId === myTraceId);
182
- if (traceMatch) {
183
- debug(`✓ CURSOR_TRACE_ID match: port=${traceMatch.port}, traceId=${myTraceId}`);
184
- return traceMatch;
184
+ const traceMatches = servers.filter(s => s.cursorTraceId === myTraceId);
185
+ if (traceMatches.length === 1) {
186
+ // Unique trace ID match - perfect!
187
+ debug(`✓ CURSOR_TRACE_ID unique match: port=${traceMatches[0].port}`);
188
+ return traceMatches[0];
189
+ }
190
+ else if (traceMatches.length > 1) {
191
+ // Multiple servers with same trace ID - use workspace matching among them
192
+ debug(`Multiple servers (${traceMatches.length}) with same CURSOR_TRACE_ID, using workspace match`);
193
+ // Try exact workspace match among trace-matched servers
194
+ const exactMatch = traceMatches.find(s => s.workspaces?.includes(projectPath));
195
+ if (exactMatch) {
196
+ debug(`✓ CURSOR_TRACE_ID + workspace match: port=${exactMatch.port}`);
197
+ return exactMatch;
198
+ }
199
+ // Try prefix match among trace-matched servers
200
+ for (const server of traceMatches) {
201
+ for (const ws of server.workspaces || []) {
202
+ if (projectPath.startsWith(ws + path.sep) || ws.startsWith(projectPath + path.sep)) {
203
+ debug(`✓ CURSOR_TRACE_ID + prefix match: port=${server.port}`);
204
+ return server;
205
+ }
206
+ }
207
+ }
208
+ // Fall back to most recent among trace-matched servers
209
+ const sorted = traceMatches.sort((a, b) => b.timestamp - a.timestamp);
210
+ debug(`✓ CURSOR_TRACE_ID + most recent: port=${sorted[0].port}`);
211
+ return sorted[0];
185
212
  }
186
213
  }
187
- // Strategy 1: Exact workspace match + traceId filter if available
214
+ // Strategy 1: Exact workspace match (when no trace ID or no trace match)
188
215
  const workspaceMatches = servers.filter(s => s.workspaces?.includes(projectPath));
189
216
  if (workspaceMatches.length === 1) {
190
217
  debug(`✓ Exact workspace match (single): port=${workspaceMatches[0].port}`);
@@ -233,14 +260,31 @@ function findExtensionForProject(projectPath) {
233
260
  const servers = getLiveServers();
234
261
  if (servers.length === 0)
235
262
  return null;
236
- // Strategy 0: CURSOR_TRACE_ID match (highest priority)
263
+ // Strategy 0: CURSOR_TRACE_ID match - but only if UNIQUE
237
264
  const myTraceId = process.env['CURSOR_TRACE_ID'] || '';
238
265
  if (myTraceId) {
239
- const traceMatch = servers.find(s => s.cursorTraceId === myTraceId);
240
- if (traceMatch)
241
- return traceMatch;
266
+ const traceMatches = servers.filter(s => s.cursorTraceId === myTraceId);
267
+ if (traceMatches.length === 1) {
268
+ return traceMatches[0];
269
+ }
270
+ else if (traceMatches.length > 1) {
271
+ // Multiple servers with same trace ID - try workspace match among them
272
+ const exactMatch = traceMatches.find(s => s.workspaces?.includes(projectPath));
273
+ if (exactMatch)
274
+ return exactMatch;
275
+ // Prefix match among trace-matched
276
+ for (const server of traceMatches) {
277
+ for (const ws of server.workspaces || []) {
278
+ if (projectPath.startsWith(ws + path.sep) || ws.startsWith(projectPath + path.sep)) {
279
+ return server;
280
+ }
281
+ }
282
+ }
283
+ // Most recent among trace-matched
284
+ return traceMatches.sort((a, b) => b.timestamp - a.timestamp)[0];
285
+ }
242
286
  }
243
- // Strategy 1: Exact workspace match
287
+ // Strategy 1: Exact workspace match (when no trace ID or no trace match)
244
288
  const workspaceMatches = servers.filter(s => s.workspaces?.includes(projectPath));
245
289
  if (workspaceMatches.length >= 1) {
246
290
  return workspaceMatches.sort((a, b) => b.timestamp - a.timestamp)[0];
@@ -412,34 +456,529 @@ function handleMessage(message) {
412
456
  break;
413
457
  }
414
458
  }
459
+ // Track tried servers to avoid retry loops
460
+ let triedServerPorts = new Set();
415
461
  /**
416
462
  * Request feedback from user via Extension
463
+ * Uses single-connection with fast retry strategy:
464
+ * - Sorts servers by relevance (workspace match, most recent)
465
+ * - Tries one server at a time
466
+ * - If rejected (PROJECT_NOT_IN_WINDOW or no webview), immediately tries next
467
+ * - Falls back to browser if all fail
417
468
  */
418
469
  async function requestFeedback(projectDirectory, summary, timeout, agentName) {
419
- await ensureConnectedForProject(projectDirectory);
470
+ // Reset tried servers
471
+ triedServerPorts.clear();
472
+ // Get all servers sorted by relevance
473
+ const servers = await getSortedServers(projectDirectory);
474
+ if (servers.length === 0) {
475
+ debug('No Extension servers found, falling back to browser');
476
+ return requestFeedbackViaBrowser(projectDirectory, summary, timeout);
477
+ }
478
+ debug(`Found ${servers.length} server(s), trying in order of relevance...`);
479
+ // Try servers one by one until success
480
+ return tryServersSequentially(servers, projectDirectory, summary, timeout, agentName);
481
+ }
482
+ /**
483
+ * Get servers sorted by relevance for the project
484
+ */
485
+ async function getSortedServers(projectPath) {
486
+ const servers = await getLiveServersAsync();
487
+ if (servers.length === 0) {
488
+ return [];
489
+ }
490
+ // Calculate workspace frequency for uniqueness bonus
491
+ const workspaceFrequency = new Map();
492
+ servers.forEach(s => {
493
+ s.workspaces?.forEach(ws => {
494
+ workspaceFrequency.set(ws, (workspaceFrequency.get(ws) || 0) + 1);
495
+ });
496
+ });
497
+ // Score each server
498
+ const scored = servers.map(server => {
499
+ let score = 0;
500
+ let reasons = [];
501
+ // 1. FOCUSED WINDOW (+500) - Highest priority!
502
+ if (server.focused) {
503
+ score += 500;
504
+ reasons.push('focused');
505
+ }
506
+ // 2. Workspace UNIQUENESS bonus (+1000)
507
+ // If this project is ONLY in this server's workspaces, huge bonus
508
+ if (server.workspaces?.includes(projectPath)) {
509
+ const freq = workspaceFrequency.get(projectPath) || 1;
510
+ if (freq === 1) {
511
+ score += 1000;
512
+ reasons.push('unique-ws');
513
+ }
514
+ else {
515
+ score += 100;
516
+ reasons.push('shared-ws');
517
+ }
518
+ }
519
+ // 3. Prefix match (+50)
520
+ for (const ws of server.workspaces || []) {
521
+ if (projectPath.startsWith(ws + path.sep)) {
522
+ score += 50;
523
+ reasons.push('prefix');
524
+ break;
525
+ }
526
+ }
527
+ // 4. CURSOR_TRACE_ID match (+10) - Low priority since often shared
528
+ const myTraceId = process.env['CURSOR_TRACE_ID'] || '';
529
+ if (myTraceId && server.cursorTraceId === myTraceId) {
530
+ score += 10;
531
+ reasons.push('trace');
532
+ }
533
+ // 5. More recent timestamp (tiny bonus)
534
+ score += server.timestamp / (10 ** 14);
535
+ return { server, score, reasons };
536
+ });
537
+ // Sort by score descending
538
+ scored.sort((a, b) => b.score - a.score);
539
+ debug(`Server scores:`);
540
+ scored.forEach(s => {
541
+ debug(` port=${s.server.port} score=${s.score.toFixed(0)} [${s.reasons.join(', ')}]`);
542
+ });
543
+ return scored.map(s => s.server);
544
+ }
545
+ /**
546
+ * Try servers one by one until success or all fail
547
+ */
548
+ async function tryServersSequentially(servers, projectDirectory, summary, timeout, agentName) {
549
+ for (const server of servers) {
550
+ // Skip already tried servers
551
+ if (triedServerPorts.has(server.port)) {
552
+ continue;
553
+ }
554
+ triedServerPorts.add(server.port);
555
+ debug(`Trying server port ${server.port}...`);
556
+ try {
557
+ const result = await tryServerOnce(server, projectDirectory, summary, timeout, agentName);
558
+ return result;
559
+ }
560
+ catch (err) {
561
+ const errorMessage = err.message || '';
562
+ // These errors mean we should try next server
563
+ if (errorMessage === 'PROJECT_NOT_IN_WINDOW' ||
564
+ errorMessage.includes('No feedback panel') ||
565
+ errorMessage.includes('Connection failed')) {
566
+ debug(`Server ${server.port} rejected: ${errorMessage}, trying next...`);
567
+ continue;
568
+ }
569
+ // Other errors (timeout, user cancelled) should propagate
570
+ throw err;
571
+ }
572
+ }
573
+ // All servers failed, fall back to browser
574
+ debug('All servers failed, falling back to browser');
575
+ return requestFeedbackViaBrowser(projectDirectory, summary, timeout);
576
+ }
577
+ /**
578
+ * Try a single server, returns result or throws error
579
+ */
580
+ async function tryServerOnce(server, projectDirectory, summary, timeout, agentName) {
581
+ const url = `ws://127.0.0.1:${server.port}/ws`;
420
582
  const sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
421
583
  return new Promise((resolve, reject) => {
422
- // Set timeout
423
- const timeoutHandle = setTimeout(() => {
424
- pendingFeedbackResolvers.delete(sessionId);
425
- reject(new Error(`Feedback timeout after ${timeout} seconds`));
584
+ let conn = null;
585
+ let resolved = false;
586
+ const cleanup = () => {
587
+ if (conn && conn.readyState === WebSocket.OPEN) {
588
+ conn.close();
589
+ }
590
+ };
591
+ // Timeout for this attempt (use a shorter timeout for connection phase)
592
+ const connectionTimeout = setTimeout(() => {
593
+ if (!resolved) {
594
+ resolved = true;
595
+ cleanup();
596
+ reject(new Error('Connection failed'));
597
+ }
598
+ }, CONNECTION_TIMEOUT_MS);
599
+ // Overall timeout
600
+ const overallTimeout = setTimeout(() => {
601
+ if (!resolved) {
602
+ resolved = true;
603
+ cleanup();
604
+ reject(new Error(`Feedback timeout after ${timeout} seconds`));
605
+ }
426
606
  }, timeout * 1000);
427
- // Store resolver
428
- pendingFeedbackResolvers.set(sessionId, {
429
- resolve,
430
- reject,
431
- timeout: timeoutHandle
607
+ try {
608
+ conn = new WebSocket(url);
609
+ conn.on('open', () => {
610
+ clearTimeout(connectionTimeout);
611
+ debug(`Connected to server port ${server.port}`);
612
+ // Register
613
+ conn.send(JSON.stringify({
614
+ type: 'register',
615
+ clientType: 'mcp-server',
616
+ pid: process.pid,
617
+ parentPid: process.ppid
618
+ }));
619
+ // Send feedback request
620
+ conn.send(JSON.stringify({
621
+ type: 'feedback_request',
622
+ session_id: sessionId,
623
+ project_directory: projectDirectory,
624
+ summary,
625
+ timeout,
626
+ agent_name: agentName
627
+ }));
628
+ debug(`Feedback request sent: session=${sessionId}`);
629
+ });
630
+ conn.on('message', (data) => {
631
+ try {
632
+ const message = JSON.parse(data.toString());
633
+ debug(`Received from port ${server.port}: ${message.type}`);
634
+ switch (message.type) {
635
+ case 'feedback_result':
636
+ if (message.session_id === sessionId && !resolved) {
637
+ resolved = true;
638
+ clearTimeout(overallTimeout);
639
+ cleanup();
640
+ resolve({
641
+ feedback: message.feedback,
642
+ images: message.images || []
643
+ });
644
+ }
645
+ break;
646
+ case 'feedback_error':
647
+ if (message.session_id === sessionId && !resolved) {
648
+ resolved = true;
649
+ clearTimeout(overallTimeout);
650
+ cleanup();
651
+ reject(new Error(message.error));
652
+ }
653
+ break;
654
+ }
655
+ }
656
+ catch (e) {
657
+ debug(`Parse error: ${e}`);
658
+ }
659
+ });
660
+ conn.on('error', (err) => {
661
+ if (!resolved) {
662
+ resolved = true;
663
+ clearTimeout(connectionTimeout);
664
+ clearTimeout(overallTimeout);
665
+ reject(new Error(`Connection failed: ${err.message}`));
666
+ }
667
+ });
668
+ conn.on('close', () => {
669
+ if (!resolved) {
670
+ resolved = true;
671
+ clearTimeout(connectionTimeout);
672
+ clearTimeout(overallTimeout);
673
+ reject(new Error('Connection closed'));
674
+ }
675
+ });
676
+ }
677
+ catch (e) {
678
+ if (!resolved) {
679
+ resolved = true;
680
+ clearTimeout(connectionTimeout);
681
+ clearTimeout(overallTimeout);
682
+ reject(new Error(`Connection failed: ${e}`));
683
+ }
684
+ }
685
+ });
686
+ }
687
+ // ============================================================================
688
+ // Browser Fallback
689
+ // ============================================================================
690
+ /**
691
+ * Open system default browser
692
+ */
693
+ function openBrowser(url) {
694
+ const platform = os.platform();
695
+ let cmd;
696
+ if (platform === 'darwin') {
697
+ cmd = `open "${url}"`;
698
+ }
699
+ else if (platform === 'win32') {
700
+ cmd = `start "" "${url}"`;
701
+ }
702
+ else {
703
+ cmd = `xdg-open "${url}"`;
704
+ }
705
+ exec(cmd, (error) => {
706
+ if (error) {
707
+ debug(`Failed to open browser: ${error.message}`);
708
+ }
709
+ });
710
+ }
711
+ /**
712
+ * Generate HTML page for browser feedback
713
+ */
714
+ function generateBrowserHtml(summary, port) {
715
+ const escapedSummary = summary
716
+ .replace(/&/g, '&')
717
+ .replace(/</g, '&lt;')
718
+ .replace(/>/g, '&gt;')
719
+ .replace(/"/g, '&quot;')
720
+ .replace(/\n/g, '<br>');
721
+ return `<!DOCTYPE html>
722
+ <html lang="en">
723
+ <head>
724
+ <meta charset="UTF-8">
725
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
726
+ <title>MCP Feedback</title>
727
+ <style>
728
+ * { box-sizing: border-box; margin: 0; padding: 0; }
729
+ body {
730
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
731
+ background: #1e1e1e;
732
+ color: #d4d4d4;
733
+ min-height: 100vh;
734
+ display: flex;
735
+ align-items: center;
736
+ justify-content: center;
737
+ padding: 20px;
738
+ }
739
+ .container {
740
+ max-width: 800px;
741
+ width: 100%;
742
+ background: #252526;
743
+ border-radius: 12px;
744
+ box-shadow: 0 8px 32px rgba(0,0,0,0.3);
745
+ overflow: hidden;
746
+ }
747
+ .header {
748
+ background: #007acc;
749
+ padding: 16px 24px;
750
+ display: flex;
751
+ align-items: center;
752
+ gap: 12px;
753
+ }
754
+ .header h1 {
755
+ font-size: 18px;
756
+ font-weight: 600;
757
+ color: white;
758
+ }
759
+ .header .icon { font-size: 24px; }
760
+ .content { padding: 24px; }
761
+ .ai-message {
762
+ background: #2d2d2d;
763
+ border: 1px solid #3c3c3c;
764
+ border-radius: 8px;
765
+ padding: 16px;
766
+ margin-bottom: 20px;
767
+ max-height: 400px;
768
+ overflow-y: auto;
769
+ line-height: 1.6;
770
+ }
771
+ .label {
772
+ font-size: 12px;
773
+ color: #888;
774
+ margin-bottom: 8px;
775
+ display: flex;
776
+ align-items: center;
777
+ gap: 6px;
778
+ }
779
+ .quick-btns {
780
+ display: flex;
781
+ gap: 8px;
782
+ margin-bottom: 16px;
783
+ flex-wrap: wrap;
784
+ }
785
+ .quick-btn {
786
+ padding: 8px 16px;
787
+ background: transparent;
788
+ border: 1px solid #3c3c3c;
789
+ color: #d4d4d4;
790
+ border-radius: 20px;
791
+ cursor: pointer;
792
+ font-size: 13px;
793
+ transition: all 0.2s;
794
+ }
795
+ .quick-btn:hover {
796
+ background: #3c3c3c;
797
+ border-color: #569cd6;
798
+ }
799
+ textarea {
800
+ width: 100%;
801
+ min-height: 120px;
802
+ padding: 12px;
803
+ background: #2d2d2d;
804
+ border: 1px solid #3c3c3c;
805
+ border-radius: 8px;
806
+ color: #d4d4d4;
807
+ font-size: 14px;
808
+ font-family: inherit;
809
+ resize: vertical;
810
+ margin-bottom: 16px;
811
+ }
812
+ textarea:focus {
813
+ outline: none;
814
+ border-color: #007acc;
815
+ }
816
+ .submit-btn {
817
+ width: 100%;
818
+ padding: 14px;
819
+ background: #007acc;
820
+ border: none;
821
+ border-radius: 8px;
822
+ color: white;
823
+ font-size: 15px;
824
+ font-weight: 600;
825
+ cursor: pointer;
826
+ transition: background 0.2s;
827
+ }
828
+ .submit-btn:hover { background: #0098ff; }
829
+ .submit-btn:disabled {
830
+ background: #3c3c3c;
831
+ cursor: not-allowed;
832
+ }
833
+ .success {
834
+ text-align: center;
835
+ padding: 40px;
836
+ }
837
+ .success .icon { font-size: 48px; margin-bottom: 16px; }
838
+ .success h2 { color: #4ec9b0; margin-bottom: 8px; }
839
+ .success p { color: #888; }
840
+ </style>
841
+ </head>
842
+ <body>
843
+ <div class="container" id="feedbackForm">
844
+ <div class="header">
845
+ <span class="icon">💬</span>
846
+ <h1>MCP Feedback</h1>
847
+ </div>
848
+ <div class="content">
849
+ <div class="label">🤖 AI Summary</div>
850
+ <div class="ai-message">${escapedSummary}</div>
851
+
852
+ <div class="label">💬 Your Feedback</div>
853
+ <div class="quick-btns">
854
+ <button class="quick-btn" onclick="setFeedback('Continue')">▶️ Continue</button>
855
+ <button class="quick-btn" onclick="setFeedback('Looks good')">👍 Good</button>
856
+ <button class="quick-btn" onclick="setFeedback('Please fix it')">🔧 Fix</button>
857
+ <button class="quick-btn" onclick="setFeedback('Stop')">⏹️ Stop</button>
858
+ </div>
859
+ <textarea id="feedback" placeholder="Type your feedback here..."></textarea>
860
+ <button class="submit-btn" id="submitBtn" onclick="submitFeedback()">Send Feedback</button>
861
+ </div>
862
+ </div>
863
+
864
+ <div class="container" id="successMsg" style="display: none;">
865
+ <div class="success">
866
+ <div class="icon">✅</div>
867
+ <h2>Feedback Sent!</h2>
868
+ <p>You can close this window now.</p>
869
+ </div>
870
+ </div>
871
+
872
+ <script>
873
+ function setFeedback(text) {
874
+ document.getElementById('feedback').value = text;
875
+ }
876
+
877
+ async function submitFeedback() {
878
+ const feedback = document.getElementById('feedback').value.trim();
879
+ if (!feedback) {
880
+ alert('Please enter your feedback');
881
+ return;
882
+ }
883
+
884
+ const btn = document.getElementById('submitBtn');
885
+ btn.disabled = true;
886
+ btn.textContent = 'Sending...';
887
+
888
+ try {
889
+ const response = await fetch('/submit', {
890
+ method: 'POST',
891
+ headers: { 'Content-Type': 'application/json' },
892
+ body: JSON.stringify({ feedback })
893
+ });
894
+
895
+ if (response.ok) {
896
+ document.getElementById('feedbackForm').style.display = 'none';
897
+ document.getElementById('successMsg').style.display = 'block';
898
+ } else {
899
+ throw new Error('Submit failed');
900
+ }
901
+ } catch (e) {
902
+ btn.disabled = false;
903
+ btn.textContent = 'Send Feedback';
904
+ alert('Failed to send feedback. Please try again.');
905
+ }
906
+ }
907
+ </script>
908
+ </body>
909
+ </html>`;
910
+ }
911
+ /**
912
+ * Request feedback via browser (fallback when extension not available)
913
+ */
914
+ async function requestFeedbackViaBrowser(projectDirectory, summary, timeout) {
915
+ return new Promise((resolve, reject) => {
916
+ // Find available port
917
+ const server = http.createServer();
918
+ server.listen(0, '127.0.0.1', () => {
919
+ const address = server.address();
920
+ if (!address || typeof address === 'string') {
921
+ reject(new Error('Failed to start HTTP server'));
922
+ return;
923
+ }
924
+ const port = address.port;
925
+ debug(`Browser feedback server started on port ${port}`);
926
+ let feedbackReceived = false;
927
+ // Handle requests
928
+ server.on('request', (req, res) => {
929
+ if (req.method === 'GET' && req.url === '/') {
930
+ // Serve HTML page
931
+ res.writeHead(200, { 'Content-Type': 'text/html' });
932
+ res.end(generateBrowserHtml(summary, port));
933
+ }
934
+ else if (req.method === 'POST' && req.url === '/submit') {
935
+ // Handle feedback submission
936
+ let body = '';
937
+ req.on('data', chunk => body += chunk);
938
+ req.on('end', () => {
939
+ try {
940
+ const data = JSON.parse(body);
941
+ feedbackReceived = true;
942
+ res.writeHead(200, { 'Content-Type': 'application/json' });
943
+ res.end(JSON.stringify({ success: true }));
944
+ // Close server and resolve
945
+ setTimeout(() => {
946
+ server.close();
947
+ resolve({
948
+ feedback: data.feedback || '',
949
+ images: []
950
+ });
951
+ }, 1000);
952
+ }
953
+ catch (e) {
954
+ res.writeHead(400);
955
+ res.end('Invalid request');
956
+ }
957
+ });
958
+ }
959
+ else {
960
+ res.writeHead(404);
961
+ res.end('Not found');
962
+ }
963
+ });
964
+ // Open browser
965
+ const url = `http://127.0.0.1:${port}`;
966
+ debug(`Opening browser: ${url}`);
967
+ openBrowser(url);
968
+ // Timeout
969
+ const timeoutHandle = setTimeout(() => {
970
+ if (!feedbackReceived) {
971
+ server.close();
972
+ reject(new Error(`Browser feedback timeout after ${timeout} seconds`));
973
+ }
974
+ }, timeout * 1000);
975
+ server.on('close', () => {
976
+ clearTimeout(timeoutHandle);
977
+ });
978
+ });
979
+ server.on('error', (err) => {
980
+ reject(new Error(`Failed to start browser feedback server: ${err.message}`));
432
981
  });
433
- // Send request to Extension
434
- ws?.send(JSON.stringify({
435
- type: 'feedback_request',
436
- session_id: sessionId,
437
- project_directory: projectDirectory,
438
- summary,
439
- timeout,
440
- agent_name: agentName // For multi-agent display
441
- }));
442
- debug(`Feedback request sent: session=${sessionId}, agent=${agentName || 'default'}`);
443
982
  });
444
983
  }
445
984
  // ============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-feedback-enhanced",
3
- "version": "0.1.64",
3
+ "version": "0.1.67",
4
4
  "description": "MCP Feedback Enhanced Server - Interactive feedback collection for AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",