claude-mpm 4.0.32__py3-none-any.whl → 4.0.34__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/VERSION +1 -1
- claude_mpm/agents/templates/documentation.json +51 -34
- claude_mpm/agents/templates/research.json +0 -11
- claude_mpm/cli/__init__.py +63 -26
- claude_mpm/cli/commands/agent_manager.py +10 -8
- claude_mpm/core/framework_loader.py +173 -84
- claude_mpm/dashboard/static/css/dashboard.css +449 -0
- claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +774 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +257 -3
- claude_mpm/dashboard/static/js/components/build-tracker.js +289 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +168 -39
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +17 -0
- claude_mpm/dashboard/static/js/components/session-manager.js +23 -3
- claude_mpm/dashboard/static/js/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/js/dashboard.js +207 -31
- claude_mpm/dashboard/static/js/socket-client.js +85 -6
- claude_mpm/dashboard/templates/index.html +1 -0
- claude_mpm/hooks/claude_hooks/connection_pool.py +12 -2
- claude_mpm/hooks/claude_hooks/event_handlers.py +81 -19
- claude_mpm/hooks/claude_hooks/hook_handler.py +72 -10
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +398 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +10 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +34 -48
- claude_mpm/services/agents/deployment/agent_template_builder.py +18 -10
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +10 -25
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +189 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +3 -2
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +10 -3
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +10 -14
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +8 -85
- claude_mpm/services/agents/memory/content_manager.py +98 -105
- claude_mpm/services/event_bus/__init__.py +18 -0
- claude_mpm/services/event_bus/event_bus.py +334 -0
- claude_mpm/services/event_bus/relay.py +301 -0
- claude_mpm/services/events/__init__.py +44 -0
- claude_mpm/services/events/consumers/__init__.py +18 -0
- claude_mpm/services/events/consumers/dead_letter.py +296 -0
- claude_mpm/services/events/consumers/logging.py +183 -0
- claude_mpm/services/events/consumers/metrics.py +242 -0
- claude_mpm/services/events/consumers/socketio.py +376 -0
- claude_mpm/services/events/core.py +470 -0
- claude_mpm/services/events/interfaces.py +230 -0
- claude_mpm/services/events/producers/__init__.py +14 -0
- claude_mpm/services/events/producers/hook.py +269 -0
- claude_mpm/services/events/producers/system.py +327 -0
- claude_mpm/services/mcp_gateway/core/process_pool.py +411 -0
- claude_mpm/services/mcp_gateway/server/stdio_server.py +13 -0
- claude_mpm/services/monitor_build_service.py +345 -0
- claude_mpm/services/socketio/event_normalizer.py +667 -0
- claude_mpm/services/socketio/handlers/connection.py +78 -20
- claude_mpm/services/socketio/handlers/hook.py +14 -5
- claude_mpm/services/socketio/migration_utils.py +329 -0
- claude_mpm/services/socketio/server/broadcaster.py +26 -33
- claude_mpm/services/socketio/server/core.py +4 -3
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.0.34.dist-info}/METADATA +4 -3
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.0.34.dist-info}/RECORD +67 -46
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.0.34.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.0.34.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.0.34.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.0.34.dist-info}/top_level.txt +0 -0
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
|
+
import time
|
|
6
7
|
from datetime import datetime
|
|
7
8
|
from pathlib import Path
|
|
8
|
-
from typing import Any, Dict, Optional
|
|
9
|
+
from typing import Any, Dict, Optional, Set, Tuple
|
|
9
10
|
|
|
10
11
|
# Import resource handling for packaged installations
|
|
11
12
|
try:
|
|
@@ -86,6 +87,22 @@ class FrameworkLoader:
|
|
|
86
87
|
self.agents_dir = agents_dir
|
|
87
88
|
self.framework_version = None
|
|
88
89
|
self.framework_last_modified = None
|
|
90
|
+
|
|
91
|
+
# Performance optimization: Initialize caches
|
|
92
|
+
self._agent_capabilities_cache: Optional[str] = None
|
|
93
|
+
self._agent_capabilities_cache_time: float = 0
|
|
94
|
+
self._deployed_agents_cache: Optional[Set[str]] = None
|
|
95
|
+
self._deployed_agents_cache_time: float = 0
|
|
96
|
+
self._agent_metadata_cache: Dict[str, Tuple[Optional[Dict[str, Any]], float]] = {}
|
|
97
|
+
self._memories_cache: Optional[Dict[str, Any]] = None
|
|
98
|
+
self._memories_cache_time: float = 0
|
|
99
|
+
|
|
100
|
+
# Cache TTL settings (in seconds)
|
|
101
|
+
self.CAPABILITIES_CACHE_TTL = 60 # 60 seconds for capabilities
|
|
102
|
+
self.DEPLOYED_AGENTS_CACHE_TTL = 30 # 30 seconds for deployed agents
|
|
103
|
+
self.METADATA_CACHE_TTL = 60 # 60 seconds for agent metadata
|
|
104
|
+
self.MEMORIES_CACHE_TTL = 60 # 60 seconds for memories
|
|
105
|
+
|
|
89
106
|
self.framework_content = self._load_framework_content()
|
|
90
107
|
|
|
91
108
|
# Initialize agent registry
|
|
@@ -94,6 +111,32 @@ class FrameworkLoader:
|
|
|
94
111
|
# Initialize output style manager (must be after content is loaded)
|
|
95
112
|
self.output_style_manager = None
|
|
96
113
|
# Defer initialization until first use to ensure content is loaded
|
|
114
|
+
|
|
115
|
+
def clear_all_caches(self) -> None:
|
|
116
|
+
"""Clear all caches to force reload on next access."""
|
|
117
|
+
self.logger.info("Clearing all framework loader caches")
|
|
118
|
+
self._agent_capabilities_cache = None
|
|
119
|
+
self._agent_capabilities_cache_time = 0
|
|
120
|
+
self._deployed_agents_cache = None
|
|
121
|
+
self._deployed_agents_cache_time = 0
|
|
122
|
+
self._agent_metadata_cache.clear()
|
|
123
|
+
self._memories_cache = None
|
|
124
|
+
self._memories_cache_time = 0
|
|
125
|
+
|
|
126
|
+
def clear_agent_caches(self) -> None:
|
|
127
|
+
"""Clear agent-related caches (capabilities, deployed agents, metadata)."""
|
|
128
|
+
self.logger.info("Clearing agent-related caches")
|
|
129
|
+
self._agent_capabilities_cache = None
|
|
130
|
+
self._agent_capabilities_cache_time = 0
|
|
131
|
+
self._deployed_agents_cache = None
|
|
132
|
+
self._deployed_agents_cache_time = 0
|
|
133
|
+
self._agent_metadata_cache.clear()
|
|
134
|
+
|
|
135
|
+
def clear_memory_caches(self) -> None:
|
|
136
|
+
"""Clear memory-related caches."""
|
|
137
|
+
self.logger.info("Clearing memory caches")
|
|
138
|
+
self._memories_cache = None
|
|
139
|
+
self._memories_cache_time = 0
|
|
97
140
|
|
|
98
141
|
def _initialize_output_style(self) -> None:
|
|
99
142
|
"""Initialize output style management and deploy if applicable."""
|
|
@@ -486,10 +529,20 @@ class FrameworkLoader:
|
|
|
486
529
|
def _get_deployed_agents(self) -> set:
|
|
487
530
|
"""
|
|
488
531
|
Get a set of deployed agent names from .claude/agents/ directories.
|
|
532
|
+
Uses caching to avoid repeated filesystem scans.
|
|
489
533
|
|
|
490
534
|
Returns:
|
|
491
535
|
Set of agent names (file stems) that are deployed
|
|
492
536
|
"""
|
|
537
|
+
# Check if cache is valid
|
|
538
|
+
current_time = time.time()
|
|
539
|
+
if (self._deployed_agents_cache is not None and
|
|
540
|
+
current_time - self._deployed_agents_cache_time < self.DEPLOYED_AGENTS_CACHE_TTL):
|
|
541
|
+
self.logger.debug(f"Using cached deployed agents (age: {current_time - self._deployed_agents_cache_time:.1f}s)")
|
|
542
|
+
return self._deployed_agents_cache
|
|
543
|
+
|
|
544
|
+
# Cache miss or expired - perform actual scan
|
|
545
|
+
self.logger.debug("Scanning for deployed agents (cache miss or expired)")
|
|
493
546
|
deployed = set()
|
|
494
547
|
|
|
495
548
|
# Check multiple locations for deployed agents
|
|
@@ -507,11 +560,17 @@ class FrameworkLoader:
|
|
|
507
560
|
self.logger.debug(f"Found deployed agent: {agent_file.stem} in {agents_dir}")
|
|
508
561
|
|
|
509
562
|
self.logger.debug(f"Total deployed agents found: {len(deployed)}")
|
|
563
|
+
|
|
564
|
+
# Update cache
|
|
565
|
+
self._deployed_agents_cache = deployed
|
|
566
|
+
self._deployed_agents_cache_time = current_time
|
|
567
|
+
|
|
510
568
|
return deployed
|
|
511
569
|
|
|
512
570
|
def _load_actual_memories(self, content: Dict[str, Any]) -> None:
|
|
513
571
|
"""
|
|
514
572
|
Load actual memories from both user and project directories.
|
|
573
|
+
Uses caching to avoid repeated file I/O operations.
|
|
515
574
|
|
|
516
575
|
Loading order:
|
|
517
576
|
1. User-level memories from ~/.claude-mpm/memories/ (global defaults)
|
|
@@ -524,6 +583,23 @@ class FrameworkLoader:
|
|
|
524
583
|
Args:
|
|
525
584
|
content: Dictionary to update with actual memories
|
|
526
585
|
"""
|
|
586
|
+
# Check if cache is valid
|
|
587
|
+
current_time = time.time()
|
|
588
|
+
if (self._memories_cache is not None and
|
|
589
|
+
current_time - self._memories_cache_time < self.MEMORIES_CACHE_TTL):
|
|
590
|
+
cache_age = current_time - self._memories_cache_time
|
|
591
|
+
self.logger.debug(f"Using cached memories (age: {cache_age:.1f}s)")
|
|
592
|
+
|
|
593
|
+
# Apply cached memories to content
|
|
594
|
+
if "actual_memories" in self._memories_cache:
|
|
595
|
+
content["actual_memories"] = self._memories_cache["actual_memories"]
|
|
596
|
+
if "agent_memories" in self._memories_cache:
|
|
597
|
+
content["agent_memories"] = self._memories_cache["agent_memories"]
|
|
598
|
+
return
|
|
599
|
+
|
|
600
|
+
# Cache miss or expired - perform actual loading
|
|
601
|
+
self.logger.debug("Loading memories from disk (cache miss or expired)")
|
|
602
|
+
|
|
527
603
|
# Define memory directories in priority order (user first, then project)
|
|
528
604
|
user_memories_dir = Path.home() / ".claude-mpm" / "memories"
|
|
529
605
|
project_memories_dir = Path.cwd() / ".claude-mpm" / "memories"
|
|
@@ -575,6 +651,14 @@ class FrameworkLoader:
|
|
|
575
651
|
memory_size = len(memory_content.encode('utf-8'))
|
|
576
652
|
self.logger.debug(f"Aggregated {agent_name} memory: {memory_size:,} bytes")
|
|
577
653
|
|
|
654
|
+
# Update cache with loaded memories
|
|
655
|
+
self._memories_cache = {}
|
|
656
|
+
if "actual_memories" in content:
|
|
657
|
+
self._memories_cache["actual_memories"] = content["actual_memories"]
|
|
658
|
+
if "agent_memories" in content:
|
|
659
|
+
self._memories_cache["agent_memories"] = content["agent_memories"]
|
|
660
|
+
self._memories_cache_time = current_time
|
|
661
|
+
|
|
578
662
|
# Log detailed summary
|
|
579
663
|
if loaded_count > 0 or skipped_count > 0:
|
|
580
664
|
# Count unique agents with memories
|
|
@@ -726,11 +810,10 @@ class FrameworkLoader:
|
|
|
726
810
|
Aggregate multiple memory entries into a single memory string.
|
|
727
811
|
|
|
728
812
|
Strategy:
|
|
729
|
-
-
|
|
730
|
-
- Preserve all bullet-point items (lines starting with -)
|
|
731
|
-
-
|
|
732
|
-
-
|
|
733
|
-
- Preserve unique entries from both sources
|
|
813
|
+
- Simplified to support list-based memories only
|
|
814
|
+
- Preserve all unique bullet-point items (lines starting with -)
|
|
815
|
+
- Remove exact duplicates
|
|
816
|
+
- Project-level memories take precedence over user-level
|
|
734
817
|
|
|
735
818
|
Args:
|
|
736
819
|
memory_entries: List of memory entries with source, content, and path
|
|
@@ -745,97 +828,52 @@ class FrameworkLoader:
|
|
|
745
828
|
if len(memory_entries) == 1:
|
|
746
829
|
return memory_entries[0]["content"]
|
|
747
830
|
|
|
748
|
-
# Parse all memories into
|
|
749
|
-
|
|
750
|
-
unsectioned_items = {} # Items without a section header
|
|
831
|
+
# Parse all memories into a simple list
|
|
832
|
+
all_items = {} # Dict to track items and their source
|
|
751
833
|
metadata_lines = []
|
|
834
|
+
agent_id = None
|
|
752
835
|
|
|
753
836
|
for entry in memory_entries:
|
|
754
837
|
content = entry["content"]
|
|
755
838
|
source = entry["source"]
|
|
756
839
|
|
|
757
|
-
# Parse content into sections and unsectioned items
|
|
758
|
-
current_section = None
|
|
759
|
-
current_items = []
|
|
760
|
-
|
|
761
840
|
for line in content.split('\n'):
|
|
841
|
+
# Check for header to extract agent_id
|
|
842
|
+
if line.startswith('# Agent Memory:'):
|
|
843
|
+
agent_id = line.replace('# Agent Memory:', '').strip()
|
|
762
844
|
# Check for metadata lines
|
|
763
|
-
|
|
845
|
+
elif line.startswith('<!-- ') and line.endswith(' -->'):
|
|
764
846
|
# Only keep metadata from project source or if not already present
|
|
765
847
|
if source == "project" or line not in metadata_lines:
|
|
766
848
|
metadata_lines.append(line)
|
|
767
|
-
# Check for
|
|
768
|
-
elif line.startswith('
|
|
769
|
-
#
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
all_sections[current_section] = {}
|
|
773
|
-
# Store items with their source
|
|
774
|
-
for item in current_items:
|
|
775
|
-
# Use content as key to detect duplicates
|
|
776
|
-
all_sections[current_section][item] = source
|
|
849
|
+
# Check for list items
|
|
850
|
+
elif line.strip().startswith('-'):
|
|
851
|
+
# Normalize the item for comparison
|
|
852
|
+
item_text = line.strip()
|
|
853
|
+
normalized = item_text.lstrip('- ').strip().lower()
|
|
777
854
|
|
|
778
|
-
#
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
# Check for content lines (including unsectioned bullet points)
|
|
782
|
-
elif line.strip():
|
|
783
|
-
# If it's a bullet point or regular content
|
|
784
|
-
if current_section:
|
|
785
|
-
# Add to current section
|
|
786
|
-
current_items.append(line)
|
|
787
|
-
elif line.strip().startswith('-'):
|
|
788
|
-
# It's an unsectioned bullet point - preserve it
|
|
789
|
-
# Use content as key to detect duplicates
|
|
790
|
-
# Project source overrides user source
|
|
791
|
-
if line not in unsectioned_items or source == "project":
|
|
792
|
-
unsectioned_items[line] = source
|
|
793
|
-
# Skip other non-bullet unsectioned content (like headers)
|
|
794
|
-
elif not line.strip().startswith('#'):
|
|
795
|
-
# Include non-header orphaned content in unsectioned items
|
|
796
|
-
if line not in unsectioned_items or source == "project":
|
|
797
|
-
unsectioned_items[line] = source
|
|
798
|
-
|
|
799
|
-
# Save last section if exists
|
|
800
|
-
if current_section and current_items:
|
|
801
|
-
if current_section not in all_sections:
|
|
802
|
-
all_sections[current_section] = {}
|
|
803
|
-
for item in current_items:
|
|
804
|
-
# Project source overrides user source
|
|
805
|
-
if item not in all_sections[current_section] or source == "project":
|
|
806
|
-
all_sections[current_section][item] = source
|
|
855
|
+
# Add item if new or if project source overrides user source
|
|
856
|
+
if normalized not in all_items or source == "project":
|
|
857
|
+
all_items[normalized] = (item_text, source)
|
|
807
858
|
|
|
808
|
-
# Build aggregated content
|
|
859
|
+
# Build aggregated content as simple list
|
|
809
860
|
lines = []
|
|
810
861
|
|
|
811
|
-
# Add metadata
|
|
812
|
-
if metadata_lines:
|
|
813
|
-
lines.extend(metadata_lines)
|
|
814
|
-
lines.append("")
|
|
815
|
-
|
|
816
862
|
# Add header
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
863
|
+
if agent_id:
|
|
864
|
+
lines.append(f"# Agent Memory: {agent_id}")
|
|
865
|
+
else:
|
|
866
|
+
lines.append("# Agent Memory")
|
|
821
867
|
|
|
822
|
-
# Add
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
lines.append(item)
|
|
827
|
-
lines.append("") # Empty line after unsectioned items
|
|
868
|
+
# Add latest timestamp from metadata
|
|
869
|
+
from datetime import datetime
|
|
870
|
+
lines.append(f"<!-- Last Updated: {datetime.now().isoformat()}Z -->")
|
|
871
|
+
lines.append("")
|
|
828
872
|
|
|
829
|
-
# Add
|
|
830
|
-
for
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
# Sort items to ensure consistent output
|
|
835
|
-
for item in sorted(section_items.keys()):
|
|
836
|
-
lines.append(item)
|
|
837
|
-
|
|
838
|
-
lines.append("") # Empty line after section
|
|
873
|
+
# Add all unique items (sorted for consistency)
|
|
874
|
+
for normalized_key in sorted(all_items.keys()):
|
|
875
|
+
item_text, source = all_items[normalized_key]
|
|
876
|
+
lines.append(item_text)
|
|
839
877
|
|
|
840
878
|
return '\n'.join(lines)
|
|
841
879
|
|
|
@@ -1392,7 +1430,20 @@ Extract tickets from these patterns:
|
|
|
1392
1430
|
return instructions
|
|
1393
1431
|
|
|
1394
1432
|
def _generate_agent_capabilities_section(self) -> str:
|
|
1395
|
-
"""Generate dynamic agent capabilities section from deployed agents.
|
|
1433
|
+
"""Generate dynamic agent capabilities section from deployed agents.
|
|
1434
|
+
Uses caching to avoid repeated file I/O and parsing operations."""
|
|
1435
|
+
|
|
1436
|
+
# Check if cache is valid
|
|
1437
|
+
current_time = time.time()
|
|
1438
|
+
if (self._agent_capabilities_cache is not None and
|
|
1439
|
+
current_time - self._agent_capabilities_cache_time < self.CAPABILITIES_CACHE_TTL):
|
|
1440
|
+
cache_age = current_time - self._agent_capabilities_cache_time
|
|
1441
|
+
self.logger.debug(f"Using cached agent capabilities (age: {cache_age:.1f}s)")
|
|
1442
|
+
return self._agent_capabilities_cache
|
|
1443
|
+
|
|
1444
|
+
# Cache miss or expired - generate capabilities
|
|
1445
|
+
self.logger.debug("Generating agent capabilities (cache miss or expired)")
|
|
1446
|
+
|
|
1396
1447
|
try:
|
|
1397
1448
|
from pathlib import Path
|
|
1398
1449
|
|
|
@@ -1419,7 +1470,7 @@ Extract tickets from these patterns:
|
|
|
1419
1470
|
if agent_file.name.startswith("."):
|
|
1420
1471
|
continue
|
|
1421
1472
|
|
|
1422
|
-
# Parse agent metadata
|
|
1473
|
+
# Parse agent metadata (with caching)
|
|
1423
1474
|
agent_data = self._parse_agent_metadata(agent_file)
|
|
1424
1475
|
if agent_data:
|
|
1425
1476
|
agent_id = agent_data["id"]
|
|
@@ -1431,7 +1482,11 @@ Extract tickets from these patterns:
|
|
|
1431
1482
|
|
|
1432
1483
|
if not all_agents:
|
|
1433
1484
|
self.logger.warning(f"No agents found in any location: {agents_dirs}")
|
|
1434
|
-
|
|
1485
|
+
result = self._get_fallback_capabilities()
|
|
1486
|
+
# Cache the fallback result too
|
|
1487
|
+
self._agent_capabilities_cache = result
|
|
1488
|
+
self._agent_capabilities_cache_time = current_time
|
|
1489
|
+
return result
|
|
1435
1490
|
|
|
1436
1491
|
# Log agent collection summary
|
|
1437
1492
|
project_agents = [aid for aid, (_, pri) in all_agents.items() if pri == 0]
|
|
@@ -1449,7 +1504,11 @@ Extract tickets from these patterns:
|
|
|
1449
1504
|
deployed_agents = [agent_data for agent_data, _ in all_agents.values()]
|
|
1450
1505
|
|
|
1451
1506
|
if not deployed_agents:
|
|
1452
|
-
|
|
1507
|
+
result = self._get_fallback_capabilities()
|
|
1508
|
+
# Cache the fallback result
|
|
1509
|
+
self._agent_capabilities_cache = result
|
|
1510
|
+
self._agent_capabilities_cache_time = current_time
|
|
1511
|
+
return result
|
|
1453
1512
|
|
|
1454
1513
|
# Sort agents alphabetically by ID
|
|
1455
1514
|
deployed_agents.sort(key=lambda x: x["id"])
|
|
@@ -1496,19 +1555,46 @@ Extract tickets from these patterns:
|
|
|
1496
1555
|
# Add summary
|
|
1497
1556
|
section += f"\n**Total Available Agents**: {len(deployed_agents)}\n"
|
|
1498
1557
|
|
|
1558
|
+
# Cache the generated capabilities
|
|
1559
|
+
self._agent_capabilities_cache = section
|
|
1560
|
+
self._agent_capabilities_cache_time = current_time
|
|
1561
|
+
self.logger.debug(f"Cached agent capabilities section ({len(section)} chars)")
|
|
1562
|
+
|
|
1499
1563
|
return section
|
|
1500
1564
|
|
|
1501
1565
|
except Exception as e:
|
|
1502
1566
|
self.logger.warning(f"Could not generate dynamic agent capabilities: {e}")
|
|
1503
|
-
|
|
1567
|
+
result = self._get_fallback_capabilities()
|
|
1568
|
+
# Cache even the fallback result
|
|
1569
|
+
self._agent_capabilities_cache = result
|
|
1570
|
+
self._agent_capabilities_cache_time = current_time
|
|
1571
|
+
return result
|
|
1504
1572
|
|
|
1505
1573
|
def _parse_agent_metadata(self, agent_file: Path) -> Optional[Dict[str, Any]]:
|
|
1506
1574
|
"""Parse agent metadata from deployed agent file.
|
|
1575
|
+
Uses caching based on file path and modification time.
|
|
1507
1576
|
|
|
1508
1577
|
Returns:
|
|
1509
1578
|
Dictionary with agent metadata directly from YAML frontmatter.
|
|
1510
1579
|
"""
|
|
1511
1580
|
try:
|
|
1581
|
+
# Check cache based on file path and modification time
|
|
1582
|
+
cache_key = str(agent_file)
|
|
1583
|
+
file_mtime = agent_file.stat().st_mtime
|
|
1584
|
+
current_time = time.time()
|
|
1585
|
+
|
|
1586
|
+
# Check if we have cached data for this file
|
|
1587
|
+
if cache_key in self._agent_metadata_cache:
|
|
1588
|
+
cached_data, cached_mtime = self._agent_metadata_cache[cache_key]
|
|
1589
|
+
# Use cache if file hasn't been modified and cache isn't too old
|
|
1590
|
+
if (cached_mtime == file_mtime and
|
|
1591
|
+
current_time - cached_mtime < self.METADATA_CACHE_TTL):
|
|
1592
|
+
self.logger.debug(f"Using cached metadata for {agent_file.name}")
|
|
1593
|
+
return cached_data
|
|
1594
|
+
|
|
1595
|
+
# Cache miss or expired - parse the file
|
|
1596
|
+
self.logger.debug(f"Parsing metadata for {agent_file.name} (cache miss or expired)")
|
|
1597
|
+
|
|
1512
1598
|
import yaml
|
|
1513
1599
|
|
|
1514
1600
|
with open(agent_file, "r") as f:
|
|
@@ -1546,6 +1632,9 @@ Extract tickets from these patterns:
|
|
|
1546
1632
|
# IMPORTANT: Do NOT add spaces to tools field - it breaks deployment!
|
|
1547
1633
|
# Tools must remain as comma-separated without spaces: "Read,Write,Edit"
|
|
1548
1634
|
|
|
1635
|
+
# Cache the parsed metadata
|
|
1636
|
+
self._agent_metadata_cache[cache_key] = (agent_data, file_mtime)
|
|
1637
|
+
|
|
1549
1638
|
return agent_data
|
|
1550
1639
|
|
|
1551
1640
|
except Exception as e:
|