claude-mpm 4.1.1__py3-none-any.whl → 4.1.2__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.
- claude_mpm/BUILD_NUMBER +1 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/__main__.py +1 -1
- claude_mpm/agents/BASE_PM.md +74 -46
- claude_mpm/agents/INSTRUCTIONS.md +11 -153
- claude_mpm/agents/WORKFLOW.md +61 -321
- claude_mpm/agents/__init__.py +11 -11
- claude_mpm/agents/agent_loader.py +23 -20
- claude_mpm/agents/agent_loader_integration.py +1 -1
- claude_mpm/agents/agents_metadata.py +27 -0
- claude_mpm/agents/async_agent_loader.py +5 -8
- claude_mpm/agents/base_agent_loader.py +36 -25
- claude_mpm/agents/frontmatter_validator.py +6 -6
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +9 -9
- claude_mpm/agents/templates/api_qa.json +47 -2
- claude_mpm/agents/templates/imagemagick.json +256 -0
- claude_mpm/agents/templates/qa.json +41 -2
- claude_mpm/agents/templates/ticketing.json +5 -5
- claude_mpm/agents/templates/web_qa.json +50 -2
- claude_mpm/cli/__init__.py +51 -46
- claude_mpm/cli/__main__.py +1 -1
- claude_mpm/cli/commands/__init__.py +10 -12
- claude_mpm/cli/commands/agent_manager.py +186 -181
- claude_mpm/cli/commands/agents.py +271 -268
- claude_mpm/cli/commands/aggregate.py +30 -29
- claude_mpm/cli/commands/cleanup.py +50 -44
- claude_mpm/cli/commands/cleanup_orphaned_agents.py +25 -25
- claude_mpm/cli/commands/config.py +162 -127
- claude_mpm/cli/commands/doctor.py +52 -62
- claude_mpm/cli/commands/info.py +37 -25
- claude_mpm/cli/commands/mcp.py +3 -7
- claude_mpm/cli/commands/mcp_command_router.py +14 -18
- claude_mpm/cli/commands/mcp_install_commands.py +28 -23
- claude_mpm/cli/commands/mcp_pipx_config.py +58 -49
- claude_mpm/cli/commands/mcp_server_commands.py +23 -17
- claude_mpm/cli/commands/memory.py +192 -141
- claude_mpm/cli/commands/monitor.py +117 -88
- claude_mpm/cli/commands/run.py +120 -84
- claude_mpm/cli/commands/run_config_checker.py +4 -5
- claude_mpm/cli/commands/socketio_monitor.py +17 -19
- claude_mpm/cli/commands/tickets.py +92 -92
- claude_mpm/cli/parser.py +1 -5
- claude_mpm/cli/parsers/__init__.py +1 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +50 -98
- claude_mpm/cli/parsers/agents_parser.py +2 -3
- claude_mpm/cli/parsers/base_parser.py +7 -5
- claude_mpm/cli/parsers/mcp_parser.py +4 -2
- claude_mpm/cli/parsers/monitor_parser.py +26 -18
- claude_mpm/cli/shared/__init__.py +10 -10
- claude_mpm/cli/shared/argument_patterns.py +57 -71
- claude_mpm/cli/shared/base_command.py +61 -53
- claude_mpm/cli/shared/error_handling.py +62 -58
- claude_mpm/cli/shared/output_formatters.py +78 -77
- claude_mpm/cli/startup_logging.py +204 -172
- claude_mpm/cli/utils.py +10 -11
- claude_mpm/cli_module/__init__.py +1 -1
- claude_mpm/cli_module/args.py +1 -1
- claude_mpm/cli_module/migration_example.py +5 -5
- claude_mpm/config/__init__.py +9 -9
- claude_mpm/config/agent_config.py +15 -14
- claude_mpm/config/experimental_features.py +4 -4
- claude_mpm/config/paths.py +0 -1
- claude_mpm/config/socketio_config.py +5 -6
- claude_mpm/constants.py +1 -2
- claude_mpm/core/__init__.py +8 -8
- claude_mpm/core/agent_name_normalizer.py +1 -1
- claude_mpm/core/agent_registry.py +20 -23
- claude_mpm/core/agent_session_manager.py +3 -3
- claude_mpm/core/base_service.py +7 -15
- claude_mpm/core/cache.py +4 -6
- claude_mpm/core/claude_runner.py +85 -113
- claude_mpm/core/config.py +43 -28
- claude_mpm/core/config_aliases.py +0 -9
- claude_mpm/core/config_constants.py +52 -30
- claude_mpm/core/constants.py +0 -1
- claude_mpm/core/container.py +18 -27
- claude_mpm/core/exceptions.py +2 -2
- claude_mpm/core/factories.py +10 -12
- claude_mpm/core/framework_loader.py +581 -280
- claude_mpm/core/hook_manager.py +26 -22
- claude_mpm/core/hook_performance_config.py +58 -47
- claude_mpm/core/injectable_service.py +1 -1
- claude_mpm/core/interactive_session.py +61 -152
- claude_mpm/core/interfaces.py +1 -100
- claude_mpm/core/lazy.py +5 -5
- claude_mpm/core/log_manager.py +587 -0
- claude_mpm/core/logger.py +125 -8
- claude_mpm/core/logging_config.py +15 -15
- claude_mpm/core/minimal_framework_loader.py +5 -8
- claude_mpm/core/oneshot_session.py +15 -33
- claude_mpm/core/optimized_agent_loader.py +4 -6
- claude_mpm/core/optimized_startup.py +2 -1
- claude_mpm/core/output_style_manager.py +147 -106
- claude_mpm/core/pm_hook_interceptor.py +0 -1
- claude_mpm/core/service_registry.py +11 -8
- claude_mpm/core/session_manager.py +1 -2
- claude_mpm/core/shared/__init__.py +1 -1
- claude_mpm/core/shared/config_loader.py +101 -97
- claude_mpm/core/shared/path_resolver.py +72 -68
- claude_mpm/core/shared/singleton_manager.py +56 -50
- claude_mpm/core/socketio_pool.py +26 -6
- claude_mpm/core/tool_access_control.py +4 -5
- claude_mpm/core/typing_utils.py +50 -59
- claude_mpm/core/unified_agent_registry.py +14 -19
- claude_mpm/core/unified_config.py +4 -6
- claude_mpm/core/unified_paths.py +197 -109
- claude_mpm/dashboard/open_dashboard.py +2 -4
- claude_mpm/experimental/cli_enhancements.py +51 -36
- claude_mpm/generators/agent_profile_generator.py +2 -4
- claude_mpm/hooks/base_hook.py +1 -2
- claude_mpm/hooks/claude_hooks/connection_pool.py +72 -26
- claude_mpm/hooks/claude_hooks/event_handlers.py +93 -38
- claude_mpm/hooks/claude_hooks/hook_handler.py +130 -76
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +104 -77
- claude_mpm/hooks/claude_hooks/memory_integration.py +2 -4
- claude_mpm/hooks/claude_hooks/response_tracking.py +15 -11
- claude_mpm/hooks/claude_hooks/tool_analysis.py +12 -18
- claude_mpm/hooks/memory_integration_hook.py +5 -5
- claude_mpm/hooks/tool_call_interceptor.py +1 -1
- claude_mpm/hooks/validation_hooks.py +4 -4
- claude_mpm/init.py +4 -9
- claude_mpm/models/__init__.py +2 -2
- claude_mpm/models/agent_session.py +11 -14
- claude_mpm/scripts/mcp_server.py +20 -11
- claude_mpm/scripts/mcp_wrapper.py +5 -5
- claude_mpm/scripts/mpm_doctor.py +321 -0
- claude_mpm/scripts/socketio_daemon.py +28 -25
- claude_mpm/scripts/socketio_daemon_hardened.py +298 -258
- claude_mpm/scripts/socketio_server_manager.py +116 -95
- claude_mpm/services/__init__.py +49 -49
- claude_mpm/services/agent_capabilities_service.py +12 -18
- claude_mpm/services/agents/__init__.py +22 -22
- claude_mpm/services/agents/agent_builder.py +140 -119
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +9 -9
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +19 -20
- claude_mpm/services/agents/deployment/agent_definition_factory.py +1 -5
- claude_mpm/services/agents/deployment/agent_deployment.py +136 -106
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -8
- claude_mpm/services/agents/deployment/agent_environment_manager.py +2 -7
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +6 -10
- claude_mpm/services/agents/deployment/agent_format_converter.py +11 -15
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +2 -3
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +5 -5
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +13 -19
- claude_mpm/services/agents/deployment/agent_restore_handler.py +0 -1
- claude_mpm/services/agents/deployment/agent_template_builder.py +26 -35
- claude_mpm/services/agents/deployment/agent_validator.py +0 -1
- claude_mpm/services/agents/deployment/agent_version_manager.py +7 -9
- claude_mpm/services/agents/deployment/agent_versioning.py +3 -3
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +6 -7
- claude_mpm/services/agents/deployment/async_agent_deployment.py +51 -38
- claude_mpm/services/agents/deployment/config/__init__.py +1 -1
- claude_mpm/services/agents/deployment/config/deployment_config.py +7 -8
- claude_mpm/services/agents/deployment/deployment_type_detector.py +1 -1
- claude_mpm/services/agents/deployment/deployment_wrapper.py +18 -18
- claude_mpm/services/agents/deployment/facade/__init__.py +1 -1
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +0 -3
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +3 -4
- claude_mpm/services/agents/deployment/interface_adapter.py +5 -7
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +345 -276
- claude_mpm/services/agents/deployment/pipeline/__init__.py +2 -2
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +6 -4
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +3 -3
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +2 -2
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +14 -13
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +0 -1
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +8 -9
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +1 -1
- claude_mpm/services/agents/deployment/processors/__init__.py +1 -1
- claude_mpm/services/agents/deployment/processors/agent_processor.py +20 -16
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +5 -12
- claude_mpm/services/agents/deployment/results/__init__.py +1 -1
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +1 -1
- claude_mpm/services/agents/deployment/strategies/__init__.py +2 -2
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +1 -7
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +1 -4
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +2 -3
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +3 -7
- claude_mpm/services/agents/deployment/validation/__init__.py +1 -1
- claude_mpm/services/agents/deployment/validation/agent_validator.py +1 -1
- claude_mpm/services/agents/deployment/validation/template_validator.py +2 -2
- claude_mpm/services/agents/deployment/validation/validation_result.py +2 -6
- claude_mpm/services/agents/loading/__init__.py +1 -1
- claude_mpm/services/agents/loading/agent_profile_loader.py +6 -12
- claude_mpm/services/agents/loading/base_agent_manager.py +5 -5
- claude_mpm/services/agents/loading/framework_agent_loader.py +2 -4
- claude_mpm/services/agents/management/__init__.py +1 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -3
- claude_mpm/services/agents/management/agent_management_service.py +5 -9
- claude_mpm/services/agents/memory/__init__.py +4 -4
- claude_mpm/services/agents/memory/agent_memory_manager.py +280 -160
- claude_mpm/services/agents/memory/agent_persistence_service.py +0 -2
- claude_mpm/services/agents/memory/content_manager.py +44 -38
- claude_mpm/services/agents/memory/template_generator.py +4 -6
- claude_mpm/services/agents/registry/__init__.py +10 -6
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +30 -27
- claude_mpm/services/agents/registry/modification_tracker.py +3 -6
- claude_mpm/services/async_session_logger.py +1 -2
- claude_mpm/services/claude_session_logger.py +1 -2
- claude_mpm/services/command_deployment_service.py +173 -0
- claude_mpm/services/command_handler_service.py +20 -22
- claude_mpm/services/core/__init__.py +25 -25
- claude_mpm/services/core/base.py +0 -5
- claude_mpm/services/core/interfaces/__init__.py +32 -32
- claude_mpm/services/core/interfaces/agent.py +0 -21
- claude_mpm/services/core/interfaces/communication.py +0 -27
- claude_mpm/services/core/interfaces/infrastructure.py +0 -56
- claude_mpm/services/core/interfaces/service.py +0 -29
- claude_mpm/services/diagnostics/__init__.py +1 -1
- claude_mpm/services/diagnostics/checks/__init__.py +6 -6
- claude_mpm/services/diagnostics/checks/agent_check.py +89 -80
- claude_mpm/services/diagnostics/checks/base_check.py +12 -16
- claude_mpm/services/diagnostics/checks/claude_desktop_check.py +84 -81
- claude_mpm/services/diagnostics/checks/common_issues_check.py +99 -91
- claude_mpm/services/diagnostics/checks/configuration_check.py +82 -77
- claude_mpm/services/diagnostics/checks/filesystem_check.py +67 -68
- claude_mpm/services/diagnostics/checks/installation_check.py +254 -94
- claude_mpm/services/diagnostics/checks/mcp_check.py +90 -88
- claude_mpm/services/diagnostics/checks/monitor_check.py +75 -76
- claude_mpm/services/diagnostics/checks/startup_log_check.py +67 -73
- claude_mpm/services/diagnostics/diagnostic_runner.py +67 -59
- claude_mpm/services/diagnostics/doctor_reporter.py +107 -70
- claude_mpm/services/diagnostics/models.py +21 -19
- claude_mpm/services/event_aggregator.py +10 -17
- claude_mpm/services/event_bus/__init__.py +1 -1
- claude_mpm/services/event_bus/config.py +54 -35
- claude_mpm/services/event_bus/event_bus.py +76 -71
- claude_mpm/services/event_bus/relay.py +74 -64
- claude_mpm/services/events/__init__.py +11 -11
- claude_mpm/services/events/consumers/__init__.py +3 -3
- claude_mpm/services/events/consumers/dead_letter.py +71 -63
- claude_mpm/services/events/consumers/logging.py +39 -37
- claude_mpm/services/events/consumers/metrics.py +56 -57
- claude_mpm/services/events/consumers/socketio.py +82 -81
- claude_mpm/services/events/core.py +110 -99
- claude_mpm/services/events/interfaces.py +56 -72
- claude_mpm/services/events/producers/__init__.py +1 -1
- claude_mpm/services/events/producers/hook.py +38 -38
- claude_mpm/services/events/producers/system.py +46 -44
- claude_mpm/services/exceptions.py +81 -80
- claude_mpm/services/framework_claude_md_generator/__init__.py +2 -4
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -5
- claude_mpm/services/framework_claude_md_generator/content_validator.py +1 -1
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +4 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +0 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +0 -2
- claude_mpm/services/framework_claude_md_generator/version_manager.py +4 -5
- claude_mpm/services/hook_service.py +6 -9
- claude_mpm/services/infrastructure/__init__.py +1 -1
- claude_mpm/services/infrastructure/context_preservation.py +8 -12
- claude_mpm/services/infrastructure/monitoring.py +21 -23
- claude_mpm/services/mcp_gateway/__init__.py +37 -37
- claude_mpm/services/mcp_gateway/auto_configure.py +95 -103
- claude_mpm/services/mcp_gateway/config/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/config/config_loader.py +23 -25
- claude_mpm/services/mcp_gateway/config/config_schema.py +5 -5
- claude_mpm/services/mcp_gateway/config/configuration.py +9 -6
- claude_mpm/services/mcp_gateway/core/__init__.py +10 -10
- claude_mpm/services/mcp_gateway/core/base.py +0 -3
- claude_mpm/services/mcp_gateway/core/interfaces.py +1 -38
- claude_mpm/services/mcp_gateway/core/process_pool.py +99 -93
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +65 -62
- claude_mpm/services/mcp_gateway/core/startup_verification.py +75 -74
- claude_mpm/services/mcp_gateway/main.py +2 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +5 -8
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +1 -1
- claude_mpm/services/mcp_gateway/server/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +12 -19
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +4 -3
- claude_mpm/services/mcp_gateway/server/stdio_server.py +79 -71
- claude_mpm/services/mcp_gateway/tools/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +5 -6
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +13 -22
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +79 -78
- claude_mpm/services/mcp_gateway/tools/hello_world.py +12 -14
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +42 -49
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +51 -55
- claude_mpm/services/memory/__init__.py +3 -3
- claude_mpm/services/memory/builder.py +3 -6
- claude_mpm/services/memory/cache/__init__.py +1 -1
- claude_mpm/services/memory/cache/shared_prompt_cache.py +3 -5
- claude_mpm/services/memory/cache/simple_cache.py +1 -1
- claude_mpm/services/memory/indexed_memory.py +5 -7
- claude_mpm/services/memory/optimizer.py +7 -10
- claude_mpm/services/memory/router.py +8 -9
- claude_mpm/services/memory_hook_service.py +48 -34
- claude_mpm/services/monitor_build_service.py +77 -73
- claude_mpm/services/port_manager.py +130 -108
- claude_mpm/services/project/analyzer.py +12 -10
- claude_mpm/services/project/registry.py +11 -11
- claude_mpm/services/recovery_manager.py +10 -19
- claude_mpm/services/response_tracker.py +0 -1
- claude_mpm/services/runner_configuration_service.py +19 -20
- claude_mpm/services/session_management_service.py +7 -11
- claude_mpm/services/shared/__init__.py +1 -1
- claude_mpm/services/shared/async_service_base.py +58 -50
- claude_mpm/services/shared/config_service_base.py +73 -67
- claude_mpm/services/shared/lifecycle_service_base.py +82 -78
- claude_mpm/services/shared/manager_base.py +94 -82
- claude_mpm/services/shared/service_factory.py +96 -98
- claude_mpm/services/socketio/__init__.py +3 -3
- claude_mpm/services/socketio/client_proxy.py +5 -5
- claude_mpm/services/socketio/event_normalizer.py +199 -181
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +5 -4
- claude_mpm/services/socketio/handlers/connection.py +163 -136
- claude_mpm/services/socketio/handlers/file.py +13 -14
- claude_mpm/services/socketio/handlers/git.py +12 -7
- claude_mpm/services/socketio/handlers/hook.py +49 -44
- claude_mpm/services/socketio/handlers/memory.py +0 -1
- claude_mpm/services/socketio/handlers/project.py +0 -1
- claude_mpm/services/socketio/handlers/registry.py +37 -19
- claude_mpm/services/socketio/migration_utils.py +98 -84
- claude_mpm/services/socketio/server/__init__.py +1 -1
- claude_mpm/services/socketio/server/broadcaster.py +81 -87
- claude_mpm/services/socketio/server/core.py +65 -54
- claude_mpm/services/socketio/server/eventbus_integration.py +95 -56
- claude_mpm/services/socketio/server/main.py +64 -38
- claude_mpm/services/socketio_client_manager.py +10 -12
- claude_mpm/services/subprocess_launcher_service.py +4 -7
- claude_mpm/services/system_instructions_service.py +13 -14
- claude_mpm/services/ticket_manager.py +2 -2
- claude_mpm/services/utility_service.py +5 -13
- claude_mpm/services/version_control/__init__.py +16 -16
- claude_mpm/services/version_control/branch_strategy.py +5 -8
- claude_mpm/services/version_control/conflict_resolution.py +9 -23
- claude_mpm/services/version_control/git_operations.py +5 -7
- claude_mpm/services/version_control/semantic_versioning.py +16 -17
- claude_mpm/services/version_control/version_parser.py +13 -18
- claude_mpm/services/version_service.py +10 -11
- claude_mpm/storage/__init__.py +1 -1
- claude_mpm/storage/state_storage.py +22 -28
- claude_mpm/utils/__init__.py +6 -6
- claude_mpm/utils/agent_dependency_loader.py +47 -33
- claude_mpm/utils/config_manager.py +11 -14
- claude_mpm/utils/dependency_cache.py +1 -1
- claude_mpm/utils/dependency_manager.py +13 -17
- claude_mpm/utils/dependency_strategies.py +8 -10
- claude_mpm/utils/environment_context.py +3 -9
- claude_mpm/utils/error_handler.py +3 -13
- claude_mpm/utils/file_utils.py +1 -1
- claude_mpm/utils/path_operations.py +8 -12
- claude_mpm/utils/robust_installer.py +110 -33
- claude_mpm/utils/subprocess_utils.py +5 -6
- claude_mpm/validation/agent_validator.py +3 -6
- claude_mpm/validation/frontmatter_validator.py +1 -1
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/METADATA +1 -1
- claude_mpm-4.1.2.dist-info/RECORD +498 -0
- claude_mpm-4.1.1.dist-info/RECORD +0 -494
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/top_level.txt +0 -0
|
@@ -12,7 +12,6 @@ Key Features:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import json
|
|
15
|
-
import logging
|
|
16
15
|
import os
|
|
17
16
|
from pathlib import Path
|
|
18
17
|
from typing import Any, Dict, List, Optional, Tuple
|
|
@@ -26,112 +25,114 @@ from .agent_version_manager import AgentVersionManager
|
|
|
26
25
|
|
|
27
26
|
class MultiSourceAgentDeploymentService:
|
|
28
27
|
"""Service for deploying agents from multiple sources with version comparison.
|
|
29
|
-
|
|
28
|
+
|
|
30
29
|
This service ensures that the highest version of each agent is deployed,
|
|
31
30
|
regardless of whether it comes from system templates, project agents, or
|
|
32
31
|
user agents.
|
|
33
|
-
|
|
32
|
+
|
|
34
33
|
WHY: The current system processes agents from a single source at a time,
|
|
35
34
|
which can result in lower version agents being deployed if they exist in
|
|
36
35
|
a higher priority source. This service fixes that by comparing versions
|
|
37
36
|
across all sources.
|
|
38
37
|
"""
|
|
39
|
-
|
|
38
|
+
|
|
40
39
|
def __init__(self):
|
|
41
40
|
"""Initialize the multi-source deployment service."""
|
|
42
41
|
self.logger = get_logger(__name__)
|
|
43
42
|
self.version_manager = AgentVersionManager()
|
|
44
|
-
|
|
43
|
+
|
|
45
44
|
def discover_agents_from_all_sources(
|
|
46
|
-
self,
|
|
45
|
+
self,
|
|
47
46
|
system_templates_dir: Optional[Path] = None,
|
|
48
47
|
project_agents_dir: Optional[Path] = None,
|
|
49
48
|
user_agents_dir: Optional[Path] = None,
|
|
50
|
-
working_directory: Optional[Path] = None
|
|
49
|
+
working_directory: Optional[Path] = None,
|
|
51
50
|
) -> Dict[str, List[Dict[str, Any]]]:
|
|
52
51
|
"""Discover agents from all available sources.
|
|
53
|
-
|
|
52
|
+
|
|
54
53
|
Args:
|
|
55
54
|
system_templates_dir: Directory containing system agent templates
|
|
56
55
|
project_agents_dir: Directory containing project-specific agents
|
|
57
56
|
user_agents_dir: Directory containing user custom agents
|
|
58
57
|
working_directory: Current working directory for finding project agents
|
|
59
|
-
|
|
58
|
+
|
|
60
59
|
Returns:
|
|
61
60
|
Dictionary mapping agent names to list of agent info from different sources
|
|
62
61
|
"""
|
|
63
62
|
agents_by_name = {}
|
|
64
|
-
|
|
63
|
+
|
|
65
64
|
# Determine directories if not provided
|
|
66
65
|
if not system_templates_dir:
|
|
67
66
|
# Use default system templates location
|
|
68
67
|
from claude_mpm.config.paths import paths
|
|
68
|
+
|
|
69
69
|
system_templates_dir = paths.agents_dir / "templates"
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
if not project_agents_dir and working_directory:
|
|
72
72
|
# Check for project agents in working directory
|
|
73
73
|
project_agents_dir = working_directory / ".claude-mpm" / "agents"
|
|
74
74
|
if not project_agents_dir.exists():
|
|
75
75
|
project_agents_dir = None
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
if not user_agents_dir:
|
|
78
78
|
# Check for user agents in home directory
|
|
79
79
|
user_agents_dir = Path.home() / ".claude-mpm" / "agents"
|
|
80
80
|
if not user_agents_dir.exists():
|
|
81
81
|
user_agents_dir = None
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
# Discover agents from each source
|
|
84
84
|
sources = [
|
|
85
85
|
("system", system_templates_dir),
|
|
86
86
|
("project", project_agents_dir),
|
|
87
|
-
("user", user_agents_dir)
|
|
87
|
+
("user", user_agents_dir),
|
|
88
88
|
]
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
for source_name, source_dir in sources:
|
|
91
91
|
if source_dir and source_dir.exists():
|
|
92
|
-
self.logger.debug(
|
|
92
|
+
self.logger.debug(
|
|
93
|
+
f"Discovering agents from {source_name} source: {source_dir}"
|
|
94
|
+
)
|
|
93
95
|
discovery_service = AgentDiscoveryService(source_dir)
|
|
94
96
|
agents = discovery_service.list_available_agents()
|
|
95
|
-
|
|
97
|
+
|
|
96
98
|
for agent_info in agents:
|
|
97
99
|
agent_name = agent_info.get("name")
|
|
98
100
|
if not agent_name:
|
|
99
101
|
continue
|
|
100
|
-
|
|
102
|
+
|
|
101
103
|
# Add source information
|
|
102
104
|
agent_info["source"] = source_name
|
|
103
105
|
agent_info["source_dir"] = str(source_dir)
|
|
104
|
-
|
|
106
|
+
|
|
105
107
|
# Initialize list if this is the first occurrence of this agent
|
|
106
108
|
if agent_name not in agents_by_name:
|
|
107
109
|
agents_by_name[agent_name] = []
|
|
108
|
-
|
|
110
|
+
|
|
109
111
|
agents_by_name[agent_name].append(agent_info)
|
|
110
|
-
|
|
112
|
+
|
|
111
113
|
self.logger.info(
|
|
112
114
|
f"Discovered {len(agents)} agents from {source_name} source"
|
|
113
115
|
)
|
|
114
|
-
|
|
116
|
+
|
|
115
117
|
return agents_by_name
|
|
116
|
-
|
|
118
|
+
|
|
117
119
|
def select_highest_version_agents(
|
|
118
|
-
self,
|
|
119
|
-
agents_by_name: Dict[str, List[Dict[str, Any]]]
|
|
120
|
+
self, agents_by_name: Dict[str, List[Dict[str, Any]]]
|
|
120
121
|
) -> Dict[str, Dict[str, Any]]:
|
|
121
122
|
"""Select the highest version agent from multiple sources.
|
|
122
|
-
|
|
123
|
+
|
|
123
124
|
Args:
|
|
124
125
|
agents_by_name: Dictionary mapping agent names to list of agent info
|
|
125
|
-
|
|
126
|
+
|
|
126
127
|
Returns:
|
|
127
128
|
Dictionary mapping agent names to the highest version agent info
|
|
128
129
|
"""
|
|
129
130
|
selected_agents = {}
|
|
130
|
-
|
|
131
|
+
|
|
131
132
|
for agent_name, agent_versions in agents_by_name.items():
|
|
132
133
|
if not agent_versions:
|
|
133
134
|
continue
|
|
134
|
-
|
|
135
|
+
|
|
135
136
|
# If only one version exists, use it
|
|
136
137
|
if len(agent_versions) == 1:
|
|
137
138
|
selected_agents[agent_name] = agent_versions[0]
|
|
@@ -139,51 +140,61 @@ class MultiSourceAgentDeploymentService:
|
|
|
139
140
|
f"Agent '{agent_name}' has single source: {agent_versions[0]['source']}"
|
|
140
141
|
)
|
|
141
142
|
continue
|
|
142
|
-
|
|
143
|
+
|
|
143
144
|
# Compare versions to find the highest
|
|
144
145
|
highest_version_agent = None
|
|
145
146
|
highest_version_tuple = (0, 0, 0)
|
|
146
|
-
|
|
147
|
+
|
|
147
148
|
for agent_info in agent_versions:
|
|
148
149
|
version_str = agent_info.get("version", "0.0.0")
|
|
149
150
|
version_tuple = self.version_manager.parse_version(version_str)
|
|
150
|
-
|
|
151
|
+
|
|
151
152
|
self.logger.debug(
|
|
152
153
|
f"Agent '{agent_name}' from {agent_info['source']}: "
|
|
153
154
|
f"version {version_str} -> {version_tuple}"
|
|
154
155
|
)
|
|
155
|
-
|
|
156
|
+
|
|
156
157
|
# Compare with current highest
|
|
157
|
-
if
|
|
158
|
+
if (
|
|
159
|
+
self.version_manager.compare_versions(
|
|
160
|
+
version_tuple, highest_version_tuple
|
|
161
|
+
)
|
|
162
|
+
> 0
|
|
163
|
+
):
|
|
158
164
|
highest_version_agent = agent_info
|
|
159
165
|
highest_version_tuple = version_tuple
|
|
160
|
-
|
|
166
|
+
|
|
161
167
|
if highest_version_agent:
|
|
162
168
|
selected_agents[agent_name] = highest_version_agent
|
|
163
169
|
self.logger.info(
|
|
164
170
|
f"Selected agent '{agent_name}' version {highest_version_agent['version']} "
|
|
165
171
|
f"from {highest_version_agent['source']} source"
|
|
166
172
|
)
|
|
167
|
-
|
|
173
|
+
|
|
168
174
|
# Log if a higher priority source was overridden by version
|
|
169
175
|
for other_agent in agent_versions:
|
|
170
176
|
if other_agent != highest_version_agent:
|
|
171
|
-
|
|
177
|
+
self.version_manager.parse_version(
|
|
172
178
|
other_agent.get("version", "0.0.0")
|
|
173
179
|
)
|
|
174
|
-
if
|
|
180
|
+
if (
|
|
181
|
+
other_agent["source"] == "project"
|
|
182
|
+
and highest_version_agent["source"] == "system"
|
|
183
|
+
):
|
|
175
184
|
self.logger.warning(
|
|
176
185
|
f"Project agent '{agent_name}' v{other_agent['version']} "
|
|
177
186
|
f"overridden by higher system version v{highest_version_agent['version']}"
|
|
178
187
|
)
|
|
179
|
-
elif other_agent["source"] == "user" and highest_version_agent[
|
|
188
|
+
elif other_agent["source"] == "user" and highest_version_agent[
|
|
189
|
+
"source"
|
|
190
|
+
] in ["system", "project"]:
|
|
180
191
|
self.logger.warning(
|
|
181
192
|
f"User agent '{agent_name}' v{other_agent['version']} "
|
|
182
193
|
f"overridden by higher {highest_version_agent['source']} version v{highest_version_agent['version']}"
|
|
183
194
|
)
|
|
184
|
-
|
|
195
|
+
|
|
185
196
|
return selected_agents
|
|
186
|
-
|
|
197
|
+
|
|
187
198
|
def get_agents_for_deployment(
|
|
188
199
|
self,
|
|
189
200
|
system_templates_dir: Optional[Path] = None,
|
|
@@ -192,10 +203,10 @@ class MultiSourceAgentDeploymentService:
|
|
|
192
203
|
working_directory: Optional[Path] = None,
|
|
193
204
|
excluded_agents: Optional[List[str]] = None,
|
|
194
205
|
config: Optional[Config] = None,
|
|
195
|
-
cleanup_outdated: bool = True
|
|
206
|
+
cleanup_outdated: bool = True,
|
|
196
207
|
) -> Tuple[Dict[str, Path], Dict[str, str], Dict[str, Any]]:
|
|
197
208
|
"""Get the highest version agents from all sources for deployment.
|
|
198
|
-
|
|
209
|
+
|
|
199
210
|
Args:
|
|
200
211
|
system_templates_dir: Directory containing system agent templates
|
|
201
212
|
project_agents_dir: Directory containing project-specific agents
|
|
@@ -204,7 +215,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
204
215
|
excluded_agents: List of agent names to exclude from deployment
|
|
205
216
|
config: Configuration object for additional filtering
|
|
206
217
|
cleanup_outdated: Whether to cleanup outdated user agents (default: True)
|
|
207
|
-
|
|
218
|
+
|
|
208
219
|
Returns:
|
|
209
220
|
Tuple of:
|
|
210
221
|
- Dictionary mapping agent names to template file paths
|
|
@@ -216,48 +227,52 @@ class MultiSourceAgentDeploymentService:
|
|
|
216
227
|
system_templates_dir=system_templates_dir,
|
|
217
228
|
project_agents_dir=project_agents_dir,
|
|
218
229
|
user_agents_dir=user_agents_dir,
|
|
219
|
-
working_directory=working_directory
|
|
230
|
+
working_directory=working_directory,
|
|
220
231
|
)
|
|
221
|
-
|
|
232
|
+
|
|
222
233
|
# Select highest version for each agent
|
|
223
234
|
selected_agents = self.select_highest_version_agents(agents_by_name)
|
|
224
|
-
|
|
235
|
+
|
|
225
236
|
# Clean up outdated user agents if enabled
|
|
226
237
|
cleanup_results = {"removed": [], "preserved": [], "errors": []}
|
|
227
238
|
if cleanup_outdated:
|
|
228
239
|
# Check if cleanup is enabled in config or environment
|
|
229
240
|
cleanup_enabled = True
|
|
230
|
-
|
|
241
|
+
|
|
231
242
|
# Check environment variable first (for CI/CD and testing)
|
|
232
243
|
env_cleanup = os.environ.get("CLAUDE_MPM_CLEANUP_USER_AGENTS", "").lower()
|
|
233
244
|
if env_cleanup in ["false", "0", "no", "disabled"]:
|
|
234
245
|
cleanup_enabled = False
|
|
235
|
-
self.logger.debug(
|
|
236
|
-
|
|
246
|
+
self.logger.debug(
|
|
247
|
+
"User agent cleanup disabled via environment variable"
|
|
248
|
+
)
|
|
249
|
+
|
|
237
250
|
# Check config if environment doesn't disable it
|
|
238
251
|
if cleanup_enabled and config:
|
|
239
|
-
cleanup_enabled = config.get(
|
|
240
|
-
|
|
252
|
+
cleanup_enabled = config.get(
|
|
253
|
+
"agent_deployment.cleanup_outdated_user_agents", True
|
|
254
|
+
)
|
|
255
|
+
|
|
241
256
|
if cleanup_enabled:
|
|
242
257
|
cleanup_results = self.cleanup_outdated_user_agents(
|
|
243
258
|
agents_by_name, selected_agents
|
|
244
259
|
)
|
|
245
|
-
|
|
260
|
+
|
|
246
261
|
# Apply exclusion filters
|
|
247
262
|
if excluded_agents:
|
|
248
263
|
for agent_name in excluded_agents:
|
|
249
264
|
if agent_name in selected_agents:
|
|
250
265
|
self.logger.info(f"Excluding agent '{agent_name}' from deployment")
|
|
251
266
|
del selected_agents[agent_name]
|
|
252
|
-
|
|
267
|
+
|
|
253
268
|
# Apply config-based filtering if provided
|
|
254
269
|
if config:
|
|
255
270
|
selected_agents = self._apply_config_filters(selected_agents, config)
|
|
256
|
-
|
|
271
|
+
|
|
257
272
|
# Create deployment mappings
|
|
258
273
|
agents_to_deploy = {}
|
|
259
274
|
agent_sources = {}
|
|
260
|
-
|
|
275
|
+
|
|
261
276
|
for agent_name, agent_info in selected_agents.items():
|
|
262
277
|
template_path = Path(agent_info["path"])
|
|
263
278
|
if template_path.exists():
|
|
@@ -265,7 +280,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
265
280
|
file_stem = template_path.stem
|
|
266
281
|
agents_to_deploy[file_stem] = template_path
|
|
267
282
|
agent_sources[file_stem] = agent_info["source"]
|
|
268
|
-
|
|
283
|
+
|
|
269
284
|
# Also keep the display name mapping for logging
|
|
270
285
|
if file_stem != agent_name:
|
|
271
286
|
self.logger.debug(f"Mapping '{agent_name}' -> '{file_stem}'")
|
|
@@ -273,93 +288,92 @@ class MultiSourceAgentDeploymentService:
|
|
|
273
288
|
self.logger.warning(
|
|
274
289
|
f"Template file not found for agent '{agent_name}': {template_path}"
|
|
275
290
|
)
|
|
276
|
-
|
|
291
|
+
|
|
277
292
|
self.logger.info(
|
|
278
293
|
f"Selected {len(agents_to_deploy)} agents for deployment "
|
|
279
294
|
f"(system: {sum(1 for s in agent_sources.values() if s == 'system')}, "
|
|
280
295
|
f"project: {sum(1 for s in agent_sources.values() if s == 'project')}, "
|
|
281
296
|
f"user: {sum(1 for s in agent_sources.values() if s == 'user')})"
|
|
282
297
|
)
|
|
283
|
-
|
|
298
|
+
|
|
284
299
|
return agents_to_deploy, agent_sources, cleanup_results
|
|
285
|
-
|
|
300
|
+
|
|
286
301
|
def cleanup_outdated_user_agents(
|
|
287
302
|
self,
|
|
288
303
|
agents_by_name: Dict[str, List[Dict[str, Any]]],
|
|
289
|
-
selected_agents: Dict[str, Dict[str, Any]]
|
|
304
|
+
selected_agents: Dict[str, Dict[str, Any]],
|
|
290
305
|
) -> Dict[str, Any]:
|
|
291
306
|
"""Remove outdated user agents when project or system agents have higher versions.
|
|
292
|
-
|
|
307
|
+
|
|
293
308
|
WHY: When project agents are updated to newer versions, outdated user agent
|
|
294
309
|
copies should be removed to prevent confusion and ensure the latest version
|
|
295
310
|
is always used. User agents with same or higher versions are preserved to
|
|
296
311
|
respect user customizations.
|
|
297
|
-
|
|
312
|
+
|
|
298
313
|
Args:
|
|
299
314
|
agents_by_name: Dictionary mapping agent names to list of agent info from different sources
|
|
300
315
|
selected_agents: Dictionary mapping agent names to the selected highest version agent
|
|
301
|
-
|
|
316
|
+
|
|
302
317
|
Returns:
|
|
303
318
|
Dictionary with cleanup results:
|
|
304
319
|
- removed: List of removed agent info
|
|
305
320
|
- preserved: List of preserved agent info with reasons
|
|
306
321
|
- errors: List of errors during cleanup
|
|
307
322
|
"""
|
|
308
|
-
cleanup_results = {
|
|
309
|
-
|
|
310
|
-
"preserved": [],
|
|
311
|
-
"errors": []
|
|
312
|
-
}
|
|
313
|
-
|
|
323
|
+
cleanup_results = {"removed": [], "preserved": [], "errors": []}
|
|
324
|
+
|
|
314
325
|
# Get user agents directory
|
|
315
326
|
user_agents_dir = Path.home() / ".claude-mpm" / "agents"
|
|
316
|
-
|
|
327
|
+
|
|
317
328
|
# Safety check - only operate on user agents directory
|
|
318
329
|
if not user_agents_dir.exists():
|
|
319
330
|
self.logger.debug("User agents directory does not exist, no cleanup needed")
|
|
320
331
|
return cleanup_results
|
|
321
|
-
|
|
332
|
+
|
|
322
333
|
for agent_name, agent_versions in agents_by_name.items():
|
|
323
334
|
# Skip if only one version exists
|
|
324
335
|
if len(agent_versions) < 2:
|
|
325
336
|
continue
|
|
326
|
-
|
|
337
|
+
|
|
327
338
|
selected = selected_agents.get(agent_name)
|
|
328
339
|
if not selected:
|
|
329
340
|
continue
|
|
330
|
-
|
|
341
|
+
|
|
331
342
|
# Process each version of this agent
|
|
332
343
|
for agent_info in agent_versions:
|
|
333
344
|
# Only consider user agents for cleanup
|
|
334
345
|
if agent_info["source"] != "user":
|
|
335
346
|
continue
|
|
336
|
-
|
|
347
|
+
|
|
337
348
|
# Safety check - ensure path is within user agents directory
|
|
338
349
|
user_agent_path = Path(agent_info["path"])
|
|
339
350
|
try:
|
|
340
351
|
# Resolve paths to compare them safely
|
|
341
352
|
resolved_user_path = user_agent_path.resolve()
|
|
342
353
|
resolved_user_agents_dir = user_agents_dir.resolve()
|
|
343
|
-
|
|
354
|
+
|
|
344
355
|
# Verify the agent is actually in the user agents directory
|
|
345
|
-
if not str(resolved_user_path).startswith(
|
|
356
|
+
if not str(resolved_user_path).startswith(
|
|
357
|
+
str(resolved_user_agents_dir)
|
|
358
|
+
):
|
|
346
359
|
self.logger.warning(
|
|
347
360
|
f"Skipping cleanup for {agent_name}: path {user_agent_path} "
|
|
348
361
|
f"is not within user agents directory"
|
|
349
362
|
)
|
|
350
|
-
cleanup_results["errors"].append(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
363
|
+
cleanup_results["errors"].append(
|
|
364
|
+
{
|
|
365
|
+
"agent": agent_name,
|
|
366
|
+
"error": "Path outside user agents directory",
|
|
367
|
+
}
|
|
368
|
+
)
|
|
354
369
|
continue
|
|
355
370
|
except Exception as e:
|
|
356
371
|
self.logger.error(f"Error resolving paths for {agent_name}: {e}")
|
|
357
|
-
cleanup_results["errors"].append(
|
|
358
|
-
"agent": agent_name,
|
|
359
|
-
|
|
360
|
-
})
|
|
372
|
+
cleanup_results["errors"].append(
|
|
373
|
+
{"agent": agent_name, "error": f"Path resolution error: {e}"}
|
|
374
|
+
)
|
|
361
375
|
continue
|
|
362
|
-
|
|
376
|
+
|
|
363
377
|
# Compare versions
|
|
364
378
|
user_version = self.version_manager.parse_version(
|
|
365
379
|
agent_info.get("version", "0.0.0")
|
|
@@ -367,13 +381,16 @@ class MultiSourceAgentDeploymentService:
|
|
|
367
381
|
selected_version = self.version_manager.parse_version(
|
|
368
382
|
selected.get("version", "0.0.0")
|
|
369
383
|
)
|
|
370
|
-
|
|
384
|
+
|
|
371
385
|
version_comparison = self.version_manager.compare_versions(
|
|
372
386
|
user_version, selected_version
|
|
373
387
|
)
|
|
374
|
-
|
|
388
|
+
|
|
375
389
|
# Determine action based on version comparison and selected source
|
|
376
|
-
if version_comparison < 0 and selected["source"] in [
|
|
390
|
+
if version_comparison < 0 and selected["source"] in [
|
|
391
|
+
"project",
|
|
392
|
+
"system",
|
|
393
|
+
]:
|
|
377
394
|
# User agent has lower version than selected project/system agent - remove it
|
|
378
395
|
if user_agent_path.exists():
|
|
379
396
|
try:
|
|
@@ -384,30 +401,32 @@ class MultiSourceAgentDeploymentService:
|
|
|
384
401
|
f"(superseded by {selected['source']} "
|
|
385
402
|
f"v{self.version_manager.format_version_display(selected_version)})"
|
|
386
403
|
)
|
|
387
|
-
|
|
404
|
+
|
|
388
405
|
# Remove the file
|
|
389
406
|
user_agent_path.unlink()
|
|
390
|
-
|
|
391
|
-
cleanup_results["removed"].append(
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
407
|
+
|
|
408
|
+
cleanup_results["removed"].append(
|
|
409
|
+
{
|
|
410
|
+
"name": agent_name,
|
|
411
|
+
"version": self.version_manager.format_version_display(
|
|
412
|
+
user_version
|
|
413
|
+
),
|
|
414
|
+
"path": str(user_agent_path),
|
|
415
|
+
"reason": f"Superseded by {selected['source']} v{self.version_manager.format_version_display(selected_version)}",
|
|
416
|
+
}
|
|
417
|
+
)
|
|
397
418
|
except PermissionError as e:
|
|
398
419
|
error_msg = f"Permission denied removing {agent_name}: {e}"
|
|
399
420
|
self.logger.error(error_msg)
|
|
400
|
-
cleanup_results["errors"].append(
|
|
401
|
-
"agent": agent_name,
|
|
402
|
-
|
|
403
|
-
})
|
|
421
|
+
cleanup_results["errors"].append(
|
|
422
|
+
{"agent": agent_name, "error": error_msg}
|
|
423
|
+
)
|
|
404
424
|
except Exception as e:
|
|
405
425
|
error_msg = f"Error removing {agent_name}: {e}"
|
|
406
426
|
self.logger.error(error_msg)
|
|
407
|
-
cleanup_results["errors"].append(
|
|
408
|
-
"agent": agent_name,
|
|
409
|
-
|
|
410
|
-
})
|
|
427
|
+
cleanup_results["errors"].append(
|
|
428
|
+
{"agent": agent_name, "error": error_msg}
|
|
429
|
+
)
|
|
411
430
|
else:
|
|
412
431
|
# Preserve the user agent
|
|
413
432
|
if version_comparison >= 0:
|
|
@@ -416,18 +435,22 @@ class MultiSourceAgentDeploymentService:
|
|
|
416
435
|
reason = "User agent is the selected version"
|
|
417
436
|
else:
|
|
418
437
|
reason = "User customization preserved"
|
|
419
|
-
|
|
420
|
-
cleanup_results["preserved"].append(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
438
|
+
|
|
439
|
+
cleanup_results["preserved"].append(
|
|
440
|
+
{
|
|
441
|
+
"name": agent_name,
|
|
442
|
+
"version": self.version_manager.format_version_display(
|
|
443
|
+
user_version
|
|
444
|
+
),
|
|
445
|
+
"reason": reason,
|
|
446
|
+
}
|
|
447
|
+
)
|
|
448
|
+
|
|
426
449
|
self.logger.debug(
|
|
427
450
|
f"Preserving user agent {agent_name} "
|
|
428
451
|
f"v{self.version_manager.format_version_display(user_version)}: {reason}"
|
|
429
452
|
)
|
|
430
|
-
|
|
453
|
+
|
|
431
454
|
# Log cleanup summary
|
|
432
455
|
if cleanup_results["removed"]:
|
|
433
456
|
self.logger.info(
|
|
@@ -441,65 +464,67 @@ class MultiSourceAgentDeploymentService:
|
|
|
441
464
|
self.logger.warning(
|
|
442
465
|
f"Encountered {len(cleanup_results['errors'])} errors during cleanup"
|
|
443
466
|
)
|
|
444
|
-
|
|
467
|
+
|
|
445
468
|
return cleanup_results
|
|
446
|
-
|
|
469
|
+
|
|
447
470
|
def _apply_config_filters(
|
|
448
|
-
self,
|
|
449
|
-
selected_agents: Dict[str, Dict[str, Any]],
|
|
450
|
-
config: Config
|
|
471
|
+
self, selected_agents: Dict[str, Dict[str, Any]], config: Config
|
|
451
472
|
) -> Dict[str, Dict[str, Any]]:
|
|
452
473
|
"""Apply configuration-based filtering to selected agents.
|
|
453
|
-
|
|
474
|
+
|
|
454
475
|
Args:
|
|
455
476
|
selected_agents: Dictionary of selected agents
|
|
456
477
|
config: Configuration object
|
|
457
|
-
|
|
478
|
+
|
|
458
479
|
Returns:
|
|
459
480
|
Filtered dictionary of agents
|
|
460
481
|
"""
|
|
461
482
|
filtered_agents = {}
|
|
462
|
-
|
|
483
|
+
|
|
463
484
|
# Get exclusion patterns from config
|
|
464
485
|
exclusion_patterns = config.get("agent_deployment.exclusion_patterns", [])
|
|
465
|
-
|
|
486
|
+
|
|
466
487
|
# Get environment-specific exclusions
|
|
467
488
|
environment = config.get("environment", "development")
|
|
468
489
|
env_exclusions = config.get(f"agent_deployment.{environment}_exclusions", [])
|
|
469
|
-
|
|
490
|
+
|
|
470
491
|
for agent_name, agent_info in selected_agents.items():
|
|
471
492
|
# Check exclusion patterns
|
|
472
493
|
excluded = False
|
|
473
|
-
|
|
494
|
+
|
|
474
495
|
for pattern in exclusion_patterns:
|
|
475
496
|
if pattern in agent_name:
|
|
476
|
-
self.logger.debug(
|
|
497
|
+
self.logger.debug(
|
|
498
|
+
f"Excluding '{agent_name}' due to pattern '{pattern}'"
|
|
499
|
+
)
|
|
477
500
|
excluded = True
|
|
478
501
|
break
|
|
479
|
-
|
|
502
|
+
|
|
480
503
|
# Check environment exclusions
|
|
481
504
|
if not excluded and agent_name in env_exclusions:
|
|
482
|
-
self.logger.debug(
|
|
505
|
+
self.logger.debug(
|
|
506
|
+
f"Excluding '{agent_name}' due to {environment} environment"
|
|
507
|
+
)
|
|
483
508
|
excluded = True
|
|
484
|
-
|
|
509
|
+
|
|
485
510
|
if not excluded:
|
|
486
511
|
filtered_agents[agent_name] = agent_info
|
|
487
|
-
|
|
512
|
+
|
|
488
513
|
return filtered_agents
|
|
489
|
-
|
|
514
|
+
|
|
490
515
|
def compare_deployed_versions(
|
|
491
516
|
self,
|
|
492
517
|
deployed_agents_dir: Path,
|
|
493
518
|
agents_to_deploy: Dict[str, Path],
|
|
494
|
-
agent_sources: Dict[str, str]
|
|
519
|
+
agent_sources: Dict[str, str],
|
|
495
520
|
) -> Dict[str, Any]:
|
|
496
521
|
"""Compare deployed agent versions with candidates for deployment.
|
|
497
|
-
|
|
522
|
+
|
|
498
523
|
Args:
|
|
499
524
|
deployed_agents_dir: Directory containing currently deployed agents
|
|
500
525
|
agents_to_deploy: Dictionary mapping agent names to template paths
|
|
501
526
|
agent_sources: Dictionary mapping agent names to their sources
|
|
502
|
-
|
|
527
|
+
|
|
503
528
|
Returns:
|
|
504
529
|
Dictionary with comparison results including which agents need updates
|
|
505
530
|
"""
|
|
@@ -510,111 +535,142 @@ class MultiSourceAgentDeploymentService:
|
|
|
510
535
|
"orphaned_agents": [], # Agents without templates
|
|
511
536
|
"version_upgrades": [],
|
|
512
537
|
"version_downgrades": [],
|
|
513
|
-
"source_changes": []
|
|
538
|
+
"source_changes": [],
|
|
514
539
|
}
|
|
515
|
-
|
|
540
|
+
|
|
516
541
|
for agent_name, template_path in agents_to_deploy.items():
|
|
517
542
|
deployed_file = deployed_agents_dir / f"{agent_name}.md"
|
|
518
|
-
|
|
543
|
+
|
|
519
544
|
if not deployed_file.exists():
|
|
520
|
-
comparison_results["new_agents"].append(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
545
|
+
comparison_results["new_agents"].append(
|
|
546
|
+
{
|
|
547
|
+
"name": agent_name,
|
|
548
|
+
"source": agent_sources[agent_name],
|
|
549
|
+
"template": str(template_path),
|
|
550
|
+
}
|
|
551
|
+
)
|
|
525
552
|
comparison_results["needs_update"].append(agent_name)
|
|
526
553
|
continue
|
|
527
|
-
|
|
554
|
+
|
|
528
555
|
# Read template version
|
|
529
556
|
try:
|
|
530
557
|
template_data = json.loads(template_path.read_text())
|
|
531
558
|
metadata = template_data.get("metadata", {})
|
|
532
559
|
template_version = self.version_manager.parse_version(
|
|
533
|
-
template_data.get("agent_version")
|
|
534
|
-
template_data.get("version")
|
|
535
|
-
metadata.get("version", "0.0.0")
|
|
560
|
+
template_data.get("agent_version")
|
|
561
|
+
or template_data.get("version")
|
|
562
|
+
or metadata.get("version", "0.0.0")
|
|
536
563
|
)
|
|
537
564
|
except Exception as e:
|
|
538
565
|
self.logger.warning(f"Error reading template for '{agent_name}': {e}")
|
|
539
566
|
continue
|
|
540
|
-
|
|
567
|
+
|
|
541
568
|
# Read deployed version
|
|
542
569
|
try:
|
|
543
570
|
deployed_content = deployed_file.read_text()
|
|
544
|
-
deployed_version, _, _ =
|
|
545
|
-
|
|
571
|
+
deployed_version, _, _ = (
|
|
572
|
+
self.version_manager.extract_version_from_frontmatter(
|
|
573
|
+
deployed_content
|
|
574
|
+
)
|
|
546
575
|
)
|
|
547
|
-
|
|
576
|
+
|
|
548
577
|
# Extract source from deployed agent if available
|
|
549
578
|
deployed_source = "unknown"
|
|
550
579
|
if "source:" in deployed_content:
|
|
551
580
|
import re
|
|
552
|
-
|
|
581
|
+
|
|
582
|
+
source_match = re.search(
|
|
583
|
+
r"^source:\s*(.+)$", deployed_content, re.MULTILINE
|
|
584
|
+
)
|
|
553
585
|
if source_match:
|
|
554
586
|
deployed_source = source_match.group(1).strip()
|
|
555
|
-
|
|
587
|
+
|
|
556
588
|
# If source is still unknown, try to infer it from deployment context
|
|
557
589
|
if deployed_source == "unknown":
|
|
558
|
-
deployed_source = self._infer_agent_source_from_context(
|
|
590
|
+
deployed_source = self._infer_agent_source_from_context(
|
|
591
|
+
agent_name, deployed_agents_dir
|
|
592
|
+
)
|
|
559
593
|
except Exception as e:
|
|
560
594
|
self.logger.warning(f"Error reading deployed agent '{agent_name}': {e}")
|
|
561
595
|
comparison_results["needs_update"].append(agent_name)
|
|
562
596
|
continue
|
|
563
|
-
|
|
597
|
+
|
|
564
598
|
# Compare versions
|
|
565
599
|
version_comparison = self.version_manager.compare_versions(
|
|
566
600
|
template_version, deployed_version
|
|
567
601
|
)
|
|
568
|
-
|
|
602
|
+
|
|
569
603
|
if version_comparison > 0:
|
|
570
604
|
# Template version is higher
|
|
571
|
-
comparison_results["version_upgrades"].append(
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
605
|
+
comparison_results["version_upgrades"].append(
|
|
606
|
+
{
|
|
607
|
+
"name": agent_name,
|
|
608
|
+
"deployed_version": self.version_manager.format_version_display(
|
|
609
|
+
deployed_version
|
|
610
|
+
),
|
|
611
|
+
"new_version": self.version_manager.format_version_display(
|
|
612
|
+
template_version
|
|
613
|
+
),
|
|
614
|
+
"source": agent_sources[agent_name],
|
|
615
|
+
"previous_source": deployed_source,
|
|
616
|
+
}
|
|
617
|
+
)
|
|
578
618
|
comparison_results["needs_update"].append(agent_name)
|
|
579
|
-
|
|
619
|
+
|
|
580
620
|
if deployed_source != agent_sources[agent_name]:
|
|
581
|
-
comparison_results["source_changes"].append(
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
621
|
+
comparison_results["source_changes"].append(
|
|
622
|
+
{
|
|
623
|
+
"name": agent_name,
|
|
624
|
+
"from_source": deployed_source,
|
|
625
|
+
"to_source": agent_sources[agent_name],
|
|
626
|
+
}
|
|
627
|
+
)
|
|
586
628
|
elif version_comparison < 0:
|
|
587
629
|
# Deployed version is higher (shouldn't happen with proper version management)
|
|
588
|
-
comparison_results["version_downgrades"].append(
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
630
|
+
comparison_results["version_downgrades"].append(
|
|
631
|
+
{
|
|
632
|
+
"name": agent_name,
|
|
633
|
+
"deployed_version": self.version_manager.format_version_display(
|
|
634
|
+
deployed_version
|
|
635
|
+
),
|
|
636
|
+
"template_version": self.version_manager.format_version_display(
|
|
637
|
+
template_version
|
|
638
|
+
),
|
|
639
|
+
"warning": "Deployed version is higher than template",
|
|
640
|
+
}
|
|
641
|
+
)
|
|
594
642
|
# Don't add to needs_update - keep the higher version
|
|
595
643
|
else:
|
|
596
644
|
# Versions are equal
|
|
597
|
-
comparison_results["up_to_date"].append(
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
645
|
+
comparison_results["up_to_date"].append(
|
|
646
|
+
{
|
|
647
|
+
"name": agent_name,
|
|
648
|
+
"version": self.version_manager.format_version_display(
|
|
649
|
+
deployed_version
|
|
650
|
+
),
|
|
651
|
+
"source": agent_sources[agent_name],
|
|
652
|
+
}
|
|
653
|
+
)
|
|
654
|
+
|
|
603
655
|
# Check for orphaned agents (deployed but no template)
|
|
604
|
-
orphaned = self._detect_orphaned_agents_simple(
|
|
656
|
+
orphaned = self._detect_orphaned_agents_simple(
|
|
657
|
+
deployed_agents_dir, agents_to_deploy
|
|
658
|
+
)
|
|
605
659
|
comparison_results["orphaned_agents"] = orphaned
|
|
606
|
-
|
|
660
|
+
|
|
607
661
|
# Log summary
|
|
608
662
|
summary_parts = [
|
|
609
663
|
f"{len(comparison_results['needs_update'])} need updates",
|
|
610
664
|
f"{len(comparison_results['up_to_date'])} up to date",
|
|
611
|
-
f"{len(comparison_results['new_agents'])} new agents"
|
|
665
|
+
f"{len(comparison_results['new_agents'])} new agents",
|
|
612
666
|
]
|
|
613
667
|
if comparison_results["orphaned_agents"]:
|
|
614
|
-
summary_parts.append(
|
|
615
|
-
|
|
668
|
+
summary_parts.append(
|
|
669
|
+
f"{len(comparison_results['orphaned_agents'])} orphaned"
|
|
670
|
+
)
|
|
671
|
+
|
|
616
672
|
self.logger.info(f"Version comparison complete: {', '.join(summary_parts)}")
|
|
617
|
-
|
|
673
|
+
|
|
618
674
|
if comparison_results["version_upgrades"]:
|
|
619
675
|
for upgrade in comparison_results["version_upgrades"]:
|
|
620
676
|
self.logger.info(
|
|
@@ -622,14 +678,14 @@ class MultiSourceAgentDeploymentService:
|
|
|
622
678
|
f"{upgrade['deployed_version']} -> {upgrade['new_version']} "
|
|
623
679
|
f"(from {upgrade['source']})"
|
|
624
680
|
)
|
|
625
|
-
|
|
681
|
+
|
|
626
682
|
if comparison_results["source_changes"]:
|
|
627
683
|
for change in comparison_results["source_changes"]:
|
|
628
684
|
self.logger.info(
|
|
629
685
|
f" Source change: {change['name']} "
|
|
630
686
|
f"from {change['from_source']} to {change['to_source']}"
|
|
631
687
|
)
|
|
632
|
-
|
|
688
|
+
|
|
633
689
|
if comparison_results["version_downgrades"]:
|
|
634
690
|
for downgrade in comparison_results["version_downgrades"]:
|
|
635
691
|
# Changed from warning to debug - deployed versions higher than templates
|
|
@@ -639,7 +695,7 @@ class MultiSourceAgentDeploymentService:
|
|
|
639
695
|
f"{downgrade['deployed_version']} is higher than template "
|
|
640
696
|
f"{downgrade['template_version']} (keeping deployed version)"
|
|
641
697
|
)
|
|
642
|
-
|
|
698
|
+
|
|
643
699
|
# Log orphaned agents if found
|
|
644
700
|
if comparison_results["orphaned_agents"]:
|
|
645
701
|
self.logger.info(
|
|
@@ -651,57 +707,72 @@ class MultiSourceAgentDeploymentService:
|
|
|
651
707
|
f" - {orphan['name']} v{orphan['version']} "
|
|
652
708
|
f"(consider removing or creating a template)"
|
|
653
709
|
)
|
|
654
|
-
|
|
710
|
+
|
|
655
711
|
return comparison_results
|
|
656
|
-
|
|
657
|
-
def _infer_agent_source_from_context(
|
|
712
|
+
|
|
713
|
+
def _infer_agent_source_from_context(
|
|
714
|
+
self, agent_name: str, deployed_agents_dir: Path
|
|
715
|
+
) -> str:
|
|
658
716
|
"""Infer the source of a deployed agent when source metadata is missing.
|
|
659
|
-
|
|
717
|
+
|
|
660
718
|
This method attempts to determine the agent source based on:
|
|
661
719
|
1. Deployment context (development vs pipx)
|
|
662
720
|
2. Agent naming patterns
|
|
663
721
|
3. Known system agents
|
|
664
|
-
|
|
722
|
+
|
|
665
723
|
Args:
|
|
666
724
|
agent_name: Name of the agent
|
|
667
725
|
deployed_agents_dir: Directory where agent is deployed
|
|
668
|
-
|
|
726
|
+
|
|
669
727
|
Returns:
|
|
670
728
|
Inferred source string (system/project/user)
|
|
671
729
|
"""
|
|
672
730
|
# List of known system agents that ship with claude-mpm
|
|
673
731
|
system_agents = {
|
|
674
|
-
"pm",
|
|
675
|
-
"
|
|
732
|
+
"pm",
|
|
733
|
+
"engineer",
|
|
734
|
+
"qa",
|
|
735
|
+
"research",
|
|
736
|
+
"documentation",
|
|
737
|
+
"ops",
|
|
738
|
+
"security",
|
|
739
|
+
"web-ui",
|
|
740
|
+
"api-qa",
|
|
741
|
+
"version-control",
|
|
676
742
|
}
|
|
677
|
-
|
|
743
|
+
|
|
678
744
|
# If this is a known system agent, it's from system
|
|
679
745
|
if agent_name in system_agents:
|
|
680
746
|
return "system"
|
|
681
|
-
|
|
747
|
+
|
|
682
748
|
# Check deployment context
|
|
683
749
|
from ....core.unified_paths import get_path_manager
|
|
750
|
+
|
|
684
751
|
path_manager = get_path_manager()
|
|
685
|
-
|
|
752
|
+
|
|
686
753
|
# If deployed_agents_dir is under user home/.claude/agents, check context
|
|
687
754
|
user_claude_dir = Path.home() / ".claude" / "agents"
|
|
688
755
|
if deployed_agents_dir == user_claude_dir:
|
|
689
756
|
# Check if we're in development mode
|
|
690
757
|
try:
|
|
691
758
|
from ....core.unified_paths import DeploymentContext, PathContext
|
|
759
|
+
|
|
692
760
|
deployment_context = PathContext.detect_deployment_context()
|
|
693
|
-
|
|
694
|
-
if deployment_context in (
|
|
761
|
+
|
|
762
|
+
if deployment_context in (
|
|
763
|
+
DeploymentContext.DEVELOPMENT,
|
|
764
|
+
DeploymentContext.EDITABLE_INSTALL,
|
|
765
|
+
):
|
|
695
766
|
# In development mode, unknown agents are likely system agents being tested
|
|
696
767
|
return "system"
|
|
697
|
-
|
|
768
|
+
if deployment_context == DeploymentContext.PIPX_INSTALL:
|
|
698
769
|
# In pipx mode, unknown agents could be system agents
|
|
699
770
|
# Check if agent follows system naming patterns
|
|
700
|
-
if agent_name.count(
|
|
771
|
+
if agent_name.count("-") <= 2 and len(agent_name) <= 20:
|
|
701
772
|
return "system"
|
|
702
773
|
except Exception:
|
|
703
774
|
pass
|
|
704
|
-
|
|
775
|
+
|
|
705
776
|
# Check if deployed to project-specific directory
|
|
706
777
|
try:
|
|
707
778
|
project_root = path_manager.project_root
|
|
@@ -709,165 +780,163 @@ class MultiSourceAgentDeploymentService:
|
|
|
709
780
|
return "project"
|
|
710
781
|
except Exception:
|
|
711
782
|
pass
|
|
712
|
-
|
|
783
|
+
|
|
713
784
|
# Default inference based on naming patterns
|
|
714
785
|
# System agents typically have simple names
|
|
715
|
-
if
|
|
786
|
+
if "-" not in agent_name or agent_name.count("-") <= 1:
|
|
716
787
|
return "system"
|
|
717
|
-
|
|
788
|
+
|
|
718
789
|
# Complex names are more likely to be user/project agents
|
|
719
790
|
return "user"
|
|
720
|
-
|
|
791
|
+
|
|
721
792
|
def detect_orphaned_agents(
|
|
722
|
-
self,
|
|
723
|
-
deployed_agents_dir: Path,
|
|
724
|
-
available_agents: Dict[str, Any]
|
|
793
|
+
self, deployed_agents_dir: Path, available_agents: Dict[str, Any]
|
|
725
794
|
) -> List[Dict[str, Any]]:
|
|
726
795
|
"""Detect deployed agents that don't have corresponding templates.
|
|
727
|
-
|
|
796
|
+
|
|
728
797
|
WHY: Orphaned agents can cause confusion with version warnings.
|
|
729
798
|
This method identifies them so they can be handled appropriately.
|
|
730
|
-
|
|
799
|
+
|
|
731
800
|
Args:
|
|
732
801
|
deployed_agents_dir: Directory containing deployed agents
|
|
733
802
|
available_agents: Dictionary of available agents from all sources
|
|
734
|
-
|
|
803
|
+
|
|
735
804
|
Returns:
|
|
736
805
|
List of orphaned agent information
|
|
737
806
|
"""
|
|
738
807
|
orphaned = []
|
|
739
|
-
|
|
808
|
+
|
|
740
809
|
if not deployed_agents_dir.exists():
|
|
741
810
|
return orphaned
|
|
742
|
-
|
|
811
|
+
|
|
743
812
|
# Build a mapping of file stems to agent names for comparison
|
|
744
813
|
# Since available_agents uses display names like "Code Analysis Agent"
|
|
745
814
|
# but deployed files use stems like "code_analyzer"
|
|
746
815
|
available_stems = set()
|
|
747
816
|
stem_to_name = {}
|
|
748
|
-
|
|
817
|
+
|
|
749
818
|
for agent_name, agent_sources in available_agents.items():
|
|
750
819
|
# Get the file path from the first source to extract the stem
|
|
751
|
-
if
|
|
820
|
+
if (
|
|
821
|
+
agent_sources
|
|
822
|
+
and isinstance(agent_sources, list)
|
|
823
|
+
and len(agent_sources) > 0
|
|
824
|
+
):
|
|
752
825
|
first_source = agent_sources[0]
|
|
753
|
-
if
|
|
754
|
-
file_path = Path(first_source[
|
|
826
|
+
if "file_path" in first_source:
|
|
827
|
+
file_path = Path(first_source["file_path"])
|
|
755
828
|
stem = file_path.stem
|
|
756
829
|
available_stems.add(stem)
|
|
757
830
|
stem_to_name[stem] = agent_name
|
|
758
|
-
|
|
831
|
+
|
|
759
832
|
for deployed_file in deployed_agents_dir.glob("*.md"):
|
|
760
833
|
agent_stem = deployed_file.stem
|
|
761
|
-
|
|
834
|
+
|
|
762
835
|
# Skip if this agent has a template (check by stem, not display name)
|
|
763
836
|
if agent_stem in available_stems:
|
|
764
837
|
continue
|
|
765
|
-
|
|
838
|
+
|
|
766
839
|
# This is an orphaned agent
|
|
767
840
|
try:
|
|
768
841
|
deployed_content = deployed_file.read_text()
|
|
769
|
-
deployed_version, _, _ =
|
|
770
|
-
|
|
842
|
+
deployed_version, _, _ = (
|
|
843
|
+
self.version_manager.extract_version_from_frontmatter(
|
|
844
|
+
deployed_content
|
|
845
|
+
)
|
|
846
|
+
)
|
|
847
|
+
version_str = self.version_manager.format_version_display(
|
|
848
|
+
deployed_version
|
|
771
849
|
)
|
|
772
|
-
version_str = self.version_manager.format_version_display(deployed_version)
|
|
773
850
|
except Exception:
|
|
774
851
|
version_str = "unknown"
|
|
775
|
-
|
|
776
|
-
orphaned.append(
|
|
777
|
-
"name": agent_stem,
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
})
|
|
781
|
-
|
|
852
|
+
|
|
853
|
+
orphaned.append(
|
|
854
|
+
{"name": agent_stem, "file": str(deployed_file), "version": version_str}
|
|
855
|
+
)
|
|
856
|
+
|
|
782
857
|
return orphaned
|
|
783
|
-
|
|
858
|
+
|
|
784
859
|
def _detect_orphaned_agents_simple(
|
|
785
|
-
self,
|
|
786
|
-
deployed_agents_dir: Path,
|
|
787
|
-
agents_to_deploy: Dict[str, Path]
|
|
860
|
+
self, deployed_agents_dir: Path, agents_to_deploy: Dict[str, Path]
|
|
788
861
|
) -> List[Dict[str, Any]]:
|
|
789
862
|
"""Simple orphan detection that works with agents_to_deploy structure.
|
|
790
|
-
|
|
863
|
+
|
|
791
864
|
Args:
|
|
792
865
|
deployed_agents_dir: Directory containing deployed agents
|
|
793
866
|
agents_to_deploy: Dictionary mapping file stems to template paths
|
|
794
|
-
|
|
867
|
+
|
|
795
868
|
Returns:
|
|
796
869
|
List of orphaned agent information
|
|
797
870
|
"""
|
|
798
871
|
orphaned = []
|
|
799
|
-
|
|
872
|
+
|
|
800
873
|
if not deployed_agents_dir.exists():
|
|
801
874
|
return orphaned
|
|
802
|
-
|
|
875
|
+
|
|
803
876
|
# agents_to_deploy already contains file stems as keys
|
|
804
877
|
available_stems = set(agents_to_deploy.keys())
|
|
805
|
-
|
|
878
|
+
|
|
806
879
|
for deployed_file in deployed_agents_dir.glob("*.md"):
|
|
807
880
|
agent_stem = deployed_file.stem
|
|
808
|
-
|
|
881
|
+
|
|
809
882
|
# Skip if this agent has a template (check by stem)
|
|
810
883
|
if agent_stem in available_stems:
|
|
811
884
|
continue
|
|
812
|
-
|
|
885
|
+
|
|
813
886
|
# This is an orphaned agent
|
|
814
887
|
try:
|
|
815
888
|
deployed_content = deployed_file.read_text()
|
|
816
|
-
deployed_version, _, _ =
|
|
817
|
-
|
|
889
|
+
deployed_version, _, _ = (
|
|
890
|
+
self.version_manager.extract_version_from_frontmatter(
|
|
891
|
+
deployed_content
|
|
892
|
+
)
|
|
893
|
+
)
|
|
894
|
+
version_str = self.version_manager.format_version_display(
|
|
895
|
+
deployed_version
|
|
818
896
|
)
|
|
819
|
-
version_str = self.version_manager.format_version_display(deployed_version)
|
|
820
897
|
except Exception:
|
|
821
898
|
version_str = "unknown"
|
|
822
|
-
|
|
823
|
-
orphaned.append(
|
|
824
|
-
"name": agent_stem,
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
})
|
|
828
|
-
|
|
899
|
+
|
|
900
|
+
orphaned.append(
|
|
901
|
+
{"name": agent_stem, "file": str(deployed_file), "version": version_str}
|
|
902
|
+
)
|
|
903
|
+
|
|
829
904
|
return orphaned
|
|
830
|
-
|
|
905
|
+
|
|
831
906
|
def cleanup_orphaned_agents(
|
|
832
|
-
self,
|
|
833
|
-
deployed_agents_dir: Path,
|
|
834
|
-
dry_run: bool = True
|
|
907
|
+
self, deployed_agents_dir: Path, dry_run: bool = True
|
|
835
908
|
) -> Dict[str, Any]:
|
|
836
909
|
"""Clean up orphaned agents that don't have templates.
|
|
837
|
-
|
|
910
|
+
|
|
838
911
|
WHY: Orphaned agents can accumulate over time and cause confusion.
|
|
839
912
|
This method provides a way to clean them up systematically.
|
|
840
|
-
|
|
913
|
+
|
|
841
914
|
Args:
|
|
842
915
|
deployed_agents_dir: Directory containing deployed agents
|
|
843
916
|
dry_run: If True, only report what would be removed
|
|
844
|
-
|
|
917
|
+
|
|
845
918
|
Returns:
|
|
846
919
|
Dictionary with cleanup results
|
|
847
920
|
"""
|
|
848
|
-
results = {
|
|
849
|
-
|
|
850
|
-
"removed": [],
|
|
851
|
-
"errors": []
|
|
852
|
-
}
|
|
853
|
-
|
|
921
|
+
results = {"orphaned": [], "removed": [], "errors": []}
|
|
922
|
+
|
|
854
923
|
# First, discover all available agents from all sources
|
|
855
924
|
all_agents = self.discover_agents_from_all_sources()
|
|
856
|
-
|
|
857
|
-
|
|
925
|
+
set(all_agents.keys())
|
|
926
|
+
|
|
858
927
|
# Detect orphaned agents
|
|
859
928
|
orphaned = self.detect_orphaned_agents(deployed_agents_dir, all_agents)
|
|
860
929
|
results["orphaned"] = orphaned
|
|
861
|
-
|
|
930
|
+
|
|
862
931
|
if not orphaned:
|
|
863
932
|
self.logger.info("No orphaned agents found")
|
|
864
933
|
return results
|
|
865
|
-
|
|
934
|
+
|
|
866
935
|
self.logger.info(f"Found {len(orphaned)} orphaned agent(s)")
|
|
867
|
-
|
|
936
|
+
|
|
868
937
|
for orphan in orphaned:
|
|
869
938
|
agent_file = Path(orphan["file"])
|
|
870
|
-
|
|
939
|
+
|
|
871
940
|
if dry_run:
|
|
872
941
|
self.logger.info(
|
|
873
942
|
f" Would remove: {orphan['name']} v{orphan['version']}"
|
|
@@ -883,10 +952,10 @@ class MultiSourceAgentDeploymentService:
|
|
|
883
952
|
error_msg = f"Failed to remove {orphan['name']}: {e}"
|
|
884
953
|
results["errors"].append(error_msg)
|
|
885
954
|
self.logger.error(f" {error_msg}")
|
|
886
|
-
|
|
955
|
+
|
|
887
956
|
if dry_run and orphaned:
|
|
888
957
|
self.logger.info(
|
|
889
958
|
"Run with dry_run=False to actually remove orphaned agents"
|
|
890
959
|
)
|
|
891
|
-
|
|
892
|
-
return results
|
|
960
|
+
|
|
961
|
+
return results
|