empathy-framework 3.9.1__py3-none-any.whl → 3.9.3__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 (61) hide show
  1. {empathy_framework-3.9.1.dist-info → empathy_framework-3.9.3.dist-info}/METADATA +1 -1
  2. {empathy_framework-3.9.1.dist-info → empathy_framework-3.9.3.dist-info}/RECORD +58 -61
  3. empathy_healthcare_plugin/monitors/monitoring/__init__.py +9 -9
  4. empathy_llm_toolkit/agent_factory/__init__.py +6 -6
  5. empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +4 -1
  6. empathy_llm_toolkit/agent_factory/crews/health_check.py +36 -29
  7. empathy_llm_toolkit/agent_factory/framework.py +2 -1
  8. empathy_llm_toolkit/config/__init__.py +8 -8
  9. empathy_llm_toolkit/security/__init__.py +17 -17
  10. empathy_os/__init__.py +1 -1
  11. empathy_os/adaptive/__init__.py +3 -3
  12. empathy_os/cli.py +5 -8
  13. empathy_os/cli_unified.py +86 -2
  14. empathy_os/config.py +7 -4
  15. empathy_os/hot_reload/integration.py +2 -1
  16. empathy_os/hot_reload/watcher.py +8 -4
  17. empathy_os/hot_reload/websocket.py +2 -1
  18. empathy_os/memory/__init__.py +30 -30
  19. empathy_os/memory/control_panel.py +3 -1
  20. empathy_os/memory/long_term.py +3 -1
  21. empathy_os/models/__init__.py +48 -48
  22. empathy_os/monitoring/__init__.py +7 -7
  23. empathy_os/optimization/__init__.py +3 -3
  24. empathy_os/pattern_library.py +2 -7
  25. empathy_os/plugins/__init__.py +6 -6
  26. empathy_os/resilience/__init__.py +5 -5
  27. empathy_os/scaffolding/cli.py +1 -1
  28. empathy_os/telemetry/cli.py +56 -13
  29. empathy_os/telemetry/usage_tracker.py +2 -5
  30. empathy_os/test_generator/generator.py +1 -1
  31. empathy_os/tier_recommender.py +39 -79
  32. empathy_os/trust/__init__.py +7 -7
  33. empathy_os/validation/__init__.py +3 -3
  34. empathy_os/workflow_patterns/output.py +1 -1
  35. empathy_os/workflow_patterns/structural.py +4 -4
  36. empathy_os/workflows/base.py +5 -2
  37. empathy_os/workflows/code_review_pipeline.py +1 -5
  38. empathy_os/workflows/dependency_check.py +1 -5
  39. empathy_os/workflows/keyboard_shortcuts/__init__.py +5 -5
  40. empathy_os/workflows/tier_tracking.py +40 -30
  41. empathy_software_plugin/cli.py +1 -3
  42. empathy_software_plugin/wizards/advanced_debugging_wizard.py +9 -6
  43. empathy_software_plugin/wizards/code_review_wizard.py +1 -3
  44. empathy_software_plugin/wizards/debugging/__init__.py +4 -4
  45. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +1 -1
  46. empathy_software_plugin/wizards/debugging/config_loaders.py +6 -2
  47. empathy_software_plugin/wizards/debugging/language_patterns.py +4 -2
  48. empathy_software_plugin/wizards/debugging/linter_parsers.py +1 -1
  49. empathy_software_plugin/wizards/performance/profiler_parsers.py +7 -7
  50. empathy_software_plugin/wizards/security/__init__.py +6 -6
  51. empathy_software_plugin/wizards/security/vulnerability_scanner.py +1 -1
  52. empathy_software_plugin/wizards/security_analysis_wizard.py +2 -2
  53. empathy_software_plugin/wizards/testing/quality_analyzer.py +3 -9
  54. empathy_software_plugin/wizards/testing/test_suggester.py +1 -1
  55. empathy_os/.empathy/costs.json +0 -60
  56. empathy_os/.empathy/discovery_stats.json +0 -15
  57. empathy_os/.empathy/workflow_runs.json +0 -45
  58. {empathy_framework-3.9.1.dist-info → empathy_framework-3.9.3.dist-info}/WHEEL +0 -0
  59. {empathy_framework-3.9.1.dist-info → empathy_framework-3.9.3.dist-info}/entry_points.txt +0 -0
  60. {empathy_framework-3.9.1.dist-info → empathy_framework-3.9.3.dist-info}/licenses/LICENSE +0 -0
  61. {empathy_framework-3.9.1.dist-info → empathy_framework-3.9.3.dist-info}/top_level.txt +0 -0
