attune-ai 2.0.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.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""WebSocket Progress Server
|
|
2
|
+
|
|
3
|
+
Real-time progress streaming for workflow execution.
|
|
4
|
+
Enables live UI updates in VS Code and other clients.
|
|
5
|
+
|
|
6
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
7
|
+
Licensed under Fair Source 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
import signal
|
|
16
|
+
from contextlib import asynccontextmanager
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from .progress import ProgressCallback, ProgressTracker, ProgressUpdate
|
|
21
|
+
|
|
22
|
+
# Try to import websockets, provide helpful error if not available
|
|
23
|
+
WebSocketServerProtocol: Any = None
|
|
24
|
+
WEBSOCKETS_AVAILABLE = False
|
|
25
|
+
try:
|
|
26
|
+
import websockets
|
|
27
|
+
|
|
28
|
+
# Try new name first (websockets 11+), fall back to old name
|
|
29
|
+
try:
|
|
30
|
+
from websockets.server import ServerProtocol
|
|
31
|
+
|
|
32
|
+
WebSocketServerProtocol = ServerProtocol
|
|
33
|
+
except ImportError:
|
|
34
|
+
from websockets.server import WebSocketServerProtocol as _WS # type: ignore[attr-defined]
|
|
35
|
+
|
|
36
|
+
WebSocketServerProtocol = _WS
|
|
37
|
+
WEBSOCKETS_AVAILABLE = True
|
|
38
|
+
except ImportError:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ProgressServerConfig:
|
|
46
|
+
"""Configuration for the progress WebSocket server."""
|
|
47
|
+
|
|
48
|
+
host: str = "localhost"
|
|
49
|
+
port: int = 8766
|
|
50
|
+
ping_interval: float = 20.0
|
|
51
|
+
ping_timeout: float = 20.0
|
|
52
|
+
max_connections: int = 100
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ProgressServer:
|
|
56
|
+
"""WebSocket server for broadcasting workflow progress.
|
|
57
|
+
|
|
58
|
+
Clients connect and receive real-time progress updates for all
|
|
59
|
+
running workflows. Supports multiple concurrent connections.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, config: ProgressServerConfig | None = None):
|
|
63
|
+
if not WEBSOCKETS_AVAILABLE:
|
|
64
|
+
raise ImportError(
|
|
65
|
+
"websockets package is required for ProgressServer. "
|
|
66
|
+
"Install with: pip install websockets",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self.config = config or ProgressServerConfig()
|
|
70
|
+
self._clients: set[WebSocketServerProtocol] = set()
|
|
71
|
+
self._server: Any = None
|
|
72
|
+
self._running = False
|
|
73
|
+
self._trackers: dict[str, ProgressTracker] = {}
|
|
74
|
+
|
|
75
|
+
async def start(self) -> None:
|
|
76
|
+
"""Start the WebSocket server."""
|
|
77
|
+
self._running = True
|
|
78
|
+
|
|
79
|
+
self._server = await websockets.serve(
|
|
80
|
+
self._handle_connection,
|
|
81
|
+
self.config.host,
|
|
82
|
+
self.config.port,
|
|
83
|
+
ping_interval=self.config.ping_interval,
|
|
84
|
+
ping_timeout=self.config.ping_timeout,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
logger.info(f"Progress server started on ws://{self.config.host}:{self.config.port}")
|
|
88
|
+
|
|
89
|
+
async def stop(self) -> None:
|
|
90
|
+
"""Stop the WebSocket server."""
|
|
91
|
+
self._running = False
|
|
92
|
+
|
|
93
|
+
# Close all client connections
|
|
94
|
+
if self._clients:
|
|
95
|
+
await asyncio.gather(
|
|
96
|
+
*[client.close(1001, "Server shutting down") for client in self._clients],
|
|
97
|
+
return_exceptions=True,
|
|
98
|
+
)
|
|
99
|
+
self._clients.clear()
|
|
100
|
+
|
|
101
|
+
# Close server
|
|
102
|
+
if self._server:
|
|
103
|
+
self._server.close()
|
|
104
|
+
await self._server.wait_closed()
|
|
105
|
+
|
|
106
|
+
logger.info("Progress server stopped")
|
|
107
|
+
|
|
108
|
+
async def _handle_connection(self, websocket: WebSocketServerProtocol) -> None:
|
|
109
|
+
"""Handle a new client connection."""
|
|
110
|
+
if len(self._clients) >= self.config.max_connections:
|
|
111
|
+
await websocket.close(1013, "Maximum connections reached")
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
self._clients.add(websocket)
|
|
115
|
+
client_id = id(websocket)
|
|
116
|
+
logger.debug(f"Client {client_id} connected. Total clients: {len(self._clients)}")
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
# Send welcome message with current state
|
|
120
|
+
await websocket.send(
|
|
121
|
+
json.dumps(
|
|
122
|
+
{
|
|
123
|
+
"type": "connected",
|
|
124
|
+
"message": "Connected to Empathy progress server",
|
|
125
|
+
"active_workflows": list(self._trackers.keys()),
|
|
126
|
+
},
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Handle incoming messages (subscriptions, etc.)
|
|
131
|
+
async for message in websocket:
|
|
132
|
+
await self._handle_message(websocket, message)
|
|
133
|
+
|
|
134
|
+
except websockets.exceptions.ConnectionClosed:
|
|
135
|
+
pass
|
|
136
|
+
finally:
|
|
137
|
+
self._clients.discard(websocket)
|
|
138
|
+
logger.debug(f"Client {client_id} disconnected. Total clients: {len(self._clients)}")
|
|
139
|
+
|
|
140
|
+
async def _handle_message(self, websocket: WebSocketServerProtocol, message: str) -> None:
|
|
141
|
+
"""Handle incoming message from client."""
|
|
142
|
+
try:
|
|
143
|
+
data = json.loads(message)
|
|
144
|
+
msg_type = data.get("type")
|
|
145
|
+
|
|
146
|
+
if msg_type == "ping":
|
|
147
|
+
await websocket.send(json.dumps({"type": "pong"}))
|
|
148
|
+
|
|
149
|
+
elif msg_type == "subscribe":
|
|
150
|
+
# Client wants updates for specific workflow
|
|
151
|
+
workflow_id = data.get("workflow_id")
|
|
152
|
+
if workflow_id:
|
|
153
|
+
# Could track per-client subscriptions here
|
|
154
|
+
await websocket.send(
|
|
155
|
+
json.dumps({"type": "subscribed", "workflow_id": workflow_id}),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
elif msg_type == "get_status":
|
|
159
|
+
# Client wants current status of all workflows
|
|
160
|
+
await websocket.send(
|
|
161
|
+
json.dumps(
|
|
162
|
+
{
|
|
163
|
+
"type": "status",
|
|
164
|
+
"active_workflows": list(self._trackers.keys()),
|
|
165
|
+
"client_count": len(self._clients),
|
|
166
|
+
},
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
except json.JSONDecodeError:
|
|
171
|
+
await websocket.send(json.dumps({"type": "error", "message": "Invalid JSON"}))
|
|
172
|
+
|
|
173
|
+
async def broadcast(self, update: ProgressUpdate) -> None:
|
|
174
|
+
"""Broadcast a progress update to all connected clients."""
|
|
175
|
+
if not self._clients:
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
message = json.dumps({"type": "progress", **update.to_dict()})
|
|
179
|
+
|
|
180
|
+
# Broadcast to all clients
|
|
181
|
+
await asyncio.gather(
|
|
182
|
+
*[self._send_safe(client, message) for client in self._clients],
|
|
183
|
+
return_exceptions=True,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
async def _send_safe(self, client: WebSocketServerProtocol, message: str) -> None:
|
|
187
|
+
"""Send message to client with error handling."""
|
|
188
|
+
try:
|
|
189
|
+
await client.send(message)
|
|
190
|
+
except websockets.exceptions.ConnectionClosed:
|
|
191
|
+
self._clients.discard(client)
|
|
192
|
+
|
|
193
|
+
def create_tracker(
|
|
194
|
+
self,
|
|
195
|
+
workflow_name: str,
|
|
196
|
+
workflow_id: str,
|
|
197
|
+
stage_names: list[str],
|
|
198
|
+
) -> ProgressTracker:
|
|
199
|
+
"""Create a progress tracker that broadcasts to this server.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
workflow_name: Name of the workflow
|
|
203
|
+
workflow_id: Unique ID for this run
|
|
204
|
+
stage_names: List of stage names
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
ProgressTracker configured to broadcast updates
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
tracker = ProgressTracker(
|
|
211
|
+
workflow_name=workflow_name,
|
|
212
|
+
workflow_id=workflow_id,
|
|
213
|
+
stage_names=stage_names,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Store tracker
|
|
217
|
+
self._trackers[workflow_id] = tracker
|
|
218
|
+
|
|
219
|
+
# Add async callback to broadcast updates
|
|
220
|
+
async def broadcast_callback(update: ProgressUpdate) -> None:
|
|
221
|
+
await self.broadcast(update)
|
|
222
|
+
|
|
223
|
+
tracker.add_async_callback(broadcast_callback)
|
|
224
|
+
|
|
225
|
+
return tracker
|
|
226
|
+
|
|
227
|
+
def remove_tracker(self, workflow_id: str) -> None:
|
|
228
|
+
"""Remove a tracker when workflow completes."""
|
|
229
|
+
self._trackers.pop(workflow_id, None)
|
|
230
|
+
|
|
231
|
+
def get_callback(self) -> ProgressCallback:
|
|
232
|
+
"""Get a synchronous callback that queues broadcasts.
|
|
233
|
+
|
|
234
|
+
Useful for integration with sync code that can't await.
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
def callback(update: ProgressUpdate) -> None:
|
|
238
|
+
# Schedule broadcast in event loop
|
|
239
|
+
try:
|
|
240
|
+
loop = asyncio.get_event_loop()
|
|
241
|
+
if loop.is_running():
|
|
242
|
+
asyncio.create_task(self.broadcast(update))
|
|
243
|
+
else:
|
|
244
|
+
loop.run_until_complete(self.broadcast(update))
|
|
245
|
+
except RuntimeError:
|
|
246
|
+
# No event loop, skip
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
return callback
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# Global server instance for singleton pattern
|
|
253
|
+
_server_instance: ProgressServer | None = None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def get_progress_server(config: ProgressServerConfig | None = None) -> ProgressServer:
|
|
257
|
+
"""Get or create the global progress server instance."""
|
|
258
|
+
global _server_instance
|
|
259
|
+
|
|
260
|
+
if _server_instance is None:
|
|
261
|
+
_server_instance = ProgressServer(config)
|
|
262
|
+
|
|
263
|
+
return _server_instance
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@asynccontextmanager
|
|
267
|
+
async def progress_server_context(config: ProgressServerConfig | None = None):
|
|
268
|
+
"""Context manager for running the progress server.
|
|
269
|
+
|
|
270
|
+
Usage:
|
|
271
|
+
async with progress_server_context() as server:
|
|
272
|
+
tracker = server.create_tracker(...)
|
|
273
|
+
# Run workflow with tracker
|
|
274
|
+
"""
|
|
275
|
+
server = get_progress_server(config)
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
await server.start()
|
|
279
|
+
yield server
|
|
280
|
+
finally:
|
|
281
|
+
await server.stop()
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
async def run_server(host: str = "localhost", port: int = 8766) -> None:
|
|
285
|
+
"""Run the progress server standalone.
|
|
286
|
+
|
|
287
|
+
Can be run as: python -m attune.workflows.progress_server
|
|
288
|
+
"""
|
|
289
|
+
config = ProgressServerConfig(host=host, port=port)
|
|
290
|
+
server = ProgressServer(config)
|
|
291
|
+
|
|
292
|
+
# Handle shutdown signals
|
|
293
|
+
loop = asyncio.get_event_loop()
|
|
294
|
+
for sig in (signal.SIGTERM, signal.SIGINT):
|
|
295
|
+
loop.add_signal_handler(sig, lambda: asyncio.create_task(server.stop()))
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
await server.start()
|
|
299
|
+
print(f"Progress server running on ws://{host}:{port}")
|
|
300
|
+
print("Press Ctrl+C to stop")
|
|
301
|
+
|
|
302
|
+
# Keep running until stopped
|
|
303
|
+
while server._running:
|
|
304
|
+
await asyncio.sleep(1)
|
|
305
|
+
|
|
306
|
+
except KeyboardInterrupt:
|
|
307
|
+
pass
|
|
308
|
+
finally:
|
|
309
|
+
await server.stop()
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# CLI entry point
|
|
313
|
+
if __name__ == "__main__":
|
|
314
|
+
import argparse
|
|
315
|
+
|
|
316
|
+
parser = argparse.ArgumentParser(description="Empathy Progress WebSocket Server")
|
|
317
|
+
parser.add_argument("--host", default="localhost", help="Host to bind to")
|
|
318
|
+
parser.add_argument("--port", type=int, default=8766, help="Port to listen on")
|
|
319
|
+
|
|
320
|
+
args = parser.parse_args()
|
|
321
|
+
|
|
322
|
+
asyncio.run(run_server(host=args.host, port=args.port))
|