netra-zen 1.2.0__py3-none-any.whl → 1.2.1__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.
- {netra_zen-1.2.0.dist-info → netra_zen-1.2.1.dist-info}/METADATA +992 -992
- netra_zen-1.2.1.dist-info/RECORD +36 -0
- {netra_zen-1.2.0.dist-info → netra_zen-1.2.1.dist-info}/licenses/LICENSE.md +1 -1
- {netra_zen-1.2.0.dist-info → netra_zen-1.2.1.dist-info}/top_level.txt +1 -0
- scripts/__init__.py +1 -1
- scripts/__main__.py +5 -5
- scripts/agent_cli.py +7334 -7334
- scripts/agent_logs.py +327 -327
- scripts/demo_log_collection.py +146 -146
- scripts/embed_release_credentials.py +75 -75
- scripts/test_apex_telemetry_debug.py +221 -221
- scripts/verify_log_transmission.py +140 -140
- shared/README.md +47 -0
- shared/TIMING_FIX_COMPLETE.md +80 -0
- shared/__init__.py +12 -0
- shared/types/__init__.py +21 -0
- shared/types/websocket_closure_codes.py +124 -0
- shared/windows_encoding.py +45 -0
- zen/__init__.py +7 -7
- zen/__main__.py +11 -11
- zen/telemetry/__init__.py +14 -14
- zen/telemetry/apex_telemetry.py +259 -259
- zen/telemetry/embedded_credentials.py +60 -59
- zen/telemetry/manager.py +249 -249
- zen_orchestrator.py +3058 -3058
- netra_zen-1.2.0.dist-info/RECORD +0 -30
- {netra_zen-1.2.0.dist-info → netra_zen-1.2.1.dist-info}/WHEEL +0 -0
- {netra_zen-1.2.0.dist-info → netra_zen-1.2.1.dist-info}/entry_points.txt +0 -0
@@ -1,140 +1,140 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Verification script to prove JSONL logs are bundled in payload
|
4
|
-
"""
|
5
|
-
|
6
|
-
import json
|
7
|
-
import sys
|
8
|
-
from pathlib import Path
|
9
|
-
|
10
|
-
# Add parent directory to path
|
11
|
-
sys.path.insert(0, str(Path(__file__).parent.parent))
|
12
|
-
|
13
|
-
from scripts.agent_logs import collect_recent_logs
|
14
|
-
|
15
|
-
|
16
|
-
def verify_log_bundling(log_path: str):
|
17
|
-
"""
|
18
|
-
Verify that logs are properly collected and bundled
|
19
|
-
|
20
|
-
Args:
|
21
|
-
log_path: Path to JSONL file or directory
|
22
|
-
"""
|
23
|
-
print("=" * 70)
|
24
|
-
print("JSONL LOG TRANSMISSION VERIFICATION")
|
25
|
-
print("=" * 70)
|
26
|
-
print()
|
27
|
-
|
28
|
-
# Step 1: Collect logs
|
29
|
-
print("Step 1: Collecting logs from file...")
|
30
|
-
result = collect_recent_logs(limit=1, base_path=log_path)
|
31
|
-
|
32
|
-
if not result:
|
33
|
-
print("❌ FAILED: No logs collected")
|
34
|
-
return False
|
35
|
-
|
36
|
-
logs, files_read, file_info = result
|
37
|
-
print(f"✓ Successfully collected {len(logs)} log entries from {files_read} file(s)")
|
38
|
-
print()
|
39
|
-
|
40
|
-
# Step 2: Show file details
|
41
|
-
print("Step 2: File details...")
|
42
|
-
for info in file_info:
|
43
|
-
print(f" File: {info['name']}")
|
44
|
-
print(f" Hash: {info['hash']}")
|
45
|
-
print(f" Entries: {info['entries']}")
|
46
|
-
print()
|
47
|
-
|
48
|
-
# Step 3: Simulate payload creation
|
49
|
-
print("Step 3: Simulating WebSocket payload creation...")
|
50
|
-
payload = {
|
51
|
-
"type": "message_create",
|
52
|
-
"run_id": "test-run-id",
|
53
|
-
"payload": {
|
54
|
-
"message": "Test message with logs",
|
55
|
-
"jsonl_logs": logs # This is where logs are added
|
56
|
-
}
|
57
|
-
}
|
58
|
-
|
59
|
-
print(f"✓ Payload created with 'jsonl_logs' key")
|
60
|
-
print(f" Payload keys: {list(payload['payload'].keys())}")
|
61
|
-
print()
|
62
|
-
|
63
|
-
# Step 4: Verify payload size
|
64
|
-
print("Step 4: Calculating payload size...")
|
65
|
-
payload_json = json.dumps(payload)
|
66
|
-
payload_size_bytes = len(payload_json.encode('utf-8'))
|
67
|
-
payload_size_kb = payload_size_bytes / 1024
|
68
|
-
payload_size_mb = payload_size_kb / 1024
|
69
|
-
|
70
|
-
if payload_size_mb >= 1:
|
71
|
-
size_str = f"{payload_size_mb:.2f} MB"
|
72
|
-
elif payload_size_kb >= 1:
|
73
|
-
size_str = f"{payload_size_kb:.2f} KB"
|
74
|
-
else:
|
75
|
-
size_str = f"{payload_size_bytes} bytes"
|
76
|
-
|
77
|
-
print(f"✓ Total payload size: {size_str}")
|
78
|
-
print()
|
79
|
-
|
80
|
-
# Step 5: Show sample log entries
|
81
|
-
print("Step 5: Sample log entries in payload...")
|
82
|
-
if logs:
|
83
|
-
print(f" First entry keys: {list(logs[0].keys())}")
|
84
|
-
print(f" First entry timestamp: {logs[0].get('timestamp', 'N/A')}")
|
85
|
-
print(f" Last entry timestamp: {logs[-1].get('timestamp', 'N/A')}")
|
86
|
-
print()
|
87
|
-
|
88
|
-
# Step 6: Verify transmission-ready
|
89
|
-
print("Step 6: Transmission verification...")
|
90
|
-
print(f"✓ Payload is valid JSON: {payload_json is not None}")
|
91
|
-
print(f"✓ Payload contains 'jsonl_logs': {'jsonl_logs' in payload['payload']}")
|
92
|
-
print(f"✓ Log count in payload: {len(payload['payload']['jsonl_logs'])}")
|
93
|
-
print()
|
94
|
-
|
95
|
-
print("=" * 70)
|
96
|
-
print("✅ VERIFICATION COMPLETE")
|
97
|
-
print("=" * 70)
|
98
|
-
print()
|
99
|
-
print("PROOF OF TRANSMISSION:")
|
100
|
-
print(f" • {len(logs)} JSONL log entries are bundled in the payload")
|
101
|
-
print(f" • Payload size: {size_str}")
|
102
|
-
print(f" • Ready for WebSocket transmission to backend")
|
103
|
-
print()
|
104
|
-
|
105
|
-
# Optional: Save proof file
|
106
|
-
proof_file = Path("/tmp/zen_transmission_proof.json")
|
107
|
-
proof_payload = {
|
108
|
-
"verification_timestamp": "verification_run",
|
109
|
-
"log_count": len(logs),
|
110
|
-
"files_read": files_read,
|
111
|
-
"file_info": file_info,
|
112
|
-
"payload_size": size_str,
|
113
|
-
"sample_first_entry": logs[0] if logs else None,
|
114
|
-
"sample_last_entry": logs[-1] if logs else None,
|
115
|
-
"payload_structure": {
|
116
|
-
"type": payload["type"],
|
117
|
-
"run_id": payload["run_id"],
|
118
|
-
"payload_keys": list(payload["payload"].keys()),
|
119
|
-
"jsonl_logs_present": "jsonl_logs" in payload["payload"],
|
120
|
-
"jsonl_logs_count": len(payload["payload"]["jsonl_logs"])
|
121
|
-
}
|
122
|
-
}
|
123
|
-
|
124
|
-
with open(proof_file, 'w') as f:
|
125
|
-
json.dump(proof_payload, f, indent=2)
|
126
|
-
|
127
|
-
print(f"📝 Detailed proof saved to: {proof_file}")
|
128
|
-
print()
|
129
|
-
|
130
|
-
return True
|
131
|
-
|
132
|
-
|
133
|
-
if __name__ == "__main__":
|
134
|
-
if len(sys.argv) < 2:
|
135
|
-
print("Usage: python verify_log_transmission.py <path-to-jsonl-file>")
|
136
|
-
sys.exit(1)
|
137
|
-
|
138
|
-
log_path = sys.argv[1]
|
139
|
-
success = verify_log_bundling(log_path)
|
140
|
-
sys.exit(0 if success else 1)
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Verification script to prove JSONL logs are bundled in payload
|
4
|
+
"""
|
5
|
+
|
6
|
+
import json
|
7
|
+
import sys
|
8
|
+
from pathlib import Path
|
9
|
+
|
10
|
+
# Add parent directory to path
|
11
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
12
|
+
|
13
|
+
from scripts.agent_logs import collect_recent_logs
|
14
|
+
|
15
|
+
|
16
|
+
def verify_log_bundling(log_path: str):
|
17
|
+
"""
|
18
|
+
Verify that logs are properly collected and bundled
|
19
|
+
|
20
|
+
Args:
|
21
|
+
log_path: Path to JSONL file or directory
|
22
|
+
"""
|
23
|
+
print("=" * 70)
|
24
|
+
print("JSONL LOG TRANSMISSION VERIFICATION")
|
25
|
+
print("=" * 70)
|
26
|
+
print()
|
27
|
+
|
28
|
+
# Step 1: Collect logs
|
29
|
+
print("Step 1: Collecting logs from file...")
|
30
|
+
result = collect_recent_logs(limit=1, base_path=log_path)
|
31
|
+
|
32
|
+
if not result:
|
33
|
+
print("❌ FAILED: No logs collected")
|
34
|
+
return False
|
35
|
+
|
36
|
+
logs, files_read, file_info = result
|
37
|
+
print(f"✓ Successfully collected {len(logs)} log entries from {files_read} file(s)")
|
38
|
+
print()
|
39
|
+
|
40
|
+
# Step 2: Show file details
|
41
|
+
print("Step 2: File details...")
|
42
|
+
for info in file_info:
|
43
|
+
print(f" File: {info['name']}")
|
44
|
+
print(f" Hash: {info['hash']}")
|
45
|
+
print(f" Entries: {info['entries']}")
|
46
|
+
print()
|
47
|
+
|
48
|
+
# Step 3: Simulate payload creation
|
49
|
+
print("Step 3: Simulating WebSocket payload creation...")
|
50
|
+
payload = {
|
51
|
+
"type": "message_create",
|
52
|
+
"run_id": "test-run-id",
|
53
|
+
"payload": {
|
54
|
+
"message": "Test message with logs",
|
55
|
+
"jsonl_logs": logs # This is where logs are added
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
print(f"✓ Payload created with 'jsonl_logs' key")
|
60
|
+
print(f" Payload keys: {list(payload['payload'].keys())}")
|
61
|
+
print()
|
62
|
+
|
63
|
+
# Step 4: Verify payload size
|
64
|
+
print("Step 4: Calculating payload size...")
|
65
|
+
payload_json = json.dumps(payload)
|
66
|
+
payload_size_bytes = len(payload_json.encode('utf-8'))
|
67
|
+
payload_size_kb = payload_size_bytes / 1024
|
68
|
+
payload_size_mb = payload_size_kb / 1024
|
69
|
+
|
70
|
+
if payload_size_mb >= 1:
|
71
|
+
size_str = f"{payload_size_mb:.2f} MB"
|
72
|
+
elif payload_size_kb >= 1:
|
73
|
+
size_str = f"{payload_size_kb:.2f} KB"
|
74
|
+
else:
|
75
|
+
size_str = f"{payload_size_bytes} bytes"
|
76
|
+
|
77
|
+
print(f"✓ Total payload size: {size_str}")
|
78
|
+
print()
|
79
|
+
|
80
|
+
# Step 5: Show sample log entries
|
81
|
+
print("Step 5: Sample log entries in payload...")
|
82
|
+
if logs:
|
83
|
+
print(f" First entry keys: {list(logs[0].keys())}")
|
84
|
+
print(f" First entry timestamp: {logs[0].get('timestamp', 'N/A')}")
|
85
|
+
print(f" Last entry timestamp: {logs[-1].get('timestamp', 'N/A')}")
|
86
|
+
print()
|
87
|
+
|
88
|
+
# Step 6: Verify transmission-ready
|
89
|
+
print("Step 6: Transmission verification...")
|
90
|
+
print(f"✓ Payload is valid JSON: {payload_json is not None}")
|
91
|
+
print(f"✓ Payload contains 'jsonl_logs': {'jsonl_logs' in payload['payload']}")
|
92
|
+
print(f"✓ Log count in payload: {len(payload['payload']['jsonl_logs'])}")
|
93
|
+
print()
|
94
|
+
|
95
|
+
print("=" * 70)
|
96
|
+
print("✅ VERIFICATION COMPLETE")
|
97
|
+
print("=" * 70)
|
98
|
+
print()
|
99
|
+
print("PROOF OF TRANSMISSION:")
|
100
|
+
print(f" • {len(logs)} JSONL log entries are bundled in the payload")
|
101
|
+
print(f" • Payload size: {size_str}")
|
102
|
+
print(f" • Ready for WebSocket transmission to backend")
|
103
|
+
print()
|
104
|
+
|
105
|
+
# Optional: Save proof file
|
106
|
+
proof_file = Path("/tmp/zen_transmission_proof.json")
|
107
|
+
proof_payload = {
|
108
|
+
"verification_timestamp": "verification_run",
|
109
|
+
"log_count": len(logs),
|
110
|
+
"files_read": files_read,
|
111
|
+
"file_info": file_info,
|
112
|
+
"payload_size": size_str,
|
113
|
+
"sample_first_entry": logs[0] if logs else None,
|
114
|
+
"sample_last_entry": logs[-1] if logs else None,
|
115
|
+
"payload_structure": {
|
116
|
+
"type": payload["type"],
|
117
|
+
"run_id": payload["run_id"],
|
118
|
+
"payload_keys": list(payload["payload"].keys()),
|
119
|
+
"jsonl_logs_present": "jsonl_logs" in payload["payload"],
|
120
|
+
"jsonl_logs_count": len(payload["payload"]["jsonl_logs"])
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
with open(proof_file, 'w') as f:
|
125
|
+
json.dump(proof_payload, f, indent=2)
|
126
|
+
|
127
|
+
print(f"📝 Detailed proof saved to: {proof_file}")
|
128
|
+
print()
|
129
|
+
|
130
|
+
return True
|
131
|
+
|
132
|
+
|
133
|
+
if __name__ == "__main__":
|
134
|
+
if len(sys.argv) < 2:
|
135
|
+
print("Usage: python verify_log_transmission.py <path-to-jsonl-file>")
|
136
|
+
sys.exit(1)
|
137
|
+
|
138
|
+
log_path = sys.argv[1]
|
139
|
+
success = verify_log_bundling(log_path)
|
140
|
+
sys.exit(0 if success else 1)
|
shared/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Shared Utilities (Vendored Subset)
|
2
|
+
|
3
|
+
This directory contains a **minimal vendored subset** of the Apex `shared/` package, containing only the files required by `zen --apex` CLI functionality.
|
4
|
+
|
5
|
+
## Included Files
|
6
|
+
|
7
|
+
### 1. `__init__.py`
|
8
|
+
Package initialization stub with basic documentation.
|
9
|
+
|
10
|
+
### 2. `windows_encoding.py`
|
11
|
+
Windows UTF-8 console encoding fixes. The `setup_windows_encoding()` function is called early in `agent_cli.py` startup to ensure proper Unicode handling on Windows platforms.
|
12
|
+
|
13
|
+
### 3. `types/__init__.py`
|
14
|
+
Type definitions package that re-exports WebSocket closure code utilities.
|
15
|
+
|
16
|
+
### 4. `types/websocket_closure_codes.py`
|
17
|
+
WebSocket closure code validation utilities:
|
18
|
+
- `WebSocketClosureCode`: Enum of standard RFC 6455 closure codes
|
19
|
+
- `WebSocketClosureCategory`: Categories for classifying closure types
|
20
|
+
- `categorize_closure_code()`: Categorize a code into normal/client/server/infrastructure
|
21
|
+
- `get_closure_description()`: Human-readable description of closure codes
|
22
|
+
- `is_infrastructure_error()`: Check if a code represents infrastructure failure
|
23
|
+
|
24
|
+
## Maintenance
|
25
|
+
|
26
|
+
⚠️ **Important**: These files are vendored from the Apex repository. If Apex updates its closure-code definitions or Windows encoding logic, these files must be manually synchronized.
|
27
|
+
|
28
|
+
### What's NOT Included
|
29
|
+
|
30
|
+
Everything else under Apex's `shared/` directory is intentionally excluded because it's not referenced by `agent_cli.py`. This keeps the vendored code minimal and avoids pulling in backend logic, secrets, or unnecessary dependencies.
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
These modules are imported by `scripts/agent_cli.py`:
|
35
|
+
|
36
|
+
```python
|
37
|
+
from shared.windows_encoding import setup_windows_encoding
|
38
|
+
from shared.types.websocket_closure_codes import (
|
39
|
+
WebSocketClosureCode,
|
40
|
+
WebSocketClosureCategory,
|
41
|
+
categorize_closure_code,
|
42
|
+
get_closure_description,
|
43
|
+
is_infrastructure_error
|
44
|
+
)
|
45
|
+
```
|
46
|
+
|
47
|
+
The `setup_windows_encoding()` function is called immediately at startup, before any console I/O operations.
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# WebSocket Timing Fix - Complete Implementation
|
2
|
+
|
3
|
+
## Problem Statement
|
4
|
+
The CLI was sending messages too early, before the server completed its lifecycle phases and entered the message processing loop (Phase 5). Messages sent before Phase 5 were lost because the server's message handler wasn't active yet.
|
5
|
+
|
6
|
+
## Root Cause
|
7
|
+
The CLI code had backward compatibility logic that allowed connections to proceed even when the handshake failed, causing messages to be sent before the server was ready.
|
8
|
+
|
9
|
+
## Complete Fix Implementation
|
10
|
+
|
11
|
+
### 1. Connection Method (lines 2879-2928)
|
12
|
+
**REMOVED:** Backward compatibility code that returned `True` even when handshake failed
|
13
|
+
**ADDED:**
|
14
|
+
- Proper failure when handshake doesn't complete
|
15
|
+
- Retry mechanism with 3-second delay
|
16
|
+
- Second handshake attempt after delay
|
17
|
+
- WebSocket closure on failure
|
18
|
+
|
19
|
+
### 2. Handshake Response Processing (lines 3307-3363)
|
20
|
+
**CHANGED:** Message type from `session_acknowledged` to `handshake_acknowledged`
|
21
|
+
**ADDED:**
|
22
|
+
- Wait for `handshake_complete` confirmation
|
23
|
+
- 500ms delay after handshake_complete for server to enter Phase 5
|
24
|
+
- Fallback delay if no handshake_complete received
|
25
|
+
|
26
|
+
### 3. Message Sending Validation (lines 3703-3722)
|
27
|
+
**ADDED:** Check for `self.connected` flag before sending messages
|
28
|
+
- Ensures handshake is fully complete
|
29
|
+
- Prevents messages being sent during server initialization
|
30
|
+
- Clear error messages when connection not ready
|
31
|
+
|
32
|
+
## Order of Operations (Enforced)
|
33
|
+
|
34
|
+
1. **Connect** → WebSocket connection established
|
35
|
+
2. **Wait** for `connection_established` event
|
36
|
+
3. **Handshake** → Wait for `handshake_response`
|
37
|
+
4. **Acknowledge** → Send `handshake_acknowledged` with thread_id
|
38
|
+
5. **Confirm** → Wait for `handshake_complete` message
|
39
|
+
6. **Delay** → Add 500ms for server to enter Phase 5 (Processing)
|
40
|
+
7. **Ready** → NOW messages can be safely sent
|
41
|
+
|
42
|
+
## Server Phases Reference
|
43
|
+
|
44
|
+
```
|
45
|
+
Phase 1: INITIALIZING → Accept connection, assign ID
|
46
|
+
Phase 2: AUTHENTICATING → Validate user credentials
|
47
|
+
Phase 3: HANDSHAKING → Exchange thread IDs
|
48
|
+
Phase 4: READY → Initialize services, register with router
|
49
|
+
Phase 5: PROCESSING → Message loop active ✓ (Messages accepted here!)
|
50
|
+
Phase 6: CLEANING_UP → Coordinated cleanup
|
51
|
+
Phase 7: CLOSED → Terminal state
|
52
|
+
```
|
53
|
+
|
54
|
+
## Key Benefits
|
55
|
+
|
56
|
+
✓ **No Message Loss** - Messages only sent when server is ready to process them
|
57
|
+
✓ **Proper Sequencing** - Enforces documented WebSocket lifecycle
|
58
|
+
✓ **Retry Logic** - Handles transient delays during server startup
|
59
|
+
✓ **Clear Errors** - Descriptive messages when connection fails
|
60
|
+
✓ **Fail Fast** - Connection fails quickly if server isn't ready
|
61
|
+
|
62
|
+
## Testing Verification
|
63
|
+
|
64
|
+
The fix ensures:
|
65
|
+
- `self.connected` is only set to `True` after full handshake completion
|
66
|
+
- `send_message()` validates both `self.connected` and `self.current_thread_id`
|
67
|
+
- Messages cannot be sent until server reaches Phase 5 (Processing)
|
68
|
+
- Connection properly fails if server doesn't complete handshake
|
69
|
+
|
70
|
+
## Files Modified
|
71
|
+
|
72
|
+
- `scripts/agent_cli.py`:
|
73
|
+
- `connect()` method - lines 2879-2928
|
74
|
+
- `_perform_handshake()` method - lines 3057-3058
|
75
|
+
- `_process_handshake_response()` method - lines 3307-3363
|
76
|
+
- `send_message()` method - lines 3703-3722
|
77
|
+
|
78
|
+
## Implementation Complete
|
79
|
+
|
80
|
+
The timing issue is now fully resolved. The CLI will properly wait for the server to complete all initialization phases before sending any messages, preventing message loss and ensuring reliable communication.
|
shared/__init__.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
Shared utilities package (minimal vendored subset for zen --apex CLI).
|
3
|
+
|
4
|
+
This package contains only the minimal files required by agent_cli.py:
|
5
|
+
- windows_encoding.py: Windows UTF-8 console fixes
|
6
|
+
- types/websocket_closure_codes.py: WebSocket closure code validation
|
7
|
+
|
8
|
+
TODO: Manually sync these files if Apex updates its closure-code definitions
|
9
|
+
or Windows handling logic.
|
10
|
+
"""
|
11
|
+
|
12
|
+
__version__ = "1.0.0"
|
shared/types/__init__.py
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
"""
|
2
|
+
Shared type definitions (minimal vendored subset for zen --apex CLI).
|
3
|
+
|
4
|
+
This package provides WebSocket closure code utilities required by agent_cli.py.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from shared.types.websocket_closure_codes import (
|
8
|
+
WebSocketClosureCode,
|
9
|
+
WebSocketClosureCategory,
|
10
|
+
categorize_closure_code,
|
11
|
+
get_closure_description,
|
12
|
+
is_infrastructure_error
|
13
|
+
)
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
'WebSocketClosureCode',
|
17
|
+
'WebSocketClosureCategory',
|
18
|
+
'categorize_closure_code',
|
19
|
+
'get_closure_description',
|
20
|
+
'is_infrastructure_error'
|
21
|
+
]
|
@@ -0,0 +1,124 @@
|
|
1
|
+
"""
|
2
|
+
WebSocket closure code definitions and categorization.
|
3
|
+
|
4
|
+
Provides enums and helper functions for validating and categorizing WebSocket
|
5
|
+
closure codes according to RFC 6455 and common extensions.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from enum import IntEnum
|
9
|
+
from typing import Optional
|
10
|
+
|
11
|
+
|
12
|
+
class WebSocketClosureCode(IntEnum):
|
13
|
+
"""
|
14
|
+
Standard WebSocket closure codes from RFC 6455 and extensions.
|
15
|
+
|
16
|
+
See: https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1
|
17
|
+
"""
|
18
|
+
# Standard codes (1000-1015)
|
19
|
+
NORMAL_CLOSURE = 1000
|
20
|
+
GOING_AWAY = 1001
|
21
|
+
PROTOCOL_ERROR = 1002
|
22
|
+
UNSUPPORTED_DATA = 1003
|
23
|
+
# 1004 is reserved
|
24
|
+
NO_STATUS_RECEIVED = 1005
|
25
|
+
ABNORMAL_CLOSURE = 1006
|
26
|
+
INVALID_FRAME_PAYLOAD_DATA = 1007
|
27
|
+
POLICY_VIOLATION = 1008
|
28
|
+
MESSAGE_TOO_BIG = 1009
|
29
|
+
MANDATORY_EXTENSION = 1010
|
30
|
+
INTERNAL_SERVER_ERROR = 1011
|
31
|
+
SERVICE_RESTART = 1012
|
32
|
+
TRY_AGAIN_LATER = 1013
|
33
|
+
BAD_GATEWAY = 1014
|
34
|
+
TLS_HANDSHAKE_FAILURE = 1015
|
35
|
+
|
36
|
+
# Custom application codes (4000-4999)
|
37
|
+
# These are application-specific and can be defined as needed
|
38
|
+
|
39
|
+
|
40
|
+
class WebSocketClosureCategory(IntEnum):
|
41
|
+
"""
|
42
|
+
Categories for WebSocket closure codes to classify failure types.
|
43
|
+
"""
|
44
|
+
NORMAL = 0 # Expected closures (1000, 1001)
|
45
|
+
CLIENT_ERROR = 1 # Client-side errors (1002-1003, 1007-1010)
|
46
|
+
SERVER_ERROR = 2 # Server-side errors (1011-1014)
|
47
|
+
INFRASTRUCTURE = 3 # Infrastructure/network errors (1006, 1015)
|
48
|
+
UNKNOWN = 4 # Unrecognized codes
|
49
|
+
|
50
|
+
|
51
|
+
def categorize_closure_code(code: int) -> WebSocketClosureCategory:
|
52
|
+
"""
|
53
|
+
Categorize a WebSocket closure code into a failure category.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
code: The WebSocket closure code
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
The category of the closure code
|
60
|
+
"""
|
61
|
+
# Normal closures
|
62
|
+
if code in (1000, 1001):
|
63
|
+
return WebSocketClosureCategory.NORMAL
|
64
|
+
|
65
|
+
# Infrastructure/network errors
|
66
|
+
if code in (1006, 1015):
|
67
|
+
return WebSocketClosureCategory.INFRASTRUCTURE
|
68
|
+
|
69
|
+
# Server errors
|
70
|
+
if code in (1011, 1012, 1013, 1014):
|
71
|
+
return WebSocketClosureCategory.SERVER_ERROR
|
72
|
+
|
73
|
+
# Client errors
|
74
|
+
if code in (1002, 1003, 1007, 1008, 1009, 1010):
|
75
|
+
return WebSocketClosureCategory.CLIENT_ERROR
|
76
|
+
|
77
|
+
# Unknown/unrecognized codes
|
78
|
+
return WebSocketClosureCategory.UNKNOWN
|
79
|
+
|
80
|
+
|
81
|
+
def is_infrastructure_error(code: int) -> bool:
|
82
|
+
"""
|
83
|
+
Check if a closure code represents an infrastructure/network error.
|
84
|
+
|
85
|
+
Infrastructure errors are typically transient and may be retryable.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
code: The WebSocket closure code
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
True if the code represents an infrastructure error
|
92
|
+
"""
|
93
|
+
return categorize_closure_code(code) == WebSocketClosureCategory.INFRASTRUCTURE
|
94
|
+
|
95
|
+
|
96
|
+
def get_closure_description(code: int) -> str:
|
97
|
+
"""
|
98
|
+
Get a human-readable description of a WebSocket closure code.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
code: The WebSocket closure code
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
A description of what the closure code means
|
105
|
+
"""
|
106
|
+
descriptions = {
|
107
|
+
1000: "Normal closure - connection completed successfully",
|
108
|
+
1001: "Going away - endpoint is going away (e.g., server shutdown, browser navigation)",
|
109
|
+
1002: "Protocol error - endpoint received a malformed message",
|
110
|
+
1003: "Unsupported data - endpoint received data of unsupported type",
|
111
|
+
1005: "No status received - no status code was provided (internal use only)",
|
112
|
+
1006: "Abnormal closure - connection closed without close frame (network/infrastructure issue)",
|
113
|
+
1007: "Invalid frame payload data - message contains invalid UTF-8 or violates payload requirements",
|
114
|
+
1008: "Policy violation - endpoint received message that violates its policy",
|
115
|
+
1009: "Message too big - endpoint received message that is too large to process",
|
116
|
+
1010: "Mandatory extension - client expected server to negotiate an extension",
|
117
|
+
1011: "Internal server error - server encountered unexpected condition",
|
118
|
+
1012: "Service restart - server is restarting",
|
119
|
+
1013: "Try again later - server is temporarily overloaded or under maintenance",
|
120
|
+
1014: "Bad gateway - server acting as gateway received invalid response",
|
121
|
+
1015: "TLS handshake failure - TLS handshake failed (internal use only)",
|
122
|
+
}
|
123
|
+
|
124
|
+
return descriptions.get(code, f"Unknown closure code: {code}")
|
@@ -0,0 +1,45 @@
|
|
1
|
+
"""
|
2
|
+
Windows UTF-8 console encoding fixes.
|
3
|
+
|
4
|
+
Ensures proper UTF-8 handling on Windows consoles by setting appropriate
|
5
|
+
encoding for stdin, stdout, and stderr streams.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import sys
|
9
|
+
import io
|
10
|
+
|
11
|
+
|
12
|
+
def setup_windows_encoding():
|
13
|
+
"""
|
14
|
+
Configure Windows console to use UTF-8 encoding.
|
15
|
+
|
16
|
+
This function reconfigures sys.stdin, sys.stdout, and sys.stderr to use
|
17
|
+
UTF-8 encoding with error handling on Windows platforms. This prevents
|
18
|
+
Unicode encoding errors when displaying special characters or emoji.
|
19
|
+
|
20
|
+
Must be called early in the startup sequence before any console I/O.
|
21
|
+
"""
|
22
|
+
if sys.platform == "win32":
|
23
|
+
# Reconfigure stdout and stderr to use UTF-8 with error handling
|
24
|
+
if sys.stdout is not None:
|
25
|
+
sys.stdout = io.TextIOWrapper(
|
26
|
+
sys.stdout.buffer,
|
27
|
+
encoding='utf-8',
|
28
|
+
errors='replace',
|
29
|
+
line_buffering=True
|
30
|
+
)
|
31
|
+
|
32
|
+
if sys.stderr is not None:
|
33
|
+
sys.stderr = io.TextIOWrapper(
|
34
|
+
sys.stderr.buffer,
|
35
|
+
encoding='utf-8',
|
36
|
+
errors='replace',
|
37
|
+
line_buffering=True
|
38
|
+
)
|
39
|
+
|
40
|
+
if sys.stdin is not None:
|
41
|
+
sys.stdin = io.TextIOWrapper(
|
42
|
+
sys.stdin.buffer,
|
43
|
+
encoding='utf-8',
|
44
|
+
errors='replace'
|
45
|
+
)
|
zen/__init__.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
"""Zen namespace package placeholder.
|
2
|
-
|
3
|
-
This lightweight module exists so repository scripts can import
|
4
|
-
`zen.telemetry` directly without requiring the full orchestrator package.
|
5
|
-
"""
|
6
|
-
|
7
|
-
__all__: list[str] = []
|
1
|
+
"""Zen namespace package placeholder.
|
2
|
+
|
3
|
+
This lightweight module exists so repository scripts can import
|
4
|
+
`zen.telemetry` directly without requiring the full orchestrator package.
|
5
|
+
"""
|
6
|
+
|
7
|
+
__all__: list[str] = []
|
zen/__main__.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
"""Module entry point to support `python -m zen` invocation."""
|
2
|
-
|
3
|
-
from zen_orchestrator import run
|
4
|
-
|
5
|
-
|
6
|
-
def main() -> None:
|
7
|
-
run()
|
8
|
-
|
9
|
-
|
10
|
-
if __name__ == "__main__":
|
11
|
-
main()
|
1
|
+
"""Module entry point to support `python -m zen` invocation."""
|
2
|
+
|
3
|
+
from zen_orchestrator import run
|
4
|
+
|
5
|
+
|
6
|
+
def main() -> None:
|
7
|
+
run()
|
8
|
+
|
9
|
+
|
10
|
+
if __name__ == "__main__":
|
11
|
+
main()
|