empathy_os/cli_unified.py CHANGED
@@ -513,6 +513,85 @@ def workflow_recommend(
513
513
  )
514
514
 
515
515
 
516
+ # =============================================================================
517
+ # TELEMETRY SUBCOMMAND GROUP
518
+ # =============================================================================
519
+
520
+ telemetry_app = typer.Typer(help="View and manage local usage telemetry")
521
+ app.add_typer(telemetry_app, name="telemetry")
522
+
523
+
524
+ @telemetry_app.command("show")
525
+ def telemetry_show(
526
+ limit: int = typer.Option(20, "--limit", "-l", help="Number of entries to show"),
527
+ days: int | None = typer.Option(None, "--days", "-d", help="Only show last N days"),
528
+ ):
529
+ """Show recent LLM calls and usage stats."""
530
+ args = [sys.executable, "-m", "empathy_os.cli", "telemetry", "show", "--limit", str(limit)]
531
+ if days:
532
+ args.extend(["--days", str(days)])
533
+ subprocess.run(args, check=False)
534
+
535
+
536
+ @telemetry_app.command("savings")
537
+ def telemetry_savings(
538
+ days: int = typer.Option(30, "--days", "-d", help="Number of days to analyze"),
539
+ ):
540
+ """Calculate cost savings vs baseline (all PREMIUM)."""
541
+ subprocess.run(
542
+ [sys.executable, "-m", "empathy_os.cli", "telemetry", "savings", "--days", str(days)],
543
+ check=False,
544
+ )
545
+
546
+
547
+ @telemetry_app.command("compare")
548
+ def telemetry_compare(
549
+ period1: int = typer.Option(7, "--period1", "-p1", help="First period in days"),
550
+ period2: int = typer.Option(30, "--period2", "-p2", help="Second period in days"),
551
+ ):
552
+ """Compare usage across two time periods."""
553
+ subprocess.run(
554
+ [
555
+ sys.executable,
556
+ "-m",
557
+ "empathy_os.cli",
558
+ "telemetry",
559
+ "compare",
560
+ "--period1",
561
+ str(period1),
562
+ "--period2",
563
+ str(period2),
564
+ ],
565
+ check=False,
566
+ )
567
+
568
+
569
+ @telemetry_app.command("export")
570
+ def telemetry_export(
571
+ format_type: str = typer.Option("json", "--format", "-f", help="Export format (json, csv)"),
572
+ output: Path | None = typer.Option(None, "--output", "-o", help="Output file path"),
573
+ days: int | None = typer.Option(None, "--days", "-d", help="Only export last N days"),
574
+ ):
575
+ """Export telemetry data to JSON or CSV."""
576
+ args = [sys.executable, "-m", "empathy_os.cli", "telemetry", "export", "--format", format_type]
577
+ if output:
578
+ args.extend(["--output", str(output)])
579
+ if days:
580
+ args.extend(["--days", str(days)])
581
+ subprocess.run(args, check=False)
582
+
583
+
584
+ @telemetry_app.command("reset")
585
+ def telemetry_reset(
586
+ confirm: bool = typer.Option(False, "--confirm", help="Confirm deletion"),
587
+ ):
588
+ """Clear all telemetry data (use with caution)."""
589
+ args = [sys.executable, "-m", "empathy_os.cli", "telemetry", "reset"]
590
+ if confirm:
591
+ args.append("--confirm")
592
+ subprocess.run(args, check=False)
593
+
594
+
516
595
  # =============================================================================
517
596
  # TIER RECOMMENDATION SUBCOMMAND GROUP
518
597
  # =============================================================================
