claude-mpm 4.7.5__py3-none-any.whl → 4.7.6__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/cli/commands/configure.py +72 -14
- claude_mpm/hooks/__init__.py +6 -0
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +27 -10
- claude_mpm/hooks/kuzu_response_hook.py +183 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +2 -2
- claude_mpm/services/mcp_service_verifier.py +1 -1
- claude_mpm/services/memory_hook_service.py +85 -3
- claude_mpm/utils/agent_dependency_loader.py +38 -18
- claude_mpm/utils/robust_installer.py +73 -19
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.6.dist-info}/METADATA +1 -1
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.6.dist-info}/RECORD +17 -15
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.6.dist-info}/WHEEL +0 -0
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.6.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.6.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.6.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.7.
|
1
|
+
4.7.6
|
@@ -374,7 +374,9 @@ class ConfigureCommand(BaseCommand):
|
|
374
374
|
self.console.print(menu_panel)
|
375
375
|
self.console.print()
|
376
376
|
|
377
|
-
|
377
|
+
choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="q")
|
378
|
+
# Strip whitespace to handle leading/trailing spaces
|
379
|
+
return choice.strip().lower()
|
378
380
|
|
379
381
|
def _manage_agents(self) -> None:
|
380
382
|
"""Agent management interface."""
|
@@ -425,7 +427,7 @@ class ConfigureCommand(BaseCommand):
|
|
425
427
|
table.add_column("ID", style="dim", width=3)
|
426
428
|
table.add_column("Name", style="cyan", width=22)
|
427
429
|
table.add_column("Status", width=12)
|
428
|
-
table.add_column("Description", style="
|
430
|
+
table.add_column("Description", style="bold cyan", width=45)
|
429
431
|
table.add_column("Model/Tools", style="dim", width=20)
|
430
432
|
|
431
433
|
for idx, agent in enumerate(agents, 1):
|
@@ -456,12 +458,11 @@ class ConfigureCommand(BaseCommand):
|
|
456
458
|
except Exception:
|
457
459
|
tools_display = "Default"
|
458
460
|
|
459
|
-
# Truncate description for table display
|
460
|
-
|
461
|
-
agent.description[:42]
|
462
|
-
|
463
|
-
|
464
|
-
)
|
461
|
+
# Truncate description for table display with bright styling
|
462
|
+
if len(agent.description) > 42:
|
463
|
+
desc_display = f"[cyan]{agent.description[:42]}[/cyan][dim]...[/dim]"
|
464
|
+
else:
|
465
|
+
desc_display = f"[cyan]{agent.description}[/cyan]"
|
465
466
|
|
466
467
|
table.add_row(str(idx), agent.name, status, desc_display, tools_display)
|
467
468
|
|
@@ -821,6 +822,61 @@ class ConfigureCommand(BaseCommand):
|
|
821
822
|
with self.console.pager():
|
822
823
|
self.console.print(syntax)
|
823
824
|
|
825
|
+
def _reset_agent_defaults(self, agents: List[AgentConfig]) -> None:
|
826
|
+
"""Reset an agent to default enabled state and remove custom template.
|
827
|
+
|
828
|
+
This method:
|
829
|
+
- Prompts for agent ID
|
830
|
+
- Resets agent to enabled state
|
831
|
+
- Removes any custom template overrides
|
832
|
+
- Shows success/error messages
|
833
|
+
"""
|
834
|
+
agent_id = Prompt.ask("Enter agent ID to reset to defaults")
|
835
|
+
|
836
|
+
try:
|
837
|
+
idx = int(agent_id) - 1
|
838
|
+
if 0 <= idx < len(agents):
|
839
|
+
agent = agents[idx]
|
840
|
+
|
841
|
+
# Confirm the reset action
|
842
|
+
if not Confirm.ask(
|
843
|
+
f"[yellow]Reset '{agent.name}' to defaults? This will:[/yellow]\n"
|
844
|
+
" - Enable the agent\n"
|
845
|
+
" - Remove custom template (if any)\n"
|
846
|
+
"[yellow]Continue?[/yellow]"
|
847
|
+
):
|
848
|
+
self.console.print("[yellow]Reset cancelled.[/yellow]")
|
849
|
+
Prompt.ask("Press Enter to continue")
|
850
|
+
return
|
851
|
+
|
852
|
+
# Enable the agent
|
853
|
+
self.agent_manager.set_agent_enabled(agent.name, True)
|
854
|
+
|
855
|
+
# Remove custom template if exists
|
856
|
+
template_path = self._get_agent_template_path(agent.name)
|
857
|
+
if template_path.exists() and not str(template_path).startswith(
|
858
|
+
str(self.agent_manager.templates_dir)
|
859
|
+
):
|
860
|
+
# This is a custom template, remove it
|
861
|
+
template_path.unlink(missing_ok=True)
|
862
|
+
self.console.print(
|
863
|
+
f"[green]✓ Removed custom template for '{agent.name}'[/green]"
|
864
|
+
)
|
865
|
+
|
866
|
+
self.console.print(
|
867
|
+
f"[green]✓ Agent '{agent.name}' reset to defaults![/green]"
|
868
|
+
)
|
869
|
+
self.console.print(
|
870
|
+
"[dim]Agent is now enabled with system template.[/dim]"
|
871
|
+
)
|
872
|
+
else:
|
873
|
+
self.console.print("[red]Invalid agent ID.[/red]")
|
874
|
+
|
875
|
+
except ValueError:
|
876
|
+
self.console.print("[red]Invalid input. Please enter a number.[/red]")
|
877
|
+
|
878
|
+
Prompt.ask("Press Enter to continue")
|
879
|
+
|
824
880
|
def _view_agent_details(self, agents: List[AgentConfig]) -> None:
|
825
881
|
"""View detailed information about an agent."""
|
826
882
|
agent_id = Prompt.ask("Enter agent ID to view")
|
@@ -1331,7 +1387,7 @@ class ConfigureCommand(BaseCommand):
|
|
1331
1387
|
table.add_column("ID", style="dim", width=5)
|
1332
1388
|
table.add_column("Agent", style="cyan", width=25)
|
1333
1389
|
table.add_column("Status", width=15)
|
1334
|
-
table.add_column("Description", style="
|
1390
|
+
table.add_column("Description", style="bold cyan", width=45)
|
1335
1391
|
|
1336
1392
|
for idx, agent in enumerate(agents, 1):
|
1337
1393
|
# Agent is ENABLED if NOT in disabled list
|
@@ -1341,11 +1397,13 @@ class ConfigureCommand(BaseCommand):
|
|
1341
1397
|
if is_enabled
|
1342
1398
|
else "[red]✗ Disabled[/red]"
|
1343
1399
|
)
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1400
|
+
# Format description with bright styling
|
1401
|
+
if len(agent.description) > 42:
|
1402
|
+
desc_display = (
|
1403
|
+
f"[cyan]{agent.description[:42]}[/cyan][dim]...[/dim]"
|
1404
|
+
)
|
1405
|
+
else:
|
1406
|
+
desc_display = f"[cyan]{agent.description}[/cyan]"
|
1349
1407
|
table.add_row(str(idx), agent.name, status, desc_display)
|
1350
1408
|
|
1351
1409
|
self.console.print(table)
|
claude_mpm/hooks/__init__.py
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
"""Hook system for claude-mpm."""
|
2
2
|
|
3
3
|
from .base_hook import BaseHook, HookContext, HookResult, HookType
|
4
|
+
from .kuzu_enrichment_hook import KuzuEnrichmentHook, get_kuzu_enrichment_hook
|
4
5
|
from .kuzu_memory_hook import KuzuMemoryHook, get_kuzu_memory_hook
|
6
|
+
from .kuzu_response_hook import KuzuResponseHook, get_kuzu_response_hook
|
5
7
|
|
6
8
|
__all__ = [
|
7
9
|
"BaseHook",
|
8
10
|
"HookContext",
|
9
11
|
"HookResult",
|
10
12
|
"HookType",
|
13
|
+
"KuzuEnrichmentHook",
|
11
14
|
"KuzuMemoryHook",
|
15
|
+
"KuzuResponseHook",
|
16
|
+
"get_kuzu_enrichment_hook",
|
12
17
|
"get_kuzu_memory_hook",
|
18
|
+
"get_kuzu_response_hook",
|
13
19
|
]
|
@@ -0,0 +1,263 @@
|
|
1
|
+
"""
|
2
|
+
Kuzu-Memory Pre-Delegation Enrichment Hook
|
3
|
+
==========================================
|
4
|
+
|
5
|
+
Enriches agent delegation context with relevant memories from kuzu-memory
|
6
|
+
before the agent receives the task. This is the READ side of bidirectional
|
7
|
+
enrichment.
|
8
|
+
|
9
|
+
WHY: Agents need access to relevant historical knowledge when performing tasks.
|
10
|
+
This hook retrieves memories from kuzu-memory and injects them into the
|
11
|
+
delegation context.
|
12
|
+
|
13
|
+
DESIGN DECISIONS:
|
14
|
+
- Priority 10 to run early, before other context modifications
|
15
|
+
- Reuses KuzuMemoryHook's retrieval methods for consistency
|
16
|
+
- Injects memories as a dedicated section in agent context
|
17
|
+
- Falls back gracefully if kuzu-memory is not available
|
18
|
+
"""
|
19
|
+
|
20
|
+
import logging
|
21
|
+
from typing import Any, Dict, Optional
|
22
|
+
|
23
|
+
from claude_mpm.hooks.base_hook import HookContext, HookResult, PreDelegationHook
|
24
|
+
from claude_mpm.hooks.kuzu_memory_hook import get_kuzu_memory_hook
|
25
|
+
|
26
|
+
logger = logging.getLogger(__name__)
|
27
|
+
|
28
|
+
|
29
|
+
class KuzuEnrichmentHook(PreDelegationHook):
|
30
|
+
"""
|
31
|
+
Hook that enriches agent delegation context with kuzu-memory.
|
32
|
+
|
33
|
+
This hook:
|
34
|
+
1. Extracts the task/prompt from delegation context
|
35
|
+
2. Retrieves relevant memories from kuzu-memory
|
36
|
+
3. Injects memories into agent context
|
37
|
+
4. Formats memories for optimal agent understanding
|
38
|
+
"""
|
39
|
+
|
40
|
+
def __init__(self):
|
41
|
+
"""Initialize the kuzu-memory enrichment hook."""
|
42
|
+
super().__init__(name="kuzu_memory_enrichment", priority=10)
|
43
|
+
|
44
|
+
# Reuse the kuzu-memory hook instance for retrieval
|
45
|
+
self.kuzu_hook = get_kuzu_memory_hook()
|
46
|
+
self.enabled = self.kuzu_hook.enabled
|
47
|
+
|
48
|
+
if not self.enabled:
|
49
|
+
logger.info(
|
50
|
+
"Kuzu-memory enrichment hook disabled (kuzu-memory not available)"
|
51
|
+
)
|
52
|
+
else:
|
53
|
+
logger.info("Kuzu-memory enrichment hook enabled")
|
54
|
+
|
55
|
+
def validate(self, context: HookContext) -> bool:
|
56
|
+
"""
|
57
|
+
Validate if hook should process this context.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
context: Hook context to validate
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
True if hook should execute
|
64
|
+
"""
|
65
|
+
if not self.enabled:
|
66
|
+
return False
|
67
|
+
|
68
|
+
# Check base validation (enabled, correct hook type, has agent)
|
69
|
+
if not super().validate(context):
|
70
|
+
return False
|
71
|
+
|
72
|
+
# Must have agent and context data
|
73
|
+
if not context.data.get("agent"):
|
74
|
+
return False
|
75
|
+
|
76
|
+
return True
|
77
|
+
|
78
|
+
def execute(self, context: HookContext) -> HookResult:
|
79
|
+
"""
|
80
|
+
Enrich delegation context with relevant memories.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
context: Hook context containing delegation data
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
HookResult with enriched context
|
87
|
+
"""
|
88
|
+
if not self.enabled:
|
89
|
+
return HookResult(success=True, data=context.data, modified=False)
|
90
|
+
|
91
|
+
try:
|
92
|
+
# Extract query for memory retrieval
|
93
|
+
query = self._extract_query_from_context(context.data)
|
94
|
+
|
95
|
+
if not query:
|
96
|
+
logger.debug("No query extracted from context for memory retrieval")
|
97
|
+
return HookResult(success=True, data=context.data, modified=False)
|
98
|
+
|
99
|
+
# Retrieve relevant memories
|
100
|
+
memories = self.kuzu_hook._retrieve_memories(query)
|
101
|
+
|
102
|
+
if not memories:
|
103
|
+
logger.debug("No relevant memories found")
|
104
|
+
return HookResult(success=True, data=context.data, modified=False)
|
105
|
+
|
106
|
+
# Enrich context with memories
|
107
|
+
enriched_data = self._enrich_delegation_context(
|
108
|
+
context.data, memories, context.data.get("agent", "Agent")
|
109
|
+
)
|
110
|
+
|
111
|
+
logger.info(
|
112
|
+
f"Enriched delegation context with {len(memories)} memories for {context.data.get('agent')}"
|
113
|
+
)
|
114
|
+
|
115
|
+
return HookResult(
|
116
|
+
success=True,
|
117
|
+
data=enriched_data,
|
118
|
+
modified=True,
|
119
|
+
metadata={
|
120
|
+
"memories_added": len(memories),
|
121
|
+
"memory_source": "kuzu",
|
122
|
+
"agent": context.data.get("agent"),
|
123
|
+
},
|
124
|
+
)
|
125
|
+
|
126
|
+
except Exception as e:
|
127
|
+
logger.error(f"Error in kuzu enrichment hook: {e}")
|
128
|
+
# Don't fail the delegation if memory enrichment fails
|
129
|
+
return HookResult(
|
130
|
+
success=True,
|
131
|
+
data=context.data,
|
132
|
+
modified=False,
|
133
|
+
error=f"Memory enrichment failed: {e}",
|
134
|
+
)
|
135
|
+
|
136
|
+
def _extract_query_from_context(self, data: Dict[str, Any]) -> Optional[str]:
|
137
|
+
"""
|
138
|
+
Extract query text for memory retrieval.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
data: Delegation context data
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
Query string or None
|
145
|
+
"""
|
146
|
+
# Try various context fields
|
147
|
+
delegation_context = data.get("context", {})
|
148
|
+
|
149
|
+
# Handle string context
|
150
|
+
if isinstance(delegation_context, str):
|
151
|
+
return delegation_context
|
152
|
+
|
153
|
+
# Handle dict context
|
154
|
+
if isinstance(delegation_context, dict):
|
155
|
+
# Try common fields
|
156
|
+
for field in ["prompt", "task", "query", "user_request", "description"]:
|
157
|
+
if field in delegation_context:
|
158
|
+
value = delegation_context[field]
|
159
|
+
if isinstance(value, str):
|
160
|
+
return value
|
161
|
+
|
162
|
+
# If no specific field, join all string values
|
163
|
+
text_parts = [
|
164
|
+
str(v) for v in delegation_context.values() if isinstance(v, str)
|
165
|
+
]
|
166
|
+
if text_parts:
|
167
|
+
return " ".join(text_parts)
|
168
|
+
|
169
|
+
# Fallback: try to get task or instruction directly
|
170
|
+
if "task" in data and isinstance(data["task"], str):
|
171
|
+
return data["task"]
|
172
|
+
|
173
|
+
if "instruction" in data and isinstance(data["instruction"], str):
|
174
|
+
return data["instruction"]
|
175
|
+
|
176
|
+
return None
|
177
|
+
|
178
|
+
def _enrich_delegation_context(
|
179
|
+
self, original_data: Dict[str, Any], memories: list, agent_name: str
|
180
|
+
) -> Dict[str, Any]:
|
181
|
+
"""
|
182
|
+
Enrich delegation context with memories.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
original_data: Original delegation data
|
186
|
+
memories: Retrieved memories
|
187
|
+
agent_name: Name of the agent
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
Enriched delegation data
|
191
|
+
"""
|
192
|
+
# Format memories
|
193
|
+
memory_section = self._format_memory_section(memories, agent_name)
|
194
|
+
|
195
|
+
# Create enriched data
|
196
|
+
enriched_data = original_data.copy()
|
197
|
+
|
198
|
+
# Get existing context
|
199
|
+
delegation_context = enriched_data.get("context", {})
|
200
|
+
if isinstance(delegation_context, str):
|
201
|
+
delegation_context = {"prompt": delegation_context}
|
202
|
+
|
203
|
+
# Add memory section
|
204
|
+
if isinstance(delegation_context, dict):
|
205
|
+
# Prepend memory section to context
|
206
|
+
delegation_context["kuzu_memories"] = memory_section
|
207
|
+
|
208
|
+
# If there's a main prompt/task, prepend memory note
|
209
|
+
for field in ["prompt", "task", "instruction"]:
|
210
|
+
if field in delegation_context and isinstance(
|
211
|
+
delegation_context[field], str
|
212
|
+
):
|
213
|
+
delegation_context[field] = (
|
214
|
+
f"{memory_section}\n\n{delegation_context[field]}"
|
215
|
+
)
|
216
|
+
break
|
217
|
+
else:
|
218
|
+
# If context is not dict, create new dict with memory
|
219
|
+
delegation_context = {
|
220
|
+
"kuzu_memories": memory_section,
|
221
|
+
"original_context": delegation_context,
|
222
|
+
}
|
223
|
+
|
224
|
+
enriched_data["context"] = delegation_context
|
225
|
+
enriched_data["_kuzu_enriched"] = True
|
226
|
+
|
227
|
+
return enriched_data
|
228
|
+
|
229
|
+
def _format_memory_section(self, memories: list, agent_name: str) -> str:
|
230
|
+
"""
|
231
|
+
Format memories into a readable section.
|
232
|
+
|
233
|
+
Args:
|
234
|
+
memories: List of memory dictionaries
|
235
|
+
agent_name: Name of the agent
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
Formatted memory section
|
239
|
+
"""
|
240
|
+
memory_text = self.kuzu_hook._format_memories(memories)
|
241
|
+
|
242
|
+
return f"""
|
243
|
+
=== RELEVANT KNOWLEDGE FROM KUZU MEMORY ===
|
244
|
+
{agent_name}, you have access to these relevant memories from the knowledge graph:
|
245
|
+
|
246
|
+
{memory_text}
|
247
|
+
|
248
|
+
INSTRUCTIONS: Review these memories before proceeding. Apply learned patterns and avoid known mistakes.
|
249
|
+
Use this knowledge to provide more informed and contextual responses.
|
250
|
+
===========================================
|
251
|
+
"""
|
252
|
+
|
253
|
+
|
254
|
+
# Create a singleton instance
|
255
|
+
_kuzu_enrichment_hook = None
|
256
|
+
|
257
|
+
|
258
|
+
def get_kuzu_enrichment_hook() -> KuzuEnrichmentHook:
|
259
|
+
"""Get the singleton kuzu-memory enrichment hook instance."""
|
260
|
+
global _kuzu_enrichment_hook
|
261
|
+
if _kuzu_enrichment_hook is None:
|
262
|
+
_kuzu_enrichment_hook = KuzuEnrichmentHook()
|
263
|
+
return _kuzu_enrichment_hook
|
@@ -157,9 +157,13 @@ class KuzuMemoryHook(SubmitHook):
|
|
157
157
|
List of relevant memory dictionaries
|
158
158
|
"""
|
159
159
|
try:
|
160
|
-
#
|
160
|
+
# Type narrowing: ensure kuzu_memory_cmd is not None before using
|
161
|
+
if self.kuzu_memory_cmd is None:
|
162
|
+
return []
|
163
|
+
|
164
|
+
# Use kuzu-memory recall command (v1.2.7+ syntax)
|
161
165
|
result = subprocess.run(
|
162
|
-
[self.kuzu_memory_cmd, "recall", query, "--format", "json"],
|
166
|
+
[self.kuzu_memory_cmd, "memory", "recall", query, "--format", "json"],
|
163
167
|
capture_output=True,
|
164
168
|
text=True,
|
165
169
|
timeout=5,
|
@@ -168,10 +172,21 @@ class KuzuMemoryHook(SubmitHook):
|
|
168
172
|
)
|
169
173
|
|
170
174
|
if result.returncode == 0 and result.stdout:
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
+
try:
|
176
|
+
# Parse JSON with strict=False to handle control characters
|
177
|
+
data = json.loads(result.stdout, strict=False)
|
178
|
+
# v1.2.7 returns dict with 'memories' key, not array
|
179
|
+
if isinstance(data, dict):
|
180
|
+
memories = data.get("memories", [])
|
181
|
+
else:
|
182
|
+
memories = data if isinstance(data, list) else []
|
183
|
+
return memories
|
184
|
+
except json.JSONDecodeError as e:
|
185
|
+
logger.warning(f"Failed to parse kuzu-memory JSON output: {e}")
|
186
|
+
logger.debug(f"Raw output: {result.stdout[:200]}")
|
187
|
+
return [] # Graceful fallback
|
188
|
+
|
189
|
+
except (subprocess.TimeoutExpired, Exception) as e:
|
175
190
|
logger.debug(f"Memory retrieval failed: {e}")
|
176
191
|
|
177
192
|
return []
|
@@ -255,12 +270,12 @@ Note: Use the memories above to provide more informed and contextual responses.
|
|
255
270
|
Returns:
|
256
271
|
True if storage was successful
|
257
272
|
"""
|
258
|
-
if not self.enabled:
|
273
|
+
if not self.enabled or self.kuzu_memory_cmd is None:
|
259
274
|
return False
|
260
275
|
|
261
276
|
try:
|
262
|
-
# Use kuzu-memory
|
263
|
-
cmd = [self.kuzu_memory_cmd, "
|
277
|
+
# Use kuzu-memory store command (v1.2.7+ syntax)
|
278
|
+
cmd = [self.kuzu_memory_cmd, "memory", "store", content]
|
264
279
|
|
265
280
|
# Execute store command in project directory
|
266
281
|
result = subprocess.run(
|
@@ -273,8 +288,10 @@ Note: Use the memories above to provide more informed and contextual responses.
|
|
273
288
|
)
|
274
289
|
|
275
290
|
if result.returncode == 0:
|
276
|
-
logger.debug(f"Stored memory: {content[:50]}...")
|
291
|
+
logger.debug(f"Stored memory in kuzu: {content[:50]}...")
|
277
292
|
return True
|
293
|
+
logger.warning(f"Failed to store memory in kuzu: {result.stderr}")
|
294
|
+
return False
|
278
295
|
|
279
296
|
except Exception as e:
|
280
297
|
logger.error(f"Failed to store memory: {e}")
|
@@ -0,0 +1,183 @@
|
|
1
|
+
"""
|
2
|
+
Kuzu-Memory Response Learning Hook
|
3
|
+
===================================
|
4
|
+
|
5
|
+
Captures assistant responses and extracts learnings to store in kuzu-memory.
|
6
|
+
This completes the bidirectional enrichment cycle:
|
7
|
+
- KuzuMemoryHook enriches prompts with memories (READ)
|
8
|
+
- KuzuResponseHook stores learnings from responses (WRITE)
|
9
|
+
|
10
|
+
WHY: To automatically capture and persist important information from agent
|
11
|
+
responses, enabling continuous learning across conversations.
|
12
|
+
|
13
|
+
DESIGN DECISIONS:
|
14
|
+
- Priority 80 to run late after main processing
|
15
|
+
- Reuses KuzuMemoryHook's storage methods for consistency
|
16
|
+
- Graceful degradation if kuzu-memory is not available
|
17
|
+
- Extracts structured learnings using patterns and AI
|
18
|
+
"""
|
19
|
+
|
20
|
+
import logging
|
21
|
+
from typing import Any, Optional
|
22
|
+
|
23
|
+
from claude_mpm.hooks.base_hook import (
|
24
|
+
HookContext,
|
25
|
+
HookResult,
|
26
|
+
PostDelegationHook,
|
27
|
+
)
|
28
|
+
from claude_mpm.hooks.kuzu_memory_hook import get_kuzu_memory_hook
|
29
|
+
|
30
|
+
logger = logging.getLogger(__name__)
|
31
|
+
|
32
|
+
|
33
|
+
class KuzuResponseHook(PostDelegationHook):
|
34
|
+
"""
|
35
|
+
Hook that captures agent responses and stores learnings in kuzu-memory.
|
36
|
+
|
37
|
+
This hook:
|
38
|
+
1. Processes agent responses after delegation completes
|
39
|
+
2. Extracts important learnings and information
|
40
|
+
3. Stores memories in kuzu-memory for future retrieval
|
41
|
+
4. Tags memories for better categorization
|
42
|
+
"""
|
43
|
+
|
44
|
+
def __init__(self):
|
45
|
+
"""Initialize the kuzu-memory response learning hook."""
|
46
|
+
super().__init__(name="kuzu_response_learner", priority=80)
|
47
|
+
|
48
|
+
# Reuse the kuzu-memory hook instance for storage
|
49
|
+
self.kuzu_hook = get_kuzu_memory_hook()
|
50
|
+
self.enabled = self.kuzu_hook.enabled
|
51
|
+
|
52
|
+
if not self.enabled:
|
53
|
+
logger.info(
|
54
|
+
"Kuzu-memory response hook disabled (kuzu-memory not available)"
|
55
|
+
)
|
56
|
+
else:
|
57
|
+
logger.info("Kuzu-memory response learning hook enabled")
|
58
|
+
|
59
|
+
def validate(self, context: HookContext) -> bool:
|
60
|
+
"""
|
61
|
+
Validate if hook should process this context.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
context: Hook context to validate
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
True if hook should execute
|
68
|
+
"""
|
69
|
+
if not self.enabled:
|
70
|
+
return False
|
71
|
+
|
72
|
+
# Check base validation (enabled, correct hook type, has result)
|
73
|
+
if not super().validate(context):
|
74
|
+
return False
|
75
|
+
|
76
|
+
# Must have result data to extract learnings from
|
77
|
+
result_data = context.data.get("result")
|
78
|
+
if not result_data:
|
79
|
+
return False
|
80
|
+
|
81
|
+
return True
|
82
|
+
|
83
|
+
def execute(self, context: HookContext) -> HookResult:
|
84
|
+
"""
|
85
|
+
Extract and store learnings from agent responses.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
context: Hook context containing response data
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
HookResult with success status and metadata
|
92
|
+
"""
|
93
|
+
if not self.enabled:
|
94
|
+
return HookResult(success=True, data=context.data, modified=False)
|
95
|
+
|
96
|
+
try:
|
97
|
+
# Extract response content from various possible formats
|
98
|
+
result_data = context.data.get("result", {})
|
99
|
+
response_content = self._extract_response_content(result_data)
|
100
|
+
|
101
|
+
if not response_content:
|
102
|
+
logger.debug("No response content found for learning extraction")
|
103
|
+
return HookResult(success=True, data=context.data, modified=False)
|
104
|
+
|
105
|
+
# Extract and store learnings
|
106
|
+
count = self.kuzu_hook.extract_and_store_learnings(response_content)
|
107
|
+
|
108
|
+
if count > 0:
|
109
|
+
logger.info(f"Stored {count} learnings from agent response")
|
110
|
+
return HookResult(
|
111
|
+
success=True,
|
112
|
+
data=context.data,
|
113
|
+
modified=False,
|
114
|
+
metadata={"learnings_stored": count, "memory_backend": "kuzu"},
|
115
|
+
)
|
116
|
+
|
117
|
+
return HookResult(success=True, data=context.data, modified=False)
|
118
|
+
|
119
|
+
except Exception as e:
|
120
|
+
logger.error(f"Error in kuzu response hook: {e}")
|
121
|
+
# Don't fail the operation if learning extraction fails
|
122
|
+
return HookResult(
|
123
|
+
success=True,
|
124
|
+
data=context.data,
|
125
|
+
modified=False,
|
126
|
+
error=f"Learning extraction failed: {e}",
|
127
|
+
)
|
128
|
+
|
129
|
+
def _extract_response_content(self, result_data: Any) -> Optional[str]:
|
130
|
+
"""
|
131
|
+
Extract response content from various result formats.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
result_data: Result data in various possible formats
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
Extracted response content as string, or None
|
138
|
+
"""
|
139
|
+
if not result_data:
|
140
|
+
return None
|
141
|
+
|
142
|
+
# Handle dict format
|
143
|
+
if isinstance(result_data, dict):
|
144
|
+
# Try common response fields
|
145
|
+
for field in ["content", "text", "response", "output", "message"]:
|
146
|
+
if field in result_data:
|
147
|
+
content = result_data[field]
|
148
|
+
if isinstance(content, str):
|
149
|
+
return content
|
150
|
+
if isinstance(content, dict):
|
151
|
+
# Recursively extract from nested dict
|
152
|
+
return self._extract_response_content(content)
|
153
|
+
|
154
|
+
# If dict has no recognizable fields, try converting to string
|
155
|
+
return str(result_data)
|
156
|
+
|
157
|
+
# Handle string format
|
158
|
+
if isinstance(result_data, str):
|
159
|
+
return result_data
|
160
|
+
|
161
|
+
# Handle list format (concatenate items)
|
162
|
+
if isinstance(result_data, list):
|
163
|
+
items = []
|
164
|
+
for item in result_data:
|
165
|
+
extracted = self._extract_response_content(item)
|
166
|
+
if extracted:
|
167
|
+
items.append(extracted)
|
168
|
+
return "\n\n".join(items) if items else None
|
169
|
+
|
170
|
+
# Fallback to string conversion
|
171
|
+
return str(result_data) if result_data else None
|
172
|
+
|
173
|
+
|
174
|
+
# Create a singleton instance
|
175
|
+
_kuzu_response_hook = None
|
176
|
+
|
177
|
+
|
178
|
+
def get_kuzu_response_hook() -> KuzuResponseHook:
|
179
|
+
"""Get the singleton kuzu-memory response hook instance."""
|
180
|
+
global _kuzu_response_hook
|
181
|
+
if _kuzu_response_hook is None:
|
182
|
+
_kuzu_response_hook = KuzuResponseHook()
|
183
|
+
return _kuzu_response_hook
|
@@ -495,7 +495,7 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
495
495
|
base_cmd = config["mcp_command"]
|
496
496
|
if len(base_cmd) > 0 and base_cmd[0] == config["package"]:
|
497
497
|
# Simple case where first command is the package name
|
498
|
-
mcp_command = ["pipx", "run", config["package"]
|
498
|
+
mcp_command = ["pipx", "run", config["package"], *base_cmd[1:]]
|
499
499
|
else:
|
500
500
|
# Complex case - just try running the package with mcp arg
|
501
501
|
mcp_command = ["pipx", "run", config["package"], "mcp"]
|
@@ -509,7 +509,7 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
509
509
|
base_cmd = config["mcp_command"]
|
510
510
|
if service_name == "kuzu-memory":
|
511
511
|
# Special case for kuzu-memory with args
|
512
|
-
mcp_command = ["pipx", "run", base_cmd[0]
|
512
|
+
mcp_command = ["pipx", "run", base_cmd[0], *base_cmd[1:]]
|
513
513
|
else:
|
514
514
|
mcp_command = ["pipx", "run", *base_cmd]
|
515
515
|
else:
|
@@ -550,7 +550,7 @@ class MCPServiceVerifier:
|
|
550
550
|
|
551
551
|
try:
|
552
552
|
# Build test command - add --help to test without side effects
|
553
|
-
test_cmd = [command
|
553
|
+
test_cmd = [command, *args[:2]] if args else [command] # Include base args
|
554
554
|
test_cmd.append("--help")
|
555
555
|
|
556
556
|
result = subprocess.run(
|
@@ -41,8 +41,8 @@ class MemoryHookService(BaseService, MemoryHookInterface):
|
|
41
41
|
These hooks ensure memory is properly managed and persisted.
|
42
42
|
|
43
43
|
DESIGN DECISION: We register hooks for key lifecycle events:
|
44
|
-
- Before Claude interaction: Load relevant memories
|
45
|
-
- After Claude interaction: Save new memories
|
44
|
+
- Before Claude interaction: Load relevant memories (kuzu-memory + legacy)
|
45
|
+
- After Claude interaction: Save new memories (kuzu-memory + legacy)
|
46
46
|
- On error: Ensure memory state is preserved
|
47
47
|
"""
|
48
48
|
if not self.hook_service:
|
@@ -90,11 +90,93 @@ class MemoryHookService(BaseService, MemoryHookInterface):
|
|
90
90
|
if success2:
|
91
91
|
self.registered_hooks.append("memory_save")
|
92
92
|
|
93
|
-
self.logger.debug("
|
93
|
+
self.logger.debug("Legacy memory hooks registered successfully")
|
94
|
+
|
95
|
+
# Register kuzu-memory hooks if available
|
96
|
+
self._register_kuzu_memory_hooks()
|
94
97
|
|
95
98
|
except Exception as e:
|
96
99
|
self.logger.warning(f"Failed to register memory hooks: {e}")
|
97
100
|
|
101
|
+
def _register_kuzu_memory_hooks(self):
|
102
|
+
"""Register kuzu-memory bidirectional enrichment hooks.
|
103
|
+
|
104
|
+
WHY: Kuzu-memory provides persistent knowledge graph storage that works
|
105
|
+
across conversations. This enables:
|
106
|
+
1. Delegation context enrichment with relevant memories (READ)
|
107
|
+
2. Automatic learning extraction from responses (WRITE)
|
108
|
+
|
109
|
+
DESIGN DECISION: These hooks are separate from legacy memory hooks to
|
110
|
+
allow independent evolution and configuration. Both systems can coexist.
|
111
|
+
"""
|
112
|
+
try:
|
113
|
+
# Check if kuzu-memory is enabled in config
|
114
|
+
from claude_mpm.core.config import Config
|
115
|
+
|
116
|
+
config = Config()
|
117
|
+
kuzu_config = config.get("memory.kuzu", {})
|
118
|
+
if isinstance(kuzu_config, dict):
|
119
|
+
kuzu_enabled = kuzu_config.get("enabled", True)
|
120
|
+
enrichment_enabled = kuzu_config.get("enrichment", True)
|
121
|
+
learning_enabled = kuzu_config.get("learning", True)
|
122
|
+
else:
|
123
|
+
# Default to enabled if config section doesn't exist
|
124
|
+
kuzu_enabled = True
|
125
|
+
enrichment_enabled = True
|
126
|
+
learning_enabled = True
|
127
|
+
|
128
|
+
if not kuzu_enabled:
|
129
|
+
self.logger.debug("Kuzu-memory disabled in configuration")
|
130
|
+
return
|
131
|
+
|
132
|
+
from claude_mpm.hooks import (
|
133
|
+
get_kuzu_enrichment_hook,
|
134
|
+
get_kuzu_response_hook,
|
135
|
+
)
|
136
|
+
|
137
|
+
# Get kuzu-memory hooks
|
138
|
+
enrichment_hook = get_kuzu_enrichment_hook()
|
139
|
+
learning_hook = get_kuzu_response_hook()
|
140
|
+
|
141
|
+
# Register enrichment hook (PreDelegationHook) if enabled
|
142
|
+
if enrichment_hook.enabled and enrichment_enabled:
|
143
|
+
success = self.hook_service.register_hook(enrichment_hook)
|
144
|
+
if success:
|
145
|
+
self.registered_hooks.append("kuzu_memory_enrichment")
|
146
|
+
self.logger.info(
|
147
|
+
"✅ Kuzu-memory enrichment enabled (prompts → memories)"
|
148
|
+
)
|
149
|
+
else:
|
150
|
+
self.logger.warning(
|
151
|
+
"Failed to register kuzu-memory enrichment hook"
|
152
|
+
)
|
153
|
+
elif not enrichment_enabled:
|
154
|
+
self.logger.debug("Kuzu-memory enrichment disabled in configuration")
|
155
|
+
|
156
|
+
# Register learning hook (PostDelegationHook) if enabled
|
157
|
+
if learning_hook.enabled and learning_enabled:
|
158
|
+
success = self.hook_service.register_hook(learning_hook)
|
159
|
+
if success:
|
160
|
+
self.registered_hooks.append("kuzu_response_learner")
|
161
|
+
self.logger.info(
|
162
|
+
"✅ Kuzu-memory learning enabled (responses → memories)"
|
163
|
+
)
|
164
|
+
else:
|
165
|
+
self.logger.warning("Failed to register kuzu-memory learning hook")
|
166
|
+
elif not learning_enabled:
|
167
|
+
self.logger.debug("Kuzu-memory learning disabled in configuration")
|
168
|
+
|
169
|
+
# If neither hook is enabled, kuzu-memory is not available
|
170
|
+
if not enrichment_hook.enabled and not learning_hook.enabled:
|
171
|
+
self.logger.debug(
|
172
|
+
"Kuzu-memory not available. Install with: pipx install kuzu-memory"
|
173
|
+
)
|
174
|
+
|
175
|
+
except ImportError as e:
|
176
|
+
self.logger.debug(f"Kuzu-memory hooks not available: {e}")
|
177
|
+
except Exception as e:
|
178
|
+
self.logger.warning(f"Failed to register kuzu-memory hooks: {e}")
|
179
|
+
|
98
180
|
def _load_relevant_memories_hook(self, context):
|
99
181
|
"""Hook function to load relevant memories before Claude interaction.
|
100
182
|
|
@@ -14,7 +14,7 @@ import logging
|
|
14
14
|
import subprocess
|
15
15
|
import sys
|
16
16
|
import time
|
17
|
-
from typing import Dict, List, Optional, Set, Tuple
|
17
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
18
18
|
|
19
19
|
from packaging.requirements import InvalidRequirement, Requirement
|
20
20
|
|
@@ -72,7 +72,7 @@ class AgentDependencyLoader:
|
|
72
72
|
Returns:
|
73
73
|
Dictionary mapping agent IDs to their file paths
|
74
74
|
"""
|
75
|
-
deployed_agents = {}
|
75
|
+
deployed_agents: Dict[str, Path] = {}
|
76
76
|
claude_agents_dir = Path.cwd() / ".claude" / "agents"
|
77
77
|
|
78
78
|
if not claude_agents_dir.exists():
|
@@ -409,7 +409,7 @@ class AgentDependencyLoader:
|
|
409
409
|
Returns:
|
410
410
|
Analysis results including missing and satisfied dependencies
|
411
411
|
"""
|
412
|
-
results = {
|
412
|
+
results: Dict[str, Any] = {
|
413
413
|
"agents": {},
|
414
414
|
"summary": {
|
415
415
|
"total_agents": len(self.deployed_agents),
|
@@ -422,7 +422,7 @@ class AgentDependencyLoader:
|
|
422
422
|
}
|
423
423
|
|
424
424
|
for agent_id, deps in self.agent_dependencies.items():
|
425
|
-
agent_result = {
|
425
|
+
agent_result: Dict[str, Dict[str, List[str]]] = {
|
426
426
|
"python": {"satisfied": [], "missing": [], "outdated": []},
|
427
427
|
"system": {"satisfied": [], "missing": []},
|
428
428
|
}
|
@@ -476,8 +476,8 @@ class AgentDependencyLoader:
|
|
476
476
|
"""
|
477
477
|
import sys
|
478
478
|
|
479
|
-
compatible = []
|
480
|
-
incompatible = []
|
479
|
+
compatible: List[str] = []
|
480
|
+
incompatible: List[str] = []
|
481
481
|
|
482
482
|
for dep in dependencies:
|
483
483
|
try:
|
@@ -593,20 +593,37 @@ class AgentDependencyLoader:
|
|
593
593
|
try:
|
594
594
|
cmd = [sys.executable, "-m", "pip", "install"]
|
595
595
|
|
596
|
-
# Check
|
596
|
+
# Check environment and add appropriate flags
|
597
|
+
import os
|
597
598
|
import sysconfig
|
598
599
|
|
599
|
-
|
600
|
-
|
601
|
-
|
600
|
+
# Check if in virtualenv
|
601
|
+
in_virtualenv = (
|
602
|
+
(hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix)
|
603
|
+
or (hasattr(sys, "real_prefix"))
|
604
|
+
or (os.environ.get("VIRTUAL_ENV") is not None)
|
605
|
+
)
|
602
606
|
|
603
|
-
if
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
)
|
609
|
-
|
607
|
+
if in_virtualenv:
|
608
|
+
# In virtualenv - no special flags needed
|
609
|
+
logger.debug("Installing in virtualenv (no special flags)")
|
610
|
+
else:
|
611
|
+
# Check for PEP 668 managed environment
|
612
|
+
stdlib_path = sysconfig.get_path("stdlib")
|
613
|
+
marker_file = Path(stdlib_path) / "EXTERNALLY-MANAGED"
|
614
|
+
parent_marker = marker_file.parent.parent / "EXTERNALLY-MANAGED"
|
615
|
+
|
616
|
+
if marker_file.exists() or parent_marker.exists():
|
617
|
+
logger.warning(
|
618
|
+
"PEP 668 managed environment detected. "
|
619
|
+
"Installing with --break-system-packages flag. "
|
620
|
+
"Consider using a virtual environment instead."
|
621
|
+
)
|
622
|
+
cmd.append("--break-system-packages")
|
623
|
+
else:
|
624
|
+
# Normal system Python - use --user
|
625
|
+
cmd.append("--user")
|
626
|
+
logger.debug("Installing with --user flag")
|
610
627
|
|
611
628
|
cmd.extend(compatible)
|
612
629
|
|
@@ -946,13 +963,16 @@ class AgentDependencyLoader:
|
|
946
963
|
|
947
964
|
def check_deployed_agent_dependencies(
|
948
965
|
auto_install: bool = False, verbose: bool = False
|
949
|
-
) ->
|
966
|
+
) -> int:
|
950
967
|
"""
|
951
968
|
Check dependencies for currently deployed agents.
|
952
969
|
|
953
970
|
Args:
|
954
971
|
auto_install: If True, automatically install missing Python dependencies
|
955
972
|
verbose: If True, enable verbose logging
|
973
|
+
|
974
|
+
Returns:
|
975
|
+
Status code: 0 if all dependencies satisfied, 1 if missing dependencies
|
956
976
|
"""
|
957
977
|
if verbose:
|
958
978
|
logging.getLogger().setLevel(logging.DEBUG)
|
@@ -9,6 +9,7 @@ DESIGN DECISION: We implement exponential backoff for retries and provide
|
|
9
9
|
multiple installation strategies (pip, conda, source) to maximize success rate.
|
10
10
|
"""
|
11
11
|
|
12
|
+
import os
|
12
13
|
import re
|
13
14
|
import subprocess
|
14
15
|
import sys
|
@@ -80,6 +81,7 @@ class RobustPackageInstaller:
|
|
80
81
|
self.use_cache = use_cache
|
81
82
|
self.attempts: List[InstallAttempt] = []
|
82
83
|
self.success_cache: Dict[str, bool] = {}
|
84
|
+
self.in_virtualenv = self._check_virtualenv()
|
83
85
|
self.is_pep668_managed = self._check_pep668_managed()
|
84
86
|
self.pep668_warning_shown = False
|
85
87
|
|
@@ -201,6 +203,35 @@ class RobustPackageInstaller:
|
|
201
203
|
except Exception as e:
|
202
204
|
return False, f"Unexpected error: {e!s}"
|
203
205
|
|
206
|
+
def _check_virtualenv(self) -> bool:
|
207
|
+
"""
|
208
|
+
Check if running inside a virtual environment.
|
209
|
+
|
210
|
+
WHY: Virtual environments are already isolated and don't need
|
211
|
+
--user or --break-system-packages flags. In fact, using --user
|
212
|
+
in a virtualenv causes errors.
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
True if in a virtualenv, False otherwise
|
216
|
+
"""
|
217
|
+
# Multiple ways to detect virtualenv
|
218
|
+
return (
|
219
|
+
(
|
220
|
+
# venv creates sys.base_prefix
|
221
|
+
hasattr(sys, "base_prefix")
|
222
|
+
and sys.base_prefix != sys.prefix
|
223
|
+
)
|
224
|
+
or (
|
225
|
+
# virtualenv creates sys.real_prefix
|
226
|
+
hasattr(sys, "real_prefix")
|
227
|
+
)
|
228
|
+
or (
|
229
|
+
# VIRTUAL_ENV environment variable
|
230
|
+
os.environ.get("VIRTUAL_ENV")
|
231
|
+
is not None
|
232
|
+
)
|
233
|
+
)
|
234
|
+
|
204
235
|
def _check_pep668_managed(self) -> bool:
|
205
236
|
"""
|
206
237
|
Check if Python environment is PEP 668 externally managed.
|
@@ -211,6 +242,11 @@ class RobustPackageInstaller:
|
|
211
242
|
Returns:
|
212
243
|
True if PEP 668 managed, False otherwise
|
213
244
|
"""
|
245
|
+
# If in virtualenv, PEP 668 doesn't apply
|
246
|
+
if self.in_virtualenv:
|
247
|
+
logger.debug("Running in virtualenv, PEP 668 restrictions don't apply")
|
248
|
+
return False
|
249
|
+
|
214
250
|
# Check for EXTERNALLY-MANAGED marker file
|
215
251
|
stdlib_path = sysconfig.get_path("stdlib")
|
216
252
|
marker_file = Path(stdlib_path) / "EXTERNALLY-MANAGED"
|
@@ -240,7 +276,7 @@ class RobustPackageInstaller:
|
|
240
276
|
"Your Python installation is marked as externally managed (PEP 668).\n"
|
241
277
|
"This typically means you're using a system Python managed by Homebrew, apt, etc.\n"
|
242
278
|
"\n"
|
243
|
-
"Installing packages with --break-system-packages
|
279
|
+
"Installing packages with --break-system-packages flag...\n"
|
244
280
|
"\n"
|
245
281
|
"RECOMMENDED: Use a virtual environment instead:\n"
|
246
282
|
" python -m venv .venv\n"
|
@@ -255,8 +291,10 @@ class RobustPackageInstaller:
|
|
255
291
|
"""
|
256
292
|
Build the installation command for a given strategy.
|
257
293
|
|
258
|
-
WHY:
|
259
|
-
|
294
|
+
WHY: Proper environment detection ensures we use the right pip flags:
|
295
|
+
- Virtualenv: No special flags needed (already isolated)
|
296
|
+
- PEP 668 system: Use --break-system-packages only
|
297
|
+
- Normal system: Use --user for user-local install
|
260
298
|
|
261
299
|
Args:
|
262
300
|
package_spec: Package specification
|
@@ -267,14 +305,19 @@ class RobustPackageInstaller:
|
|
267
305
|
"""
|
268
306
|
base_cmd = [sys.executable, "-m", "pip", "install"]
|
269
307
|
|
270
|
-
#
|
271
|
-
if self.
|
308
|
+
# Determine appropriate flags based on environment
|
309
|
+
if self.in_virtualenv:
|
310
|
+
# In virtualenv - no special flags needed
|
311
|
+
logger.debug("Installing in virtualenv (no special flags)")
|
312
|
+
elif self.is_pep668_managed:
|
313
|
+
# System Python with PEP 668 - use --break-system-packages only
|
272
314
|
self._show_pep668_warning()
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
)
|
315
|
+
base_cmd.append("--break-system-packages")
|
316
|
+
logger.debug("Added --break-system-packages flag for PEP 668 environment")
|
317
|
+
else:
|
318
|
+
# Normal system Python - use --user for user-local install
|
319
|
+
base_cmd.append("--user")
|
320
|
+
logger.debug("Added --user flag for user-local installation")
|
278
321
|
|
279
322
|
# Add cache control
|
280
323
|
if not self.use_cache:
|
@@ -612,11 +655,16 @@ class RobustPackageInstaller:
|
|
612
655
|
try:
|
613
656
|
cmd = [sys.executable, "-m", "pip", "install"]
|
614
657
|
|
615
|
-
# Add
|
616
|
-
if self.
|
658
|
+
# Add appropriate flags based on environment
|
659
|
+
if self.in_virtualenv:
|
660
|
+
logger.debug("Batch install in virtualenv (no special flags)")
|
661
|
+
elif self.is_pep668_managed:
|
617
662
|
self._show_pep668_warning()
|
618
|
-
cmd.
|
619
|
-
logger.debug("Added
|
663
|
+
cmd.append("--break-system-packages")
|
664
|
+
logger.debug("Added --break-system-packages for batch installation")
|
665
|
+
else:
|
666
|
+
cmd.append("--user")
|
667
|
+
logger.debug("Added --user flag for batch installation")
|
620
668
|
|
621
669
|
cmd.extend(packages)
|
622
670
|
|
@@ -654,12 +702,18 @@ class RobustPackageInstaller:
|
|
654
702
|
lines.append("INSTALLATION REPORT")
|
655
703
|
lines.append("=" * 60)
|
656
704
|
|
657
|
-
# Add
|
658
|
-
|
659
|
-
|
705
|
+
# Add environment status
|
706
|
+
lines.append("")
|
707
|
+
if self.in_virtualenv:
|
708
|
+
lines.append("✓ Environment: Virtual Environment (isolated)")
|
709
|
+
lines.append(" No special pip flags needed")
|
710
|
+
elif self.is_pep668_managed:
|
660
711
|
lines.append("⚠️ PEP 668 Managed Environment: YES")
|
661
|
-
lines.append(" Installations used --break-system-packages
|
712
|
+
lines.append(" Installations used --break-system-packages flag")
|
662
713
|
lines.append(" Consider using a virtual environment for better isolation")
|
714
|
+
else:
|
715
|
+
lines.append("Environment: System Python")
|
716
|
+
lines.append(" Installations used --user flag for user-local install")
|
663
717
|
|
664
718
|
# Summary
|
665
719
|
total_attempts = len(self.attempts)
|
@@ -672,7 +726,7 @@ class RobustPackageInstaller:
|
|
672
726
|
lines.append("")
|
673
727
|
|
674
728
|
# Details by package
|
675
|
-
packages = {}
|
729
|
+
packages: Dict[str, List[InstallAttempt]] = {}
|
676
730
|
for attempt in self.attempts:
|
677
731
|
if attempt.package not in packages:
|
678
732
|
packages[attempt.package] = []
|
@@ -1,5 +1,5 @@
|
|
1
1
|
claude_mpm/BUILD_NUMBER,sha256=9JfxhnDtr-8l3kCP2U5TVXSErptHoga8m7XA8zqgGOc,4
|
2
|
-
claude_mpm/VERSION,sha256=
|
2
|
+
claude_mpm/VERSION,sha256=gVLWgYgcXVc7Af4ejsObqWB0dhZ1OktjUoRmgOIPx1M,6
|
3
3
|
claude_mpm/__init__.py,sha256=UCw6j9e_tZQ3kJtTqmdfNv7MHyw9nD1jkj80WurwM2g,2064
|
4
4
|
claude_mpm/__main__.py,sha256=Ro5UBWBoQaSAIoSqWAr7zkbLyvi4sSy28WShqAhKJG0,723
|
5
5
|
claude_mpm/constants.py,sha256=cChN3myrAcF3jC-6DvHnBFTEnwlDk-TAsIXPvUZr_yw,5953
|
@@ -79,7 +79,7 @@ claude_mpm/cli/commands/analyze_code.py,sha256=yWZpG0aL4XlhcthtvbUqnFSlnvvseWO3V
|
|
79
79
|
claude_mpm/cli/commands/cleanup.py,sha256=TukZoPVfAFSF4ICfKCQUibczDE73EJP8nbEbfuT8GhE,19768
|
80
80
|
claude_mpm/cli/commands/cleanup_orphaned_agents.py,sha256=JR8crvgrz7Sa6d-SI-gKywok5S9rwc_DzDVk_h85sVs,4467
|
81
81
|
claude_mpm/cli/commands/config.py,sha256=Yfi8WO-10_MYz2QipFw-yEzVvHKNQ6iSQXeyW5J85Cg,18559
|
82
|
-
claude_mpm/cli/commands/configure.py,sha256=
|
82
|
+
claude_mpm/cli/commands/configure.py,sha256=22wvCVGeIUFk2mBvvG9Lhd-Jzq8GRLmGg0W9mndxMOY,82489
|
83
83
|
claude_mpm/cli/commands/dashboard.py,sha256=4jPTmTl97DRNNJlYREWeE1iDdkct1uL-vv24MZn9fj4,11403
|
84
84
|
claude_mpm/cli/commands/debug.py,sha256=YCfJ3aYf6hOCvLW_grdfINdEqI4RXVS28VJ7tkZBFS8,47115
|
85
85
|
claude_mpm/cli/commands/doctor.py,sha256=nNKLZG3Qv_UsHNgrmetrWKgS7Pe2Jn5vq5aXyl60wKQ,7310
|
@@ -368,10 +368,12 @@ claude_mpm/experimental/__init__.py,sha256=R_aclOvWpvSTHWAx9QXyg9OIPVK2dXT5tQJhx
|
|
368
368
|
claude_mpm/experimental/cli_enhancements.py,sha256=PfAt-SI-crBoE0Dtx1JecpS5_6OT_0apJbo28KS6HUI,11541
|
369
369
|
claude_mpm/generators/__init__.py,sha256=rG8vwF_BjPmeMKvyMXpUA8uJ-7mtW2HTNfalZzgRlNk,153
|
370
370
|
claude_mpm/generators/agent_profile_generator.py,sha256=yTEFdZPUt4lAfXlQuIIxzRwOrWMaJhEJ3Z6Ofm48Rlc,5740
|
371
|
-
claude_mpm/hooks/__init__.py,sha256=
|
371
|
+
claude_mpm/hooks/__init__.py,sha256=y-mfGUb4xTCENUIj4RfAhK2oJTIL0qMI2gDatdf52nc,567
|
372
372
|
claude_mpm/hooks/base_hook.py,sha256=wKbT_0g3dhvkA48pTz4GJpZQw8URhaT0LpZnCc7CEas,5026
|
373
373
|
claude_mpm/hooks/instruction_reinforcement.py,sha256=PnjfDSZ_72gbzHnRoug7qtXfpW5d1cxnmittpnPd2ws,11059
|
374
|
-
claude_mpm/hooks/
|
374
|
+
claude_mpm/hooks/kuzu_enrichment_hook.py,sha256=jghoEZX8fA6HZ1kM_5l93cuCyy-AMBjWp-nPW5EgaTk,8729
|
375
|
+
claude_mpm/hooks/kuzu_memory_hook.py,sha256=ockbKXXc69Yiryq2uWNCAUpSxtN8eqAec9i5BB1MRaI,12265
|
376
|
+
claude_mpm/hooks/kuzu_response_hook.py,sha256=iyVrsOrGpp-VFOjKC5GUnXro088Ftex-vHmfHsmAUv8,6136
|
375
377
|
claude_mpm/hooks/memory_integration_hook.py,sha256=F8Hf35hmbmhxi-qHQJac4zoWIr60ob3PCHa4P_rbxO8,16635
|
376
378
|
claude_mpm/hooks/tool_call_interceptor.py,sha256=tYUBJHjbtaI5-HSWcz0aeUW0CaiQPypuDOTULQ0BCNI,7506
|
377
379
|
claude_mpm/hooks/validation_hooks.py,sha256=i5uOaXifItwJfIdjjLCxAwrkKdkmo2Qa4qMc5-dwsKw,6465
|
@@ -416,8 +418,8 @@ claude_mpm/services/exceptions.py,sha256=5lVZETr_6-xk0ItH7BTfYUiX5RlckS1e8ah_Ual
|
|
416
418
|
claude_mpm/services/hook_installer_service.py,sha256=x3H3bFVlmhK4Ue1K279f44lwMEw3W1p3zoETGfjIH_w,19708
|
417
419
|
claude_mpm/services/hook_service.py,sha256=I6JILbackBsdvrDNQ9TeGSB7XNqozNRP26T4E9_ROtU,15693
|
418
420
|
claude_mpm/services/mcp_config_manager.py,sha256=W_zlFXzaQ64BoM4FuOJ47ywUUw0Rcf_u_ivvrpt2ZZY,67241
|
419
|
-
claude_mpm/services/mcp_service_verifier.py,sha256=
|
420
|
-
claude_mpm/services/memory_hook_service.py,sha256=
|
421
|
+
claude_mpm/services/mcp_service_verifier.py,sha256=xOo1SqbQ1J07TnJ_aNN7yCjna4UBZwnQQu6RrNCgoNc,25604
|
422
|
+
claude_mpm/services/memory_hook_service.py,sha256=BZhCYFpWV_7FguDL0MY4k8JOZJFmCbYWNMfDywM9G5Q,15304
|
421
423
|
claude_mpm/services/monitor_build_service.py,sha256=8gWR9CaqgXdG6-OjOFXGpk28GCcJTlHhojkUYnMCebI,12160
|
422
424
|
claude_mpm/services/orphan_detection.py,sha256=cEFE96FhC6EJSa53rUjbgjDj_E1_Ej-0KoYDQST22t0,26339
|
423
425
|
claude_mpm/services/port_manager.py,sha256=83GJ1uSF9pXsECXHtf4M8jBHzOptdh3Lpt7t0GyY2Wc,22838
|
@@ -567,7 +569,7 @@ claude_mpm/services/diagnostics/checks/filesystem_check.py,sha256=V5HoHDYlSuoK2l
|
|
567
569
|
claude_mpm/services/diagnostics/checks/installation_check.py,sha256=JdTqZreriAavN8gkHTaaKwclwnvfaRw87Np9mDfUXlk,19548
|
568
570
|
claude_mpm/services/diagnostics/checks/instructions_check.py,sha256=VbgBorl0RpFvxKQ_SC1gibTmGSiXaKSp-vVZt6hbH1g,16290
|
569
571
|
claude_mpm/services/diagnostics/checks/mcp_check.py,sha256=eG8BVWclwRRYh6G1ew1Z_DE1ksRirRxEy3fIe3cu58k,12174
|
570
|
-
claude_mpm/services/diagnostics/checks/mcp_services_check.py,sha256=
|
572
|
+
claude_mpm/services/diagnostics/checks/mcp_services_check.py,sha256=y6hpDMl3ZoT9WXIgxgbPaebgPqmgfiFruoed7224xos,43776
|
571
573
|
claude_mpm/services/diagnostics/checks/monitor_check.py,sha256=hZk301gvM8XAnfcOdtzE-e9XMBW3hgmDwSVxVSmF9CY,10097
|
572
574
|
claude_mpm/services/diagnostics/checks/startup_log_check.py,sha256=qQQI9Ezk7Sa5NpvJrtDY_0bWhoLPASZJn0hpNR8pEsI,12341
|
573
575
|
claude_mpm/services/event_bus/__init__.py,sha256=ETCo4a6puIeyVWAv55uCDjjhzNyUwbVAHEcAVkVapx8,688
|
@@ -761,7 +763,7 @@ claude_mpm/tools/code_tree_builder.py,sha256=8EaW94NjEObraiW811dDoQ8xKixrzCr4YoL
|
|
761
763
|
claude_mpm/tools/code_tree_events.py,sha256=10tf9ZGkPIXzWqYvwq0SIJdIOwKNnxgKWYOmRzVGeLc,13396
|
762
764
|
claude_mpm/tools/socketio_debug.py,sha256=QpG_GoN9VY8OZ0tcg8bqIF2oB_ZbKTjGp0oD0xYmsFo,22226
|
763
765
|
claude_mpm/utils/__init__.py,sha256=rkxNE0tDfKEN2lr2LLBuToNSvkF-yjF38607eUXL1vk,353
|
764
|
-
claude_mpm/utils/agent_dependency_loader.py,sha256=
|
766
|
+
claude_mpm/utils/agent_dependency_loader.py,sha256=A_OliEjBIwJn2e2N_7RLsh2zIhC5KdDLQTlMMV2DByc,36678
|
765
767
|
claude_mpm/utils/common.py,sha256=6hcs3Y6lx596Oj4H3CEQId-NIn8XEZJw1Ud3GAKwXKA,14341
|
766
768
|
claude_mpm/utils/config_manager.py,sha256=iWsPM0uo31Ela-XhFe9DIw3RuVxJf94_yFQ1HkibAlY,15876
|
767
769
|
claude_mpm/utils/console.py,sha256=2Agd91xj5NSMgvfUFJIUwVnpghnKjrd8EYH7inF12M4,279
|
@@ -778,15 +780,15 @@ claude_mpm/utils/import_migration_example.py,sha256=sKlV-apfkHDJzzez6uubHXjx91YF
|
|
778
780
|
claude_mpm/utils/imports.py,sha256=IGcW0W6V-2jTmkVlO-l0Zodo3LRA3zkaumD9J2qn7dE,6654
|
779
781
|
claude_mpm/utils/log_cleanup.py,sha256=1d8K1V2uOFPGJJKFOk6E_AMj22sifAibOg-VQyGooWo,22301
|
780
782
|
claude_mpm/utils/path_operations.py,sha256=gvd6Ye5rMFxIKnFzWwIL6jYy8wGkjSH3onYAan6zXkA,10869
|
781
|
-
claude_mpm/utils/robust_installer.py,sha256=
|
783
|
+
claude_mpm/utils/robust_installer.py,sha256=ixif5ExezKr5eOtgoY7W629tcDm-lZtUe5_Igkq7ZYQ,27108
|
782
784
|
claude_mpm/utils/session_logging.py,sha256=_6eoyCvVKhu2OhgRzC5FvMfFnD9et75lzCqARbd_i-k,3059
|
783
785
|
claude_mpm/utils/subprocess_utils.py,sha256=D0izRT8anjiUb_JG72zlJR_JAw1cDkb7kalNmfX9KlM,10443
|
784
786
|
claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
|
785
787
|
claude_mpm/validation/agent_validator.py,sha256=GprtAvu80VyMXcKGsK_VhYiXWA6BjKHv7O6HKx0AB9w,20917
|
786
788
|
claude_mpm/validation/frontmatter_validator.py,sha256=YpJlYNNYcV8u6hIOi3_jaRsDnzhbcQpjCBE6eyBKaFY,7076
|
787
|
-
claude_mpm-4.7.
|
788
|
-
claude_mpm-4.7.
|
789
|
-
claude_mpm-4.7.
|
790
|
-
claude_mpm-4.7.
|
791
|
-
claude_mpm-4.7.
|
792
|
-
claude_mpm-4.7.
|
789
|
+
claude_mpm-4.7.6.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
|
790
|
+
claude_mpm-4.7.6.dist-info/METADATA,sha256=zrxNvScK7d0H2C78k6Wuq_mJ3rQHbjCWTfzoC0JvWNQ,17517
|
791
|
+
claude_mpm-4.7.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
792
|
+
claude_mpm-4.7.6.dist-info/entry_points.txt,sha256=Vlw3GNi-OtTpKSrez04iNrPmxNxYDpIWxmJCxiZ5Tx8,526
|
793
|
+
claude_mpm-4.7.6.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
|
794
|
+
claude_mpm-4.7.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|