portacode 1.4.13.dev0__py3-none-any.whl → 1.4.13.dev1__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.4.13.dev0'
32
- __version_tuple__ = version_tuple = (1, 4, 13, 'dev0')
31
+ __version__ = version = '1.4.13.dev1'
32
+ __version_tuple__ = version_tuple = (1, 4, 13, 'dev1')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -27,6 +27,8 @@ The Portacode server acts as a **routing middleman** between client sessions and
27
27
 
28
28
  - **`source_client_session`** (Server → Device): Server **adds this** when forwarding client commands to devices (so device knows which client sent the command and can target responses back). Clients never include this field.
29
29
 
30
+ Device handlers that want to respond only to the session that issued a given command can rely on the helper defined in `portacode/connection/handlers/base.py` (`BaseHandler.send_response_to_source_session`). It wraps the boilerplate of reusing `request_id`/`trace`, injecting `client_sessions=[source_client_session]`, and sending via the control channel so the handler does not need to manually reconstruct routing metadata for each reply.
31
+
30
32
  This document describes the complete protocol for communicating with devices through the server, guiding app developers on how to get their client sessions to communicate with devices.
31
33
 
32
34
  ## Table of Contents
@@ -88,6 +88,42 @@ class BaseHandler(ABC):
88
88
  if reply_channel:
89
89
  payload["reply_channel"] = reply_channel
90
90
  await self.control_channel.send(payload)
91
+
92
+ async def send_response_to_source_session(
93
+ self,
94
+ message: Dict[str, Any],
95
+ payload: Dict[str, Any],
96
+ reply_channel: Optional[str] = None,
97
+ ) -> None:
98
+ """Send a response directly to the client session that issued ``message``.
99
+
100
+ This bypasses ``ClientSessionManager`` routing and ensures only the session
101
+ whose ``source_client_session`` was injected by the server will receive the
102
+ response. The helper also copies ``request_id``/``trace`` timing data so tracing
103
+ and logging remain consistent.
104
+ """
105
+ response = dict(payload)
106
+ request_id = message.get("request_id")
107
+ if request_id and "request_id" not in response:
108
+ response["request_id"] = request_id
109
+
110
+ if "trace" in message and "request_id" in message and "trace" not in response:
111
+ trace_data = dict(message["trace"])
112
+ handler_complete_time = ntp_clock.now_ms()
113
+ if handler_complete_time is not None:
114
+ trace_data["handler_complete"] = handler_complete_time
115
+ if "client_send" in trace_data:
116
+ trace_data["ping"] = handler_complete_time - trace_data["client_send"]
117
+ response["trace"] = trace_data
118
+
119
+ source_client_session = message.get("source_client_session")
120
+ if source_client_session:
121
+ response["client_sessions"] = [source_client_session]
122
+
123
+ if reply_channel:
124
+ response["reply_channel"] = reply_channel
125
+
126
+ await self.control_channel.send(response)
91
127
 
92
128
  async def send_error(self, message: str, reply_channel: Optional[str] = None, project_id: str = None) -> None:
