@nbakka/mcp-appium 2.0.99 → 3.0.1

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/lib/server.js CHANGED
@@ -794,275 +794,553 @@ tool(
794
794
  }
795
795
  );
796
796
 
797
- // Global state for tracking approval sessions
798
- let approvalSessions = new Map();
799
-
797
+ // Tool 1: Review Test Cases - Launch HTML UI for approval
800
798
  tool(
801
799
  "review_testcases",
802
800
  "Open test cases in browser for manual approval.",
803
801
  {
804
- testCases: zod_1.z
805
- .array(zod_1.z.array(zod_1.z.string()))
806
- .describe("test cases array generated by tool generate_testcases_from_ticket_data")
802
+ testCases: zod_1.z.array(zod_1.z.array(zod_1.z.string())).describe("test cases array generated by tool generate_testcases_from_ticket_data")
807
803
  },
808
804
  async ({ testCases }) => {
809
805
  try {
810
- const sessionId = Date.now().toString();
811
-
812
- function parseTestCases(testCasesArray) {
813
- const parsed = { new: [], modify: [], remove: [] };
814
- testCasesArray.forEach(tc => {
815
- if (tc.length === 4 && tc[2] === 'Modify') {
816
- parsed.modify.push({ id: tc[3], original: tc[0], modified: tc[1] });
817
- } else if (tc.length === 3 && tc[1] === 'Remove') {
818
- parsed.remove.push({ id: tc[2], description: tc[0] });
819
- } else if (tc.length === 2 && tc[1] === 'New') {
820
- parsed.new.push({
821
- description: tc[0],
822
- id: `NEW-${Date.now()}-${Math.random().toString(36).slice(2)}`
823
- });
824
- }
825
- });
826
- return parsed;
806
+ // These should already be imported at the top of your server file
807
+ // const express = require('express');
808
+ // const open = require('open');
809
+ const app = express();
810
+ const port = 3001;
811
+
812
+ // Generate unique session ID
813
+ const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
814
+
815
+ // Store approval status
816
+ let approvalStatus = 'pending';
817
+ let finalTestCases = JSON.parse(JSON.stringify(testCases)); // Deep copy
818
+
819
+ // Serve static files from review-ui folder if it exists
820
+ const reviewUiPath = path.join(__dirname, 'review-ui');
821
+ try {
822
+ await fs.access(reviewUiPath);
823
+ app.use('/static', express.static(reviewUiPath));
824
+ } catch (err) {
825
+ // review-ui folder doesn't exist, continue without it
827
826
  }
828
827
 
829
- const parsedTestCases = parseTestCases(testCases);
828
+ app.use(express.json());
829
+ app.use(express.static('public'));
830
+
831
+ // Main review page
832
+ app.get('/', (req, res) => {
833
+ res.send(`
834
+ <!DOCTYPE html>
835
+ <html lang="en">
836
+ <head>
837
+ <meta charset="UTF-8">
838
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
839
+ <title>Test Cases Review & Approval</title>
840
+ <style>
841
+ * {
842
+ margin: 0;
843
+ padding: 0;
844
+ box-sizing: border-box;
845
+ }
830
846
 
831
- // Initialize session with proper locking mechanism
832
- approvalSessions.set(sessionId, {
833
- status: 'pending',
834
- testCases: parsedTestCases,
835
- originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)),
836
- server: null,
837
- startTime: Date.now(),
838
- locked: false
839
- });
847
+ body {
848
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
849
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
850
+ min-height: 100vh;
851
+ padding: 20px;
852
+ }
840
853
 
841
- const app = express();
854
+ .container {
855
+ max-width: 1200px;
856
+ margin: 0 auto;
857
+ background: white;
858
+ border-radius: 15px;
859
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
860
+ overflow: hidden;
861
+ }
842
862
 
843
- // Try multiple ports if 3001 is busy
844
- let port = 3001;
845
- let server = null;
846
-
847
- const tryStartServer = (portToTry) => {
848
- return new Promise((resolve, reject) => {
849
- const testServer = app.listen(portToTry, () => {
850
- resolve({ server: testServer, port: portToTry });
851
- }).on('error', (err) => {
852
- if (err.code === 'EADDRINUSE') {
853
- reject(err);
854
- } else {
855
- reject(err);
856
- }
857
- });
858
- });
859
- };
863
+ .header {
864
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
865
+ color: white;
866
+ padding: 30px;
867
+ text-align: center;
868
+ }
860
869
 
861
- // Try ports 3001-3010
862
- for (let i = 0; i < 10; i++) {
863
- try {
864
- const result = await tryStartServer(port + i);
865
- server = result.server;
866
- port = result.port;
867
- break;
868
- } catch (err) {
869
- if (i === 9) throw new Error(`No available ports found (tried ${port}-${port + 9})`);
870
+ .header h1 {
871
+ font-size: 2.5rem;
872
+ margin-bottom: 10px;
873
+ font-weight: 700;
870
874
  }
871
- }
872
875
 
873
- app.use(express.json());
874
- app.use(express.static(path.join(__dirname, 'review-ui')));
876
+ .header p {
877
+ font-size: 1.1rem;
878
+ opacity: 0.9;
879
+ }
875
880
 
876
- app.get("/", (_req, res) =>
877
- res.sendFile(path.join(__dirname, 'review-ui', 'index.html'))
878
- );
881
+ .stats {
882
+ display: flex;
883
+ justify-content: space-around;
884
+ background: #f8f9fa;
885
+ padding: 20px;
886
+ border-bottom: 1px solid #e9ecef;
887
+ }
879
888
 
880
- app.get("/api/testcases", (_req, res) => {
881
- const session = approvalSessions.get(sessionId);
882
- if (!session) return res.status(404).json({ error: 'Session not found' });
883
- res.json({ sessionId, testCases: session.testCases });
884
- });
889
+ .stat-item {
890
+ text-align: center;
891
+ }
885
892
 
886
- app.post(`/delete/${sessionId}`, (req, res) => {
887
- const session = approvalSessions.get(sessionId);
888
- if (!session) return res.status(404).json({ error: 'Session not found' });
893
+ .stat-number {
894
+ font-size: 2rem;
895
+ font-weight: bold;
896
+ color: #495057;
897
+ }
889
898
 
890
- const { type, id } = req.body;
891
- if (!['new', 'modify', 'remove'].includes(type) || !id) {
892
- return res.status(400).json({ error: 'Invalid delete request' });
899
+ .stat-label {
900
+ color: #6c757d;
901
+ margin-top: 5px;
893
902
  }
894
- try {
895
- if (session.testCases[type] && Array.isArray(session.testCases[type])) {
896
- session.testCases[type] = session.testCases[type].filter(tc => tc.id !== id);
897
- approvalSessions.set(sessionId, session);
898
- return res.json({ status: 'deleted', testCases: session.testCases });
899
- } else {
900
- return res.status(400).json({ error: `Invalid type: ${type}` });
901
- }
902
- } catch (error) {
903
- console.error('Delete error:', error);
904
- return res.status(500).json({ error: 'Failed to delete test case' });
903
+
904
+ .content {
905
+ padding: 30px;
905
906
  }
906
- });
907
907
 
908
- app.post(`/update/${sessionId}`, (req, res) => {
909
- const session = approvalSessions.get(sessionId);
910
- if (!session) return res.status(404).json({ error: 'Session not found' });
908
+ .test-case {
909
+ background: #fff;
910
+ border: 2px solid #e9ecef;
911
+ border-radius: 12px;
912
+ margin-bottom: 20px;
913
+ overflow: hidden;
914
+ transition: all 0.3s ease;
915
+ position: relative;
916
+ }
911
917
 
912
- try {
913
- const { type, id, data } = req.body;
914
- if (!['new', 'modify'].includes(type) || !id || !data) {
915
- return res.status(400).json({ error: 'Invalid update request' });
916
- }
918
+ .test-case:hover {
919
+ border-color: #4facfe;
920
+ box-shadow: 0 8px 25px rgba(79, 172, 254, 0.15);
921
+ }
917
922
 
918
- const items = session.testCases[type];
919
- const index = items.findIndex(tc => tc.id === id);
920
- if (index === -1) {
921
- return res.status(404).json({ error: 'Test case not found' });
922
- }
923
+ .test-case.deleted {
924
+ opacity: 0.5;
925
+ background: #f8f9fa;
926
+ border-color: #dc3545;
927
+ }
923
928
 
924
- // Update the test case
925
- if (type === 'new') {
926
- items[index].description = data.description;
927
- } else if (type === 'modify') {
928
- items[index].modified = data.modified;
929
- }
929
+ .test-case-header {
930
+ background: #f8f9fa;
931
+ padding: 15px 20px;
932
+ border-bottom: 1px solid #e9ecef;
933
+ display: flex;
934
+ justify-content: between;
935
+ align-items: center;
936
+ }
930
937
 
931
- approvalSessions.set(sessionId, session);
932
- return res.json({ status: 'updated', testCases: session.testCases });
933
- } catch (error) {
934
- console.error('Update error:', error);
935
- return res.status(500).json({ error: 'Failed to update test case' });
938
+ .test-case-id {
939
+ font-weight: 600;
940
+ color: #495057;
936
941
  }
937
- });
938
942
 
939
- app.post(`/approve/${sessionId}`, (req, res) => {
940
- const session = approvalSessions.get(sessionId);
941
- if (!session) return res.status(404).json({ error: 'Session not found' });
943
+ .test-case-status {
944
+ padding: 5px 12px;
945
+ border-radius: 20px;
946
+ font-size: 0.85rem;
947
+ font-weight: 500;
948
+ text-transform: uppercase;
949
+ }
942
950
 
943
- try {
944
- // Use atomic update with locking
945
- session.locked = true;
946
- session.testCases = req.body;
947
- session.finalTestCases = JSON.parse(JSON.stringify(req.body)); // Deep copy
948
- session.status = 'approved';
949
- session.approvedAt = Date.now();
950
- approvalSessions.set(sessionId, session);
951
-
952
- console.log(`Session ${sessionId} approved at ${new Date().toISOString()}`);
953
- res.json({ status: 'approved', message: 'Test cases approved successfully!' });
954
- } catch (error) {
955
- console.error('Approval error:', error);
956
- res.status(500).json({ error: 'Failed to approve test cases' });
951
+ .status-new {
952
+ background: #d4edda;
953
+ color: #155724;
954
+ }
955
+
956
+ .status-modify {
957
+ background: #fff3cd;
958
+ color: #856404;
959
+ }
960
+
961
+ .status-remove {
962
+ background: #f8d7da;
963
+ color: #721c24;
964
+ }
965
+
966
+ .test-case-content {
967
+ padding: 20px;
968
+ }
969
+
970
+ .test-case-title {
971
+ font-size: 1.1rem;
972
+ font-weight: 500;
973
+ color: #212529;
974
+ margin-bottom: 15px;
975
+ line-height: 1.5;
976
+ }
977
+
978
+ .test-case-title input {
979
+ width: 100%;
980
+ border: 1px solid #ced4da;
981
+ border-radius: 6px;
982
+ padding: 10px;
983
+ font-size: 1rem;
984
+ transition: border-color 0.3s ease;
985
+ }
986
+
987
+ .test-case-title input:focus {
988
+ outline: none;
989
+ border-color: #4facfe;
990
+ box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
991
+ }
992
+
993
+ .test-case-actions {
994
+ display: flex;
995
+ gap: 10px;
996
+ }
997
+
998
+ .btn {
999
+ padding: 8px 16px;
1000
+ border: none;
1001
+ border-radius: 6px;
1002
+ cursor: pointer;
1003
+ font-size: 0.9rem;
1004
+ transition: all 0.3s ease;
1005
+ font-weight: 500;
1006
+ }
1007
+
1008
+ .btn-delete {
1009
+ background: #dc3545;
1010
+ color: white;
957
1011
  }
1012
+
1013
+ .btn-delete:hover {
1014
+ background: #c82333;
1015
+ }
1016
+
1017
+ .btn-restore {
1018
+ background: #28a745;
1019
+ color: white;
1020
+ }
1021
+
1022
+ .btn-restore:hover {
1023
+ background: #218838;
1024
+ }
1025
+
1026
+ .footer {
1027
+ background: #f8f9fa;
1028
+ padding: 30px;
1029
+ text-align: center;
1030
+ border-top: 1px solid #e9ecef;
1031
+ }
1032
+
1033
+ .btn-primary {
1034
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
1035
+ color: white;
1036
+ padding: 15px 40px;
1037
+ font-size: 1.1rem;
1038
+ border: none;
1039
+ border-radius: 50px;
1040
+ cursor: pointer;
1041
+ transition: all 0.3s ease;
1042
+ margin: 0 10px;
1043
+ }
1044
+
1045
+ .btn-primary:hover {
1046
+ transform: translateY(-2px);
1047
+ box-shadow: 0 10px 30px rgba(79, 172, 254, 0.4);
1048
+ }
1049
+
1050
+ .btn-secondary {
1051
+ background: #6c757d;
1052
+ color: white;
1053
+ padding: 15px 40px;
1054
+ font-size: 1.1rem;
1055
+ border: none;
1056
+ border-radius: 50px;
1057
+ cursor: pointer;
1058
+ transition: all 0.3s ease;
1059
+ margin: 0 10px;
1060
+ }
1061
+
1062
+ .btn-secondary:hover {
1063
+ background: #5a6268;
1064
+ transform: translateY(-2px);
1065
+ }
1066
+
1067
+ .notification {
1068
+ position: fixed;
1069
+ top: 20px;
1070
+ right: 20px;
1071
+ padding: 15px 25px;
1072
+ background: #28a745;
1073
+ color: white;
1074
+ border-radius: 8px;
1075
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
1076
+ display: none;
1077
+ z-index: 1000;
1078
+ }
1079
+ </style>
1080
+ </head>
1081
+ <body>
1082
+ <div class="container">
1083
+ <div class="header">
1084
+ <h1>Test Cases Review</h1>
1085
+ <p>Review, edit, and approve test cases for JIRA ticket</p>
1086
+ </div>
1087
+
1088
+ <div class="stats">
1089
+ <div class="stat-item">
1090
+ <div class="stat-number" id="totalCount">${testCases.length}</div>
1091
+ <div class="stat-label">Total Test Cases</div>
1092
+ </div>
1093
+ <div class="stat-item">
1094
+ <div class="stat-number" id="activeCount">${testCases.length}</div>
1095
+ <div class="stat-label">Active</div>
1096
+ </div>
1097
+ <div class="stat-item">
1098
+ <div class="stat-number" id="deletedCount">0</div>
1099
+ <div class="stat-label">Deleted</div>
1100
+ </div>
1101
+ </div>
1102
+
1103
+ <div class="content">
1104
+ <div id="testCases">
1105
+ ${testCases.map((testCase, index) => `
1106
+ <div class="test-case" data-index="${index}">
1107
+ <div class="test-case-header">
1108
+ <span class="test-case-id">Test Case #${index + 1}</span>
1109
+ <span class="test-case-status status-${testCase[2] ? testCase[2].toLowerCase() : 'new'}">${testCase[2] || 'New'}</span>
1110
+ </div>
1111
+ <div class="test-case-content">
1112
+ <div class="test-case-title">
1113
+ <input type="text" value="${testCase[0].replace(/"/g, '&quot;')}" data-index="${index}">
1114
+ </div>
1115
+ ${testCase[2] === 'Modify' ? `
1116
+ <div class="original-case">
1117
+ <small><strong>Original:</strong> ${testCase[3] || 'N/A'}</small>
1118
+ </div>
1119
+ ` : ''}
1120
+ ${testCase[2] === 'Remove' ? `
1121
+ <div class="remove-case">
1122
+ <small><strong>TCMS ID:</strong> ${testCase[2] || 'N/A'}</small>
1123
+ </div>
1124
+ ` : ''}
1125
+ <div class="test-case-actions">
1126
+ <button class="btn btn-delete" onclick="toggleDelete(${index})">Delete</button>
1127
+ </div>
1128
+ </div>
1129
+ </div>
1130
+ `).join('')}
1131
+ </div>
1132
+ </div>
1133
+
1134
+ <div class="footer">
1135
+ <button class="btn-primary" onclick="approveTestCases()">Approve Test Cases</button>
1136
+ <button class="btn-secondary" onclick="resetAll()">Reset All</button>
1137
+ </div>
1138
+ </div>
1139
+
1140
+ <div class="notification" id="notification"></div>
1141
+
1142
+ <script>
1143
+ let testCases = ${JSON.stringify(testCases)};
1144
+ let deletedIndices = new Set();
1145
+
1146
+ function updateStats() {
1147
+ document.getElementById('activeCount').textContent = testCases.length - deletedIndices.size;
1148
+ document.getElementById('deletedCount').textContent = deletedIndices.size;
1149
+ }
1150
+
1151
+ function toggleDelete(index) {
1152
+ const testCaseEl = document.querySelector(\`[data-index="\${index}"]\`);
1153
+ const btn = testCaseEl.querySelector('.btn-delete');
1154
+
1155
+ if (deletedIndices.has(index)) {
1156
+ // Restore
1157
+ deletedIndices.delete(index);
1158
+ testCaseEl.classList.remove('deleted');
1159
+ btn.textContent = 'Delete';
1160
+ btn.className = 'btn btn-delete';
1161
+ } else {
1162
+ // Delete
1163
+ deletedIndices.add(index);
1164
+ testCaseEl.classList.add('deleted');
1165
+ btn.textContent = 'Restore';
1166
+ btn.className = 'btn btn-restore';
1167
+ }
1168
+ updateStats();
1169
+ }
1170
+
1171
+ function resetAll() {
1172
+ deletedIndices.clear();
1173
+ document.querySelectorAll('.test-case').forEach((el, index) => {
1174
+ el.classList.remove('deleted');
1175
+ const btn = el.querySelector('.btn-delete, .btn-restore');
1176
+ btn.textContent = 'Delete';
1177
+ btn.className = 'btn btn-delete';
1178
+
1179
+ // Reset input value
1180
+ const input = el.querySelector('input');
1181
+ input.value = testCases[index][0];
1182
+ });
1183
+ updateStats();
1184
+ }
1185
+
1186
+ function showNotification(message, type = 'success') {
1187
+ const notification = document.getElementById('notification');
1188
+ notification.textContent = message;
1189
+ notification.style.background = type === 'success' ? '#28a745' : '#dc3545';
1190
+ notification.style.display = 'block';
1191
+
1192
+ setTimeout(() => {
1193
+ notification.style.display = 'none';
1194
+ }, 3000);
1195
+ }
1196
+
1197
+ function approveTestCases() {
1198
+ // Collect updated test cases
1199
+ const updatedTestCases = [];
1200
+ document.querySelectorAll('.test-case').forEach((el, index) => {
1201
+ if (!deletedIndices.has(index)) {
1202
+ const input = el.querySelector('input');
1203
+ const originalTestCase = [...testCases[index]];
1204
+ originalTestCase[0] = input.value;
1205
+ updatedTestCases.push(originalTestCase);
1206
+ }
1207
+ });
1208
+
1209
+ // Send approval to server
1210
+ fetch('/approve', {
1211
+ method: 'POST',
1212
+ headers: {
1213
+ 'Content-Type': 'application/json',
1214
+ },
1215
+ body: JSON.stringify({
1216
+ sessionId: '${sessionId}',
1217
+ testCases: updatedTestCases
1218
+ })
1219
+ })
1220
+ .then(response => response.json())
1221
+ .then(data => {
1222
+ if (data.success) {
1223
+ showNotification('Test cases approved successfully!');
1224
+ setTimeout(() => {
1225
+ window.close();
1226
+ }, 2000);
1227
+ } else {
1228
+ showNotification('Error approving test cases', 'error');
1229
+ }
1230
+ })
1231
+ .catch(error => {
1232
+ showNotification('Error approving test cases', 'error');
1233
+ console.error('Error:', error);
1234
+ });
1235
+ }
1236
+
1237
+ // Update test cases when input changes
1238
+ document.addEventListener('input', function(e) {
1239
+ if (e.target.tagName === 'INPUT') {
1240
+ const index = parseInt(e.target.getAttribute('data-index'));
1241
+ testCases[index][0] = e.target.value;
1242
+ }
1243
+ });
1244
+ </script>
1245
+ </body>
1246
+ </html>
1247
+ `);
958
1248
  });
959
1249
 
960
- app.post(`/cancel/${sessionId}`, (_req, res) => {
961
- const session = approvalSessions.get(sessionId);
962
- if (session) {
963
- session.status = 'cancelled';
964
- session.cancelledAt = Date.now();
965
- approvalSessions.set(sessionId, session);
966
- console.log(`Session ${sessionId} cancelled at ${new Date().toISOString()}`);
1250
+ // Approval endpoint
1251
+ app.post('/approve', (req, res) => {
1252
+ try {
1253
+ const { testCases: approvedTestCases } = req.body;
1254
+ finalTestCases = approvedTestCases;
1255
+ approvalStatus = 'approved';
1256
+
1257
+ // Save to global state for the check tool
1258
+ global.approvalSessions = global.approvalSessions || {};
1259
+ global.approvalSessions[sessionId] = {
1260
+ status: 'approved',
1261
+ testCases: finalTestCases,
1262
+ timestamp: Date.now()
1263
+ };
1264
+
1265
+ res.json({ success: true, message: 'Test cases approved successfully' });
1266
+
1267
+ // Close server after approval
1268
+ setTimeout(() => {
1269
+ server.close();
1270
+ }, 3000);
1271
+ } catch (error) {
1272
+ res.status(500).json({ success: false, message: error.message });
967
1273
  }
968
- res.json({ status: 'cancelled', message: 'Review cancelled' });
969
1274
  });
970
1275
 
971
- // Update session with server reference
972
- const stored = approvalSessions.get(sessionId);
973
- stored.server = server;
974
- approvalSessions.set(sessionId, stored);
1276
+ // Start server
1277
+ const server = app.listen(port, () => {
1278
+ console.log(`Test case review server running at http://localhost:${port}`);
1279
+ });
975
1280
 
976
1281
  // Open browser
977
- try {
978
- const { default: open } = await import('open');
979
- await open(`http://localhost:${port}`);
980
- } catch { /* silent */ }
981
-
982
- // Timeout safeguard - but don't override approved status
983
- setTimeout(() => {
984
- const s = approvalSessions.get(sessionId);
985
- if (s && s.status === 'pending') {
986
- s.status = 'timeout';
987
- s.server?.close();
988
- approvalSessions.set(sessionId, s);
989
- }
990
- }, 300000);
1282
+ await open(`http://localhost:${port}`);
991
1283
 
992
- return `✅ Test case review interface opened successfully!
993
- Session ID: ${sessionId}
994
- Test Cases Count: ${testCases.length}
995
- Browser URL: http://localhost:${port}
996
- Status: review_started
1284
+ // Store session globally for status checking
1285
+ global.approvalSessions = global.approvalSessions || {};
1286
+ global.approvalSessions[sessionId] = {
1287
+ status: 'pending',
1288
+ testCases: testCases,
1289
+ timestamp: Date.now()
1290
+ };
997
1291
 
998
- Instructions: Use check_approval_status tool with session ID "${sessionId}" to poll for approval status.`;
1292
+ return `✅ Test case review session started. Session ID: ${sessionId}. Browser opened for manual review and approval.`;
999
1293
 
1000
1294
  } catch (err) {
1001
- return `❌ Error setting up review interface: ${err.message}`;
1295
+ return `❌ Error starting test case review: ${err.message}`;
1002
1296
  }
1003
1297
  }
1004
1298
  );
1005
1299
 
1300
+ // Tool 2: Check Approval Status
1006
1301
  tool(
1007
1302
  "check_approval_status",
1008
- "Check the approval status of test cases review session (waits 20 seconds before checking)",
1009
- { sessionId: zod_1.z.string().describe("Session ID from review_testcases") },
1303
+ "Check the approval status of test cases review session (waits 25 seconds before checking)",
1304
+ {
1305
+ sessionId: zod_1.z.string().describe("Session ID from review_testcases")
1306
+ },
1010
1307
  async ({ sessionId }) => {
1011
- const session = approvalSessions.get(sessionId);
1012
- if (!session) return `❌ Session not found. Invalid session ID: ${sessionId}`;
1013
-
1014
- const now = Date.now();
1015
- const elapsed = Math.floor((now - session.startTime) / 1000);
1016
-
1017
- // Check if already approved BEFORE waiting
1018
- if (session.status === 'approved') {
1019
- const approvedTestCases = session.finalTestCases || session.testCases;
1020
- session.server?.close();
1021
- approvalSessions.delete(sessionId);
1022
- return `✅ Test cases approved. Elapsed: ${elapsed}s\n${JSON.stringify(approvedTestCases, null, 2)}`;
1023
- }
1308
+ try {
1309
+ // Wait for 25 seconds
1310
+ await new Promise(resolve => setTimeout(resolve, 25000));
1024
1311
 
1025
- if (session.status === 'cancelled') {
1026
- session.server?.close();
1027
- approvalSessions.delete(sessionId);
1028
- return `❌ Review cancelled. Elapsed: ${elapsed}s`;
1029
- }
1312
+ // Check global approval sessions
1313
+ if (!global.approvalSessions || !global.approvalSessions[sessionId]) {
1314
+ return "❌ Session not found. Please ensure the review session is still active.";
1315
+ }
1030
1316
 
1031
- if (session.status === 'timeout' || elapsed > 300) {
1032
- session.server?.close();
1033
- approvalSessions.delete(sessionId);
1034
- return `⏰ Review session timed out. Elapsed: ${elapsed}s`;
1035
- }
1317
+ const session = global.approvalSessions[sessionId];
1036
1318
 
1037
- // Wait 20 seconds before checking status again
1038
- await new Promise(r => setTimeout(r, 20000));
1039
-
1040
- // Re-fetch session after wait to check for status changes
1041
- const refreshed = approvalSessions.get(sessionId);
1042
- if (!refreshed) return `❌ Session expired during wait`;
1043
-
1044
- const newElapsed = Math.floor((Date.now() - refreshed.startTime) / 1000);
1045
-
1046
- if (refreshed.status === 'approved') {
1047
- const approvedTestCases = refreshed.finalTestCases || refreshed.testCases;
1048
- refreshed.server?.close();
1049
- approvalSessions.delete(sessionId);
1050
- return `✅ Test cases approved. Elapsed: ${newElapsed}s\n${JSON.stringify(approvedTestCases, null, 2)}`;
1051
- } else if (refreshed.status === 'cancelled') {
1052
- refreshed.server?.close();
1053
- approvalSessions.delete(sessionId);
1054
- return `❌ Review cancelled. Elapsed: ${newElapsed}s`;
1055
- } else if (refreshed.status === 'timeout' || newElapsed > 300) {
1056
- refreshed.server?.close();
1057
- approvalSessions.delete(sessionId);
1058
- return `⏰ Review session timed out. Elapsed: ${newElapsed}s`;
1059
- }
1319
+ if (session.status === 'approved') {
1320
+ const result = {
1321
+ status: 'approved',
1322
+ testCases: session.testCases,
1323
+ approvedCount: session.testCases.length,
1324
+ sessionId: sessionId
1325
+ };
1060
1326
 
1061
- return `⏳ Still pending. Elapsed: ${newElapsed}s Remaining: ${Math.max(0, 300 - newElapsed)}s`;
1062
- }
1063
- );
1327
+ // Clean up session after returning result
1328
+ delete global.approvalSessions[sessionId];
1064
1329
 
1330
+ return `✅ Test cases approved successfully!\n\nApproved ${result.approvedCount} test cases:\n\n${
1331
+ result.testCases.map((tc, index) =>
1332
+ `${index + 1}. ${tc[0]}${tc[2] ? ` (${tc[2]})` : ''}`
1333
+ ).join('\n')
1334
+ }\n\nSession ID: ${sessionId}`;
1335
+ } else {
1336
+ return "⏳ Still waiting for approval. The review session is active but not yet approved. Please complete the review in the browser.";
1337
+ }
1065
1338
 
1339
+ } catch (err) {
1340
+ return `❌ Error checking approval status: ${err.message}`;
1341
+ }
1342
+ }
1343
+ );
1066
1344
 
1067
1345
  return server;
1068
1346
  };