@@ -552,7 +631,7 @@ def tier_recommend(
552
631
  console.print(f" 💰 [bold]Expected Cost:[/bold] ${result.expected_cost:.3f}")
553
632
  console.print(f" 🔄 [bold]Expected Attempts:[/bold] {result.expected_attempts:.1f}")
554
633
  console.print()
555
- console.print(f" 📊 [bold]Reasoning:[/bold]")
634
+ console.print(" 📊 [bold]Reasoning:[/bold]")
556
635
  console.print(f" {result.reasoning}")
557
636
  console.print()
558
637
 
@@ -669,7 +748,12 @@ def cheatsheet():
669
748
  empathy wizard list Show available wizards
670
749
  empathy wizard run <name> Execute a wizard
671
750
  empathy wizard create <name> -d <domain> Create wizard (12x faster)
672
- empathy wizard list-patterns List available patterns""",
751
+ empathy wizard list-patterns List available patterns
752
+
753
+ [bold]Usage Telemetry[/bold]
754
+ empathy telemetry show View recent LLM calls & costs
755
+ empathy telemetry savings Calculate cost savings (tier routing)
756
+ empathy telemetry export Export usage data (JSON/CSV)""",
673
757
  title="[bold blue]Empathy Framework Cheatsheet[/bold blue]",
674
758
  ),
675
759
  )
empathy_os/config.py CHANGED
@@ -14,7 +14,10 @@ import json
14
14
  import os
15
15
  from dataclasses import asdict, dataclass, field
16
16
  from pathlib import Path
17
- from typing import Any
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ if TYPE_CHECKING:
20
+ from empathy_os.workflows.config import ModelConfig
18
21
 
19
22
  try:
20
23
  import yaml
@@ -23,8 +26,6 @@ try:
23
26
  except ImportError:
24
27
  YAML_AVAILABLE = False
25
28
 
26
- from empathy_os.workflows.config import ModelConfig
27
-
28
29
 
29
30
  def _validate_file_path(path: str, allowed_dir: str | None = None) -> Path:
30
31
  """Validate file path to prevent path traversal and arbitrary writes.
@@ -120,7 +121,7 @@ class EmpathyConfig:
120
121
  metadata: dict[str, Any] = field(default_factory=dict)
121
122
 
122
123
  # Model settings
123
- models: list[ModelConfig] = field(default_factory=list)
124
+ models: list["ModelConfig"] = field(default_factory=list)
124
125
  default_model: str | None = None
125
126
  log_path: str | None = None
126
127
  max_threads: int = 4
@@ -181,6 +182,8 @@ class EmpathyConfig:
181
182
 
182
183
  # Handle nested ModelConfig objects
183
184
  if filtered_data.get("models"):
185
+ from empathy_os.workflows.config import ModelConfig
186
+
184
187
  filtered_data["models"] = [ModelConfig(**m) for m in filtered_data["models"]]
185
188
 
186
189
  return cls(**filtered_data)
@@ -7,6 +7,7 @@ Licensed under Fair Source 0.9
7
7
  """
8
8
 
9
9
  import logging
10
+ from collections.abc import Callable
10
11
 
11
12
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
12
13
 
@@ -45,7 +46,7 @@ class HotReloadIntegration:
45
46
  def __init__(
46
47
  self,
47
48
  app: FastAPI,
48
- register_callback: callable,
49
+ register_callback: Callable[[str, type], bool],
49
50
  ):
50
51
  """Initialize hot-reload integration.
51
52
 
@@ -19,16 +19,16 @@ logger = logging.getLogger(__name__)
19
19
  class WizardFileHandler(FileSystemEventHandler):
20
20
  """Handles file system events for wizard files."""
21
21
 
22
- def __init__(self, reload_callback: Callable[[str], None]):
22
+ def __init__(self, reload_callback: Callable[[str, str], None]):
23
23
  """Initialize handler.
24
24
 
25
25
  Args:
26
- reload_callback: Function to call when wizard file changes
26
+ reload_callback: Function to call when wizard file changes (wizard_id, file_path)
27
27
 
28
28
  """
29
29
  super().__init__()
30
30
  self.reload_callback = reload_callback
31
- self._processing = set() # Prevent duplicate events
31
+ self._processing: set[str] = set() # Prevent duplicate events
32
32
 
33
33
  def on_modified(self, event: FileSystemEvent) -> None:
34
34
  """Handle file modification events.
