portacode 1.3.29__py3-none-any.whl → 1.3.31__py3-none-any.whl

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/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.3.29'
32
- __version_tuple__ = version_tuple = (1, 3, 29)
31
+ __version__ = version = '1.3.31'
32
+ __version_tuple__ = version_tuple = (1, 3, 31)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -28,12 +28,13 @@ except ImportError:
28
28
 
29
29
  class FileSystemWatcher:
30
30
  """Watches file system changes for project folders."""
31
-
31
+
32
32
  def __init__(self, project_manager):
33
33
  self.project_manager = project_manager # Reference to ProjectStateManager
34
34
  self.observer: Optional[Observer] = None
35
35
  self.event_handler: Optional[FileSystemEventHandler] = None
36
36
  self.watched_paths: Set[str] = set()
37
+ self.watch_handles: dict = {} # Map path -> watch handle for proper cleanup
37
38
  # Store reference to the event loop for thread-safe async task creation
38
39
  try:
39
40
  self.event_loop = asyncio.get_running_loop()
@@ -140,14 +141,15 @@ class FileSystemWatcher:
140
141
  if not WATCHDOG_AVAILABLE or not self.observer:
141
142
  logger.warning("Watchdog not available, cannot start watching: %s", path)
142
143
  return
143
-
144
+
144
145
  if path not in self.watched_paths:
145
146
  try:
146
147
  # Use recursive=False to watch only direct contents of each folder
147
- self.observer.schedule(self.event_handler, path, recursive=False)
148
+ watch_handle = self.observer.schedule(self.event_handler, path, recursive=False)
148
149
  self.watched_paths.add(path)
150
+ self.watch_handles[path] = watch_handle # Store handle for cleanup
149
151
  logger.info("Started watching path (non-recursive): %s", path)
150
-
152
+
151
153
  if not self.observer.is_alive():
152
154
  self.observer.start()
153
155
  logger.info("Started file system observer")
@@ -161,14 +163,15 @@ class FileSystemWatcher:
161
163
  if not WATCHDOG_AVAILABLE or not self.observer:
162
164
  logger.warning("Watchdog not available, cannot start watching git directory: %s", git_path)
163
165
  return
164
-
166
+
165
167
  if git_path not in self.watched_paths:
166
168
  try:
167
169
  # Watch .git directory recursively to catch changes in refs/, logs/, etc.
168
- self.observer.schedule(self.event_handler, git_path, recursive=True)
170
+ watch_handle = self.observer.schedule(self.event_handler, git_path, recursive=True)
169
171
  self.watched_paths.add(git_path)
172
+ self.watch_handles[git_path] = watch_handle # Store handle for cleanup
170
173
  logger.info("Started watching git directory (recursive): %s", git_path)
171
-
174
+
172
175
  if not self.observer.is_alive():
173
176
  self.observer.start()
174
177
  logger.info("Started file system observer")
@@ -181,9 +184,19 @@ class FileSystemWatcher:
181
184
  """Stop watching a specific path."""
182
185
  if not WATCHDOG_AVAILABLE or not self.observer:
183
186
  return
184
-
187
+
185
188
  if path in self.watched_paths:
186
- # Note: watchdog doesn't have direct path removal, would need to recreate observer
189
+ # Actually unschedule the watch using stored handle
190
+ watch_handle = self.watch_handles.get(path)
191
+ if watch_handle:
192
+ try:
193
+ self.observer.unschedule(watch_handle)
194
+ logger.info("Successfully unscheduled watch for: %s", path)
195
+ except Exception as e:
196
+ logger.error("Error unscheduling watch for %s: %s", path, e)
197
+ finally:
198
+ self.watch_handles.pop(path, None)
199
+
187
200
  self.watched_paths.discard(path)
188
201
  logger.debug("Stopped watching path: %s", path)
189
202
 
@@ -192,4 +205,5 @@ class FileSystemWatcher:
192
205
  if self.observer and self.observer.is_alive():
193
206
  self.observer.stop()
194
207
  self.observer.join()
195
- self.watched_paths.clear()
208
+ self.watched_paths.clear()
209
+ self.watch_handles.clear()
@@ -218,24 +218,33 @@ class TerminalListHandler(AsyncHandler):
218
218
 
219
219
  async def handle(self, message: Dict[str, Any], reply_channel: Optional[str] = None) -> None:
220
220
  """Handle the command by executing it and sending the response to the requesting client session."""
