shell-mirror 1.5.83 → 1.5.85
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/mac-agent/agent.js +26 -0
- package/package.json +1 -1
- package/public/app/terminal.html +23 -38
- package/public/app/terminal.js +90 -21
package/mac-agent/agent.js
CHANGED
|
@@ -931,6 +931,31 @@ function setupDataChannel(clientId) {
|
|
|
931
931
|
message: `Session ${message.sessionId} not found`
|
|
932
932
|
}));
|
|
933
933
|
}
|
|
934
|
+
} else if (message.type === 'session-create') {
|
|
935
|
+
// Handle new session creation via data channel
|
|
936
|
+
logToFile(`[AGENT] Client ${clientId} creating new session`);
|
|
937
|
+
|
|
938
|
+
const newSessionId = sessionManager.createSession(null, clientId);
|
|
939
|
+
|
|
940
|
+
if (newSessionId) {
|
|
941
|
+
const newSession = sessionManager.getSession(newSessionId);
|
|
942
|
+
|
|
943
|
+
// Send confirmation with updated session list
|
|
944
|
+
dataChannel.send(JSON.stringify({
|
|
945
|
+
type: 'session-created',
|
|
946
|
+
sessionId: newSessionId,
|
|
947
|
+
sessionName: newSession.name,
|
|
948
|
+
availableSessions: sessionManager.getAllSessions()
|
|
949
|
+
}));
|
|
950
|
+
|
|
951
|
+
logToFile(`[AGENT] ✅ New session created: ${newSessionId}`);
|
|
952
|
+
} else {
|
|
953
|
+
dataChannel.send(JSON.stringify({
|
|
954
|
+
type: 'error',
|
|
955
|
+
message: 'Failed to create session - maximum sessions reached'
|
|
956
|
+
}));
|
|
957
|
+
logToFile(`[AGENT] ❌ Failed to create session for client ${clientId}`);
|
|
958
|
+
}
|
|
934
959
|
}
|
|
935
960
|
} catch (err) {
|
|
936
961
|
logToFile(`[AGENT] Error parsing data channel message: ${err.message}`);
|
|
@@ -1038,6 +1063,7 @@ function startLocalServer() {
|
|
|
1038
1063
|
localWs.send(JSON.stringify({
|
|
1039
1064
|
type: 'session_created',
|
|
1040
1065
|
sessionId,
|
|
1066
|
+
sessionName: `Session ${sessionId.slice(0, 8)}`,
|
|
1041
1067
|
cols: message.cols || 120,
|
|
1042
1068
|
rows: message.rows || 30
|
|
1043
1069
|
}));
|
package/package.json
CHANGED
package/public/app/terminal.html
CHANGED
|
@@ -109,6 +109,26 @@
|
|
|
109
109
|
gap: 8px;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/* Connection Status Indicator */
|
|
113
|
+
.connection-status {
|
|
114
|
+
width: 12px;
|
|
115
|
+
height: 12px;
|
|
116
|
+
border-radius: 50%;
|
|
117
|
+
background: #ff4444;
|
|
118
|
+
margin-right: 12px;
|
|
119
|
+
flex-shrink: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.connection-status.connecting {
|
|
123
|
+
background: #ffaa44;
|
|
124
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.connection-status.connected {
|
|
128
|
+
background: #44ff44;
|
|
129
|
+
box-shadow: 0 0 6px rgba(68, 255, 68, 0.5);
|
|
130
|
+
}
|
|
131
|
+
|
|
112
132
|
/* Session Tab Bar */
|
|
113
133
|
.session-tab-bar {
|
|
114
134
|
display: flex;
|
|
@@ -158,27 +178,6 @@
|
|
|
158
178
|
background: #1a1a1a;
|
|
159
179
|
}
|
|
160
180
|
|
|
161
|
-
.session-tab-status {
|
|
162
|
-
width: 8px;
|
|
163
|
-
height: 8px;
|
|
164
|
-
border-radius: 50%;
|
|
165
|
-
flex-shrink: 0;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.session-tab-status.connected {
|
|
169
|
-
background: #44ff44;
|
|
170
|
-
box-shadow: 0 0 6px rgba(68, 255, 68, 0.5);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.session-tab-status.connecting {
|
|
174
|
-
background: #ffaa44;
|
|
175
|
-
animation: pulse 1.5s ease-in-out infinite;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.session-tab-status.disconnected {
|
|
179
|
-
background: #ff4444;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
181
|
.session-tab-name {
|
|
183
182
|
overflow: hidden;
|
|
184
183
|
text-overflow: ellipsis;
|
|
@@ -237,23 +236,6 @@
|
|
|
237
236
|
}
|
|
238
237
|
}
|
|
239
238
|
|
|
240
|
-
/* Dashboard button with status dot */
|
|
241
|
-
.dashboard-btn {
|
|
242
|
-
position: relative;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
.dashboard-btn::before {
|
|
246
|
-
content: '';
|
|
247
|
-
position: absolute;
|
|
248
|
-
top: 4px;
|
|
249
|
-
right: 4px;
|
|
250
|
-
width: 8px;
|
|
251
|
-
height: 8px;
|
|
252
|
-
border-radius: 50%;
|
|
253
|
-
background: #44ff44;
|
|
254
|
-
box-shadow: 0 0 8px rgba(68, 255, 68, 0.5);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
239
|
#terminal {
|
|
258
240
|
padding: 8px; /* Mac Terminal.app padding */
|
|
259
241
|
background-color: #000000;
|
|
@@ -355,6 +337,9 @@
|
|
|
355
337
|
</div>
|
|
356
338
|
<div id="terminal-container">
|
|
357
339
|
<div class="session-header" id="session-header" style="display: none;">
|
|
340
|
+
<!-- Connection Status Indicator -->
|
|
341
|
+
<div class="connection-status" id="connection-status"></div>
|
|
342
|
+
|
|
358
343
|
<!-- Session Tab Bar -->
|
|
359
344
|
<div class="session-tab-bar" id="session-tab-bar">
|
|
360
345
|
<!-- Tabs will be rendered here by JavaScript -->
|
package/public/app/terminal.js
CHANGED
|
@@ -153,10 +153,16 @@ const chunkAssembler = {
|
|
|
153
153
|
|
|
154
154
|
// Connection status management
|
|
155
155
|
function updateConnectionStatus(status) {
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
|
|
156
|
+
const statusElement = document.getElementById('connection-status');
|
|
157
|
+
if (!statusElement) return;
|
|
158
|
+
|
|
159
|
+
statusElement.className = 'connection-status';
|
|
160
|
+
if (status === 'connecting') {
|
|
161
|
+
statusElement.classList.add('connecting');
|
|
162
|
+
} else if (status === 'connected') {
|
|
163
|
+
statusElement.classList.add('connected');
|
|
159
164
|
}
|
|
165
|
+
// else: disconnected (default red)
|
|
160
166
|
}
|
|
161
167
|
|
|
162
168
|
// Cleanup timer for chunk assembler
|
|
@@ -382,8 +388,20 @@ function setupDirectConnection(directWs) {
|
|
|
382
388
|
|
|
383
389
|
case 'session_created':
|
|
384
390
|
console.log('[CLIENT] ✅ Direct session created:', data.sessionId);
|
|
385
|
-
currentSession = {
|
|
391
|
+
currentSession = {
|
|
392
|
+
id: data.sessionId,
|
|
393
|
+
name: data.sessionName || 'Terminal Session'
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Update available sessions
|
|
397
|
+
if (data.availableSessions) {
|
|
398
|
+
availableSessions = data.availableSessions;
|
|
399
|
+
}
|
|
400
|
+
|
|
386
401
|
updateSessionDisplay();
|
|
402
|
+
|
|
403
|
+
// Save to localStorage
|
|
404
|
+
saveSessionToLocalStorage(AGENT_ID, currentSession);
|
|
387
405
|
break;
|
|
388
406
|
|
|
389
407
|
case 'output':
|
|
@@ -958,14 +976,12 @@ function renderTabs() {
|
|
|
958
976
|
tabsHTML = sessionsToRender.map(session => {
|
|
959
977
|
const isActive = currentSession && session.id === currentSession.id;
|
|
960
978
|
const displayName = session.name || 'Terminal Session';
|
|
961
|
-
const status = getSessionStatus(session.id); // Per-session status
|
|
962
979
|
|
|
963
980
|
return `
|
|
964
981
|
<button class="session-tab ${isActive ? 'active' : ''}"
|
|
965
982
|
onclick="switchToSession('${session.id}')"
|
|
966
983
|
${isActive ? 'disabled' : ''}
|
|
967
984
|
title="${displayName}">
|
|
968
|
-
<span class="session-tab-status ${status}"></span>
|
|
969
985
|
<span class="session-tab-name">${displayName}</span>
|
|
970
986
|
</button>
|
|
971
987
|
`;
|
|
@@ -979,18 +995,27 @@ function renderTabs() {
|
|
|
979
995
|
console.log('[CLIENT] ✅ Tabs rendered:', sessionsToRender.length, 'tabs');
|
|
980
996
|
}
|
|
981
997
|
|
|
982
|
-
function
|
|
983
|
-
//
|
|
984
|
-
if (
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
return '
|
|
998
|
+
function getConnectionStatus() {
|
|
999
|
+
// Check direct WebSocket connection
|
|
1000
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
1001
|
+
return 'connected';
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Check WebRTC data channel connection
|
|
1005
|
+
if (dataChannel && dataChannel.readyState === 'open') {
|
|
1006
|
+
return 'connected';
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Check connecting states
|
|
1010
|
+
if (ws && ws.readyState === WebSocket.CONNECTING) {
|
|
1011
|
+
return 'connecting';
|
|
991
1012
|
}
|
|
992
1013
|
|
|
993
|
-
|
|
1014
|
+
if (dataChannel && dataChannel.readyState === 'connecting') {
|
|
1015
|
+
return 'connecting';
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Not connected
|
|
994
1019
|
return 'disconnected';
|
|
995
1020
|
}
|
|
996
1021
|
|
|
@@ -1021,16 +1046,60 @@ function switchToSession(sessionId) {
|
|
|
1021
1046
|
}
|
|
1022
1047
|
|
|
1023
1048
|
function createNewSession() {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1049
|
+
console.log('[CLIENT] 🆕 Creating new session...');
|
|
1050
|
+
|
|
1051
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
1052
|
+
// Direct WebSocket connection - use underscore
|
|
1053
|
+
ws.send(JSON.stringify({
|
|
1054
|
+
type: 'create_session',
|
|
1055
|
+
cols: term.cols,
|
|
1056
|
+
rows: term.rows
|
|
1057
|
+
}));
|
|
1058
|
+
} else if (dataChannel && dataChannel.readyState === 'open') {
|
|
1059
|
+
// WebRTC data channel connection - use hyphen
|
|
1060
|
+
dataChannel.send(JSON.stringify({
|
|
1061
|
+
type: 'session-create',
|
|
1062
|
+
cols: term.cols,
|
|
1063
|
+
rows: term.rows
|
|
1064
|
+
}));
|
|
1065
|
+
} else {
|
|
1066
|
+
console.error('[CLIENT] ❌ Cannot create session - no active connection');
|
|
1067
|
+
term.write('\r\n\x1b[31m❌ Cannot create session - not connected\x1b[0m\r\n');
|
|
1068
|
+
}
|
|
1028
1069
|
}
|
|
1029
1070
|
|
|
1030
1071
|
|
|
1031
1072
|
// Handle session-related data channel messages
|
|
1032
1073
|
function handleSessionMessage(message) {
|
|
1033
1074
|
switch (message.type) {
|
|
1075
|
+
case 'session-created':
|
|
1076
|
+
console.log('[CLIENT] ✅ New session created:', message.sessionId);
|
|
1077
|
+
|
|
1078
|
+
// Update current session
|
|
1079
|
+
currentSession = {
|
|
1080
|
+
id: message.sessionId,
|
|
1081
|
+
name: message.sessionName || 'Terminal Session'
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
// Update available sessions list
|
|
1085
|
+
if (message.availableSessions) {
|
|
1086
|
+
availableSessions = message.availableSessions;
|
|
1087
|
+
} else {
|
|
1088
|
+
// Add to local list if not provided
|
|
1089
|
+
availableSessions.push(currentSession);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Clear terminal for new session
|
|
1093
|
+
term.clear();
|
|
1094
|
+
term.write(`\r\n\x1b[36m✨ New session created: ${currentSession.name}\x1b[0m\r\n\r\n`);
|
|
1095
|
+
|
|
1096
|
+
// Update UI
|
|
1097
|
+
updateSessionDisplay();
|
|
1098
|
+
|
|
1099
|
+
// Save to localStorage
|
|
1100
|
+
saveSessionToLocalStorage(AGENT_ID, currentSession);
|
|
1101
|
+
break;
|
|
1102
|
+
|
|
1034
1103
|
case 'session-switched':
|
|
1035
1104
|
currentSession = {
|
|
1036
1105
|
id: message.sessionId,
|
|
@@ -1039,7 +1108,7 @@ function handleSessionMessage(message) {
|
|
|
1039
1108
|
updateSessionDisplay();
|
|
1040
1109
|
term.clear(); // Clear terminal for new session
|
|
1041
1110
|
console.log('[CLIENT] ✅ Switched to session:', currentSession);
|
|
1042
|
-
|
|
1111
|
+
|
|
1043
1112
|
// Save updated session info
|
|
1044
1113
|
saveSessionToLocalStorage(AGENT_ID, currentSession);
|
|
1045
1114
|
break;
|