portacode 0.3.11.dev0__tar.gz → 0.3.11.dev2__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.11.dev0 → portacode-0.3.11.dev2}/PKG-INFO +1 -1
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/_version.py +2 -2
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/session.py +23 -9
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/terminal_handlers.py +25 -17
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/.gitignore +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/.gitmodules +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/LICENSE +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/MANIFEST.in +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/Makefile +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/README.md +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/docker-compose.yaml +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/README.md +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/__init__.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/__main__.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/cli.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/README.md +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/__init__.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/client.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/README.md +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/base.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/registry.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/multiplex.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/terminal.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/data.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/keypair.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/service.py +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/requires.txt +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/top_level.txt +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/pyproject.toml +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/setup.cfg +0 -0
- {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/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.11.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 3, 11, '
|
|
20
|
+
__version__ = version = '0.3.11.dev2'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 11, 'dev2')
|
|
@@ -36,10 +36,11 @@ def _build_child_env() -> Dict[str, str]:
|
|
|
36
36
|
class TerminalSession:
|
|
37
37
|
"""Represents a local shell subprocess bound to a mux channel."""
|
|
38
38
|
|
|
39
|
-
def __init__(self, session_id: str, proc: Process, channel: "Channel"):
|
|
39
|
+
def __init__(self, session_id: str, proc: Process, channel: "Channel", project_id: Optional[str] = None):
|
|
40
40
|
self.id = session_id
|
|
41
41
|
self.proc = proc
|
|
42
42
|
self.channel = channel
|
|
43
|
+
self.project_id = project_id
|
|
43
44
|
self._reader_task: Optional[asyncio.Task[None]] = None
|
|
44
45
|
self._buffer: deque[str] = deque(maxlen=400)
|
|
45
46
|
|
|
@@ -139,7 +140,7 @@ class TerminalSession:
|
|
|
139
140
|
class WindowsTerminalSession(TerminalSession):
|
|
140
141
|
"""Terminal session backed by a Windows ConPTY."""
|
|
141
142
|
|
|
142
|
-
def __init__(self, session_id: str, pty, channel: "Channel"):
|
|
143
|
+
def __init__(self, session_id: str, pty, channel: "Channel", project_id: Optional[str] = None):
|
|
143
144
|
# Create a proxy for the PTY process
|
|
144
145
|
class _WinPTYProxy:
|
|
145
146
|
def __init__(self, pty):
|
|
@@ -157,7 +158,7 @@ class WindowsTerminalSession(TerminalSession):
|
|
|
157
158
|
loop = asyncio.get_running_loop()
|
|
158
159
|
await loop.run_in_executor(None, self._pty.wait)
|
|
159
160
|
|
|
160
|
-
super().__init__(session_id, _WinPTYProxy(pty), channel)
|
|
161
|
+
super().__init__(session_id, _WinPTYProxy(pty), channel, project_id)
|
|
161
162
|
self._pty = pty
|
|
162
163
|
|
|
163
164
|
async def start_io_forwarding(self) -> None:
|
|
@@ -247,7 +248,7 @@ class SessionManager:
|
|
|
247
248
|
"""Allocate a new unique channel ID for a terminal session using UUID."""
|
|
248
249
|
return uuid.uuid4().hex
|
|
249
250
|
|
|
250
|
-
async def create_session(self, shell: Optional[str] = None, cwd: Optional[str] = None) -> Dict[str, Any]:
|
|
251
|
+
async def create_session(self, shell: Optional[str] = None, cwd: Optional[str] = None, project_id: Optional[str] = None) -> Dict[str, Any]:
|
|
251
252
|
"""Create a new terminal session."""
|
|
252
253
|
# Use the same UUID for both terminal_id and channel_id to ensure consistency
|
|
253
254
|
session_uuid = uuid.uuid4().hex
|
|
@@ -279,7 +280,7 @@ class SessionManager:
|
|
|
279
280
|
raise RuntimeError("pywinpty not installed on client")
|
|
280
281
|
|
|
281
282
|
pty_proc = PtyProcess.spawn(shell, cwd=cwd or None, env=_build_child_env())
|
|
282
|
-
session = WindowsTerminalSession(term_id, pty_proc, channel)
|
|
283
|
+
session = WindowsTerminalSession(term_id, pty_proc, channel, project_id)
|
|
283
284
|
else:
|
|
284
285
|
# Unix: try real PTY for proper TTY semantics
|
|
285
286
|
try:
|
|
@@ -315,7 +316,7 @@ class SessionManager:
|
|
|
315
316
|
cwd=cwd,
|
|
316
317
|
env=_build_child_env(),
|
|
317
318
|
)
|
|
318
|
-
session = TerminalSession(term_id, proc, channel)
|
|
319
|
+
session = TerminalSession(term_id, proc, channel, project_id)
|
|
319
320
|
|
|
320
321
|
self._sessions[term_id] = session
|
|
321
322
|
await session.start_io_forwarding()
|
|
@@ -326,6 +327,7 @@ class SessionManager:
|
|
|
326
327
|
"pid": session.proc.pid,
|
|
327
328
|
"shell": shell,
|
|
328
329
|
"cwd": cwd,
|
|
330
|
+
"project_id": project_id,
|
|
329
331
|
}
|
|
330
332
|
|
|
331
333
|
def get_session(self, terminal_id: str) -> Optional[TerminalSession]:
|
|
@@ -342,8 +344,19 @@ class SessionManager:
|
|
|
342
344
|
logger.warning("session_manager: Attempted to remove non-existent session %s", terminal_id)
|
|
343
345
|
return session
|
|
344
346
|
|
|
345
|
-
def list_sessions(self) -> List[Dict[str, Any]]:
|
|
346
|
-
"""List all terminal sessions."""
|
|
347
|
+
def list_sessions(self, project_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
348
|
+
"""List all terminal sessions, optionally filtered by project_id."""
|
|
349
|
+
filtered_sessions = []
|
|
350
|
+
for s in self._sessions.values():
|
|
351
|
+
if project_id == "all":
|
|
352
|
+
filtered_sessions.append(s)
|
|
353
|
+
elif project_id is None:
|
|
354
|
+
if s.project_id is None:
|
|
355
|
+
filtered_sessions.append(s)
|
|
356
|
+
else:
|
|
357
|
+
if s.project_id == project_id:
|
|
358
|
+
filtered_sessions.append(s)
|
|
359
|
+
|
|
347
360
|
return [
|
|
348
361
|
{
|
|
349
362
|
"terminal_id": s.id,
|
|
@@ -355,8 +368,9 @@ class SessionManager:
|
|
|
355
368
|
"created_at": None, # Could add timestamp if needed
|
|
356
369
|
"shell": None, # Could store shell info if needed
|
|
357
370
|
"cwd": None, # Could store cwd info if needed
|
|
371
|
+
"project_id": s.project_id,
|
|
358
372
|
}
|
|
359
|
-
for s in
|
|
373
|
+
for s in filtered_sessions
|
|
360
374
|
]
|
|
361
375
|
|
|
362
376
|
async def reattach_sessions(self, mux):
|
{portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/terminal_handlers.py
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
-
from typing import Any, Dict
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
6
|
|
|
7
7
|
from .base import AsyncHandler
|
|
8
8
|
from .session import SessionManager
|
|
@@ -55,6 +55,7 @@ class TerminalStartHandler(AsyncHandler):
|
|
|
55
55
|
"event": "terminal_exit",
|
|
56
56
|
"terminal_id": terminal_id,
|
|
57
57
|
"returncode": session.proc.returncode,
|
|
58
|
+
"project_id": session.project_id,
|
|
58
59
|
})
|
|
59
60
|
|
|
60
61
|
# Only cleanup session if it still exists (not already removed by stop handler)
|
|
@@ -118,28 +119,30 @@ class TerminalStopHandler(AsyncHandler):
|
|
|
118
119
|
if not session:
|
|
119
120
|
logger.warning("terminal_stop: Terminal %s not found, may have already been stopped", terminal_id)
|
|
120
121
|
# Send completion event immediately for not found terminals
|
|
121
|
-
asyncio.create_task(self._send_not_found_completion(terminal_id))
|
|
122
|
+
asyncio.create_task(self._send_not_found_completion(terminal_id, None))
|
|
122
123
|
return {
|
|
123
124
|
"event": "terminal_stopped",
|
|
124
125
|
"terminal_id": terminal_id,
|
|
125
126
|
"status": "not_found",
|
|
126
|
-
"message": "Terminal was not found or already stopped"
|
|
127
|
+
"message": "Terminal was not found or already stopped",
|
|
128
|
+
"project_id": None,
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
logger.info("terminal_stop: Found session for terminal %s (PID: %s), starting background stop process",
|
|
130
132
|
terminal_id, getattr(session.proc, 'pid', 'unknown'))
|
|
131
133
|
|
|
132
134
|
# Start stop process in background without blocking the control channel
|
|
133
|
-
asyncio.create_task(self._stop_session_safely(session, terminal_id))
|
|
135
|
+
asyncio.create_task(self._stop_session_safely(session, terminal_id, session.project_id))
|
|
134
136
|
|
|
135
137
|
return {
|
|
136
138
|
"event": "terminal_stopped",
|
|
137
139
|
"terminal_id": terminal_id,
|
|
138
140
|
"status": "stopping",
|
|
139
|
-
"message": "Terminal stop process initiated"
|
|
141
|
+
"message": "Terminal stop process initiated",
|
|
142
|
+
"project_id": session.project_id,
|
|
140
143
|
}
|
|
141
144
|
|
|
142
|
-
async def _stop_session_safely(self, session, terminal_id: str) -> None:
|
|
145
|
+
async def _stop_session_safely(self, session, terminal_id: str, project_id: Optional[str] = None) -> None:
|
|
143
146
|
"""Safely stop a session in the background with timeout and error handling."""
|
|
144
147
|
logger.info("terminal_stop: Starting background stop process for terminal %s", terminal_id)
|
|
145
148
|
|
|
@@ -153,7 +156,8 @@ class TerminalStopHandler(AsyncHandler):
|
|
|
153
156
|
"event": "terminal_stop_completed",
|
|
154
157
|
"terminal_id": terminal_id,
|
|
155
158
|
"status": "success",
|
|
156
|
-
"message": "Terminal stopped successfully"
|
|
159
|
+
"message": "Terminal stopped successfully",
|
|
160
|
+
"project_id": project_id,
|
|
157
161
|
})
|
|
158
162
|
|
|
159
163
|
except asyncio.TimeoutError:
|
|
@@ -175,7 +179,8 @@ class TerminalStopHandler(AsyncHandler):
|
|
|
175
179
|
"event": "terminal_stop_completed",
|
|
176
180
|
"terminal_id": terminal_id,
|
|
177
181
|
"status": "timeout",
|
|
178
|
-
"message": "Terminal stop timed out, process was force killed"
|
|
182
|
+
"message": "Terminal stop timed out, process was force killed",
|
|
183
|
+
"project_id": project_id,
|
|
179
184
|
})
|
|
180
185
|
|
|
181
186
|
except Exception as exc:
|
|
@@ -186,16 +191,18 @@ class TerminalStopHandler(AsyncHandler):
|
|
|
186
191
|
"event": "terminal_stop_completed",
|
|
187
192
|
"terminal_id": terminal_id,
|
|
188
193
|
"status": "error",
|
|
189
|
-
"message": f"Error stopping terminal: {str(exc)}"
|
|
194
|
+
"message": f"Error stopping terminal: {str(exc)}",
|
|
195
|
+
"project_id": project_id,
|
|
190
196
|
})
|
|
191
197
|
|
|
192
|
-
async def _send_not_found_completion(self, terminal_id: str) -> None:
|
|
198
|
+
async def _send_not_found_completion(self, terminal_id: str, project_id: Optional[str] = None) -> None:
|
|
193
199
|
"""Send completion event for not found terminals."""
|
|
194
200
|
await self.control_channel.send({
|
|
195
201
|
"event": "terminal_stop_completed",
|
|
196
202
|
"terminal_id": terminal_id,
|
|
197
203
|
"status": "not_found",
|
|
198
|
-
"message": "Terminal was not found or already stopped"
|
|
204
|
+
"message": "Terminal was not found or already stopped",
|
|
205
|
+
"project_id": project_id,
|
|
199
206
|
})
|
|
200
207
|
|
|
201
208
|
|
|
@@ -213,14 +220,15 @@ class TerminalListHandler(AsyncHandler):
|
|
|
213
220
|
raise RuntimeError("Session manager not available")
|
|
214
221
|
|
|
215
222
|
# Accept project_id argument: None (default) = only no project, 'all' = all, else = filter by project_id
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
requested_project_id = message.get("project_id")
|
|
224
|
+
|
|
225
|
+
if requested_project_id == "all":
|
|
226
|
+
sessions = session_manager.list_sessions(project_id="all")
|
|
227
|
+
else:
|
|
228
|
+
sessions = session_manager.list_sessions(project_id=requested_project_id)
|
|
222
229
|
|
|
223
230
|
return {
|
|
224
231
|
"event": "terminal_list",
|
|
225
232
|
"sessions": sessions,
|
|
233
|
+
"project_id": requested_project_id,
|
|
226
234
|
}
|
|
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.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/file_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
{portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/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
|