empathy-framework 3.9.2__py3-none-any.whl → 3.10.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.
- {empathy_framework-3.9.2.dist-info → empathy_framework-3.10.1.dist-info}/METADATA +37 -5
- {empathy_framework-3.9.2.dist-info → empathy_framework-3.10.1.dist-info}/RECORD +32 -32
- empathy_llm_toolkit/agent_factory/crews/health_check.py +36 -29
- empathy_os/__init__.py +1 -1
- empathy_os/cache/hybrid.py +69 -9
- empathy_os/cli.py +183 -18
- empathy_os/cli_unified.py +113 -4
- empathy_os/config.py +7 -4
- empathy_os/hot_reload/integration.py +2 -1
- empathy_os/hot_reload/watcher.py +8 -4
- empathy_os/hot_reload/websocket.py +2 -1
- empathy_os/models/telemetry.py +900 -2
- empathy_os/test_generator/generator.py +1 -1
- empathy_os/tier_recommender.py +3 -3
- empathy_os/workflows/base.py +5 -2
- empathy_os/workflows/health_check.py +37 -0
- empathy_os/workflows/new_sample_workflow1.py +3 -3
- empathy_os/workflows/tier_tracking.py +1 -1
- empathy_software_plugin/wizards/advanced_debugging_wizard.py +9 -6
- empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +1 -1
- empathy_software_plugin/wizards/debugging/config_loaders.py +6 -2
- empathy_software_plugin/wizards/debugging/language_patterns.py +4 -2
- empathy_software_plugin/wizards/debugging/linter_parsers.py +1 -1
- empathy_software_plugin/wizards/performance/profiler_parsers.py +7 -7
- empathy_software_plugin/wizards/security/vulnerability_scanner.py +1 -1
- empathy_software_plugin/wizards/security_analysis_wizard.py +2 -2
- empathy_software_plugin/wizards/testing/quality_analyzer.py +3 -9
- empathy_software_plugin/wizards/testing/test_suggester.py +1 -1
- {empathy_framework-3.9.2.dist-info → empathy_framework-3.10.1.dist-info}/WHEEL +0 -0
- {empathy_framework-3.9.2.dist-info → empathy_framework-3.10.1.dist-info}/entry_points.txt +0 -0
- {empathy_framework-3.9.2.dist-info → empathy_framework-3.10.1.dist-info}/licenses/LICENSE +0 -0
- {empathy_framework-3.9.2.dist-info → empathy_framework-3.10.1.dist-info}/top_level.txt +0 -0
empathy_os/cli.py
CHANGED
|
@@ -41,11 +41,15 @@ from empathy_os.workflows import list_workflows as get_workflow_list
|
|
|
41
41
|
# Import telemetry CLI commands
|
|
42
42
|
try:
|
|
43
43
|
from empathy_os.telemetry.cli import (
|
|
44
|
+
cmd_agent_performance,
|
|
45
|
+
cmd_task_routing_report,
|
|
44
46
|
cmd_telemetry_compare,
|
|
45
47
|
cmd_telemetry_export,
|
|
46
48
|
cmd_telemetry_reset,
|
|
47
49
|
cmd_telemetry_savings,
|
|
48
50
|
cmd_telemetry_show,
|
|
51
|
+
cmd_test_status,
|
|
52
|
+
cmd_tier1_status,
|
|
49
53
|
)
|
|
50
54
|
|
|
51
55
|
TELEMETRY_CLI_AVAILABLE = True
|
|
@@ -2148,7 +2152,20 @@ def cmd_workflow(args):
|
|
|
2148
2152
|
|
|
2149
2153
|
wf_config = WorkflowConfig.load()
|
|
2150
2154
|
provider = wf_config.default_provider
|
|
2151
|
-
|
|
2155
|
+
|
|
2156
|
+
# Initialize workflow with tier fallback if requested
|
|
2157
|
+
use_tier_fallback = getattr(args, "use_recommended_tier", False)
|
|
2158
|
+
workflow_kwargs = {
|
|
2159
|
+
"provider": provider,
|
|
2160
|
+
"enable_tier_fallback": use_tier_fallback,
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
# Add health-check specific parameters
|
|
2164
|
+
if name == "health-check":
|
|
2165
|
+
health_score_threshold = getattr(args, "health_score_threshold", 100)
|
|
2166
|
+
workflow_kwargs["health_score_threshold"] = health_score_threshold
|
|
2167
|
+
|
|
2168
|
+
workflow = workflow_cls(**workflow_kwargs)
|
|
2152
2169
|
|
|
2153
2170
|
# Parse input
|
|
2154
2171
|
input_data = {}
|
|
@@ -2244,25 +2261,76 @@ def cmd_workflow(args):
|
|
|
2244
2261
|
}
|
|
2245
2262
|
print(json_mod.dumps(output, indent=2))
|
|
2246
2263
|
# Display the actual results - this is what users want to see
|
|
2247
|
-
elif result.success:
|
|
2248
|
-
if output_content:
|
|
2249
|
-
print(f"\n{output_content}\n")
|
|
2250
|
-
else:
|
|
2251
|
-
print("\n✓ Workflow completed successfully.\n")
|
|
2252
2264
|
else:
|
|
2253
|
-
#
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2265
|
+
# Show tier progression if tier fallback was used
|
|
2266
|
+
if use_tier_fallback and hasattr(workflow, "_tier_progression"):
|
|
2267
|
+
tier_progression = workflow._tier_progression
|
|
2268
|
+
if tier_progression:
|
|
2269
|
+
print("\n" + "=" * 60)
|
|
2270
|
+
print(" TIER PROGRESSION (Intelligent Fallback)")
|
|
2271
|
+
print("=" * 60)
|
|
2272
|
+
|
|
2273
|
+
# Group by stage
|
|
2274
|
+
stage_tiers: dict[str, list[tuple[str, bool]]] = {}
|
|
2275
|
+
for stage, tier, success in tier_progression:
|
|
2276
|
+
if stage not in stage_tiers:
|
|
2277
|
+
stage_tiers[stage] = []
|
|
2278
|
+
stage_tiers[stage].append((tier, success))
|
|
2279
|
+
|
|
2280
|
+
# Display progression for each stage
|
|
2281
|
+
for stage, attempts in stage_tiers.items():
|
|
2282
|
+
status = "✓" if any(success for _, success in attempts) else "✗"
|
|
2283
|
+
print(f"\n{status} Stage: {stage}")
|
|
2284
|
+
|
|
2285
|
+
for idx, (tier, success) in enumerate(attempts, 1):
|
|
2286
|
+
attempt_status = "✓ SUCCESS" if success else "✗ FAILED"
|
|
2287
|
+
if idx == 1:
|
|
2288
|
+
print(f" Attempt {idx}: {tier.upper():8} → {attempt_status}")
|
|
2289
|
+
else:
|
|
2290
|
+
prev_tier = attempts[idx - 2][0]
|
|
2291
|
+
print(
|
|
2292
|
+
f" Attempt {idx}: {tier.upper():8} → {attempt_status} "
|
|
2293
|
+
f"(upgraded from {prev_tier.upper()})"
|
|
2294
|
+
)
|
|
2295
|
+
|
|
2296
|
+
# Calculate cost savings (only if result has stages attribute)
|
|
2297
|
+
if hasattr(result, "stages") and result.stages:
|
|
2298
|
+
actual_cost = sum(stage.cost for stage in result.stages if stage.cost)
|
|
2299
|
+
# Estimate what cost would be if all stages used PREMIUM
|
|
2300
|
+
premium_cost = actual_cost * 3 # Conservative estimate
|
|
2301
|
+
|
|
2302
|
+
savings = premium_cost - actual_cost
|
|
2303
|
+
savings_pct = (savings / premium_cost * 100) if premium_cost > 0 else 0
|
|
2304
|
+
|
|
2305
|
+
print("\n" + "-" * 60)
|
|
2306
|
+
print("💰 Cost Savings:")
|
|
2307
|
+
print(f" Actual cost: ${actual_cost:.4f}")
|
|
2308
|
+
print(f" Premium cost: ${premium_cost:.4f} (if all PREMIUM)")
|
|
2309
|
+
print(f" Savings: ${savings:.4f} ({savings_pct:.1f}%)")
|
|
2310
|
+
print("=" * 60 + "\n")
|
|
2311
|
+
|
|
2312
|
+
# Display workflow result
|
|
2313
|
+
if result.success:
|
|
2314
|
+
if output_content:
|
|
2315
|
+
print(f"\n{output_content}\n")
|
|
2260
2316
|
else:
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2317
|
+
print("\n✓ Workflow completed successfully.\n")
|
|
2318
|
+
else:
|
|
2319
|
+
# Extract error from various result types
|
|
2320
|
+
error_msg = getattr(result, "error", None)
|
|
2321
|
+
if not error_msg:
|
|
2322
|
+
# Check for blockers (CodeReviewPipelineResult)
|
|
2323
|
+
blockers = getattr(result, "blockers", [])
|
|
2324
|
+
if blockers:
|
|
2325
|
+
error_msg = "; ".join(blockers)
|
|
2326
|
+
else:
|
|
2327
|
+
# Check metadata for error
|
|
2328
|
+
metadata = getattr(result, "metadata", {})
|
|
2329
|
+
error_msg = (
|
|
2330
|
+
metadata.get("error") if isinstance(metadata, dict) else None
|
|
2331
|
+
)
|
|
2332
|
+
error_msg = error_msg or "Unknown error"
|
|
2333
|
+
print(f"\n✗ Workflow failed: {error_msg}\n")
|
|
2266
2334
|
|
|
2267
2335
|
except KeyError as e:
|
|
2268
2336
|
print(f"Error: {e}")
|
|
@@ -2436,6 +2504,38 @@ def _cmd_telemetry_export(args):
|
|
|
2436
2504
|
return cmd_telemetry_export(args)
|
|
2437
2505
|
|
|
2438
2506
|
|
|
2507
|
+
def _cmd_tier1_status(args):
|
|
2508
|
+
"""Wrapper for tier1 status command."""
|
|
2509
|
+
if not TELEMETRY_CLI_AVAILABLE:
|
|
2510
|
+
print("Tier 1 monitoring commands not available. Install telemetry dependencies.")
|
|
2511
|
+
return 1
|
|
2512
|
+
return cmd_tier1_status(args)
|
|
2513
|
+
|
|
2514
|
+
|
|
2515
|
+
def _cmd_task_routing_report(args):
|
|
2516
|
+
"""Wrapper for task routing report command."""
|
|
2517
|
+
if not TELEMETRY_CLI_AVAILABLE:
|
|
2518
|
+
print("Tier 1 monitoring commands not available. Install telemetry dependencies.")
|
|
2519
|
+
return 1
|
|
2520
|
+
return cmd_task_routing_report(args)
|
|
2521
|
+
|
|
2522
|
+
|
|
2523
|
+
def _cmd_test_status(args):
|
|
2524
|
+
"""Wrapper for test status command."""
|
|
2525
|
+
if not TELEMETRY_CLI_AVAILABLE:
|
|
2526
|
+
print("Tier 1 monitoring commands not available. Install telemetry dependencies.")
|
|
2527
|
+
return 1
|
|
2528
|
+
return cmd_test_status(args)
|
|
2529
|
+
|
|
2530
|
+
|
|
2531
|
+
def _cmd_agent_performance(args):
|
|
2532
|
+
"""Wrapper for agent performance command."""
|
|
2533
|
+
if not TELEMETRY_CLI_AVAILABLE:
|
|
2534
|
+
print("Tier 1 monitoring commands not available. Install telemetry dependencies.")
|
|
2535
|
+
return 1
|
|
2536
|
+
return cmd_agent_performance(args)
|
|
2537
|
+
|
|
2538
|
+
|
|
2439
2539
|
def main():
|
|
2440
2540
|
"""Main CLI entry point"""
|
|
2441
2541
|
# Configure Windows-compatible asyncio event loop policy
|
|
@@ -2929,6 +3029,60 @@ def main():
|
|
|
2929
3029
|
)
|
|
2930
3030
|
parser_telemetry_export.set_defaults(func=lambda args: _cmd_telemetry_export(args))
|
|
2931
3031
|
|
|
3032
|
+
# Tier 1 automation monitoring commands
|
|
3033
|
+
|
|
3034
|
+
# tier1 command - comprehensive status
|
|
3035
|
+
parser_tier1 = subparsers.add_parser(
|
|
3036
|
+
"tier1",
|
|
3037
|
+
help="Show Tier 1 automation status (tasks, tests, coverage, agents)",
|
|
3038
|
+
)
|
|
3039
|
+
parser_tier1.add_argument(
|
|
3040
|
+
"--hours",
|
|
3041
|
+
type=int,
|
|
3042
|
+
default=24,
|
|
3043
|
+
help="Hours to analyze (default: 24)",
|
|
3044
|
+
)
|
|
3045
|
+
parser_tier1.set_defaults(func=lambda args: _cmd_tier1_status(args))
|
|
3046
|
+
|
|
3047
|
+
# tasks command - task routing report
|
|
3048
|
+
parser_tasks = subparsers.add_parser(
|
|
3049
|
+
"tasks",
|
|
3050
|
+
help="Show task routing report",
|
|
3051
|
+
)
|
|
3052
|
+
parser_tasks.add_argument(
|
|
3053
|
+
"--hours",
|
|
3054
|
+
type=int,
|
|
3055
|
+
default=24,
|
|
3056
|
+
help="Hours to analyze (default: 24)",
|
|
3057
|
+
)
|
|
3058
|
+
parser_tasks.set_defaults(func=lambda args: _cmd_task_routing_report(args))
|
|
3059
|
+
|
|
3060
|
+
# tests command - test execution status
|
|
3061
|
+
parser_tests = subparsers.add_parser(
|
|
3062
|
+
"tests",
|
|
3063
|
+
help="Show test execution status",
|
|
3064
|
+
)
|
|
3065
|
+
parser_tests.add_argument(
|
|
3066
|
+
"--hours",
|
|
3067
|
+
type=int,
|
|
3068
|
+
default=24,
|
|
3069
|
+
help="Hours to analyze (default: 24)",
|
|
3070
|
+
)
|
|
3071
|
+
parser_tests.set_defaults(func=lambda args: _cmd_test_status(args))
|
|
3072
|
+
|
|
3073
|
+
# agents command - agent performance
|
|
3074
|
+
parser_agents = subparsers.add_parser(
|
|
3075
|
+
"agents",
|
|
3076
|
+
help="Show agent performance metrics",
|
|
3077
|
+
)
|
|
3078
|
+
parser_agents.add_argument(
|
|
3079
|
+
"--hours",
|
|
3080
|
+
type=int,
|
|
3081
|
+
default=168,
|
|
3082
|
+
help="Hours to analyze (default: 168 / 7 days)",
|
|
3083
|
+
)
|
|
3084
|
+
parser_agents.set_defaults(func=lambda args: _cmd_agent_performance(args))
|
|
3085
|
+
|
|
2932
3086
|
# New command (project scaffolding)
|
|
2933
3087
|
parser_new = subparsers.add_parser("new", help="Create a new project from a template")
|
|
2934
3088
|
parser_new.add_argument(
|
|
@@ -3018,6 +3172,11 @@ def main():
|
|
|
3018
3172
|
help="Force overwrite existing config file",
|
|
3019
3173
|
)
|
|
3020
3174
|
parser_workflow.add_argument("--json", action="store_true", help="Output as JSON")
|
|
3175
|
+
parser_workflow.add_argument(
|
|
3176
|
+
"--use-recommended-tier",
|
|
3177
|
+
action="store_true",
|
|
3178
|
+
help="Enable intelligent tier fallback: start with CHEAP tier and automatically upgrade if quality gates fail",
|
|
3179
|
+
)
|
|
3021
3180
|
parser_workflow.add_argument(
|
|
3022
3181
|
"--write-tests",
|
|
3023
3182
|
action="store_true",
|
|
@@ -3028,6 +3187,12 @@ def main():
|
|
|
3028
3187
|
default="tests/generated",
|
|
3029
3188
|
help="(test-gen workflow) Output directory for generated tests",
|
|
3030
3189
|
)
|
|
3190
|
+
parser_workflow.add_argument(
|
|
3191
|
+
"--health-score-threshold",
|
|
3192
|
+
type=int,
|
|
3193
|
+
default=95,
|
|
3194
|
+
help="(health-check workflow) Minimum health score required (0-100, default: 95 for very strict quality)",
|
|
3195
|
+
)
|
|
3031
3196
|
parser_workflow.set_defaults(func=cmd_workflow)
|
|
3032
3197
|
|
|
3033
3198
|
# Sync-claude command (sync patterns to Claude Code)
|
empathy_os/cli_unified.py
CHANGED
|
@@ -462,11 +462,36 @@ def workflow_list():
|
|
|
462
462
|
def workflow_run(
|
|
463
463
|
name: str = typer.Argument(..., help="Workflow name"),
|
|
464
464
|
path: Path = typer.Option(Path(), "--path", "-p", help="Path to run on"),
|
|
465
|
+
use_recommended_tier: bool = typer.Option(
|
|
466
|
+
False,
|
|
467
|
+
"--use-recommended-tier",
|
|
468
|
+
help="Enable intelligent tier fallback: start with CHEAP tier and automatically upgrade if quality gates fail",
|
|
469
|
+
),
|
|
470
|
+
health_score_threshold: int = typer.Option(
|
|
471
|
+
95,
|
|
472
|
+
"--health-score-threshold",
|
|
473
|
+
help="(health-check workflow) Minimum health score required (0-100, default: 95 for very strict quality)",
|
|
474
|
+
),
|
|
465
475
|
):
|
|
466
476
|
"""Run a multi-model workflow."""
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
477
|
+
cmd = [
|
|
478
|
+
sys.executable,
|
|
479
|
+
"-m",
|
|
480
|
+
"empathy_os.cli",
|
|
481
|
+
"workflow",
|
|
482
|
+
"run",
|
|
483
|
+
name,
|
|
484
|
+
"--input",
|
|
485
|
+
f'{{"path": "{path}"}}',
|
|
486
|
+
]
|
|
487
|
+
|
|
488
|
+
if use_recommended_tier:
|
|
489
|
+
cmd.append("--use-recommended-tier")
|
|
490
|
+
|
|
491
|
+
if health_score_threshold != 95:
|
|
492
|
+
cmd.extend(["--health-score-threshold", str(health_score_threshold)])
|
|
493
|
+
|
|
494
|
+
subprocess.run(cmd, check=False)
|
|
470
495
|
|
|
471
496
|
|
|
472
497
|
@workflow_app.command("create")
|
|
@@ -513,6 +538,85 @@ def workflow_recommend(
|
|
|
513
538
|
)
|
|
514
539
|
|
|
515
540
|
|
|
541
|
+
# =============================================================================
|
|
542
|
+
# TELEMETRY SUBCOMMAND GROUP
|
|
543
|
+
# =============================================================================
|
|
544
|
+
|
|
545
|
+
telemetry_app = typer.Typer(help="View and manage local usage telemetry")
|
|
546
|
+
app.add_typer(telemetry_app, name="telemetry")
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
@telemetry_app.command("show")
|
|
550
|
+
def telemetry_show(
|
|
551
|
+
limit: int = typer.Option(20, "--limit", "-l", help="Number of entries to show"),
|
|
552
|
+
days: int | None = typer.Option(None, "--days", "-d", help="Only show last N days"),
|
|
553
|
+
):
|
|
554
|
+
"""Show recent LLM calls and usage stats."""
|
|
555
|
+
args = [sys.executable, "-m", "empathy_os.cli", "telemetry", "show", "--limit", str(limit)]
|
|
556
|
+
if days:
|
|
557
|
+
args.extend(["--days", str(days)])
|
|
558
|
+
subprocess.run(args, check=False)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
@telemetry_app.command("savings")
|
|
562
|
+
def telemetry_savings(
|
|
563
|
+
days: int = typer.Option(30, "--days", "-d", help="Number of days to analyze"),
|
|
564
|
+
):
|
|
565
|
+
"""Calculate cost savings vs baseline (all PREMIUM)."""
|
|
566
|
+
subprocess.run(
|
|
567
|
+
[sys.executable, "-m", "empathy_os.cli", "telemetry", "savings", "--days", str(days)],
|
|
568
|
+
check=False,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
@telemetry_app.command("compare")
|
|
573
|
+
def telemetry_compare(
|
|
574
|
+
period1: int = typer.Option(7, "--period1", "-p1", help="First period in days"),
|
|
575
|
+
period2: int = typer.Option(30, "--period2", "-p2", help="Second period in days"),
|
|
576
|
+
):
|
|
577
|
+
"""Compare usage across two time periods."""
|
|
578
|
+
subprocess.run(
|
|
579
|
+
[
|
|
580
|
+
sys.executable,
|
|
581
|
+
"-m",
|
|
582
|
+
"empathy_os.cli",
|
|
583
|
+
"telemetry",
|
|
584
|
+
"compare",
|
|
585
|
+
"--period1",
|
|
586
|
+
str(period1),
|
|
587
|
+
"--period2",
|
|
588
|
+
str(period2),
|
|
589
|
+
],
|
|
590
|
+
check=False,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
@telemetry_app.command("export")
|
|
595
|
+
def telemetry_export(
|
|
596
|
+
format_type: str = typer.Option("json", "--format", "-f", help="Export format (json, csv)"),
|
|
597
|
+
output: Path | None = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
598
|
+
days: int | None = typer.Option(None, "--days", "-d", help="Only export last N days"),
|
|
599
|
+
):
|
|
600
|
+
"""Export telemetry data to JSON or CSV."""
|
|
601
|
+
args = [sys.executable, "-m", "empathy_os.cli", "telemetry", "export", "--format", format_type]
|
|
602
|
+
if output:
|
|
603
|
+
args.extend(["--output", str(output)])
|
|
604
|
+
if days:
|
|
605
|
+
args.extend(["--days", str(days)])
|
|
606
|
+
subprocess.run(args, check=False)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
@telemetry_app.command("reset")
|
|
610
|
+
def telemetry_reset(
|
|
611
|
+
confirm: bool = typer.Option(False, "--confirm", help="Confirm deletion"),
|
|
612
|
+
):
|
|
613
|
+
"""Clear all telemetry data (use with caution)."""
|
|
614
|
+
args = [sys.executable, "-m", "empathy_os.cli", "telemetry", "reset"]
|
|
615
|
+
if confirm:
|
|
616
|
+
args.append("--confirm")
|
|
617
|
+
subprocess.run(args, check=False)
|
|
618
|
+
|
|
619
|
+
|
|
516
620
|
# =============================================================================
|
|
517
621
|
# TIER RECOMMENDATION SUBCOMMAND GROUP
|
|
518
622
|
# =============================================================================
|
|
@@ -669,7 +773,12 @@ def cheatsheet():
|
|
|
669
773
|
empathy wizard list Show available wizards
|
|
670
774
|
empathy wizard run <name> Execute a wizard
|
|
671
775
|
empathy wizard create <name> -d <domain> Create wizard (12x faster)
|
|
672
|
-
empathy wizard list-patterns List available patterns
|
|
776
|
+
empathy wizard list-patterns List available patterns
|
|
777
|
+
|
|
778
|
+
[bold]Usage Telemetry[/bold]
|
|
779
|
+
empathy telemetry show View recent LLM calls & costs
|
|
780
|
+
empathy telemetry savings Calculate cost savings (tier routing)
|
|
781
|
+
empathy telemetry export Export usage data (JSON/CSV)""",
|
|
673
782
|
title="[bold blue]Empathy Framework Cheatsheet[/bold blue]",
|
|
674
783
|
),
|
|
675
784
|
)
|
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:
|
|
49
|
+
register_callback: Callable[[str, type], bool],
|
|
49
50
|
):
|
|
50
51
|
"""Initialize hot-reload integration.
|
|
51
52
|
|
empathy_os/hot_reload/watcher.py
CHANGED
|
@@ -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
|
|
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() ->
|
|
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:
|