portacode 1.3.27.dev0__tar.gz → 1.3.27.dev1__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.

Potentially problematic release.


This version of portacode might be problematic. Click here for more details.

Files changed (91) hide show
  1. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/PKG-INFO +1 -1
  2. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/_version.py +2 -2
  3. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/base.py +57 -7
  4. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/registry.py +15 -4
  5. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/static/js/utils/ntp-clock.js +5 -3
  6. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode.egg-info/PKG-INFO +1 -1
  7. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/.claude/agents/communication-manager.md +0 -0
  8. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/.claude/settings.local.json +0 -0
  9. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/.gitignore +0 -0
  10. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/.gitmodules +0 -0
  11. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/LICENSE +0 -0
  12. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/MANIFEST.in +0 -0
  13. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/Makefile +0 -0
  14. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/README.md +0 -0
  15. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/backup.sh +0 -0
  16. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/connect.py +0 -0
  17. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/connect.sh +0 -0
  18. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/docker-compose.yaml +0 -0
  19. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/README.md +0 -0
  20. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/__init__.py +0 -0
  21. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/__main__.py +0 -0
  22. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/cli.py +0 -0
  23. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/README.md +0 -0
  24. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/__init__.py +0 -0
  25. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/client.py +0 -0
  26. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/README.md +0 -0
  27. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
  28. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/__init__.py +0 -0
  29. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/chunked_content.py +0 -0
  30. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/file_handlers.py +0 -0
  31. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
  32. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/project_state/README.md +0 -0
  33. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/project_state/__init__.py +0 -0
  34. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
  35. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/project_state/git_manager.py +0 -0
  36. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/project_state/handlers.py +0 -0
  37. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/project_state/manager.py +0 -0
  38. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/project_state/models.py +0 -0
  39. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/project_state/utils.py +0 -0
  40. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/project_state_handlers.py +0 -0
  41. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/session.py +0 -0
  42. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/system_handlers.py +0 -0
  43. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/tab_factory.py +0 -0
  44. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/handlers/terminal_handlers.py +0 -0
  45. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/multiplex.py +0 -0
  46. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/connection/terminal.py +0 -0
  47. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/data.py +0 -0
  48. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/keypair.py +0 -0
  49. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/logging_categories.py +0 -0
  50. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/service.py +0 -0
  51. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/static/js/test-ntp-clock.html +0 -0
  52. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/utils/__init__.py +0 -0
  53. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode/utils/ntp_clock.py +0 -0
  54. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode.egg-info/SOURCES.txt +0 -0
  55. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode.egg-info/dependency_links.txt +0 -0
  56. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode.egg-info/entry_points.txt +0 -0
  57. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode.egg-info/requires.txt +0 -0
  58. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/portacode.egg-info/top_level.txt +0 -0
  59. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/pyproject.toml +0 -0
  60. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/restore.sh +0 -0
  61. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/run_tests.py +0 -0
  62. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/setup.cfg +0 -0
  63. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/setup.py +0 -0
  64. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test.sh +0 -0
  65. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/README.md +0 -0
  66. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/__init__.py +0 -0
  67. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/test_device_online.py +0 -0
  68. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/test_file_operations.py +0 -0
  69. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/test_git_status_ui.py +0 -0
  70. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/test_login_flow.py +0 -0
  71. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/test_navigate_testing_folder.py +0 -0
  72. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/test_terminal_buffer_performance.py +0 -0
  73. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/test_terminal_interaction.py +0 -0
  74. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/test_terminal_loading_race_condition.py +0 -0
  75. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_modules/test_terminal_start.py +0 -0
  76. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/test_request_id.py +0 -0
  77. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/.env.example +0 -0
  78. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/README.md +0 -0
  79. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/__init__.py +0 -0
  80. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/cli.py +0 -0
  81. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/core/__init__.py +0 -0
  82. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/core/base_test.py +0 -0
  83. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/core/cli_manager.py +0 -0
  84. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/core/hierarchical_runner.py +0 -0
  85. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/core/playwright_manager.py +0 -0
  86. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/core/runner.py +0 -0
  87. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/core/shared_cli_manager.py +0 -0
  88. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/core/test_discovery.py +0 -0
  89. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/testing_framework/requirements.txt +0 -0
  90. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/todo/issues/indefinite_resource_loading.md +0 -0
  91. {portacode-1.3.27.dev0 → portacode-1.3.27.dev1}/todo/issues/premature_terminal_exit.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.3.27.dev0