93
129
  """Send an error response with client session awareness.
@@ -141,22 +177,16 @@ class AsyncHandler(BaseHandler):
141
177
  if "request_id" in message and "request_id" not in response:
142
178
  response["request_id"] = message["request_id"]
143
179
 
144
- # Pass through trace from request to response (add to existing trace, don't create new one)
145
- if "trace" in message and "request_id" in message:
146
- response["trace"] = dict(message["trace"])
180
+ if "trace" in message and "request_id" in message and "trace" not in response:
181
+ trace_data = dict(message["trace"])
147
182
  handler_complete_time = ntp_clock.now_ms()
148
183
  if handler_complete_time is not None:
149
- response["trace"]["handler_complete"] = handler_complete_time
150
- # Update ping to show total time from client_send
151
- if "client_send" in response["trace"]:
152
- response["trace"]["ping"] = handler_complete_time - response["trace"]["client_send"]
153
- logger.info(f"✅ Handler completed: {message['request_id']} ({self.command_name})")
154
-
155
- # Extract project_id from response for session targeting
156
- project_id = response.get("project_id")
157
- logger.info("handler: %s response project_id=%s, response=%s",
158
- self.command_name, project_id, response)
159
- await self.send_response(response, reply_channel, project_id)
184
+ trace_data["handler_complete"] = handler_complete_time
185
+ if "client_send" in trace_data:
186
+ trace_data["ping"] = handler_complete_time - trace_data["client_send"]
187
+ response["trace"] = trace_data
188
+ logger.info(f"✅ Handler completed: {message['request_id']} ({self.command_name})")
189
+
160
190
  else:
161
191
  logger.info("handler: %s handled response transmission directly", self.command_name)
162
192
  except Exception as exc:
@@ -216,4 +246,4 @@ class SyncHandler(BaseHandler):
216
246
  logger.exception("Error in sync handler %s: %s", self.command_name, exc)
217
247
  # Extract project_id from original message for error targeting
218
248
  project_id = message.get("project_id")
219
- await self.send_error(str(exc), reply_channel, project_id)
249
+ await self.send_error(str(exc), reply_channel, project_id)
@@ -1261,6 +1261,7 @@ class CreateProxmoxContainerHandler(SyncHandler):
1261
1261
  }
1262
1262
  if client_sessions:
1263
1263
  response["client_sessions"] = client_sessions
1264
+ response["_reply_to_source_session"] = True
1264
1265
  return response
1265
1266
 
1266
1267
 
@@ -1390,6 +1391,7 @@ class StartPortacodeServiceHandler(SyncHandler):
1390
1391
  }
1391
1392
  if client_sessions:
1392
1393
  response["client_sessions"] = client_sessions
1394
+ response["_reply_to_source_session"] = True
1393
1395
  return response
1394
1396
 
1395
1397
 
@@ -1431,6 +1433,7 @@ class StartProxmoxContainerHandler(SyncHandler):
1431
1433
  }
1432
1434
  if client_sessions:
1433
1435
  response["client_sessions"] = client_sessions
1436
+ response["_reply_to_source_session"] = True
1434
1437
  return response
1435
1438
 
1436
1439
 
@@ -1478,6 +1481,7 @@ class StopProxmoxContainerHandler(SyncHandler):
1478
1481
  }
1479
1482
  if client_sessions:
1480
1483
  response["client_sessions"] = client_sessions
1484
+ response["_reply_to_source_session"] = True
1481
1485
  return response
1482
1486
 
1483
1487
 
@@ -1523,6 +1527,7 @@ class RemoveProxmoxContainerHandler(SyncHandler):
1523
1527
  }
1524
1528
  if client_sessions:
1525
1529
  response["client_sessions"] = client_sessions
1530
+ response["_reply_to_source_session"] = True
1526
1531
  return response
1527
1532
 
1528
1533
 
@@ -1548,6 +1553,7 @@ class ConfigureProxmoxInfraHandler(SyncHandler):
1548
1553
  }
1549
1554
  if client_sessions:
1550
1555
  response["client_sessions"] = client_sessions
1556
+ response["_reply_to_source_session"] = True
1551
1557
  return response
1552
1558
 
1553
1559
 
@@ -1568,4 +1574,5 @@ class RevertProxmoxInfraHandler(SyncHandler):
1568
1574
  }
1569
1575
  if client_sessions:
1570
1576
  response["client_sessions"] = client_sessions
1577
+ response["_reply_to_source_session"] = True
1571
1578
  return response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.13.dev0
3
+ Version: 1.4.13.dev1
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=EoCeFU0-D6AjtQLTsyubZ8HHL9Z71w1CLhD054hnlNo,719
4
+ portacode/_version.py,sha256=LRfIU5637o003YhvJfKkC-W6_vMYzK36mKyg9SlBZi0,719
5
5
  portacode/cli.py,sha256=mGLKoZ-T2FBF7IA9wUq0zyG0X9__-A1ao7gajjcVRH8,21828
6
6
  portacode/data.py,sha256=5-s291bv8J354myaHm1Y7CQZTZyRzMU3TGe5U4hb-FA,1591
7
7
  portacode/keypair.py,sha256=0OO4vHDcF1XMxCDqce61xFTlFwlTcmqe5HyGsXFEt7s,5838
@@ -14,15 +14,15 @@ portacode/connection/client.py,sha256=jtLb9_YufqPkzi9t8VQH3iz_JEMisbtY6a8L9U5wei
14
14
  portacode/connection/multiplex.py,sha256=L-TxqJ_ZEbfNEfu1cwxgJ5vUdyRzZjsMy2Kx1diiZys,5237
15
15
  portacode/connection/terminal.py,sha256=oyLPOVLPlUuN_eRvHPGazB51yi8W8JEF3oOEYxucGTE,45069
16
16
  portacode/connection/handlers/README.md,sha256=HsLZG1QK1JNm67HsgL6WoDg9nxzKXxwkc5fJPFJdX5g,12169
17
- portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=7tBYNEY8EBGAPIMT606BqeHnyMOQIZVlQYpH7me26LY,97962
17
+ portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=2sFO9VsvZUl57RM6R4cgYzN4WYlIQLASlKMa7r0NMSc,98404
18
18
  portacode/connection/handlers/__init__.py,sha256=WSeBmi65GWFQPYt9M3E10rn0uZ_EPCJzNJOzSf2HZyw,2921
19
- portacode/connection/handlers/base.py,sha256=oENFb-Fcfzwk99Qx8gJQriEMiwSxwygwjOiuCH36hM4,10231
19
+ portacode/connection/handlers/base.py,sha256=YKjXeS_PDbM6RyrSauZBJvmndU2bfvCMOwckYLVLn-U,11296
20
20
  portacode/connection/handlers/chunked_content.py,sha256=h6hXRmxSeOgnIxoU8CkmvEf2Odv-ajPrpHIe_W3GKcA,9251
21
21
  portacode/connection/handlers/diff_handlers.py,sha256=iYTIRCcpEQ03vIPKZCsMTE5aZbQw6sF04M3dM6rUV8Q,24477
22
22
  portacode/connection/handlers/file_handlers.py,sha256=nAJH8nXnX07xxD28ngLpgIUzcTuRwZBNpEGEKdRqohw,39507
23
23
  portacode/connection/handlers/project_aware_file_handlers.py,sha256=AqgMnDqX2893T2NsrvUSCwjN5VKj4Pb2TN0S_SuboOE,9803
24
24
  portacode/connection/handlers/project_state_handlers.py,sha256=v6ZefGW9i7n1aZLq2jOGumJIjYb6aHlPI4m1jkYewm8,1686
25
- portacode/connection/handlers/proxmox_infra.py,sha256=QxD5FOQqRLz1Z8nhycdm2O991TWIXPjSC589xASZADQ,57264
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=bZVlM6k--CyLEBbhPvFafHTN-cyryRxy86EG6PpXoII,57628
26
26
  portacode/connection/handlers/registry.py,sha256=qXGE60sYEWg6ZtVQzFcZ5YI2XWR6lMgw4hAL9x5qR1I,6181
27
27
  portacode/connection/handlers/session.py,sha256=uNGfiO_1B9-_yjJKkpvmbiJhIl6b-UXlT86UTfd6WYE,42219
28
28
  portacode/connection/handlers/system_handlers.py,sha256=AKh7IbwptlLYrbSw5f-DHigvlaKHsg9lDP-lkAUm8cE,10755
@@ -64,7 +64,7 @@ portacode/utils/__init__.py,sha256=NgBlWTuNJESfIYJzP_3adI1yJQJR0XJLRpSdVNaBAN0,3
64
64
  portacode/utils/diff_apply.py,sha256=4Oi7ft3VUCKmiUE4VM-OeqO7Gk6H7PF3WnN4WHXtjxI,15157
65
65
  portacode/utils/diff_renderer.py,sha256=S76StnQ2DLfsz4Gg0m07UwPfRp8270PuzbNaQq-rmYk,13850
66
66
  portacode/utils/ntp_clock.py,sha256=VqCnWCTehCufE43W23oB-WUdAZGeCcLxkmIOPwInYHc,2499
67
- portacode-1.4.13.dev0.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
67
+ portacode-1.4.13.dev1.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
68
  test_modules/README.md,sha256=Do_agkm9WhSzueXjRAkV_xEj6Emy5zB3N3VKY5Roce8,9274
69
69
  test_modules/__init__.py,sha256=1LcbHodIHsB0g-g4NGjSn6AMuCoGbymvXPYLOb6Z7F0,53
70
70
  test_modules/test_device_online.py,sha256=QtYq0Dq9vME8Gp2O4fGSheqVf8LUtpsSKosXXk56gGM,1654
@@ -90,8 +90,8 @@ testing_framework/core/playwright_manager.py,sha256=Tw46qwxIhOFkS48C2IWIQHHNpEe-
90
90
  testing_framework/core/runner.py,sha256=j2QwNJmAxVBmJvcbVS7DgPJUKPNzqfLmt_4NNdaKmZU,19297
91
91
  testing_framework/core/shared_cli_manager.py,sha256=BESSNtyQb7BOlaOvZmm04T8Uezjms4KCBs2MzTxvzYQ,8790
92
92
  testing_framework/core/test_discovery.py,sha256=2FZ9fJ8Dp5dloA-fkgXoJ_gCMC_nYPBnA3Hs2xlagzM,4928
93
- portacode-1.4.13.dev0.dist-info/METADATA,sha256=NuHta1wZHQRrXvVF3tcjW6ybf3wz3ivf3ktRZrYrn3c,13051
94
- portacode-1.4.13.dev0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
95
- portacode-1.4.13.dev0.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
96
- portacode-1.4.13.dev0.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
97
- portacode-1.4.13.dev0.dist-info/RECORD,,
93
+ portacode-1.4.13.dev1.dist-info/METADATA,sha256=mSEfLnN_1l9Wjf0LHMvOp5Gai18-BsmIgQ-jD80vAVY,13051
94
+ portacode-1.4.13.dev1.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
95
+ portacode-1.4.13.dev1.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
96
+ portacode-1.4.13.dev1.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
97
+ portacode-1.4.13.dev1.dist-info/RECORD,,