@@ -40,7 +40,11 @@ class WizardFileHandler(FileSystemEventHandler):
40
40
  if event.is_directory:
41
41
  return
42
42
 
43
- file_path = event.src_path
43
+ # Convert file_path to str if it's bytes
44
+ file_path_raw = event.src_path
45
+ file_path = (
46
+ file_path_raw.decode("utf-8") if isinstance(file_path_raw, bytes) else file_path_raw
47
+ )
44
48
 
45
49
  # Only process Python files
46
50
  if not file_path.endswith(".py"):
@@ -8,6 +8,7 @@ Licensed under Fair Source 0.9
8
8
 
9
9
  import asyncio
10
10
  import logging
11
+ from collections.abc import Callable
11
12
  from typing import Any
12
13
 
13
14
  from fastapi import WebSocket
@@ -141,7 +142,7 @@ def get_notification_manager() -> ReloadNotificationManager:
141
142
  return _notification_manager
142
143
 
143
144
 
144
- def create_notification_callback() -> callable:
145
+ def create_notification_callback() -> Callable[[dict[str, Any]], None]:
145
146
  """Create a callback function for the wizard reloader.
146
147
 
147
148
  Returns:
@@ -73,48 +73,48 @@ from .graph import MemoryGraph
73
73
 
74
74
  # Long-term memory (Persistent patterns)
75
75
  from .long_term import (
76
- Classification,
77
- ClassificationRules,
78
- EncryptionManager,
79
- MemDocsStorage,
80
- PatternMetadata,
81
- SecureMemDocsIntegration,
82
- SecurePattern,
83
- SecurityError,
76
+ Classification,
77
+ ClassificationRules,
78
+ EncryptionManager,
79
+ MemDocsStorage,
80
+ PatternMetadata,
81
+ SecureMemDocsIntegration,
82
+ SecurePattern,
83
+ SecurityError,
84
84
  )
85
85
  from .long_term import PermissionError as MemoryPermissionError
86
86
  from .nodes import BugNode, Node, NodeType, PatternNode, PerformanceNode, VulnerabilityNode
87
87
 
88
88
  # Redis Bootstrap
89
89
  from .redis_bootstrap import (
90
- RedisStartMethod,
91
- RedisStatus,
92
- ensure_redis,
93
- get_redis_or_mock,
94
- stop_redis,
90
+ RedisStartMethod,
91
+ RedisStatus,
92
+ ensure_redis,
93
+ get_redis_or_mock,
94
+ stop_redis,
95
95
  )
96
96
 
97
97
  # Security components
98
98
  from .security import ( # Audit Logging; PII Scrubbing; Secrets Detection
99
- AuditEvent,
100
- AuditLogger,
101
- PIIDetection,
102
- PIIPattern,
103
- PIIScrubber,
104
- SecretDetection,
105
- SecretsDetector,
106
- SecretType,
107
- SecurityViolation,
108
- Severity,
109
- detect_secrets,
99
+ AuditEvent,
100
+ AuditLogger,
101
+ PIIDetection,
102
+ PIIPattern,
103
+ PIIScrubber,
104
+ SecretDetection,
105
+ SecretsDetector,
106
+ SecretType,
107
+ SecurityViolation,
108
+ Severity,
109
+ detect_secrets,
110
110
  )
111
111
  from .short_term import (
112
- AccessTier,
113
- AgentCredentials,
114
- ConflictContext,
115
- RedisShortTermMemory,
116
- StagedPattern,
117
- TTLStrategy,
112
+ AccessTier,
113
+ AgentCredentials,
114
+ ConflictContext,
115
+ RedisShortTermMemory,
116
+ StagedPattern,
117
+ TTLStrategy,
118
118
  )
119
119
 
120
120
  # Conversation Summary Index
@@ -560,7 +560,9 @@ class MemoryControlPanel:
560
560
  return long_term.delete_pattern(pattern_id, user_id)
561
561
  except Exception as e:
562
562
  logger.error("delete_pattern_failed", pattern_id=pattern_id, error=str(e))
563
- return False # Graceful degradation - validation errors raise, storage errors return False
563
+ return (
564
+ False # Graceful degradation - validation errors raise, storage errors return False
565
+ )
564
566
 
565
567
  def clear_short_term(self, agent_id: str = "admin") -> int:
566
568
  """Clear all short-term memory for an agent.
