shell-mirror 1.5.40 → 1.5.42

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.
@@ -50,6 +50,11 @@ let AGENT_ID;
50
50
  let CLIENT_ID;
51
51
  let SELECTED_AGENT; // Store full agent data including WebSocket URL
52
52
 
53
+ // Session management
54
+ let currentSession = null;
55
+ let availableSessions = [];
56
+ let requestedSessionId = null; // For connecting to specific session from URL
57
+
53
58
  // Connection status management
54
59
  function updateConnectionStatus(status) {
55
60
  const statusElement = document.getElementById('connection-status');
@@ -74,14 +79,16 @@ function updateConnectionStatus(status) {
74
79
  window.addEventListener('load', () => {
75
80
  loadVersionInfo();
76
81
 
77
- // Get agent ID from URL parameter
82
+ // Get agent ID and session ID from URL parameters
78
83
  const urlParams = new URLSearchParams(window.location.search);
79
84
  const agentId = urlParams.get('agent');
85
+ const sessionId = urlParams.get('session');
80
86
 
81
87
  if (agentId) {
82
88
  AGENT_ID = agentId;
83
89
  SELECTED_AGENT = { id: agentId, agentId: agentId };
84
- console.log('[CLIENT] 🔗 Connecting directly to agent:', agentId);
90
+ requestedSessionId = sessionId; // Store for session request
91
+ console.log('[CLIENT] 🔗 Connecting to agent:', agentId, sessionId ? `session: ${sessionId}` : '(new session)');
85
92
  startConnection();
86
93
  } else {
87
94
  // No agent specified, redirect to dashboard
@@ -119,7 +126,7 @@ async function loadVersionInfo() {
119
126
  function startConnection() {
120
127
  updateConnectionStatus('connecting');
121
128
  connectContainer.style.display = 'none';
122
- terminalContainer.style.display = 'block';
129
+ terminalContainer.classList.add('show');
123
130
  term.open(document.getElementById('terminal'));
124
131
  // Delay fit to ensure proper dimensions after CSS transitions
125
132
  setTimeout(() => {
@@ -164,7 +171,23 @@ async function initialize() {
164
171
  // Start polling to connect to the agent
165
172
  const intervalId = setInterval(() => {
166
173
  console.log(`[CLIENT] 📞 Sending client-hello to Agent: ${AGENT_ID}`);
167
- const sent = sendMessage({ type: 'client-hello', from: CLIENT_ID, to: AGENT_ID });
174
+
175
+ // Build session request
176
+ let sessionRequest = null;
177
+ if (requestedSessionId) {
178
+ sessionRequest = { sessionId: requestedSessionId };
179
+ console.log(`[CLIENT] 🎯 Requesting existing session: ${requestedSessionId}`);
180
+ } else {
181
+ sessionRequest = { newSession: true };
182
+ console.log(`[CLIENT] 🆕 Requesting new session`);
183
+ }
184
+
185
+ const sent = sendMessage({
186
+ type: 'client-hello',
187
+ from: CLIENT_ID,
188
+ to: AGENT_ID,
189
+ sessionRequest: sessionRequest
190
+ });
168
191
  if (!sent) {
169
192
  console.error(`[CLIENT] ❌ Failed to send client-hello - stopping attempts`);
170
193
  clearInterval(intervalId);
@@ -190,6 +213,27 @@ async function initialize() {
190
213
  console.log('[CLIENT] Received offer from agent. Stopping client-hello retries.');
191
214
  clearInterval(intervalId);
192
215
 
216
+ // Handle session assignment from agent
217
+ if (nextData.sessionId) {
218
+ currentSession = {
219
+ id: nextData.sessionId,
220
+ name: nextData.sessionName || 'Terminal Session',
221
+ isNewSession: nextData.isNewSession || false
222
+ };
223
+ console.log('[CLIENT] 📋 Session assigned:', currentSession);
224
+
225
+ // Update UI to show session info
226
+ updateSessionDisplay();
227
+
228
+ // Save session info to localStorage for dashboard
229
+ saveSessionToLocalStorage(AGENT_ID, currentSession);
230
+ }
231
+
232
+ if (nextData.availableSessions) {
233
+ availableSessions = nextData.availableSessions;
234
+ console.log('[CLIENT] 📚 Available sessions:', availableSessions);
235
+ }
236
+
193
237
  console.log('[CLIENT] Received WebRTC offer from agent.');
194
238
  await createPeerConnection();
195
239
  await peerConnection.setRemoteDescription(new RTCSessionDescription(nextData));
@@ -459,6 +503,9 @@ function setupDataChannel() {
459
503
  const message = JSON.parse(event.data);
460
504
  if (message.type === 'output') {
461
505
  term.write(message.data);
506
+ } else {
507
+ // Handle session-related messages
508
+ handleSessionMessage(message);
462
509
  }
463
510
  } catch (err) {
464
511
  console.error('[CLIENT] Error parsing data channel message:', err);
@@ -521,4 +568,165 @@ function sendMessage(message) {
521
568
  console.error(`[CLIENT] ❌ Error sending message:`, error);
522
569
  return false;
523
570
  }
571
+ }
572
+
573
+ // Session Management Functions
574
+ function updateSessionDisplay() {
575
+ const sessionHeader = document.getElementById('session-header');
576
+ const sessionName = document.getElementById('session-name');
577
+ const sessionId = document.getElementById('session-id');
578
+
579
+ if (currentSession) {
580
+ sessionHeader.style.display = 'flex';
581
+ sessionName.textContent = currentSession.name;
582
+ sessionId.textContent = `(${currentSession.id.substring(0, 8)}...)`;
583
+
584
+ // Update available sessions dropdown
585
+ updateSessionDropdown();
586
+
587
+ console.log('[CLIENT] 📋 Session display updated:', currentSession);
588
+ }
589
+ }
590
+
591
+ function updateSessionDropdown() {
592
+ const dropdownContent = document.getElementById('session-dropdown-content');
593
+
594
+ // Clear existing items except "New Session"
595
+ dropdownContent.innerHTML = `
596
+ <div class="session-dropdown-item" onclick="createNewSession()">
597
+ <span>+ New Session</span>
598
+ </div>
599
+ `;
600
+
601
+ // Add available sessions
602
+ if (availableSessions && availableSessions.length > 0) {
603
+ availableSessions.forEach(session => {
604
+ const item = document.createElement('div');
605
+ item.className = 'session-dropdown-item';
606
+ if (currentSession && session.id === currentSession.id) {
607
+ item.classList.add('current');
608
+ }
609
+
610
+ item.innerHTML = `
611
+ <span>${session.name}</span>
612
+ <small>${formatLastActivity(session.lastActivity)}</small>
613
+ `;
614
+ item.onclick = () => switchToSession(session.id);
615
+ dropdownContent.appendChild(item);
616
+ });
617
+ }
618
+ }
619
+
620
+ function formatLastActivity(timestamp) {
621
+ const now = Date.now();
622
+ const diff = now - timestamp;
623
+ const minutes = Math.floor(diff / 60000);
624
+ const hours = Math.floor(diff / 3600000);
625
+ const days = Math.floor(diff / 86400000);
626
+
627
+ if (minutes < 1) return 'now';
628
+ if (minutes < 60) return `${minutes}m`;
629
+ if (hours < 24) return `${hours}h`;
630
+ return `${days}d`;
631
+ }
632
+
633
+ function switchToSession(sessionId) {
634
+ if (!dataChannel || dataChannel.readyState !== 'open') {
635
+ console.error('[CLIENT] ❌ Cannot switch session - data channel not open');
636
+ return;
637
+ }
638
+
639
+ console.log('[CLIENT] 🔄 Switching to session:', sessionId);
640
+ dataChannel.send(JSON.stringify({
641
+ type: 'session-switch',
642
+ sessionId: sessionId
643
+ }));
644
+
645
+ // Hide dropdown
646
+ document.getElementById('session-dropdown-content').classList.remove('show');
647
+ }
648
+
649
+ function createNewSession() {
650
+ // Navigate to terminal with new session request
651
+ const url = new URL(window.location.href);
652
+ url.searchParams.delete('session'); // Remove session param to create new one
653
+ window.location.href = url.toString();
654
+ }
655
+
656
+ // Setup dropdown toggle
657
+ document.addEventListener('DOMContentLoaded', () => {
658
+ const dropdownBtn = document.getElementById('session-dropdown-btn');
659
+ const dropdownContent = document.getElementById('session-dropdown-content');
660
+
661
+ if (dropdownBtn && dropdownContent) {
662
+ dropdownBtn.onclick = (e) => {
663
+ e.stopPropagation();
664
+ dropdownContent.classList.toggle('show');
665
+ };
666
+
667
+ // Close dropdown when clicking outside
668
+ document.addEventListener('click', () => {
669
+ dropdownContent.classList.remove('show');
670
+ });
671
+ }
672
+ });
673
+
674
+ // Handle session-related data channel messages
675
+ function handleSessionMessage(message) {
676
+ switch (message.type) {
677
+ case 'session-switched':
678
+ currentSession = {
679
+ id: message.sessionId,
680
+ name: message.sessionName || 'Terminal Session'
681
+ };
682
+ updateSessionDisplay();
683
+ term.clear(); // Clear terminal for new session
684
+ console.log('[CLIENT] ✅ Switched to session:', currentSession);
685
+
686
+ // Save updated session info
687
+ saveSessionToLocalStorage(AGENT_ID, currentSession);
688
+ break;
689
+ case 'session-ended':
690
+ term.write(`\r\n\x1b[31m❌ Session ended: ${message.reason}\x1b[0m\r\n`);
691
+ if (message.code) {
692
+ term.write(`Exit code: ${message.code}\r\n`);
693
+ }
694
+ break;
695
+ case 'session-terminated':
696
+ term.write(`\r\n\x1b[31m❌ Session terminated\x1b[0m\r\n`);
697
+ term.write('🔄 Click Dashboard to start a new session\r\n');
698
+ break;
699
+ case 'error':
700
+ term.write(`\r\n\x1b[31m❌ Error: ${message.message}\x1b[0m\r\n`);
701
+ break;
702
+ }
703
+ }
704
+
705
+ // Session storage helper
706
+ function saveSessionToLocalStorage(agentId, sessionInfo) {
707
+ try {
708
+ const storedSessions = localStorage.getItem('shell-mirror-sessions');
709
+ let sessionData = storedSessions ? JSON.parse(storedSessions) : {};
710
+
711
+ if (!sessionData[agentId]) {
712
+ sessionData[agentId] = [];
713
+ }
714
+
715
+ // Remove existing session with same ID
716
+ sessionData[agentId] = sessionData[agentId].filter(s => s.id !== sessionInfo.id);
717
+
718
+ // Add updated session info
719
+ sessionData[agentId].push({
720
+ id: sessionInfo.id,
721
+ name: sessionInfo.name,
722
+ lastActivity: Date.now(),
723
+ createdAt: sessionInfo.createdAt || Date.now(),
724
+ status: 'active'
725
+ });
726
+
727
+ localStorage.setItem('shell-mirror-sessions', JSON.stringify(sessionData));
728
+ console.log('[CLIENT] 💾 Session saved to storage:', sessionInfo);
729
+ } catch (error) {
730
+ console.error('[CLIENT] Error saving session to storage:', error);
731
+ }
524
732
  }