221
- logger.info("handler: Processing command %s with reply_channel=%s",
221
+ logger.info("handler: Processing command %s with reply_channel=%s",
222
222
  self.command_name, reply_channel)
223
-
223
+
224
224
  try:
225
225
  response = await self.execute(message)
226
226
  logger.info("handler: Command %s executed successfully", self.command_name)
227
-
227
+
228
+ # Automatically copy request_id if present in the incoming message
229
+ if "request_id" in message and "request_id" not in response:
230
+ response["request_id"] = message["request_id"]
231
+
228
232
  # Get the source client session from the message
229
233
  source_client_session = message.get("source_client_session")
230
234
  project_id = response.get("project_id")
231
-
232
- logger.info("handler: %s response project_id=%s, source_client_session=%s",
235
+
236
+ logger.info("handler: %s response project_id=%s, source_client_session=%s",
233
237
  self.command_name, project_id, source_client_session)
234
-
238
+
235
239
  # Send response only to the requesting client session
236
240
  if source_client_session:
237
241
  # Add client_sessions field to target only the requesting session
238
242
  response["client_sessions"] = [source_client_session]
243
+
244
+ import json
245
+ logger.info("handler: 📤 SENDING EVENT '%s' (via direct control_channel.send)", response.get("event", "unknown"))
246
+ logger.info("handler: 📤 FULL EVENT PAYLOAD: %s", json.dumps(response, indent=2, default=str))
247
+
239
248
  await self.control_channel.send(response)
240
249
  else:
241
250
  # Fallback to original behavior if no source_client_session
@@ -249,15 +258,19 @@ class TerminalListHandler(AsyncHandler):
249
258
  session_manager = self.context.get("session_manager")
250
259
  if not session_manager:
251
260
  raise RuntimeError("Session manager not available")
252
-
261
+
253
262
  # Accept project_id argument: None (default) = only no project, 'all' = all, else = filter by project_id
254
263
  requested_project_id = message.get("project_id")
264
+ logger.info("terminal_list: requested_project_id=%r (type: %s)", requested_project_id, type(requested_project_id))
255
265
 
256
266
  if requested_project_id == "all":
267
+ logger.info("terminal_list: Using 'all' mode to list all terminals")
257
268
  sessions = session_manager.list_sessions(project_id="all")
258
269
  else:
270
+ logger.info("terminal_list: Filtering by project_id=%r", requested_project_id)
259
271
  sessions = session_manager.list_sessions(project_id=requested_project_id)
260
-
272
+
273
+ logger.info("terminal_list: Found %d sessions, returning with project_id=%r", len(sessions), requested_project_id)
261
274
  return {
262
275
  "event": "terminal_list",
263
276
  "sessions": sessions,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.3.29
3
+ Version: 1.3.31
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -1,7 +1,7 @@
1
1
  portacode/README.md,sha256=4dKtpvR8LNgZPVz37GmkQCMWIr_u25Ao63iW56s7Ke4,775
2
2
  portacode/__init__.py,sha256=oB3sV1wXr-um-RXio73UG8E5Xx6cF2ZVJveqjNmC-vQ,1086
3
3
  portacode/__main__.py,sha256=jmHTGC1hzmo9iKJLv-SSYe9BSIbPPZ2IOpecI03PlTs,296
4
- portacode/_version.py,sha256=aYLDeOqOLABTFU5Cek8bHVfq_OkQmoLcgbUEQPZsITo,706
4
+ portacode/_version.py,sha256=nj9fipyzeKa-vUpoV8y3uNnbzWCSR4Zi6utiZgnfsIg,706
5
5
  portacode/cli.py,sha256=eDqcZMVFHKzqqWxedhhx8ylu5WMVCLqeJQkbPR7RcJE,16333
6
6
  portacode/data.py,sha256=5-s291bv8J354myaHm1Y7CQZTZyRzMU3TGe5U4hb-FA,1591
7
7
  portacode/keypair.py,sha256=PAcOYqlVLOoZTPYi6LvLjfsY6BkrWbLOhSZLb8r5sHs,3635
@@ -24,10 +24,10 @@ portacode/connection/handlers/registry.py,sha256=qXGE60sYEWg6ZtVQzFcZ5YI2XWR6lMg
24
24
  portacode/connection/handlers/session.py,sha256=O7TMI5cRziOiXEBWCfBshkMpEthhjvKqGL0hhNOG1wU,26716
25
25
  portacode/connection/handlers/system_handlers.py,sha256=65V5ctT0dIBc-oWG91e62MbdvU0z6x6JCTQuIqCWmZ0,5242
26
26
  portacode/connection/handlers/tab_factory.py,sha256=VBZnwtxgeNJCsfBzUjkFWAAGBdijvai4MS2dXnhFY8U,18000
27
- portacode/connection/handlers/terminal_handlers.py,sha256=Yuo84zwKB5OiLuVtDLCQgMVrOS3T8ZOONxXpGnnougo,11019
27
+ portacode/connection/handlers/terminal_handlers.py,sha256=HRwHW1GiqG1NtHVEqXHKaYkFfQEzCDDH6YIlHcb4XD8,11866
28
28
  portacode/connection/handlers/project_state/README.md,sha256=trdd4ig6ungmwH5SpbSLfyxbL-QgPlGNU-_XrMEiXtw,10114
29
29
  portacode/connection/handlers/project_state/__init__.py,sha256=5ucIqk6Iclqg6bKkL8r_wVs5Tlt6B9J7yQH6yQUt7gc,2541
30
- portacode/connection/handlers/project_state/file_system_watcher.py,sha256=w-93ioUZZKZxzPFr8djJnGhWjMVFVdDsmo0fVAukoKk,10150
30
+ portacode/connection/handlers/project_state/file_system_watcher.py,sha256=2zingW9BoNKRijghHC2eHHdRoyDRdLmIl1yH1y-iuF8,10831
31
31
  portacode/connection/handlers/project_state/git_manager.py,sha256=oqE5jC1Xk8Sne1BruQuAqotvbX_v7vPYYQUIp0pPe3U,87964
32
32
  portacode/connection/handlers/project_state/handlers.py,sha256=03RYNeWfX_Ym9Lx4VdA6iwLSWFdjRtjWI5T1buBg4Mc,37941
33
33
  portacode/connection/handlers/project_state/manager.py,sha256=_tkVu6sruKVTMxGPj1iLv7-IMGDWYid4xl_fCUppadA,60554
@@ -38,7 +38,7 @@ portacode/static/js/utils/ntp-clock.js,sha256=KMeHGT-IlUSlxVRZZ899z25dQCJh6EJbgX
38
38
  portacode/utils/NTP_ARCHITECTURE.md,sha256=WkESTbz5SNAgdmDKk3DrHMhtYOPji_Kt3_a9arWdRig,3894
39
39
  portacode/utils/__init__.py,sha256=NgBlWTuNJESfIYJzP_3adI1yJQJR0XJLRpSdVNaBAN0,33
40
40
  portacode/utils/ntp_clock.py,sha256=6QJOVZr9VQuxIyJt9KNG4dR-nZ3bKNyipMxjqDWP89Y,5152
41
- portacode-1.3.29.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
41
+ portacode-1.3.31.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
42
42
  test_modules/README.md,sha256=Do_agkm9WhSzueXjRAkV_xEj6Emy5zB3N3VKY5Roce8,9274
43
43
  test_modules/__init__.py,sha256=1LcbHodIHsB0g-g4NGjSn6AMuCoGbymvXPYLOb6Z7F0,53
44
44
  test_modules/test_device_online.py,sha256=yiSyVaMwKAugqIX_ZIxmLXiOlmA_8IRXiUp12YmpB98,1653
@@ -63,8 +63,8 @@ testing_framework/core/playwright_manager.py,sha256=8xl-19b8NQjKNdiRyDjyeXlYyKPZ
63
63
  testing_framework/core/runner.py,sha256=j2QwNJmAxVBmJvcbVS7DgPJUKPNzqfLmt_4NNdaKmZU,19297
64
64
  testing_framework/core/shared_cli_manager.py,sha256=BESSNtyQb7BOlaOvZmm04T8Uezjms4KCBs2MzTxvzYQ,8790
65
65
  testing_framework/core/test_discovery.py,sha256=2FZ9fJ8Dp5dloA-fkgXoJ_gCMC_nYPBnA3Hs2xlagzM,4928
66
- portacode-1.3.29.dist-info/METADATA,sha256=h5FuPo8MBpf7AWq3weg0cmW-GWFPYc-x4w3cSn1Gnhw,6989
67
- portacode-1.3.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
68
- portacode-1.3.29.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
69
- portacode-1.3.29.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
70
- portacode-1.3.29.dist-info/RECORD,,
66
+ portacode-1.3.31.dist-info/METADATA,sha256=LVMS4rVdOb2nZS1adK_n1sx1ObasGJj1jvWVmSTMkeI,6989
67
+ portacode-1.3.31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
68
+ portacode-1.3.31.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
69
+ portacode-1.3.31.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
70
+ portacode-1.3.31.dist-info/RECORD,,