@@ -570,7 +570,9 @@ class SecureMemDocsIntegration:
570
570
 
571
571
  # Pattern 5: Type validation
572
572
  if custom_metadata is not None and not isinstance(custom_metadata, dict):
573
- raise TypeError(f"custom_metadata must be dict, got {type(custom_metadata).__name__}")
573
+ raise TypeError(
574
+ f"custom_metadata must be dict, got {type(custom_metadata).__name__}"
575
+ )
574
576
 
575
577
  # Step 1 & 2: PII Scrubbing + Secrets Detection (PARALLEL for performance)
576
578
  # Run both operations in parallel since they're independent
@@ -12,64 +12,64 @@ Licensed under Fair Source License 0.9
12
12
  from .empathy_executor import EmpathyLLMExecutor
13
13
  from .executor import ExecutionContext, LLMExecutor, LLMResponse, MockLLMExecutor
14
14
  from .fallback import (
15
- DEFAULT_FALLBACK_POLICY,
16
- DEFAULT_RETRY_POLICY,
17
- CircuitBreaker,
18
- CircuitBreakerState,
19
- FallbackPolicy,
20
- FallbackStep,
21
- FallbackStrategy,
22
- ResilientExecutor,
23
- RetryPolicy,
15
+ DEFAULT_FALLBACK_POLICY,
16
+ DEFAULT_RETRY_POLICY,
17
+ CircuitBreaker,
18
+ CircuitBreakerState,
19
+ FallbackPolicy,
20
+ FallbackStep,
21
+ FallbackStrategy,
22
+ ResilientExecutor,
23
+ RetryPolicy,
24
24
  )
25
25
  from .provider_config import (
26
- ProviderConfig,
27
- ProviderMode,
28
- configure_provider_cli,
29
- configure_provider_interactive,
30
- get_provider_config,
31
- reset_provider_config,
32
- set_provider_config,
26
+ ProviderConfig,
27
+ ProviderMode,
28
+ configure_provider_cli,
29
+ configure_provider_interactive,
30
+ get_provider_config,
31
+ reset_provider_config,
32
+ set_provider_config,
33
33
  )
34
34
  from .registry import (
35
- MODEL_REGISTRY,
36
- ModelInfo,
37
- ModelProvider,
38
- ModelTier,
39
- get_all_models,
40
- get_model,
41
- get_pricing_for_model,
35
+ MODEL_REGISTRY,
36
+ ModelInfo,
37
+ ModelProvider,
38
+ ModelTier,
39
+ get_all_models,
40
+ get_model,
41
+ get_pricing_for_model,
42
42
  )
43
43
  from .tasks import (
44
- CAPABLE_TASKS,
45
- CHEAP_TASKS,
46
- PREMIUM_TASKS,
47
- TASK_TIER_MAP,
48
- TaskInfo,
49
- TaskType,
50
- get_all_tasks,
51
- get_tasks_for_tier,
52
- get_tier_for_task,
53
- is_known_task,
54
- normalize_task_type,
44
+ CAPABLE_TASKS,
45
+ CHEAP_TASKS,
46
+ PREMIUM_TASKS,
47
+ TASK_TIER_MAP,
48
+ TaskInfo,
49
+ TaskType,
50
+ get_all_tasks,
51
+ get_tasks_for_tier,
52
+ get_tier_for_task,
53
+ is_known_task,
54
+ normalize_task_type,
55
55
  )
56
56
  from .telemetry import (
57
- LLMCallRecord,
58
- TelemetryAnalytics,
59
- TelemetryBackend,
60
- TelemetryStore,
61
- WorkflowRunRecord,
62
- WorkflowStageRecord,
63
- get_telemetry_store,
64
- log_llm_call,
65
- log_workflow_run,
57
+ LLMCallRecord,
58
+ TelemetryAnalytics,
59
+ TelemetryBackend,
60
+ TelemetryStore,
61
+ WorkflowRunRecord,
62
+ WorkflowStageRecord,
63
+ get_telemetry_store,
64
+ log_llm_call,
65
+ log_workflow_run,
66
66
  )
