shell-mirror 1.5.106 → 1.5.108

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.
@@ -980,16 +980,41 @@ function setupDataChannel(clientId) {
980
980
  // Handle session closure request from client
981
981
  logToFile(`[AGENT] Client ${clientId} closing session ${message.sessionId}`);
982
982
 
983
- sessionManager.terminateSession(message.sessionId);
983
+ // Get remaining sessions BEFORE termination
984
+ const closingSessionId = message.sessionId;
985
+ sessionManager.terminateSession(closingSessionId);
986
+
987
+ const remainingSessions = sessionManager.getAllSessions();
984
988
 
985
989
  // Send confirmation with updated session list
986
990
  dataChannel.send(JSON.stringify({
987
991
  type: 'session-closed',
988
- sessionId: message.sessionId,
989
- availableSessions: sessionManager.getAllSessions()
992
+ sessionId: closingSessionId,
993
+ availableSessions: remainingSessions
990
994
  }));
991
995
 
992
- logToFile(`[AGENT] Session closed: ${message.sessionId}`);
996
+ // If client requested auto-switch to next session, do it atomically
997
+ if (message.switchToSessionId && remainingSessions.find(s => s.id === message.switchToSessionId)) {
998
+ logToFile(`[AGENT] Auto-switching client to session ${message.switchToSessionId}`);
999
+ if (sessionManager.connectClientToSession(clientId, message.switchToSessionId)) {
1000
+ const newSession = sessionManager.getSession(message.switchToSessionId);
1001
+ dataChannel.send(JSON.stringify({
1002
+ type: 'session-switched',
1003
+ sessionId: message.switchToSessionId,
1004
+ sessionName: newSession.name
1005
+ }));
1006
+ // Send buffered output
1007
+ const bufferedOutput = newSession.buffer.getAll();
1008
+ if (bufferedOutput) {
1009
+ sendLargeMessage(dataChannel, { type: 'output', data: bufferedOutput }, '[AGENT]');
1010
+ }
1011
+ }
1012
+ }
1013
+
1014
+ // Send immediate heartbeat to update dashboard
1015
+ sendHeartbeat();
1016
+
1017
+ logToFile(`[AGENT] ✅ Session closed: ${closingSessionId}`);
993
1018
  }
994
1019
  } catch (err) {
995
1020
  logToFile(`[AGENT] Error parsing data channel message: ${err.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.106",
3
+ "version": "1.5.108",
4
4
  "description": "Access your Mac shell from any device securely. Perfect for mobile coding with Claude Code CLI, Gemini CLI, and any shell tool.",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -576,6 +576,6 @@
576
576
  }
577
577
  </script>
578
578
 
579
- <script src="/app/terminal.js?v=1.5.90"></script>
579
+ <script src="/app/terminal.js?v=1.5.92"></script>
580
580
  </body>
581
581
  </html>
@@ -1080,10 +1080,16 @@ function closeSession(sessionId, event) {
1080
1080
  function doCloseSession(sessionId) {
1081
1081
  console.log('[CLIENT] 🗑️ Closing session:', sessionId);
1082
1082
 
1083
- // Send close request to agent
1083
+ // Remove from available sessions
1084
+ const remainingSessions = availableSessions.filter(s => s.id !== sessionId);
1085
+ const isClosingCurrentSession = currentSession && currentSession.id === sessionId;
1086
+ const nextSession = isClosingCurrentSession && remainingSessions.length > 0 ? remainingSessions[0] : null;
1087
+
1088
+ // Send close request to agent (with auto-switch if closing active session)
1084
1089
  const closeMessage = {
1085
1090
  type: 'close_session',
1086
- sessionId: sessionId
1091
+ sessionId: sessionId,
1092
+ switchToSessionId: nextSession ? nextSession.id : null // Atomic close + switch
1087
1093
  };
1088
1094
 
1089
1095
  if (dataChannel && dataChannel.readyState === 'open') {
@@ -1092,16 +1098,25 @@ function doCloseSession(sessionId) {
1092
1098
  ws.send(JSON.stringify(closeMessage));
1093
1099
  }
1094
1100
 
1095
- // Remove from available sessions
1096
- availableSessions = availableSessions.filter(s => s.id !== sessionId);
1101
+ // Update local state
1102
+ availableSessions = remainingSessions;
1097
1103
 
1098
- // If closing current session, switch to another or show message
1099
- if (currentSession && currentSession.id === sessionId) {
1100
- if (availableSessions.length > 0) {
1101
- switchToSession(availableSessions[0].id);
1104
+ // If closing current session, update UI immediately
1105
+ if (isClosingCurrentSession) {
1106
+ // Clear terminal IMMEDIATELY to prevent garbage from closed session
1107
+ term.clear();
1108
+
1109
+ if (nextSession) {
1110
+ // Update currentSession IMMEDIATELY so renderTabs shows correct active tab
1111
+ currentSession = {
1112
+ id: nextSession.id,
1113
+ name: nextSession.name || 'Terminal Session'
1114
+ };
1115
+ // Update URL
1116
+ updateUrlWithSession(nextSession.id);
1117
+ // Note: Agent will send session-switched with buffered output
1102
1118
  } else {
1103
1119
  currentSession = null;
1104
- term.clear();
1105
1120
  term.write('\r\n\x1b[33mSession closed. Click + to create a new session.\x1b[0m\r\n');
1106
1121
  }
1107
1122
  }
@@ -1249,14 +1264,28 @@ function handleSessionMessage(message) {
1249
1264
  term.focus();
1250
1265
  break;
1251
1266
  case 'session-ended':
1252
- term.write(`\r\n\x1b[31m❌ Session ended: ${message.reason}\x1b[0m\r\n`);
1253
- if (message.code) {
1254
- term.write(`Exit code: ${message.code}\r\n`);
1267
+ // Only show if this is for the current session (ignore closed session messages)
1268
+ if (!message.sessionId || (currentSession && message.sessionId === currentSession.id)) {
1269
+ term.write(`\r\n\x1b[31m❌ Session ended: ${message.reason}\x1b[0m\r\n`);
1270
+ if (message.code) {
1271
+ term.write(`Exit code: ${message.code}\r\n`);
1272
+ }
1255
1273
  }
1256
1274
  break;
1257
1275
  case 'session-terminated':
1258
- term.write(`\r\n\x1b[31m❌ Session terminated\x1b[0m\r\n`);
1259
- term.write('🔄 Click Dashboard to start a new session\r\n');
1276
+ // Only show if this is for the current session (ignore closed session messages)
1277
+ if (!message.sessionId || (currentSession && message.sessionId === currentSession.id)) {
1278
+ term.write(`\r\n\x1b[31m❌ Session terminated\x1b[0m\r\n`);
1279
+ term.write('🔄 Click Dashboard to start a new session\r\n');
1280
+ }
1281
+ break;
1282
+ case 'session-closed':
1283
+ console.log('[CLIENT] ✅ Session closed confirmed:', message.sessionId);
1284
+ // Update available sessions from server response
1285
+ if (message.availableSessions) {
1286
+ availableSessions = message.availableSessions;
1287
+ renderTabs();
1288
+ }
1260
1289
  break;
1261
1290
  case 'error':
1262
1291
  term.write(`\r\n\x1b[31m❌ Error: ${message.message}\x1b[0m\r\n`);