3
+ Version: 1.3.27.dev1
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -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.27.dev0'
32
- __version_tuple__ = version_tuple = (1, 3, 27, 'dev0')
31
+ __version__ = version = '1.3.27.dev1'
32
+ __version_tuple__ = version_tuple = (1, 3, 27, 'dev1')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -4,6 +4,7 @@ import asyncio
4
4
  import logging
5
5
  from abc import ABC, abstractmethod
6
6
  from typing import Any, Dict, Optional, TYPE_CHECKING
7
+ from portacode.utils.ntp_clock import ntp_clock
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from ..multiplex import Channel
@@ -42,35 +43,45 @@ class BaseHandler(ABC):
42
43
 
43
44
  async def send_response(self, payload: Dict[str, Any], reply_channel: Optional[str] = None, project_id: str = None) -> None:
44
45
  """Send a response back to the gateway with client session awareness.
45
-
46
+
46
47
  Args:
47
48
  payload: Response payload
48
49
  reply_channel: Optional reply channel for backward compatibility
49
50
  project_id: Optional project filter for targeting specific sessions
50
51
  """
52
+ # Add device_send timestamp if trace present
53
+ if "trace" in payload and "request_id" in payload:
54
+ device_send_time = ntp_clock.now_ms()
55
+ if device_send_time is not None:
56
+ payload["trace"]["device_send"] = device_send_time
57
+ # Update ping to show total time from client_send
58
+ if "client_send" in payload["trace"]:
59
+ payload["trace"]["ping"] = device_send_time - payload["trace"]["client_send"]
60
+ logger.info(f"📤 Device sending traced response: {payload['request_id']}")
61
+
51
62
  # Get client session manager from context
52
63
  client_session_manager = self.context.get("client_session_manager")
53
-
64
+
54
65
  if client_session_manager and client_session_manager.has_interested_clients():
55
66
  # Get target sessions
56
67
  target_sessions = client_session_manager.get_target_sessions(project_id)
57
68
  if not target_sessions:
58
69
  logger.debug("handler: No target sessions found, skipping response send")
59
70
  return
60
-
71
+
61
72
  # Add session targeting information
62
73
  enhanced_payload = dict(payload)
63
74
  enhanced_payload["client_sessions"] = target_sessions
64
-
75
+
65
76
  # Add backward compatibility reply_channel (first session if not provided)
66
77
  if not reply_channel:
67
78
  reply_channel = client_session_manager.get_reply_channel_for_compatibility()
68
79
  if reply_channel:
69
80
  enhanced_payload["reply_channel"] = reply_channel