67
67
  from .validation import (
68
- ConfigValidator,
69
- ValidationError,
70
- ValidationResult,
71
- validate_config,
72
- validate_yaml_file,
68
+ ConfigValidator,
69
+ ValidationError,
70
+ ValidationResult,
71
+ validate_config,
72
+ validate_yaml_file,
73
73
  )
74
74
 
75
75
  __all__ = [
@@ -25,13 +25,13 @@ from empathy_os.agent_monitoring import AgentMetrics, AgentMonitor, TeamMetrics
25
25
 
26
26
  # Import telemetry classes
27
27
  from empathy_os.models.telemetry import (
28
- LLMCallRecord,
29
- TelemetryAnalytics,
30
- TelemetryStore,
31
- WorkflowRunRecord,
32
- get_telemetry_store,
33
- log_llm_call,
34
- log_workflow_run,
28
+ LLMCallRecord,
29
+ TelemetryAnalytics,
30
+ TelemetryStore,
31
+ WorkflowRunRecord,
32
+ get_telemetry_store,
33
+ log_llm_call,
34
+ log_workflow_run,
35
35
  )
36
36
 
37
37
  __all__ = [
@@ -7,9 +7,9 @@ Licensed under Fair Source License 0.9
7
7
  """
8
8
 
9
9
  from empathy_os.optimization.context_optimizer import (
10
- CompressionLevel,
11
- ContextOptimizer,
12
- optimize_xml_prompt,
10
+ CompressionLevel,
11
+ ContextOptimizer,
12
+ optimize_xml_prompt,
13
13
  )
14
14
 
15
15
  __all__ = [
@@ -262,9 +262,7 @@ class PatternLibrary:
262
262
  """
263
263
  pattern = self.patterns.get(pattern_id)
264
264
  if not pattern:
265
- raise ValueError(
266
- f"Pattern '{pattern_id}' not found. Cannot record outcome."
267
- )
265
+ raise ValueError(f"Pattern '{pattern_id}' not found. Cannot record outcome.")
268
266
  pattern.record_usage(success)
269
267
 
270
268
  def link_patterns(self, pattern_id_1: str, pattern_id_2: str):
@@ -300,10 +298,7 @@ class PatternLibrary:
300
298
  self.pattern_graph[pattern_id_2].append(pattern_id_1)
301
299
 
302
300
  def get_related_patterns(
303
- self,
304
- pattern_id: str,
305
- depth: int = 1,
306
- _visited: set[str] | None = None
301
+ self, pattern_id: str, depth: int = 1, _visited: set[str] | None = None
307
302
  ) -> list[Pattern]:
308
303
  """Get patterns related to a given pattern
309
304
 
@@ -7,12 +7,12 @@ Licensed under Fair Source 0.9
7
7
  """
8
8
 
9
9
  from .base import (
10
- BasePlugin,
11
- BaseWizard,
12
- PluginError,
13
- PluginLoadError,
14
- PluginMetadata,
15
- PluginValidationError,
10
+ BasePlugin,
11
+ BaseWizard,
12
+ PluginError,
13
+ PluginLoadError,
14
+ PluginMetadata,
15
+ PluginValidationError,
16
16
  )
17
17
  from .registry import PluginRegistry, get_global_registry
18
18
 
@@ -18,11 +18,11 @@ Licensed under Fair Source 0.9
18
18
  """
19
19
 
20
20
  from .circuit_breaker import (
21
- CircuitBreaker,
22
- CircuitOpenError,
23
- CircuitState,
24
- circuit_breaker,
25
- get_circuit_breaker,
21
+ CircuitBreaker,
22
+ CircuitOpenError,
23
+ CircuitState,
24
+ circuit_breaker,
25
+ get_circuit_breaker,
26
26
  )
27
27
  from .fallback import Fallback, fallback, with_fallback
28
28
  from .health import HealthCheck, HealthStatus, SystemHealth
@@ -218,7 +218,7 @@ Examples:
218
218
  )
219
219
 
220
220
  # List patterns command
221
- list_parser = subparsers.add_parser("list-patterns", help="List available patterns")
221
+ subparsers.add_parser("list-patterns", help="List available patterns")
222
222
 
223
223
  args = parser.parse_args()
224
224