empathy-framework 5.1.1__py3-none-any.whl → 5.3.0__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.
Files changed (106) hide show
  1. {empathy_framework-5.1.1.dist-info → empathy_framework-5.3.0.dist-info}/METADATA +79 -6
  2. {empathy_framework-5.1.1.dist-info → empathy_framework-5.3.0.dist-info}/RECORD +83 -64
  3. empathy_os/__init__.py +1 -1
  4. empathy_os/cache/hybrid.py +5 -1
  5. empathy_os/cli/commands/batch.py +8 -0
  6. empathy_os/cli/commands/profiling.py +4 -0
  7. empathy_os/cli/commands/workflow.py +8 -4
  8. empathy_os/cli_router.py +9 -0
  9. empathy_os/config.py +15 -2
  10. empathy_os/core_modules/__init__.py +15 -0
  11. empathy_os/dashboard/simple_server.py +62 -30
  12. empathy_os/mcp/__init__.py +10 -0
  13. empathy_os/mcp/server.py +506 -0
  14. empathy_os/memory/control_panel.py +1 -131
  15. empathy_os/memory/control_panel_support.py +145 -0
  16. empathy_os/memory/encryption.py +159 -0
  17. empathy_os/memory/long_term.py +46 -631
  18. empathy_os/memory/long_term_types.py +99 -0
  19. empathy_os/memory/mixins/__init__.py +25 -0
  20. empathy_os/memory/mixins/backend_init_mixin.py +249 -0
  21. empathy_os/memory/mixins/capabilities_mixin.py +208 -0
  22. empathy_os/memory/mixins/handoff_mixin.py +208 -0
  23. empathy_os/memory/mixins/lifecycle_mixin.py +49 -0
  24. empathy_os/memory/mixins/long_term_mixin.py +352 -0
  25. empathy_os/memory/mixins/promotion_mixin.py +109 -0
  26. empathy_os/memory/mixins/short_term_mixin.py +182 -0
  27. empathy_os/memory/short_term.py +61 -12
  28. empathy_os/memory/simple_storage.py +302 -0
  29. empathy_os/memory/storage_backend.py +167 -0
  30. empathy_os/memory/types.py +8 -3
  31. empathy_os/memory/unified.py +21 -1120
  32. empathy_os/meta_workflows/cli_commands/__init__.py +56 -0
  33. empathy_os/meta_workflows/cli_commands/agent_commands.py +321 -0
  34. empathy_os/meta_workflows/cli_commands/analytics_commands.py +442 -0
  35. empathy_os/meta_workflows/cli_commands/config_commands.py +232 -0
  36. empathy_os/meta_workflows/cli_commands/memory_commands.py +182 -0
  37. empathy_os/meta_workflows/cli_commands/template_commands.py +354 -0
  38. empathy_os/meta_workflows/cli_commands/workflow_commands.py +382 -0
  39. empathy_os/meta_workflows/cli_meta_workflows.py +52 -1802
  40. empathy_os/models/telemetry/__init__.py +71 -0
  41. empathy_os/models/telemetry/analytics.py +594 -0
  42. empathy_os/models/telemetry/backend.py +196 -0
  43. empathy_os/models/telemetry/data_models.py +431 -0
  44. empathy_os/models/telemetry/storage.py +489 -0
  45. empathy_os/orchestration/__init__.py +35 -0
  46. empathy_os/orchestration/execution_strategies.py +481 -0
  47. empathy_os/orchestration/meta_orchestrator.py +488 -1
  48. empathy_os/routing/workflow_registry.py +36 -0
  49. empathy_os/telemetry/agent_coordination.py +2 -3
  50. empathy_os/telemetry/agent_tracking.py +26 -7
  51. empathy_os/telemetry/approval_gates.py +18 -24
  52. empathy_os/telemetry/cli.py +19 -724
  53. empathy_os/telemetry/commands/__init__.py +14 -0
  54. empathy_os/telemetry/commands/dashboard_commands.py +696 -0
  55. empathy_os/telemetry/event_streaming.py +7 -3
  56. empathy_os/telemetry/feedback_loop.py +28 -15
  57. empathy_os/tools.py +183 -0
  58. empathy_os/workflows/__init__.py +5 -0
  59. empathy_os/workflows/autonomous_test_gen.py +860 -161
  60. empathy_os/workflows/base.py +6 -2
  61. empathy_os/workflows/code_review.py +4 -1
  62. empathy_os/workflows/document_gen/__init__.py +25 -0
  63. empathy_os/workflows/document_gen/config.py +30 -0
  64. empathy_os/workflows/document_gen/report_formatter.py +162 -0
  65. empathy_os/workflows/{document_gen.py → document_gen/workflow.py} +5 -184
  66. empathy_os/workflows/output.py +4 -1
  67. empathy_os/workflows/progress.py +8 -2
  68. empathy_os/workflows/security_audit.py +2 -2
  69. empathy_os/workflows/security_audit_phase3.py +7 -4
  70. empathy_os/workflows/seo_optimization.py +633 -0
  71. empathy_os/workflows/test_gen/__init__.py +52 -0
  72. empathy_os/workflows/test_gen/ast_analyzer.py +249 -0
  73. empathy_os/workflows/test_gen/config.py +88 -0
  74. empathy_os/workflows/test_gen/data_models.py +38 -0
  75. empathy_os/workflows/test_gen/report_formatter.py +289 -0
  76. empathy_os/workflows/test_gen/test_templates.py +381 -0
  77. empathy_os/workflows/test_gen/workflow.py +655 -0
  78. empathy_os/workflows/test_gen.py +42 -1905
  79. empathy_os/cli/parsers/cache 2.py +0 -65
  80. empathy_os/cli_router 2.py +0 -416
  81. empathy_os/dashboard/app 2.py +0 -512
  82. empathy_os/dashboard/simple_server 2.py +0 -403
  83. empathy_os/dashboard/standalone_server 2.py +0 -536
  84. empathy_os/memory/types 2.py +0 -441
  85. empathy_os/models/adaptive_routing 2.py +0 -437
  86. empathy_os/models/telemetry.py +0 -1660
  87. empathy_os/project_index/scanner_parallel 2.py +0 -291
  88. empathy_os/telemetry/agent_coordination 2.py +0 -478
  89. empathy_os/telemetry/agent_tracking 2.py +0 -350
  90. empathy_os/telemetry/approval_gates 2.py +0 -563
  91. empathy_os/telemetry/event_streaming 2.py +0 -405
  92. empathy_os/telemetry/feedback_loop 2.py +0 -557
  93. empathy_os/vscode_bridge 2.py +0 -173
  94. empathy_os/workflows/progressive/__init__ 2.py +0 -92
  95. empathy_os/workflows/progressive/cli 2.py +0 -242
  96. empathy_os/workflows/progressive/core 2.py +0 -488
  97. empathy_os/workflows/progressive/orchestrator 2.py +0 -701
  98. empathy_os/workflows/progressive/reports 2.py +0 -528
  99. empathy_os/workflows/progressive/telemetry 2.py +0 -280
  100. empathy_os/workflows/progressive/test_gen 2.py +0 -514
  101. empathy_os/workflows/progressive/workflow 2.py +0 -628
  102. {empathy_framework-5.1.1.dist-info → empathy_framework-5.3.0.dist-info}/WHEEL +0 -0
  103. {empathy_framework-5.1.1.dist-info → empathy_framework-5.3.0.dist-info}/entry_points.txt +0 -0
  104. {empathy_framework-5.1.1.dist-info → empathy_framework-5.3.0.dist-info}/licenses/LICENSE +0 -0
  105. {empathy_framework-5.1.1.dist-info → empathy_framework-5.3.0.dist-info}/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +0 -0
  106. {empathy_framework-5.1.1.dist-info → empathy_framework-5.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,145 @@
1
+ """Support classes for Memory Control Panel.
2
+
3
+ Rate limiting, authentication, and statistics tracking.
4
+
5
+ Copyright 2025 Smart AI Memory, LLC
6
+ Licensed under Fair Source 0.9
7
+ """
8
+
9
+ import hashlib
10
+ import os
11
+ import time
12
+ from collections import defaultdict
13
+ from dataclasses import dataclass
14
+
15
+ import structlog
16
+
17
+ logger = structlog.get_logger(__name__)
18
+
19
+
20
+ class RateLimiter:
21
+ """Simple in-memory rate limiter by IP address."""
22
+
23
+ def __init__(self, window_seconds: int = 60, max_requests: int = 100):
24
+ """Initialize rate limiter.
25
+
26
+ Args:
27
+ window_seconds: Time window in seconds
28
+ max_requests: Maximum requests allowed per window
29
+
30
+ Raises:
31
+ ValueError: If window_seconds or max_requests is invalid
32
+
33
+ """
34
+ if window_seconds < 1:
35
+ raise ValueError(f"window_seconds must be positive, got {window_seconds}")
36
+
37
+ if max_requests < 1:
38
+ raise ValueError(f"max_requests must be positive, got {max_requests}")
39
+
40
+ self.window_seconds = window_seconds
41
+ self.max_requests = max_requests
42
+ self._requests: dict[str, list[float]] = defaultdict(list)
43
+
44
+ def is_allowed(self, client_ip: str) -> bool:
45
+ """Check if request is allowed for this IP.
46
+
47
+ Args:
48
+ client_ip: The client IP address
49
+
50
+ Returns:
51
+ True if allowed, False if rate limited
52
+
53
+ """
54
+ now = time.time()
55
+ window_start = now - self.window_seconds
56
+
57
+ # Clean old entries
58
+ self._requests[client_ip] = [ts for ts in self._requests[client_ip] if ts > window_start]
59
+
60
+ # Check if over limit
61
+ if len(self._requests[client_ip]) >= self.max_requests:
62
+ logger.warning("rate_limit_exceeded", client_ip=client_ip)
63
+ return False
64
+
65
+ # Record this request
66
+ self._requests[client_ip].append(now)
67
+ return True
68
+
69
+ def get_remaining(self, client_ip: str) -> int:
70
+ """Get remaining requests for this IP."""
71
+ now = time.time()
72
+ window_start = now - self.window_seconds
73
+ recent = [ts for ts in self._requests[client_ip] if ts > window_start]
74
+ return max(0, self.max_requests - len(recent))
75
+
76
+
77
+ class APIKeyAuth:
78
+ """Simple API key authentication."""
79
+
80
+ def __init__(self, api_key: str | None = None):
81
+ """Initialize API key auth.
82
+
83
+ Args:
84
+ api_key: The API key to require. If None, reads from
85
+ EMPATHY_MEMORY_API_KEY env var. If still None, auth is disabled.
86
+
87
+ """
88
+ self.api_key = api_key or os.environ.get("EMPATHY_MEMORY_API_KEY")
89
+ self.enabled = bool(self.api_key)
90
+ self._key_hash: str | None = None
91
+ if self.enabled and self.api_key:
92
+ # Store hash of API key for comparison
93
+ self._key_hash = hashlib.sha256(self.api_key.encode()).hexdigest()
94
+ logger.info("api_key_auth_enabled")
95
+ else:
96
+ logger.info("api_key_auth_disabled", reason="no_key_configured")
97
+
98
+ def is_valid(self, provided_key: str | None) -> bool:
99
+ """Check if provided API key is valid.
100
+
101
+ Args:
102
+ provided_key: The key provided in the request
103
+
104
+ Returns:
105
+ True if valid or auth disabled, False otherwise
106
+
107
+ """
108
+ if not self.enabled:
109
+ return True
110
+
111
+ if not provided_key:
112
+ return False
113
+
114
+ # Constant-time comparison via hash
115
+ provided_hash = hashlib.sha256(provided_key.encode()).hexdigest()
116
+ return provided_hash == self._key_hash
117
+
118
+
119
+ @dataclass
120
+ class MemoryStats:
121
+ """Statistics for memory system."""
122
+
123
+ # Redis stats
124
+ redis_available: bool = False
125
+ redis_method: str = "none"
126
+ redis_keys_total: int = 0
127
+ redis_keys_working: int = 0
128
+ redis_keys_staged: int = 0
129
+ redis_memory_used: str = "0"
130
+
131
+ # Long-term stats
132
+ long_term_available: bool = False
133
+ patterns_total: int = 0
134
+ patterns_public: int = 0
135
+ patterns_internal: int = 0
136
+ patterns_sensitive: int = 0
137
+ patterns_encrypted: int = 0
138
+
139
+ # Performance stats
140
+ redis_ping_ms: float = 0.0
141
+ storage_bytes: int = 0
142
+ collection_time_ms: float = 0.0
143
+
144
+ # Timestamps
145
+ collected_at: str = ""
@@ -0,0 +1,159 @@
1
+ """Encryption manager for long-term memory system
2
+
3
+ Provides AES-256-GCM encryption/decryption for SENSITIVE patterns.
4
+ Extracted from long_term.py for better modularity and testability.
5
+
6
+ Key Features:
7
+ - AES-256-GCM authenticated encryption
8
+ - Master key management (environment variable, file, or generated)
9
+ - Base64-encoded output for safe storage
10
+ - Proper error handling and security logging
11
+
12
+ Copyright 2025 Smart AI Memory, LLC
13
+ Licensed under Fair Source 0.9
14
+ """
15
+
16
+ import base64
17
+ import binascii
18
+ import os
19
+ from pathlib import Path
20
+
21
+ import structlog
22
+
23
+ from .long_term_types import SecurityError
24
+
25
+ logger = structlog.get_logger(__name__)
26
+
27
+ # Check for cryptography library
28
+ try:
29
+ from cryptography.exceptions import InvalidTag
30
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
31
+
32
+ HAS_ENCRYPTION = True
33
+ except ImportError:
34
+ HAS_ENCRYPTION = False
35
+ logger.warning("cryptography library not available - encryption disabled")
36
+
37
+
38
+ class EncryptionManager:
39
+ """Manages encryption/decryption for SENSITIVE patterns.
40
+
41
+ Uses AES-256-GCM (Galois/Counter Mode) for authenticated encryption.
42
+ Keys are derived from a master key using HKDF.
43
+ """
44
+
45
+ def __init__(self, master_key: bytes | None = None):
46
+ """Initialize encryption manager.
47
+
48
+ Args:
49
+ master_key: 32-byte master key (or None to generate/load)
50
+
51
+ """
52
+ if not HAS_ENCRYPTION:
53
+ logger.warning("Encryption not available - install cryptography library")
54
+ self.enabled = False
55
+ return
56
+
57
+ self.enabled = True
58
+ self.master_key = master_key or self._load_or_generate_key()
59
+
60
+ def _load_or_generate_key(self) -> bytes:
61
+ """Load master key from environment or generate new one.
62
+
63
+ Production: Set EMPATHY_MASTER_KEY environment variable
64
+ Development: Generates ephemeral key (warning logged)
65
+ """
66
+ # Check environment variable first
67
+ if env_key := os.getenv("EMPATHY_MASTER_KEY"):
68
+ try:
69
+ return base64.b64decode(env_key)
70
+ except (binascii.Error, ValueError) as e:
71
+ logger.error("invalid_master_key_in_env", error=str(e))
72
+ raise ValueError("Invalid EMPATHY_MASTER_KEY format") from e
73
+
74
+ # Check key file
75
+ key_file = Path.home() / ".empathy" / "master.key"
76
+ if key_file.exists():
77
+ try:
78
+ return key_file.read_bytes()
79
+ except (OSError, PermissionError) as e:
80
+ logger.error("failed_to_load_key_file", error=str(e))
81
+
82
+ # Generate ephemeral key (NOT for production)
83
+ logger.warning(
84
+ "no_master_key_found",
85
+ message="Generating ephemeral encryption key - set EMPATHY_MASTER_KEY for production",
86
+ )
87
+ return AESGCM.generate_key(bit_length=256)
88
+
89
+ def encrypt(self, plaintext: str) -> str:
90
+ """Encrypt plaintext using AES-256-GCM.
91
+
92
+ Args:
93
+ plaintext: Content to encrypt
94
+
95
+ Returns:
96
+ Base64-encoded ciphertext with format: nonce||ciphertext||tag
97
+
98
+ Raises:
99
+ SecurityError: If encryption fails
100
+
101
+ """
102
+ if not self.enabled:
103
+ raise SecurityError("Encryption not available - install cryptography library")
104
+
105
+ try:
106
+ # Generate random 96-bit nonce (12 bytes)
107
+ nonce = os.urandom(12)
108
+
109
+ # Create AESGCM cipher
110
+ aesgcm = AESGCM(self.master_key)
111
+
112
+ # Encrypt and authenticate
113
+ ciphertext = aesgcm.encrypt(nonce, plaintext.encode("utf-8"), None)
114
+
115
+ # Combine nonce + ciphertext for storage
116
+ encrypted_data = nonce + ciphertext
117
+
118
+ # Return base64-encoded
119
+ return base64.b64encode(encrypted_data).decode("utf-8")
120
+
121
+ except (ValueError, TypeError, UnicodeEncodeError) as e:
122
+ logger.error("encryption_failed", error=str(e))
123
+ raise SecurityError(f"Encryption failed: {e}") from e
124
+
125
+ def decrypt(self, ciphertext_b64: str) -> str:
126
+ """Decrypt ciphertext using AES-256-GCM.
127
+
128
+ Args:
129
+ ciphertext_b64: Base64-encoded encrypted data
130
+
131
+ Returns:
132
+ Decrypted plaintext
133
+
134
+ Raises:
135
+ SecurityError: If decryption fails (invalid key, corrupted data, etc.)
136
+
137
+ """
138
+ if not self.enabled:
139
+ raise SecurityError("Encryption not available - install cryptography library")
140
+
141
+ try:
142
+ # Decode from base64
143
+ encrypted_data = base64.b64decode(ciphertext_b64)
144
+
145
+ # Extract nonce (first 12 bytes) and ciphertext (rest)
146
+ nonce = encrypted_data[:12]
147
+ ciphertext = encrypted_data[12:]
148
+
149
+ # Create AESGCM cipher
150
+ aesgcm = AESGCM(self.master_key)
151
+
152
+ # Decrypt and verify
153
+ plaintext_bytes = aesgcm.decrypt(nonce, ciphertext, None)
154
+
155
+ return plaintext_bytes.decode("utf-8")
156
+
157
+ except (ValueError, TypeError, UnicodeDecodeError, binascii.Error, InvalidTag) as e:
158
+ logger.error("decryption_failed", error=str(e))
159
+ raise SecurityError(f"Decryption failed: {e}") from e