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 +2 -2
- portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +2 -0
- portacode/connection/handlers/base.py +45 -15
- portacode/connection/handlers/proxmox_infra.py +7 -0
- {portacode-1.4.13.dev0.dist-info → portacode-1.4.13.dev1.dist-info}/METADATA +1 -1
- {portacode-1.4.13.dev0.dist-info → portacode-1.4.13.dev1.dist-info}/RECORD +10 -10
- {portacode-1.4.13.dev0.dist-info → portacode-1.4.13.dev1.dist-info}/WHEEL +0 -0
- {portacode-1.4.13.dev0.dist-info → portacode-1.4.13.dev1.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.13.dev0.dist-info → portacode-1.4.13.dev1.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.13.dev0.dist-info → portacode-1.4.13.dev1.dist-info}/top_level.txt +0 -0
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.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 4, 13, '
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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,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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
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.
|
|
94
|
-
portacode-1.4.13.
|
|
95
|
-
portacode-1.4.13.
|
|
96
|
-
portacode-1.4.13.
|
|
97
|
-
portacode-1.4.13.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|