portacode 0.3.12.dev1__tar.gz → 0.3.12.dev3__tar.gz
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.
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/PKG-INFO +1 -1
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/_version.py +2 -2
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/base.py +52 -16
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/registry.py +34 -5
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/terminal_handlers.py +10 -10
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/terminal.py +69 -2
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/.claude/settings.local.json +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/.gitignore +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/.gitmodules +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/LICENSE +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/MANIFEST.in +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/Makefile +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/README.md +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/backup.sh +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/docker-compose.yaml +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/README.md +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/__init__.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/__main__.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/cli.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/README.md +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/__init__.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/client.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/README.md +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/session.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/multiplex.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/data.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/keypair.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/service.py +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode.egg-info/requires.txt +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode.egg-info/top_level.txt +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/pyproject.toml +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/restore.sh +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/setup.cfg +0 -0
- {portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/setup.py +0 -0
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '0.3.12.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 3, 12, '
|
|
20
|
+
__version__ = version = '0.3.12.dev3'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 12, 'dev3')
|
|
@@ -40,28 +40,54 @@ class BaseHandler(ABC):
|
|
|
40
40
|
"""
|
|
41
41
|
pass
|
|
42
42
|
|
|
43
|
-
async def send_response(self, payload: Dict[str, Any], reply_channel: Optional[str] = None) -> None:
|
|
44
|
-
"""Send a response back to the gateway.
|
|
43
|
+
async def send_response(self, payload: Dict[str, Any], reply_channel: Optional[str] = None, project_id: str = None) -> None:
|
|
44
|
+
"""Send a response back to the gateway with client session awareness.
|
|
45
45
|
|
|
46
46
|
Args:
|
|
47
47
|
payload: Response payload
|
|
48
|
-
reply_channel: Optional reply channel
|
|
48
|
+
reply_channel: Optional reply channel for backward compatibility
|
|
49
|
+
project_id: Optional project filter for targeting specific sessions
|
|
49
50
|
"""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
# Get client session manager from context
|
|
52
|
+
client_session_manager = self.context.get("client_session_manager")
|
|
53
|
+
|
|
54
|
+
if client_session_manager and client_session_manager.has_interested_clients():
|
|
55
|
+
# Get target sessions
|
|
56
|
+
target_sessions = client_session_manager.get_target_sessions(project_id)
|
|
57
|
+
if not target_sessions:
|
|
58
|
+
logger.debug("handler: No target sessions found, skipping response send")
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
# Add session targeting information
|
|
62
|
+
enhanced_payload = dict(payload)
|
|
63
|
+
enhanced_payload["client_sessions"] = target_sessions
|
|
64
|
+
|
|
65
|
+
# Add backward compatibility reply_channel (first session if not provided)
|
|
66
|
+
if not reply_channel:
|
|
67
|
+
reply_channel = client_session_manager.get_reply_channel_for_compatibility()
|
|
68
|
+
if reply_channel:
|
|
69
|
+
enhanced_payload["reply_channel"] = reply_channel
|
|
70
|
+
|
|
71
|
+
logger.debug("handler: Sending response to %d client sessions: %s",
|
|
72
|
+
len(target_sessions), target_sessions)
|
|
73
|
+
|
|
74
|
+
await self.control_channel.send(enhanced_payload)
|
|
75
|
+
else:
|
|
76
|
+
# Fallback to original behavior if no client session manager or no clients
|
|
77
|
+
if reply_channel:
|
|
78
|
+
payload["reply_channel"] = reply_channel
|
|
79
|
+
await self.control_channel.send(payload)
|
|
53
80
|
|
|
54
|
-
async def send_error(self, message: str, reply_channel: Optional[str] = None) -> None:
|
|
55
|
-
"""Send an error response.
|
|
81
|
+
async def send_error(self, message: str, reply_channel: Optional[str] = None, project_id: str = None) -> None:
|
|
82
|
+
"""Send an error response with client session awareness.
|
|
56
83
|
|
|
57
84
|
Args:
|
|
58
85
|
message: Error message
|
|
59
|
-
reply_channel: Optional reply channel
|
|
86
|
+
reply_channel: Optional reply channel for backward compatibility
|
|
87
|
+
project_id: Optional project filter for targeting specific sessions
|
|
60
88
|
"""
|
|
61
89
|
payload = {"event": "error", "message": message}
|
|
62
|
-
|
|
63
|
-
payload["reply_channel"] = reply_channel
|
|
64
|
-
await self.control_channel.send(payload)
|
|
90
|
+
await self.send_response(payload, reply_channel, project_id)
|
|
65
91
|
|
|
66
92
|
|
|
67
93
|
class AsyncHandler(BaseHandler):
|
|
@@ -87,10 +113,15 @@ class AsyncHandler(BaseHandler):
|
|
|
87
113
|
try:
|
|
88
114
|
response = await self.execute(message)
|
|
89
115
|
logger.info("handler: Command %s executed successfully", self.command_name)
|
|
90
|
-
|
|
116
|
+
|
|
117
|
+
# Extract project_id from response for session targeting
|
|
118
|
+
project_id = response.get("project_id")
|
|
119
|
+
await self.send_response(response, reply_channel, project_id)
|
|
91
120
|
except Exception as exc:
|
|
92
121
|
logger.exception("handler: Error in async handler %s: %s", self.command_name, exc)
|
|
93
|
-
|
|
122
|
+
# Extract project_id from original message for error targeting
|
|
123
|
+
project_id = message.get("project_id")
|
|
124
|
+
await self.send_error(str(exc), reply_channel, project_id)
|
|
94
125
|
|
|
95
126
|
|
|
96
127
|
class SyncHandler(BaseHandler):
|
|
@@ -113,7 +144,12 @@ class SyncHandler(BaseHandler):
|
|
|
113
144
|
try:
|
|
114
145
|
loop = asyncio.get_running_loop()
|
|
115
146
|
response = await loop.run_in_executor(None, self.execute, message)
|
|
116
|
-
|
|
147
|
+
|
|
148
|
+
# Extract project_id from response for session targeting
|
|
149
|
+
project_id = response.get("project_id")
|
|
150
|
+
await self.send_response(response, reply_channel, project_id)
|
|
117
151
|
except Exception as exc:
|
|
118
152
|
logger.exception("Error in sync handler %s: %s", self.command_name, exc)
|
|
119
|
-
|
|
153
|
+
# Extract project_id from original message for error targeting
|
|
154
|
+
project_id = message.get("project_id")
|
|
155
|
+
await self.send_error(str(exc), reply_channel, project_id)
|
|
@@ -94,13 +94,42 @@ class CommandRegistry:
|
|
|
94
94
|
return True
|
|
95
95
|
except Exception as exc:
|
|
96
96
|
logger.exception("registry: Error dispatching command %s: %s", command_name, exc)
|
|
97
|
-
# Send error response
|
|
98
|
-
|
|
99
|
-
if reply_channel:
|
|
100
|
-
error_payload["reply_channel"] = reply_channel
|
|
101
|
-
await self.control_channel.send(error_payload)
|
|
97
|
+
# Send session-aware error response
|
|
98
|
+
await self._send_session_aware_error(str(exc), reply_channel, message.get("project_id"))
|
|
102
99
|
return False
|
|
103
100
|
|
|
101
|
+
async def _send_session_aware_error(self, message: str, reply_channel: Optional[str] = None, project_id: str = None) -> None:
|
|
102
|
+
"""Send an error response with client session awareness."""
|
|
103
|
+
error_payload = {"event": "error", "message": message}
|
|
104
|
+
|
|
105
|
+
# Get client session manager from context
|
|
106
|
+
client_session_manager = self.context.get("client_session_manager")
|
|
107
|
+
|
|
108
|
+
if client_session_manager and client_session_manager.has_interested_clients():
|
|
109
|
+
# Get target sessions
|
|
110
|
+
target_sessions = client_session_manager.get_target_sessions(project_id)
|
|
111
|
+
if not target_sessions:
|
|
112
|
+
logger.debug("registry: No target sessions found, skipping error send")
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
# Add session targeting information
|
|
116
|
+
error_payload["client_sessions"] = target_sessions
|
|
117
|
+
|
|
118
|
+
# Add backward compatibility reply_channel (first session if not provided)
|
|
119
|
+
if not reply_channel:
|
|
120
|
+
reply_channel = client_session_manager.get_reply_channel_for_compatibility()
|
|
121
|
+
if reply_channel:
|
|
122
|
+
error_payload["reply_channel"] = reply_channel
|
|
123
|
+
|
|
124
|
+
logger.debug("registry: Sending error to %d client sessions: %s",
|
|
125
|
+
len(target_sessions), target_sessions)
|
|
126
|
+
else:
|
|
127
|
+
# Fallback to original behavior
|
|
128
|
+
if reply_channel:
|
|
129
|
+
error_payload["reply_channel"] = reply_channel
|
|
130
|
+
|
|
131
|
+
await self.control_channel.send(error_payload)
|
|
132
|
+
|
|
104
133
|
def update_context(self, context: Dict[str, Any]) -> None:
|
|
105
134
|
"""Update the shared context for all handlers.
|
|
106
135
|
|
{portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/terminal_handlers.py
RENAMED
|
@@ -51,12 +51,12 @@ class TerminalStartHandler(AsyncHandler):
|
|
|
51
51
|
|
|
52
52
|
await session.proc.wait()
|
|
53
53
|
|
|
54
|
-
await self.
|
|
54
|
+
await self.send_response({
|
|
55
55
|
"event": "terminal_exit",
|
|
56
56
|
"terminal_id": terminal_id,
|
|
57
57
|
"returncode": session.proc.returncode,
|
|
58
58
|
"project_id": session.project_id,
|
|
59
|
-
})
|
|
59
|
+
}, project_id=session.project_id)
|
|
60
60
|
|
|
61
61
|
# Only cleanup session if it still exists (not already removed by stop handler)
|
|
62
62
|
if session_manager.get_session(terminal_id):
|
|
@@ -152,13 +152,13 @@ class TerminalStopHandler(AsyncHandler):
|
|
|
152
152
|
logger.info("terminal_stop: Successfully stopped terminal %s", terminal_id)
|
|
153
153
|
|
|
154
154
|
# Send success notification
|
|
155
|
-
await self.
|
|
155
|
+
await self.send_response({
|
|
156
156
|
"event": "terminal_stop_completed",
|
|
157
157
|
"terminal_id": terminal_id,
|
|
158
158
|
"status": "success",
|
|
159
159
|
"message": "Terminal stopped successfully",
|
|
160
160
|
"project_id": project_id,
|
|
161
|
-
})
|
|
161
|
+
}, project_id=project_id)
|
|
162
162
|
|
|
163
163
|
except asyncio.TimeoutError:
|
|
164
164
|
logger.warning("terminal_stop: Stop timeout for terminal %s, forcing kill", terminal_id)
|
|
@@ -175,35 +175,35 @@ class TerminalStopHandler(AsyncHandler):
|
|
|
175
175
|
logger.error("terminal_stop: Failed to force kill terminal %s: %s", terminal_id, kill_exc)
|
|
176
176
|
|
|
177
177
|
# Send timeout notification
|
|
178
|
-
await self.
|
|
178
|
+
await self.send_response({
|
|
179
179
|
"event": "terminal_stop_completed",
|
|
180
180
|
"terminal_id": terminal_id,
|
|
181
181
|
"status": "timeout",
|
|
182
182
|
"message": "Terminal stop timed out, process was force killed",
|
|
183
183
|
"project_id": project_id,
|
|
184
|
-
})
|
|
184
|
+
}, project_id=project_id)
|
|
185
185
|
|
|
186
186
|
except Exception as exc:
|
|
187
187
|
logger.exception("terminal_stop: Error stopping terminal %s: %s", terminal_id, exc)
|
|
188
188
|
|
|
189
189
|
# Send error notification
|
|
190
|
-
await self.
|
|
190
|
+
await self.send_response({
|
|
191
191
|
"event": "terminal_stop_completed",
|
|
192
192
|
"terminal_id": terminal_id,
|
|
193
193
|
"status": "error",
|
|
194
194
|
"message": f"Error stopping terminal: {str(exc)}",
|
|
195
195
|
"project_id": project_id,
|
|
196
|
-
})
|
|
196
|
+
}, project_id=project_id)
|
|
197
197
|
|
|
198
198
|
async def _send_not_found_completion(self, terminal_id: str, project_id: Optional[str] = None) -> None:
|
|
199
199
|
"""Send completion event for not found terminals."""
|
|
200
|
-
await self.
|
|
200
|
+
await self.send_response({
|
|
201
201
|
"event": "terminal_stop_completed",
|
|
202
202
|
"terminal_id": terminal_id,
|
|
203
203
|
"status": "not_found",
|
|
204
204
|
"message": "Terminal was not found or already stopped",
|
|
205
205
|
"project_id": project_id,
|
|
206
|
-
})
|
|
206
|
+
}, project_id=project_id)
|
|
207
207
|
|
|
208
208
|
|
|
209
209
|
class TerminalListHandler(AsyncHandler):
|
|
@@ -78,6 +78,37 @@ class ClientSessionManager:
|
|
|
78
78
|
"""Check if there are any connected clients interested in this device."""
|
|
79
79
|
return len(self._client_sessions) > 0
|
|
80
80
|
|
|
81
|
+
def get_target_sessions(self, project_id: str = None) -> List[str]:
|
|
82
|
+
"""Get list of channel_names for target client sessions.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
project_id: If specified, only include sessions for this project
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
List of channel_names to target
|
|
89
|
+
"""
|
|
90
|
+
if not self._client_sessions:
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
target_sessions = []
|
|
94
|
+
for session in self._client_sessions.values():
|
|
95
|
+
# If project_id specified, filter by project
|
|
96
|
+
if project_id and session.get("project_id") != project_id:
|
|
97
|
+
continue
|
|
98
|
+
target_sessions.append(session.get("channel_name"))
|
|
99
|
+
|
|
100
|
+
return [s for s in target_sessions if s] # Filter out None values
|
|
101
|
+
|
|
102
|
+
def get_reply_channel_for_compatibility(self) -> Optional[str]:
|
|
103
|
+
"""Get the first session's channel_name for backward compatibility.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
First available channel_name or None
|
|
107
|
+
"""
|
|
108
|
+
if not self._client_sessions:
|
|
109
|
+
return None
|
|
110
|
+
return next(iter(self._client_sessions.keys()), None)
|
|
111
|
+
|
|
81
112
|
def _write_debug_file(self) -> None:
|
|
82
113
|
"""Write current client sessions to debug JSON file."""
|
|
83
114
|
try:
|
|
@@ -205,10 +236,12 @@ class TerminalManager:
|
|
|
205
236
|
reply_chan = message.get("reply_channel")
|
|
206
237
|
|
|
207
238
|
logger.info("terminal_manager: Processing command '%s' with reply_channel=%s", cmd, reply_chan)
|
|
239
|
+
logger.debug("terminal_manager: Full message: %s", message)
|
|
208
240
|
|
|
209
241
|
# Handle client sessions update directly (special case)
|
|
210
242
|
if cmd == "client_sessions_update":
|
|
211
243
|
sessions = message.get("sessions", [])
|
|
244
|
+
logger.info("terminal_manager: Handling client_sessions_update with %d sessions", len(sessions))
|
|
212
245
|
self._client_session_manager.update_sessions(sessions)
|
|
213
246
|
logger.info("terminal_manager: Updated client sessions (%d sessions)", len(sessions))
|
|
214
247
|
continue
|
|
@@ -260,7 +293,39 @@ class TerminalManager:
|
|
|
260
293
|
payload = {"event": "error", "message": message}
|
|
261
294
|
if reply_channel:
|
|
262
295
|
payload["reply_channel"] = reply_channel
|
|
263
|
-
await self.
|
|
296
|
+
await self._send_session_aware(payload)
|
|
297
|
+
|
|
298
|
+
async def _send_session_aware(self, payload: dict, project_id: str = None) -> None:
|
|
299
|
+
"""Send a message with client session awareness.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
payload: The message payload to send
|
|
303
|
+
project_id: Optional project filter for targeting specific sessions
|
|
304
|
+
"""
|
|
305
|
+
# Check if there are any interested clients
|
|
306
|
+
if not self._client_session_manager.has_interested_clients():
|
|
307
|
+
logger.debug("terminal_manager: No interested clients, skipping message send")
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
# Get target sessions
|
|
311
|
+
target_sessions = self._client_session_manager.get_target_sessions(project_id)
|
|
312
|
+
if not target_sessions:
|
|
313
|
+
logger.debug("terminal_manager: No target sessions found, skipping message send")
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
# Add session targeting information
|
|
317
|
+
enhanced_payload = dict(payload)
|
|
318
|
+
enhanced_payload["client_sessions"] = target_sessions
|
|
319
|
+
|
|
320
|
+
# Add backward compatibility reply_channel (first session)
|
|
321
|
+
reply_channel = self._client_session_manager.get_reply_channel_for_compatibility()
|
|
322
|
+
if reply_channel and "reply_channel" not in enhanced_payload:
|
|
323
|
+
enhanced_payload["reply_channel"] = reply_channel
|
|
324
|
+
|
|
325
|
+
logger.debug("terminal_manager: Sending to %d client sessions: %s",
|
|
326
|
+
len(target_sessions), target_sessions)
|
|
327
|
+
|
|
328
|
+
await self._control_channel.send(enhanced_payload)
|
|
264
329
|
|
|
265
330
|
async def _send_terminal_list(self) -> None:
|
|
266
331
|
"""Send terminal list for reconnection reconciliation."""
|
|
@@ -272,7 +337,7 @@ class TerminalManager:
|
|
|
272
337
|
"event": "terminal_list",
|
|
273
338
|
"sessions": sessions,
|
|
274
339
|
}
|
|
275
|
-
await self.
|
|
340
|
+
await self._send_session_aware(payload)
|
|
276
341
|
except Exception as exc:
|
|
277
342
|
logger.warning("Failed to send terminal list: %s", exc)
|
|
278
343
|
|
|
@@ -282,6 +347,8 @@ class TerminalManager:
|
|
|
282
347
|
payload = {
|
|
283
348
|
"event": "request_client_sessions"
|
|
284
349
|
}
|
|
350
|
+
# This is a special case - always send regardless of current client sessions
|
|
351
|
+
# because we're trying to get the client sessions list
|
|
285
352
|
await self._control_channel.send(payload)
|
|
286
353
|
logger.info("Requested client sessions from server")
|
|
287
354
|
except Exception as exc:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md
RENAMED
|
File without changes
|
|
File without changes
|
{portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/file_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
{portacode-0.3.12.dev1 → portacode-0.3.12.dev3}/portacode/connection/handlers/system_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|