aiecs 1.0.1__py3-none-any.whl → 1.7.17__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.
Potentially problematic release.
This version of aiecs might be problematic. Click here for more details.
- aiecs/__init__.py +13 -16
- aiecs/__main__.py +7 -7
- aiecs/aiecs_client.py +269 -75
- aiecs/application/executors/operation_executor.py +79 -54
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
- aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
- aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
- aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +98 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
- aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
- aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
- aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
- aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
- aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
- aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
- aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
- aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
- aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
- aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +341 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +913 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
- aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
- aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
- aiecs/application/knowledge_graph/search/reranker.py +293 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +471 -0
- aiecs/config/__init__.py +20 -5
- aiecs/config/config.py +762 -31
- aiecs/config/graph_config.py +131 -0
- aiecs/config/tool_config.py +435 -0
- aiecs/core/__init__.py +29 -13
- aiecs/core/interface/__init__.py +2 -2
- aiecs/core/interface/execution_interface.py +22 -22
- aiecs/core/interface/storage_interface.py +37 -88
- aiecs/core/registry/__init__.py +31 -0
- aiecs/core/registry/service_registry.py +92 -0
- aiecs/domain/__init__.py +270 -1
- aiecs/domain/agent/__init__.py +191 -0
- aiecs/domain/agent/base_agent.py +3949 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1731 -0
- aiecs/domain/agent/integration/__init__.py +29 -0
- aiecs/domain/agent/integration/context_compressor.py +216 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
- aiecs/domain/agent/integration/protocols.py +281 -0
- aiecs/domain/agent/integration/retry_policy.py +218 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
- aiecs/domain/agent/lifecycle.py +291 -0
- aiecs/domain/agent/llm_agent.py +692 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +1124 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +163 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
- aiecs/domain/agent/models.py +894 -0
- aiecs/domain/agent/observability.py +479 -0
- aiecs/domain/agent/persistence.py +449 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +159 -0
- aiecs/domain/agent/prompts/formatters.py +187 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +253 -0
- aiecs/domain/agent/tool_agent.py +444 -0
- aiecs/domain/agent/tools/__init__.py +15 -0
- aiecs/domain/agent/tools/schema_generator.py +377 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +469 -0
- aiecs/domain/community/analytics.py +432 -0
- aiecs/domain/community/collaborative_workflow.py +648 -0
- aiecs/domain/community/communication_hub.py +634 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +796 -0
- aiecs/domain/community/community_manager.py +803 -0
- aiecs/domain/community/decision_engine.py +849 -0
- aiecs/domain/community/exceptions.py +231 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +234 -0
- aiecs/domain/community/resource_manager.py +461 -0
- aiecs/domain/community/shared_context_manager.py +589 -0
- aiecs/domain/context/__init__.py +40 -10
- aiecs/domain/context/context_engine.py +1910 -0
- aiecs/domain/context/conversation_models.py +87 -53
- aiecs/domain/context/graph_memory.py +582 -0
- aiecs/domain/execution/model.py +12 -4
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +148 -0
- aiecs/domain/knowledge_graph/models/evidence.py +178 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
- aiecs/domain/knowledge_graph/models/path.py +171 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
- aiecs/domain/knowledge_graph/models/query.py +261 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
- aiecs/domain/knowledge_graph/models/relation.py +202 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
- aiecs/domain/task/dsl_processor.py +172 -56
- aiecs/domain/task/model.py +20 -8
- aiecs/domain/task/task_context.py +27 -24
- aiecs/infrastructure/__init__.py +0 -2
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +837 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
- aiecs/infrastructure/graph_storage/cache.py +424 -0
- aiecs/infrastructure/graph_storage/distributed.py +223 -0
- aiecs/infrastructure/graph_storage/error_handling.py +380 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
- aiecs/infrastructure/graph_storage/metrics.py +344 -0
- aiecs/infrastructure/graph_storage/migration.py +400 -0
- aiecs/infrastructure/graph_storage/pagination.py +483 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
- aiecs/infrastructure/graph_storage/postgres.py +1563 -0
- aiecs/infrastructure/graph_storage/property_storage.py +353 -0
- aiecs/infrastructure/graph_storage/protocols.py +76 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
- aiecs/infrastructure/graph_storage/streaming.py +487 -0
- aiecs/infrastructure/graph_storage/tenant.py +412 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
- aiecs/infrastructure/messaging/websocket_manager.py +51 -35
- aiecs/infrastructure/monitoring/__init__.py +22 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
- aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
- aiecs/infrastructure/monitoring/structured_logger.py +3 -7
- aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
- aiecs/infrastructure/persistence/__init__.py +14 -1
- aiecs/infrastructure/persistence/context_engine_client.py +184 -0
- aiecs/infrastructure/persistence/database_manager.py +67 -43
- aiecs/infrastructure/persistence/file_storage.py +180 -103
- aiecs/infrastructure/persistence/redis_client.py +74 -21
- aiecs/llm/__init__.py +73 -25
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
- aiecs/llm/client_factory.py +230 -37
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +328 -0
- aiecs/llm/clients/google_function_calling_mixin.py +415 -0
- aiecs/llm/clients/googleai_client.py +314 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +1186 -0
- aiecs/llm/clients/xai_client.py +201 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +272 -0
- aiecs/llm/config/config_validator.py +206 -0
- aiecs/llm/config/model_config.py +143 -0
- aiecs/llm/protocols.py +149 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +89 -0
- aiecs/main.py +140 -121
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/module_checker.py +499 -0
- aiecs/scripts/aid/version_manager.py +235 -0
- aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
- aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
- aiecs/scripts/dependance_check/__init__.py +15 -0
- aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
- aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
- aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
- aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
- aiecs/scripts/knowledge_graph/__init__.py +3 -0
- aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
- aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
- aiecs/scripts/tools_develop/README.md +671 -0
- aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
- aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
- aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
- aiecs/scripts/tools_develop/schema_coverage.py +511 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
- aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
- aiecs/scripts/tools_develop/verify_tools.py +352 -0
- aiecs/tasks/__init__.py +0 -1
- aiecs/tasks/worker.py +115 -47
- aiecs/tools/__init__.py +194 -72
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +330 -0
- aiecs/tools/apisource/providers/__init__.py +112 -0
- aiecs/tools/apisource/providers/base.py +671 -0
- aiecs/tools/apisource/providers/census.py +397 -0
- aiecs/tools/apisource/providers/fred.py +535 -0
- aiecs/tools/apisource/providers/newsapi.py +409 -0
- aiecs/tools/apisource/providers/worldbank.py +352 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +363 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
- aiecs/tools/apisource/tool.py +832 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +334 -0
- aiecs/tools/base_tool.py +415 -21
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +607 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
- aiecs/tools/docs/content_insertion_tool.py +1320 -0
- aiecs/tools/docs/document_creator_tool.py +1464 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1016 -0
- aiecs/tools/docs/document_writer_tool.py +2008 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
- aiecs/tools/langchain_adapter.py +300 -138
- aiecs/tools/schema_generator.py +455 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +581 -0
- aiecs/tools/search_tool/cache.py +264 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +224 -0
- aiecs/tools/search_tool/core.py +778 -0
- aiecs/tools/search_tool/deduplicator.py +119 -0
- aiecs/tools/search_tool/error_handler.py +242 -0
- aiecs/tools/search_tool/metrics.py +343 -0
- aiecs/tools/search_tool/rate_limiter.py +172 -0
- aiecs/tools/search_tool/schemas.py +275 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
- aiecs/tools/statistics/data_loader_tool.py +555 -0
- aiecs/tools/statistics/data_profiler_tool.py +638 -0
- aiecs/tools/statistics/data_transformer_tool.py +580 -0
- aiecs/tools/statistics/data_visualizer_tool.py +498 -0
- aiecs/tools/statistics/model_trainer_tool.py +507 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
- aiecs/tools/task_tools/__init__.py +49 -36
- aiecs/tools/task_tools/chart_tool.py +200 -184
- aiecs/tools/task_tools/classfire_tool.py +268 -267
- aiecs/tools/task_tools/image_tool.py +220 -141
- aiecs/tools/task_tools/office_tool.py +226 -146
- aiecs/tools/task_tools/pandas_tool.py +477 -121
- aiecs/tools/task_tools/report_tool.py +390 -142
- aiecs/tools/task_tools/research_tool.py +149 -79
- aiecs/tools/task_tools/scraper_tool.py +339 -145
- aiecs/tools/task_tools/stats_tool.py +448 -209
- aiecs/tools/temp_file_manager.py +26 -24
- aiecs/tools/tool_executor/__init__.py +18 -16
- aiecs/tools/tool_executor/tool_executor.py +364 -52
- aiecs/utils/LLM_output_structor.py +74 -48
- aiecs/utils/__init__.py +14 -3
- aiecs/utils/base_callback.py +0 -3
- aiecs/utils/cache_provider.py +696 -0
- aiecs/utils/execution_utils.py +50 -31
- aiecs/utils/prompt_loader.py +1 -0
- aiecs/utils/token_usage_repository.py +37 -11
- aiecs/ws/socket_server.py +14 -4
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/METADATA +52 -15
- aiecs-1.7.17.dist-info/RECORD +337 -0
- aiecs-1.7.17.dist-info/entry_points.txt +13 -0
- aiecs/config/registry.py +0 -19
- aiecs/domain/context/content_engine.py +0 -982
- aiecs/llm/base_client.py +0 -99
- aiecs/llm/openai_client.py +0 -125
- aiecs/llm/vertex_client.py +0 -186
- aiecs/llm/xai_client.py +0 -184
- aiecs/scripts/dependency_checker.py +0 -857
- aiecs/scripts/quick_dependency_check.py +0 -269
- aiecs/tools/task_tools/search_api.py +0 -7
- aiecs-1.0.1.dist-info/RECORD +0 -90
- aiecs-1.0.1.dist-info/entry_points.txt +0 -7
- /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
- /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
- /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
- /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Community Manager
|
|
3
|
+
|
|
4
|
+
Core component for managing agent communities, including governance,
|
|
5
|
+
resource sharing, and collaborative decision-making.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from typing import Dict, List, Any, Optional, Set, Protocol, TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from aiecs.domain.context.context_engine import ContextEngine
|
|
14
|
+
import uuid
|
|
15
|
+
|
|
16
|
+
from .models.community_models import (
|
|
17
|
+
AgentCommunity,
|
|
18
|
+
CommunityMember,
|
|
19
|
+
CommunityResource,
|
|
20
|
+
CommunityDecision,
|
|
21
|
+
CollaborationSession,
|
|
22
|
+
CommunityRole,
|
|
23
|
+
GovernanceType,
|
|
24
|
+
DecisionStatus,
|
|
25
|
+
ResourceType,
|
|
26
|
+
)
|
|
27
|
+
from .exceptions import CommunityValidationError as TaskValidationError
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MemberLifecycleHooks(Protocol):
|
|
33
|
+
"""
|
|
34
|
+
Protocol defining lifecycle hooks for community member events.
|
|
35
|
+
Implement this protocol to receive notifications about member lifecycle events.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
async def on_member_join(self, community_id: str, member_id: str, member: CommunityMember) -> None:
|
|
39
|
+
"""Called when a member joins a community."""
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
async def on_member_exit(
|
|
43
|
+
self,
|
|
44
|
+
community_id: str,
|
|
45
|
+
member_id: str,
|
|
46
|
+
member: CommunityMember,
|
|
47
|
+
reason: Optional[str] = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Called when a member exits/is removed from a community."""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
async def on_member_update(
|
|
53
|
+
self,
|
|
54
|
+
community_id: str,
|
|
55
|
+
member_id: str,
|
|
56
|
+
member: CommunityMember,
|
|
57
|
+
changes: Dict[str, Any],
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Called when a member's properties are updated."""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
async def on_member_inactive(
|
|
63
|
+
self,
|
|
64
|
+
community_id: str,
|
|
65
|
+
member_id: str,
|
|
66
|
+
member: CommunityMember,
|
|
67
|
+
reason: Optional[str] = None,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Called when a member becomes inactive."""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CommunityManager:
|
|
74
|
+
"""
|
|
75
|
+
Manager for agent communities, handling governance, collaboration, and resource sharing.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, context_engine: Optional["ContextEngine"] = None) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Initialize the community manager.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
context_engine: Optional context engine for persistent storage
|
|
84
|
+
"""
|
|
85
|
+
self.context_engine = context_engine
|
|
86
|
+
|
|
87
|
+
# In-memory storage (should be replaced with persistent storage)
|
|
88
|
+
self.communities: Dict[str, AgentCommunity] = {}
|
|
89
|
+
self.members: Dict[str, CommunityMember] = {}
|
|
90
|
+
self.resources: Dict[str, CommunityResource] = {}
|
|
91
|
+
self.decisions: Dict[str, CommunityDecision] = {}
|
|
92
|
+
self.sessions: Dict[str, CollaborationSession] = {}
|
|
93
|
+
|
|
94
|
+
# Community relationships
|
|
95
|
+
# member_id -> set of community_ids
|
|
96
|
+
self.member_communities: Dict[str, Set[str]] = {}
|
|
97
|
+
# community_id -> set of member_ids
|
|
98
|
+
self.community_members: Dict[str, Set[str]] = {}
|
|
99
|
+
|
|
100
|
+
# Lifecycle hooks
|
|
101
|
+
self.lifecycle_hooks: List[MemberLifecycleHooks] = []
|
|
102
|
+
|
|
103
|
+
self._initialized = False
|
|
104
|
+
logger.info("Community manager initialized")
|
|
105
|
+
|
|
106
|
+
async def initialize(self) -> None:
|
|
107
|
+
"""Initialize the community manager."""
|
|
108
|
+
if self._initialized:
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
# Load existing communities and members from persistent storage if
|
|
112
|
+
# available
|
|
113
|
+
if self.context_engine:
|
|
114
|
+
await self._load_from_storage()
|
|
115
|
+
|
|
116
|
+
self._initialized = True
|
|
117
|
+
logger.info("Community manager initialization completed")
|
|
118
|
+
|
|
119
|
+
async def create_community(
|
|
120
|
+
self,
|
|
121
|
+
name: str,
|
|
122
|
+
description: Optional[str] = None,
|
|
123
|
+
governance_type: GovernanceType = GovernanceType.DEMOCRATIC,
|
|
124
|
+
governance_rules: Optional[Dict[str, Any]] = None,
|
|
125
|
+
creator_agent_id: Optional[str] = None,
|
|
126
|
+
) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Create a new agent community.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
name: Name of the community
|
|
132
|
+
description: Optional description
|
|
133
|
+
governance_type: Type of governance
|
|
134
|
+
governance_rules: Governance rules and policies
|
|
135
|
+
creator_agent_id: ID of the agent creating the community
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Community ID
|
|
139
|
+
"""
|
|
140
|
+
community = AgentCommunity(
|
|
141
|
+
name=name,
|
|
142
|
+
description=description,
|
|
143
|
+
governance_type=governance_type,
|
|
144
|
+
governance_rules=governance_rules or {},
|
|
145
|
+
max_members=None,
|
|
146
|
+
updated_at=None,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
self.communities[community.community_id] = community
|
|
150
|
+
self.community_members[community.community_id] = set()
|
|
151
|
+
|
|
152
|
+
# Add creator as the first leader if provided
|
|
153
|
+
if creator_agent_id:
|
|
154
|
+
await self.add_member_to_community(
|
|
155
|
+
community.community_id,
|
|
156
|
+
creator_agent_id,
|
|
157
|
+
community_role=CommunityRole.LEADER,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Auto-save to storage
|
|
161
|
+
await self._save_to_storage()
|
|
162
|
+
|
|
163
|
+
logger.info(f"Created community: {name} ({community.community_id})")
|
|
164
|
+
return community.community_id
|
|
165
|
+
|
|
166
|
+
async def add_member_to_community(
|
|
167
|
+
self,
|
|
168
|
+
community_id: str,
|
|
169
|
+
agent_id: str,
|
|
170
|
+
agent_role: str = "general",
|
|
171
|
+
community_role: CommunityRole = CommunityRole.CONTRIBUTOR,
|
|
172
|
+
specializations: Optional[List[str]] = None,
|
|
173
|
+
) -> str:
|
|
174
|
+
"""
|
|
175
|
+
Add a member to a community.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
community_id: ID of the community
|
|
179
|
+
agent_id: ID of the agent to add
|
|
180
|
+
agent_role: Functional role of the agent
|
|
181
|
+
community_role: Role within the community
|
|
182
|
+
specializations: Areas of specialization
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Member ID
|
|
186
|
+
"""
|
|
187
|
+
if community_id not in self.communities:
|
|
188
|
+
raise TaskValidationError(f"Community not found: {community_id}")
|
|
189
|
+
|
|
190
|
+
# Check if agent is already a member
|
|
191
|
+
existing_member = self._find_member_by_agent_id(community_id, agent_id)
|
|
192
|
+
if existing_member:
|
|
193
|
+
logger.warning(f"Agent {agent_id} is already a member of community {community_id}")
|
|
194
|
+
return existing_member.member_id
|
|
195
|
+
|
|
196
|
+
member = CommunityMember(
|
|
197
|
+
member_id=str(uuid.uuid4()),
|
|
198
|
+
agent_id=agent_id,
|
|
199
|
+
agent_role=agent_role,
|
|
200
|
+
community_role=community_role,
|
|
201
|
+
specializations=specializations or [],
|
|
202
|
+
last_active_at=None,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
self.members[member.member_id] = member
|
|
206
|
+
|
|
207
|
+
# Update relationships
|
|
208
|
+
if agent_id not in self.member_communities:
|
|
209
|
+
self.member_communities[agent_id] = set()
|
|
210
|
+
self.member_communities[agent_id].add(community_id)
|
|
211
|
+
self.community_members[community_id].add(member.member_id)
|
|
212
|
+
|
|
213
|
+
# Update community
|
|
214
|
+
community = self.communities[community_id]
|
|
215
|
+
community.members.append(member.member_id)
|
|
216
|
+
|
|
217
|
+
if community_role == CommunityRole.LEADER:
|
|
218
|
+
community.leaders.append(member.member_id)
|
|
219
|
+
elif community_role == CommunityRole.COORDINATOR:
|
|
220
|
+
community.coordinators.append(member.member_id)
|
|
221
|
+
|
|
222
|
+
# Auto-save to storage
|
|
223
|
+
await self._save_to_storage()
|
|
224
|
+
|
|
225
|
+
# Execute lifecycle hooks
|
|
226
|
+
await self._execute_hook("on_member_join", community_id, member.member_id, member)
|
|
227
|
+
|
|
228
|
+
logger.info(f"Added member {agent_id} to community {community_id} as {community_role}")
|
|
229
|
+
return member.member_id
|
|
230
|
+
|
|
231
|
+
async def create_community_resource(
|
|
232
|
+
self,
|
|
233
|
+
community_id: str,
|
|
234
|
+
owner_member_id: str,
|
|
235
|
+
name: str,
|
|
236
|
+
resource_type: ResourceType,
|
|
237
|
+
content: Dict[str, Any],
|
|
238
|
+
description: Optional[str] = None,
|
|
239
|
+
access_level: str = "public",
|
|
240
|
+
tags: Optional[List[str]] = None,
|
|
241
|
+
) -> str:
|
|
242
|
+
"""
|
|
243
|
+
Create a shared community resource.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
community_id: ID of the community
|
|
247
|
+
owner_member_id: ID of the member creating the resource
|
|
248
|
+
name: Name of the resource
|
|
249
|
+
resource_type: Type of resource
|
|
250
|
+
content: Resource content/data
|
|
251
|
+
description: Optional description
|
|
252
|
+
access_level: Access level (public, restricted, private)
|
|
253
|
+
tags: Tags for categorization
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Resource ID
|
|
257
|
+
"""
|
|
258
|
+
if community_id not in self.communities:
|
|
259
|
+
raise TaskValidationError(f"Community not found: {community_id}")
|
|
260
|
+
|
|
261
|
+
if owner_member_id not in self.members:
|
|
262
|
+
raise TaskValidationError(f"Member not found: {owner_member_id}")
|
|
263
|
+
|
|
264
|
+
resource = CommunityResource(
|
|
265
|
+
name=name,
|
|
266
|
+
resource_type=resource_type,
|
|
267
|
+
description=description,
|
|
268
|
+
owner_id=owner_member_id,
|
|
269
|
+
updated_at=None,
|
|
270
|
+
access_level=access_level,
|
|
271
|
+
content=content,
|
|
272
|
+
tags=tags or [],
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
self.resources[resource.resource_id] = resource
|
|
276
|
+
|
|
277
|
+
# Update community
|
|
278
|
+
community = self.communities[community_id]
|
|
279
|
+
community.shared_resources.append(resource.resource_id)
|
|
280
|
+
community.resource_count += 1
|
|
281
|
+
|
|
282
|
+
# Auto-save to storage
|
|
283
|
+
await self._save_to_storage()
|
|
284
|
+
|
|
285
|
+
logger.info(f"Created resource {name} in community {community_id}")
|
|
286
|
+
return resource.resource_id
|
|
287
|
+
|
|
288
|
+
async def propose_decision(
|
|
289
|
+
self,
|
|
290
|
+
community_id: str,
|
|
291
|
+
proposer_member_id: str,
|
|
292
|
+
title: str,
|
|
293
|
+
description: str,
|
|
294
|
+
decision_type: str,
|
|
295
|
+
implementation_plan: Optional[str] = None,
|
|
296
|
+
deadline: Optional[datetime] = None,
|
|
297
|
+
) -> str:
|
|
298
|
+
"""
|
|
299
|
+
Propose a decision for community consideration.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
community_id: ID of the community
|
|
303
|
+
proposer_member_id: ID of the member proposing
|
|
304
|
+
title: Title of the proposal
|
|
305
|
+
description: Detailed description
|
|
306
|
+
decision_type: Type of decision
|
|
307
|
+
implementation_plan: Plan for implementation
|
|
308
|
+
deadline: Implementation deadline
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Decision ID
|
|
312
|
+
"""
|
|
313
|
+
if community_id not in self.communities:
|
|
314
|
+
raise TaskValidationError(f"Community not found: {community_id}")
|
|
315
|
+
|
|
316
|
+
if proposer_member_id not in self.members:
|
|
317
|
+
raise TaskValidationError(f"Member not found: {proposer_member_id}")
|
|
318
|
+
|
|
319
|
+
decision = CommunityDecision(
|
|
320
|
+
title=title,
|
|
321
|
+
description=description,
|
|
322
|
+
proposer_id=proposer_member_id,
|
|
323
|
+
decision_type=decision_type,
|
|
324
|
+
implementation_plan=implementation_plan,
|
|
325
|
+
deadline=deadline,
|
|
326
|
+
voting_ends_at=datetime.utcnow() + timedelta(days=3), # Default 3-day voting period
|
|
327
|
+
implemented_at=None,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
self.decisions[decision.decision_id] = decision
|
|
331
|
+
|
|
332
|
+
# Update community
|
|
333
|
+
community = self.communities[community_id]
|
|
334
|
+
community.decision_count += 1
|
|
335
|
+
|
|
336
|
+
# Auto-save to storage
|
|
337
|
+
await self._save_to_storage()
|
|
338
|
+
|
|
339
|
+
logger.info(f"Proposed decision '{title}' in community {community_id}")
|
|
340
|
+
return decision.decision_id
|
|
341
|
+
|
|
342
|
+
async def vote_on_decision(
|
|
343
|
+
self,
|
|
344
|
+
decision_id: str,
|
|
345
|
+
member_id: str,
|
|
346
|
+
vote: str, # "for", "against", "abstain"
|
|
347
|
+
) -> bool:
|
|
348
|
+
"""
|
|
349
|
+
Cast a vote on a community decision.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
decision_id: ID of the decision
|
|
353
|
+
member_id: ID of the voting member
|
|
354
|
+
vote: Vote choice ("for", "against", "abstain")
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
True if vote was cast successfully
|
|
358
|
+
"""
|
|
359
|
+
if decision_id not in self.decisions:
|
|
360
|
+
raise TaskValidationError(f"Decision not found: {decision_id}")
|
|
361
|
+
|
|
362
|
+
if member_id not in self.members:
|
|
363
|
+
raise TaskValidationError(f"Member not found: {member_id}")
|
|
364
|
+
|
|
365
|
+
decision = self.decisions[decision_id]
|
|
366
|
+
|
|
367
|
+
# Check if voting is still open
|
|
368
|
+
if decision.status != DecisionStatus.VOTING and decision.status != DecisionStatus.PROPOSED:
|
|
369
|
+
raise TaskValidationError(f"Voting is closed for decision {decision_id}")
|
|
370
|
+
|
|
371
|
+
if decision.voting_ends_at and datetime.utcnow() > decision.voting_ends_at:
|
|
372
|
+
raise TaskValidationError(f"Voting period has ended for decision {decision_id}")
|
|
373
|
+
|
|
374
|
+
# Remove previous vote if exists
|
|
375
|
+
if member_id in decision.votes_for:
|
|
376
|
+
decision.votes_for.remove(member_id)
|
|
377
|
+
if member_id in decision.votes_against:
|
|
378
|
+
decision.votes_against.remove(member_id)
|
|
379
|
+
if member_id in decision.abstentions:
|
|
380
|
+
decision.abstentions.remove(member_id)
|
|
381
|
+
|
|
382
|
+
# Cast new vote
|
|
383
|
+
if vote.lower() == "for":
|
|
384
|
+
decision.votes_for.append(member_id)
|
|
385
|
+
elif vote.lower() == "against":
|
|
386
|
+
decision.votes_against.append(member_id)
|
|
387
|
+
elif vote.lower() == "abstain":
|
|
388
|
+
decision.abstentions.append(member_id)
|
|
389
|
+
else:
|
|
390
|
+
raise TaskValidationError(f"Invalid vote choice: {vote}")
|
|
391
|
+
|
|
392
|
+
# Update decision status
|
|
393
|
+
if decision.status == DecisionStatus.PROPOSED:
|
|
394
|
+
decision.status = DecisionStatus.VOTING
|
|
395
|
+
|
|
396
|
+
# Auto-save to storage
|
|
397
|
+
await self._save_to_storage()
|
|
398
|
+
|
|
399
|
+
logger.info(f"Member {member_id} voted '{vote}' on decision {decision_id}")
|
|
400
|
+
return True
|
|
401
|
+
|
|
402
|
+
async def remove_member_from_community(
|
|
403
|
+
self,
|
|
404
|
+
community_id: str,
|
|
405
|
+
member_id: str,
|
|
406
|
+
transfer_resources: bool = True,
|
|
407
|
+
new_owner_id: Optional[str] = None,
|
|
408
|
+
) -> bool:
|
|
409
|
+
"""
|
|
410
|
+
Remove a member from a community with graceful cleanup.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
community_id: ID of the community
|
|
414
|
+
member_id: ID of the member to remove
|
|
415
|
+
transfer_resources: Whether to transfer member's resources
|
|
416
|
+
new_owner_id: Optional new owner for transferred resources
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
True if member was removed successfully
|
|
420
|
+
"""
|
|
421
|
+
if community_id not in self.communities:
|
|
422
|
+
raise TaskValidationError(f"Community not found: {community_id}")
|
|
423
|
+
|
|
424
|
+
if member_id not in self.members:
|
|
425
|
+
raise TaskValidationError(f"Member not found: {member_id}")
|
|
426
|
+
|
|
427
|
+
member = self.members[member_id]
|
|
428
|
+
community = self.communities[community_id]
|
|
429
|
+
|
|
430
|
+
# Transfer or orphan resources
|
|
431
|
+
if transfer_resources:
|
|
432
|
+
await self.transfer_member_resources(
|
|
433
|
+
member_id=member_id,
|
|
434
|
+
new_owner_id=(new_owner_id or community.leaders[0] if community.leaders else None),
|
|
435
|
+
community_id=community_id,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# Remove from community member list
|
|
439
|
+
if member_id in community.members:
|
|
440
|
+
community.members.remove(member_id)
|
|
441
|
+
|
|
442
|
+
# Remove from leadership positions
|
|
443
|
+
if member_id in community.leaders:
|
|
444
|
+
community.leaders.remove(member_id)
|
|
445
|
+
|
|
446
|
+
if member_id in community.coordinators:
|
|
447
|
+
community.coordinators.remove(member_id)
|
|
448
|
+
|
|
449
|
+
# Update relationships
|
|
450
|
+
if community_id in self.community_members:
|
|
451
|
+
self.community_members[community_id].discard(member_id)
|
|
452
|
+
|
|
453
|
+
if member.agent_id in self.member_communities:
|
|
454
|
+
self.member_communities[member.agent_id].discard(community_id)
|
|
455
|
+
|
|
456
|
+
# Mark member as inactive
|
|
457
|
+
member.is_active = False
|
|
458
|
+
member.last_active_at = datetime.utcnow()
|
|
459
|
+
|
|
460
|
+
# Auto-save to storage
|
|
461
|
+
await self._save_to_storage()
|
|
462
|
+
|
|
463
|
+
# Execute lifecycle hooks
|
|
464
|
+
await self._execute_hook("on_member_exit", community_id, member_id, member, reason="removed")
|
|
465
|
+
|
|
466
|
+
logger.info(f"Removed member {member_id} from community {community_id}")
|
|
467
|
+
return True
|
|
468
|
+
|
|
469
|
+
async def transfer_member_resources(self, member_id: str, new_owner_id: Optional[str], community_id: str) -> List[str]:
|
|
470
|
+
"""
|
|
471
|
+
Transfer ownership of member's resources to another member.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
member_id: ID of the member whose resources to transfer
|
|
475
|
+
new_owner_id: ID of the new owner (None = make resources orphaned/community-owned)
|
|
476
|
+
community_id: ID of the community
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
List of transferred resource IDs
|
|
480
|
+
"""
|
|
481
|
+
if member_id not in self.members:
|
|
482
|
+
raise TaskValidationError(f"Member not found: {member_id}")
|
|
483
|
+
|
|
484
|
+
transferred_resources = []
|
|
485
|
+
|
|
486
|
+
# Find all resources owned by this member
|
|
487
|
+
for resource_id, resource in self.resources.items():
|
|
488
|
+
if resource.owner_id == member_id:
|
|
489
|
+
if new_owner_id:
|
|
490
|
+
# Transfer to new owner
|
|
491
|
+
resource.owner_id = new_owner_id
|
|
492
|
+
resource.metadata["transferred_from"] = member_id
|
|
493
|
+
resource.metadata["transferred_at"] = datetime.utcnow().isoformat()
|
|
494
|
+
resource.updated_at = datetime.utcnow()
|
|
495
|
+
else:
|
|
496
|
+
# Make community-owned (orphaned)
|
|
497
|
+
resource.owner_id = "community"
|
|
498
|
+
resource.metadata["orphaned_from"] = member_id
|
|
499
|
+
resource.metadata["orphaned_at"] = datetime.utcnow().isoformat()
|
|
500
|
+
resource.access_level = "public" # Make public for community access
|
|
501
|
+
|
|
502
|
+
transferred_resources.append(resource_id)
|
|
503
|
+
|
|
504
|
+
# Auto-save to storage
|
|
505
|
+
if transferred_resources:
|
|
506
|
+
await self._save_to_storage()
|
|
507
|
+
|
|
508
|
+
logger.info(f"Transferred {len(transferred_resources)} resources from member {member_id}")
|
|
509
|
+
return transferred_resources
|
|
510
|
+
|
|
511
|
+
async def deactivate_member(self, member_id: str, reason: Optional[str] = None) -> bool:
|
|
512
|
+
"""
|
|
513
|
+
Soft deactivation of a member (doesn't remove, just marks inactive).
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
member_id: ID of the member to deactivate
|
|
517
|
+
reason: Optional reason for deactivation
|
|
518
|
+
|
|
519
|
+
Returns:
|
|
520
|
+
True if member was deactivated successfully
|
|
521
|
+
"""
|
|
522
|
+
if member_id not in self.members:
|
|
523
|
+
raise TaskValidationError(f"Member not found: {member_id}")
|
|
524
|
+
|
|
525
|
+
member = self.members[member_id]
|
|
526
|
+
member.is_active = False
|
|
527
|
+
member.last_active_at = datetime.utcnow()
|
|
528
|
+
member.participation_level = "inactive"
|
|
529
|
+
|
|
530
|
+
if reason:
|
|
531
|
+
member.metadata["deactivation_reason"] = reason
|
|
532
|
+
member.metadata["deactivated_at"] = datetime.utcnow().isoformat()
|
|
533
|
+
|
|
534
|
+
# Auto-save to storage
|
|
535
|
+
await self._save_to_storage()
|
|
536
|
+
|
|
537
|
+
# Execute lifecycle hooks
|
|
538
|
+
await self._execute_hook("on_member_inactive", None, member_id, member, reason=reason)
|
|
539
|
+
|
|
540
|
+
logger.info(f"Deactivated member {member_id}")
|
|
541
|
+
return True
|
|
542
|
+
|
|
543
|
+
async def reactivate_member(self, member_id: str, restore_roles: bool = True) -> bool:
|
|
544
|
+
"""
|
|
545
|
+
Reactivate a previously deactivated member.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
member_id: ID of the member to reactivate
|
|
549
|
+
restore_roles: Whether to restore previous roles
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
True if member was reactivated successfully
|
|
553
|
+
"""
|
|
554
|
+
if member_id not in self.members:
|
|
555
|
+
raise TaskValidationError(f"Member not found: {member_id}")
|
|
556
|
+
|
|
557
|
+
member = self.members[member_id]
|
|
558
|
+
member.is_active = True
|
|
559
|
+
member.last_active_at = datetime.utcnow()
|
|
560
|
+
member.participation_level = "active"
|
|
561
|
+
|
|
562
|
+
# Clear deactivation metadata
|
|
563
|
+
if "deactivation_reason" in member.metadata:
|
|
564
|
+
del member.metadata["deactivation_reason"]
|
|
565
|
+
if "deactivated_at" in member.metadata:
|
|
566
|
+
member.metadata["previous_deactivation"] = member.metadata["deactivated_at"]
|
|
567
|
+
del member.metadata["deactivated_at"]
|
|
568
|
+
|
|
569
|
+
member.metadata["reactivated_at"] = datetime.utcnow().isoformat()
|
|
570
|
+
|
|
571
|
+
# Auto-save to storage
|
|
572
|
+
await self._save_to_storage()
|
|
573
|
+
|
|
574
|
+
logger.info(f"Reactivated member {member_id}")
|
|
575
|
+
return True
|
|
576
|
+
|
|
577
|
+
def _find_member_by_agent_id(self, community_id: str, agent_id: str) -> Optional[CommunityMember]:
|
|
578
|
+
"""Find a community member by agent ID."""
|
|
579
|
+
if community_id not in self.community_members:
|
|
580
|
+
return None
|
|
581
|
+
|
|
582
|
+
for member_id in self.community_members[community_id]:
|
|
583
|
+
member = self.members.get(member_id)
|
|
584
|
+
if member and member.agent_id == agent_id:
|
|
585
|
+
return member
|
|
586
|
+
|
|
587
|
+
return None
|
|
588
|
+
|
|
589
|
+
def register_lifecycle_hook(self, hook: MemberLifecycleHooks) -> None:
|
|
590
|
+
"""
|
|
591
|
+
Register a lifecycle hook handler.
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
hook: Hook handler implementing MemberLifecycleHooks protocol
|
|
595
|
+
"""
|
|
596
|
+
self.lifecycle_hooks.append(hook)
|
|
597
|
+
logger.info(f"Registered lifecycle hook: {hook.__class__.__name__}")
|
|
598
|
+
|
|
599
|
+
def unregister_lifecycle_hook(self, hook: MemberLifecycleHooks) -> bool:
|
|
600
|
+
"""
|
|
601
|
+
Unregister a lifecycle hook handler.
|
|
602
|
+
|
|
603
|
+
Args:
|
|
604
|
+
hook: Hook handler to remove
|
|
605
|
+
|
|
606
|
+
Returns:
|
|
607
|
+
True if hook was removed
|
|
608
|
+
"""
|
|
609
|
+
if hook in self.lifecycle_hooks:
|
|
610
|
+
self.lifecycle_hooks.remove(hook)
|
|
611
|
+
logger.info(f"Unregistered lifecycle hook: {hook.__class__.__name__}")
|
|
612
|
+
return True
|
|
613
|
+
return False
|
|
614
|
+
|
|
615
|
+
async def _execute_hook(
|
|
616
|
+
self,
|
|
617
|
+
hook_name: str,
|
|
618
|
+
community_id: Optional[str],
|
|
619
|
+
member_id: str,
|
|
620
|
+
member: CommunityMember,
|
|
621
|
+
**kwargs,
|
|
622
|
+
) -> None:
|
|
623
|
+
"""
|
|
624
|
+
Execute all registered hooks for a specific event.
|
|
625
|
+
|
|
626
|
+
Args:
|
|
627
|
+
hook_name: Name of the hook method to call
|
|
628
|
+
community_id: ID of the community (optional for some hooks)
|
|
629
|
+
member_id: ID of the member
|
|
630
|
+
member: Member object
|
|
631
|
+
**kwargs: Additional arguments to pass to the hook
|
|
632
|
+
"""
|
|
633
|
+
for hook in self.lifecycle_hooks:
|
|
634
|
+
try:
|
|
635
|
+
hook_method = getattr(hook, hook_name, None)
|
|
636
|
+
if hook_method:
|
|
637
|
+
if community_id:
|
|
638
|
+
await hook_method(community_id, member_id, member, **kwargs)
|
|
639
|
+
else:
|
|
640
|
+
await hook_method(member_id, member, **kwargs)
|
|
641
|
+
except Exception as e:
|
|
642
|
+
logger.error(f"Error executing lifecycle hook {hook_name}: {e}")
|
|
643
|
+
|
|
644
|
+
async def _load_from_storage(self) -> None:
|
|
645
|
+
"""
|
|
646
|
+
Load communities and members from persistent storage.
|
|
647
|
+
|
|
648
|
+
Loads:
|
|
649
|
+
- Communities
|
|
650
|
+
- Members
|
|
651
|
+
- Resources
|
|
652
|
+
- Decisions
|
|
653
|
+
- Sessions
|
|
654
|
+
- Relationships
|
|
655
|
+
"""
|
|
656
|
+
if not self.context_engine:
|
|
657
|
+
logger.warning("No context engine available for loading")
|
|
658
|
+
return
|
|
659
|
+
|
|
660
|
+
try:
|
|
661
|
+
# Load communities
|
|
662
|
+
communities_data = await self._load_data_by_key("communities")
|
|
663
|
+
if communities_data:
|
|
664
|
+
for community_id, community_dict in communities_data.items():
|
|
665
|
+
try:
|
|
666
|
+
community = AgentCommunity(**community_dict)
|
|
667
|
+
self.communities[community_id] = community
|
|
668
|
+
self.community_members[community_id] = set(community.members)
|
|
669
|
+
except Exception as e:
|
|
670
|
+
logger.error(f"Failed to load community {community_id}: {e}")
|
|
671
|
+
|
|
672
|
+
# Load members
|
|
673
|
+
members_data = await self._load_data_by_key("community_members")
|
|
674
|
+
if members_data:
|
|
675
|
+
for member_id, member_dict in members_data.items():
|
|
676
|
+
try:
|
|
677
|
+
member = CommunityMember(**member_dict)
|
|
678
|
+
self.members[member_id] = member
|
|
679
|
+
except Exception as e:
|
|
680
|
+
logger.error(f"Failed to load member {member_id}: {e}")
|
|
681
|
+
|
|
682
|
+
# Load resources
|
|
683
|
+
resources_data = await self._load_data_by_key("community_resources")
|
|
684
|
+
if resources_data:
|
|
685
|
+
for resource_id, resource_dict in resources_data.items():
|
|
686
|
+
try:
|
|
687
|
+
resource = CommunityResource(**resource_dict)
|
|
688
|
+
self.resources[resource_id] = resource
|
|
689
|
+
except Exception as e:
|
|
690
|
+
logger.error(f"Failed to load resource {resource_id}: {e}")
|
|
691
|
+
|
|
692
|
+
# Load decisions
|
|
693
|
+
decisions_data = await self._load_data_by_key("community_decisions")
|
|
694
|
+
if decisions_data:
|
|
695
|
+
for decision_id, decision_dict in decisions_data.items():
|
|
696
|
+
try:
|
|
697
|
+
decision = CommunityDecision(**decision_dict)
|
|
698
|
+
self.decisions[decision_id] = decision
|
|
699
|
+
except Exception as e:
|
|
700
|
+
logger.error(f"Failed to load decision {decision_id}: {e}")
|
|
701
|
+
|
|
702
|
+
# Load sessions
|
|
703
|
+
sessions_data = await self._load_data_by_key("community_sessions")
|
|
704
|
+
if sessions_data:
|
|
705
|
+
for session_id, session_dict in sessions_data.items():
|
|
706
|
+
try:
|
|
707
|
+
session = CollaborationSession(**session_dict)
|
|
708
|
+
self.sessions[session_id] = session
|
|
709
|
+
except Exception as e:
|
|
710
|
+
logger.error(f"Failed to load session {session_id}: {e}")
|
|
711
|
+
|
|
712
|
+
# Rebuild member_communities relationships
|
|
713
|
+
for member_id, member in self.members.items():
|
|
714
|
+
for community_id, community in self.communities.items():
|
|
715
|
+
if member_id in community.members:
|
|
716
|
+
if member.agent_id not in self.member_communities:
|
|
717
|
+
self.member_communities[member.agent_id] = set()
|
|
718
|
+
self.member_communities[member.agent_id].add(community_id)
|
|
719
|
+
|
|
720
|
+
logger.info(f"Loaded {len(self.communities)} communities, {len(self.members)} members from storage")
|
|
721
|
+
|
|
722
|
+
except Exception as e:
|
|
723
|
+
logger.error(f"Error loading from storage: {e}")
|
|
724
|
+
|
|
725
|
+
async def _load_data_by_key(self, key: str) -> Optional[Dict[str, Any]]:
|
|
726
|
+
"""Load data from context engine by key."""
|
|
727
|
+
if not self.context_engine:
|
|
728
|
+
return None
|
|
729
|
+
|
|
730
|
+
try:
|
|
731
|
+
# Try to get data from context engine
|
|
732
|
+
# The exact method depends on the context engine implementation
|
|
733
|
+
if hasattr(self.context_engine, "get_context"):
|
|
734
|
+
data = await self.context_engine.get_context(key)
|
|
735
|
+
return data
|
|
736
|
+
elif hasattr(self.context_engine, "get"):
|
|
737
|
+
data = await self.context_engine.get(key)
|
|
738
|
+
return data
|
|
739
|
+
else:
|
|
740
|
+
logger.warning("Context engine does not support get operations")
|
|
741
|
+
return None
|
|
742
|
+
except Exception as e:
|
|
743
|
+
logger.debug(f"No data found for key {key}: {e}")
|
|
744
|
+
return None
|
|
745
|
+
|
|
746
|
+
async def _save_to_storage(self) -> None:
|
|
747
|
+
"""
|
|
748
|
+
Save all communities and members to persistent storage.
|
|
749
|
+
|
|
750
|
+
Saves:
|
|
751
|
+
- Communities
|
|
752
|
+
- Members
|
|
753
|
+
- Resources
|
|
754
|
+
- Decisions
|
|
755
|
+
- Sessions
|
|
756
|
+
"""
|
|
757
|
+
if not self.context_engine:
|
|
758
|
+
logger.debug("No context engine available for saving")
|
|
759
|
+
return
|
|
760
|
+
|
|
761
|
+
try:
|
|
762
|
+
# Save communities
|
|
763
|
+
communities_data = {cid: community.model_dump() for cid, community in self.communities.items()}
|
|
764
|
+
await self._save_data_by_key("communities", communities_data)
|
|
765
|
+
|
|
766
|
+
# Save members
|
|
767
|
+
members_data = {mid: member.model_dump() for mid, member in self.members.items()}
|
|
768
|
+
await self._save_data_by_key("community_members", members_data)
|
|
769
|
+
|
|
770
|
+
# Save resources
|
|
771
|
+
resources_data = {rid: resource.model_dump() for rid, resource in self.resources.items()}
|
|
772
|
+
await self._save_data_by_key("community_resources", resources_data)
|
|
773
|
+
|
|
774
|
+
# Save decisions
|
|
775
|
+
decisions_data = {did: decision.model_dump() for did, decision in self.decisions.items()}
|
|
776
|
+
await self._save_data_by_key("community_decisions", decisions_data)
|
|
777
|
+
|
|
778
|
+
# Save sessions
|
|
779
|
+
sessions_data = {sid: session.model_dump() for sid, session in self.sessions.items()}
|
|
780
|
+
await self._save_data_by_key("community_sessions", sessions_data)
|
|
781
|
+
|
|
782
|
+
logger.debug(f"Saved {len(self.communities)} communities, {len(self.members)} members to storage")
|
|
783
|
+
|
|
784
|
+
except Exception as e:
|
|
785
|
+
logger.error(f"Error saving to storage: {e}")
|
|
786
|
+
|
|
787
|
+
async def _save_data_by_key(self, key: str, data: Dict[str, Any]) -> None:
|
|
788
|
+
"""Save data to context engine by key."""
|
|
789
|
+
if not self.context_engine:
|
|
790
|
+
return
|
|
791
|
+
|
|
792
|
+
try:
|
|
793
|
+
# The exact method depends on the context engine implementation
|
|
794
|
+
if hasattr(self.context_engine, "set_context"):
|
|
795
|
+
await self.context_engine.set_context(key, data)
|
|
796
|
+
elif hasattr(self.context_engine, "set"):
|
|
797
|
+
await self.context_engine.set(key, data)
|
|
798
|
+
elif hasattr(self.context_engine, "save"):
|
|
799
|
+
await self.context_engine.save(key, data)
|
|
800
|
+
else:
|
|
801
|
+
logger.warning("Context engine does not support set/save operations")
|
|
802
|
+
except Exception as e:
|
|
803
|
+
logger.error(f"Failed to save data for key {key}: {e}")
|