70
-
71
- logger.debug("handler: Sending response to %d client sessions: %s",
81
+
82
+ logger.debug("handler: Sending response to %d client sessions: %s",
72
83
  len(target_sessions), target_sessions)
73
-
84
+
74
85
  await self.control_channel.send(enhanced_payload)
75
86
  else:
76
87
  # Fallback to original behavior if no client session manager or no clients
@@ -110,6 +121,16 @@ class AsyncHandler(BaseHandler):
110
121
  logger.info("handler: Processing command %s with reply_channel=%s",
111
122
  self.command_name, reply_channel)
112
123
 
124
+ # Add handler_dispatch timestamp if trace present
125
+ if "trace" in message and "request_id" in message:
126
+ handler_dispatch_time = ntp_clock.now_ms()
127
+ if handler_dispatch_time is not None:
128
+ message["trace"]["handler_dispatch"] = handler_dispatch_time
129
+ # Update ping to show total time from client_send
130
+ if "client_send" in message["trace"]:
131
+ message["trace"]["ping"] = handler_dispatch_time - message["trace"]["client_send"]
132
+ logger.info(f"🔧 Handler dispatching: {message['request_id']} ({self.command_name})")
133
+
113
134
  try:
114
135
  response = await self.execute(message)
115
136
  logger.info("handler: Command %s executed successfully", self.command_name)
@@ -120,6 +141,17 @@ class AsyncHandler(BaseHandler):
120
141
  if "request_id" in message and "request_id" not in response:
121
142
  response["request_id"] = message["request_id"]
122
143
 
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"])
147
+ handler_complete_time = ntp_clock.now_ms()
148
+ 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
+
123
155
  # Extract project_id from response for session targeting
124
156
  project_id = response.get("project_id")
125
157
  logger.info("handler: %s response project_id=%s, response=%s",
@@ -151,6 +183,16 @@ class SyncHandler(BaseHandler):
151
183
 
152
184
  async def handle(self, message: Dict[str, Any], reply_channel: Optional[str] = None) -> None:
153
185
  """Handle the command by executing it in an executor and sending the response."""
186
+ # Add handler_dispatch timestamp if trace present
187
+ if "trace" in message and "request_id" in message:
188
+ handler_dispatch_time = ntp_clock.now_ms()
189
+ if handler_dispatch_time is not None:
190
+ message["trace"]["handler_dispatch"] = handler_dispatch_time
191
+ # Update ping to show total time from client_send
192
+ if "client_send" in message["trace"]:
193
+ message["trace"]["ping"] = handler_dispatch_time - message["trace"]["client_send"]
194
+ logger.info(f"🔧 Handler dispatching: {message['request_id']} ({self.command_name})")
195
+
154
196
  try:
155
197
  loop = asyncio.get_running_loop()
156
198
  response = await loop.run_in_executor(None, self.execute, message)
@@ -159,6 +201,14 @@ class SyncHandler(BaseHandler):
159
201
  if "request_id" in message and "request_id" not in response:
160
202
  response["request_id"] = message["request_id"]
161
203
 
204
+ # Pass through trace from request to response (add to existing trace, don't create new one)
205
+ if "trace" in message and "request_id" in message:
206
+ response["trace"] = dict(message["trace"])
207
+ handler_complete_time = ntp_clock.now_ms()
208
+ if handler_complete_time is not None:
209
+ response["trace"]["handler_complete"] = handler_complete_time
210
+ logger.info(f"✅ Handler completed: {message['request_id']} ({self.command_name})")
211
+
162
212
  # Extract project_id from response for session targeting
163
213
  project_id = response.get("project_id")
164
214
  await self.send_response(response, reply_channel, project_id)
@@ -2,6 +2,7 @@
2
2
 
3
3
  import logging
4
4
  from typing import Dict, Type, Any, Optional, List, TYPE_CHECKING
5
+ from portacode.utils.ntp_clock import ntp_clock
5
6
 
6
7
  if TYPE_CHECKING:
7
8
  from ..multiplex import Channel
@@ -72,22 +73,32 @@ class CommandRegistry:
72
73
 
73
74
  async def dispatch(self, command_name: str, message: Dict[str, Any], reply_channel: Optional[str] = None) -> bool:
74
75
  """Dispatch a command to its handler.
75
-
76
+
76
77
  Args:
77
78
  command_name: The command name
78
79
  message: The command message
79
80
  reply_channel: Optional reply channel
80
-
81
+
81
82
  Returns:
82
83
  True if handler was found and executed, False otherwise
83
84
  """
84
85
  logger.info("registry: Dispatching command '%s' with reply_channel=%s", command_name, reply_channel)
85
-
86
+
87
+ # Add device_receive timestamp if trace present
88
+ if "trace" in message and "request_id" in message:
89
+ device_receive_time = ntp_clock.now_ms()
90
+ if device_receive_time is not None:
91
+ message["trace"]["device_receive"] = device_receive_time
92
+ # Update ping to show total time from client_send
93
+ if "client_send" in message["trace"]:
94
+ message["trace"]["ping"] = device_receive_time - message["trace"]["client_send"]
95
+ logger.info(f"📨 Device received traced message: {message['request_id']}")
96
+
86
97
  handler = self.get_handler(command_name)
87
98
  if handler is None:
88
99
  logger.warning("registry: No handler found for command: %s", command_name)
89
100
  return False
90
-
101
+
91
102
  try:
92
103
  await handler.handle(message, reply_channel)
93
104
  logger.info("registry: Successfully dispatched command '%s'", command_name)
@@ -42,6 +42,8 @@ class NTPClock {
42
42
  this._syncAttempts++;
43
43
 
44
44
  try {
45
+ // Capture local time BEFORE the fetch to avoid timing drift
46
+ const localTimeBeforeFetch = Date.now();
45
47
  const t0 = performance.now();
46
48
  const response = await fetch('https://cloudflare.com/cdn-cgi/trace');
47
49
  const t1 = performance.now();
@@ -49,11 +51,11 @@ class NTPClock {
49
51
  const text = await response.text();
50
52
  const serverTime = this._parseCloudflareTime(text);
51
53
 
52
- const localTime = Date.now();
53
54
  const latency = (t1 - t0) / 2; // Estimate one-way latency
54
55
 
55
- // Calculate offset accounting for latency
56
- this.offset = serverTime - localTime + latency;
56
+ // Calculate offset: server generated timestamp at local time (localTimeBeforeFetch + latency)
57
+ // So offset = serverTime - (localTimeBeforeFetch + latency)
58
+ this.offset = serverTime - (localTimeBeforeFetch + latency);
57
59
  this.lastSync = Date.now();
58
60
 
59
61
  console.log(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.3.27.dev0
3
+ Version: 1.3.27.dev1
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
File without changes
File without changes