claude-mpm 4.0.17__py3-none-any.whl → 4.0.20__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.
Files changed (46) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__main__.py +4 -0
  3. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +38 -2
  4. claude_mpm/agents/OUTPUT_STYLE.md +84 -0
  5. claude_mpm/agents/templates/qa.json +24 -12
  6. claude_mpm/cli/__init__.py +85 -1
  7. claude_mpm/cli/__main__.py +4 -0
  8. claude_mpm/cli/commands/mcp_install_commands.py +62 -5
  9. claude_mpm/cli/commands/mcp_server_commands.py +60 -79
  10. claude_mpm/cli/commands/memory.py +32 -5
  11. claude_mpm/cli/commands/run.py +33 -6
  12. claude_mpm/cli/parsers/base_parser.py +5 -0
  13. claude_mpm/cli/parsers/run_parser.py +5 -0
  14. claude_mpm/cli/utils.py +17 -4
  15. claude_mpm/core/base_service.py +1 -1
  16. claude_mpm/core/config.py +70 -5
  17. claude_mpm/core/framework_loader.py +342 -31
  18. claude_mpm/core/interactive_session.py +55 -1
  19. claude_mpm/core/oneshot_session.py +7 -1
  20. claude_mpm/core/output_style_manager.py +468 -0
  21. claude_mpm/core/unified_paths.py +190 -21
  22. claude_mpm/hooks/claude_hooks/hook_handler.py +91 -16
  23. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +3 -0
  24. claude_mpm/init.py +1 -0
  25. claude_mpm/scripts/mcp_server.py +68 -0
  26. claude_mpm/scripts/mcp_wrapper.py +39 -0
  27. claude_mpm/services/agents/deployment/agent_deployment.py +151 -7
  28. claude_mpm/services/agents/deployment/agent_template_builder.py +37 -1
  29. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +441 -0
  30. claude_mpm/services/agents/memory/__init__.py +0 -2
  31. claude_mpm/services/agents/memory/agent_memory_manager.py +737 -43
  32. claude_mpm/services/agents/memory/content_manager.py +144 -14
  33. claude_mpm/services/agents/memory/template_generator.py +7 -354
  34. claude_mpm/services/mcp_gateway/core/singleton_manager.py +312 -0
  35. claude_mpm/services/mcp_gateway/core/startup_verification.py +315 -0
  36. claude_mpm/services/mcp_gateway/main.py +7 -0
  37. claude_mpm/services/mcp_gateway/server/stdio_server.py +184 -176
  38. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +453 -0
  39. claude_mpm/services/subprocess_launcher_service.py +5 -0
  40. {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/METADATA +1 -1
  41. {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/RECORD +45 -38
  42. {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/entry_points.txt +1 -0
  43. claude_mpm/services/agents/memory/analyzer.py +0 -430
  44. {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/WHEEL +0 -0
  45. {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/licenses/LICENSE +0 -0
  46. {claude_mpm-4.0.17.dist-info → claude_mpm-4.0.20.dist-info}/top_level.txt +0 -0
@@ -58,6 +58,64 @@ class FrameworkLoader:
58
58
 
59
59
  # Initialize agent registry
60
60
  self.agent_registry = AgentRegistryAdapter(self.framework_path)
61
+
62
+ # Initialize output style manager (must be after content is loaded)
63
+ self.output_style_manager = None
64
+ # Defer initialization until first use to ensure content is loaded
65
+
66
+ def _initialize_output_style(self) -> None:
67
+ """Initialize output style management and deploy if applicable."""
68
+ try:
69
+ from claude_mpm.core.output_style_manager import OutputStyleManager
70
+
71
+ self.output_style_manager = OutputStyleManager()
72
+
73
+ # Log detailed output style status
74
+ self._log_output_style_status()
75
+
76
+ # Extract and save output style content (pass self to reuse loaded content)
77
+ output_style_content = self.output_style_manager.extract_output_style_content(framework_loader=self)
78
+ output_style_path = self.output_style_manager.save_output_style(output_style_content)
79
+
80
+ # Deploy to Claude Desktop if supported
81
+ deployed = self.output_style_manager.deploy_output_style(output_style_content)
82
+
83
+ if deployed:
84
+ self.logger.info("✅ Output style deployed to Claude Desktop >= 1.0.83")
85
+ else:
86
+ self.logger.info("📝 Output style will be injected into instructions for older Claude versions")
87
+
88
+ except Exception as e:
89
+ self.logger.warning(f"❌ Failed to initialize output style manager: {e}")
90
+ # Continue without output style management
91
+
92
+ def _log_output_style_status(self) -> None:
93
+ """Log comprehensive output style status information."""
94
+ if not self.output_style_manager:
95
+ return
96
+
97
+ # Claude version detection
98
+ claude_version = self.output_style_manager.claude_version
99
+ if claude_version:
100
+ self.logger.info(f"Claude Desktop version detected: {claude_version}")
101
+
102
+ # Check if version supports output styles
103
+ if self.output_style_manager.supports_output_styles():
104
+ self.logger.info("✅ Claude Desktop supports output styles (>= 1.0.83)")
105
+
106
+ # Check deployment status
107
+ output_style_path = self.output_style_manager.output_style_path
108
+ if output_style_path.exists():
109
+ self.logger.info(f"📁 Output style file exists: {output_style_path}")
110
+ else:
111
+ self.logger.info(f"📝 Output style will be created at: {output_style_path}")
112
+
113
+ else:
114
+ self.logger.info(f"⚠️ Claude Desktop {claude_version} does not support output styles (< 1.0.83)")
115
+ self.logger.info("📝 Output style content will be injected into framework instructions")
116
+ else:
117
+ self.logger.info("⚠️ Claude Desktop not detected or version unknown")
118
+ self.logger.info("📝 Output style content will be injected into framework instructions as fallback")
61
119
 
62
120
  def _detect_framework_path(self) -> Optional[Path]:
63
121
  """Auto-detect claude-mpm framework using unified path management."""
@@ -228,6 +286,29 @@ class FrameworkLoader:
228
286
  self.logger.error(f"Failed to load {file_type}: {e}")
229
287
  return None
230
288
 
289
+ def _migrate_memory_file(self, old_path: Path, new_path: Path) -> None:
290
+ """
291
+ Migrate memory file from old naming convention to new.
292
+
293
+ WHY: Supports backward compatibility by automatically migrating from
294
+ the old {agent_id}_agent.md and {agent_id}.md formats to the new {agent_id}_memories.md format.
295
+
296
+ Args:
297
+ old_path: Path to the old file
298
+ new_path: Path to the new file
299
+ """
300
+ if old_path.exists() and not new_path.exists():
301
+ try:
302
+ # Read content from old file
303
+ content = old_path.read_text(encoding="utf-8")
304
+ # Write to new file
305
+ new_path.write_text(content, encoding="utf-8")
306
+ # Remove old file
307
+ old_path.unlink()
308
+ self.logger.info(f"Migrated memory file from {old_path.name} to {new_path.name}")
309
+ except Exception as e:
310
+ self.logger.error(f"Failed to migrate memory file {old_path.name}: {e}")
311
+
231
312
  def _load_instructions_file(self, content: Dict[str, Any]) -> None:
232
313
  """
233
314
  Load INSTRUCTIONS.md or legacy CLAUDE.md from working directory.
@@ -344,23 +425,22 @@ class FrameworkLoader:
344
425
 
345
426
  def _load_actual_memories(self, content: Dict[str, Any]) -> None:
346
427
  """
347
- Load actual memories from .claude-mpm/memories/ directory.
428
+ Load actual memories from both user and project directories.
429
+
430
+ Loading order:
431
+ 1. User-level memories from ~/.claude-mpm/memories/ (global defaults)
432
+ 2. Project-level memories from ./.claude-mpm/memories/ (overrides user)
348
433
 
349
434
  This loads:
350
- 1. PM memories from PM.md (always loaded)
351
- 2. Agent memories from <agent>.md (only if agent is deployed)
435
+ 1. PM memories from PM_memories.md (always loaded)
436
+ 2. Agent memories from <agent>_memories.md (only if agent is deployed)
352
437
 
353
438
  Args:
354
439
  content: Dictionary to update with actual memories
355
440
  """
356
- memories_dir = Path.cwd() / ".claude-mpm" / "memories"
357
-
358
- # Log the memory loading process
359
- if memories_dir.exists():
360
- self.logger.info(f"Loading memory files from: {memories_dir}")
361
- else:
362
- self.logger.debug(f"No memories directory found at: {memories_dir}")
363
- return
441
+ # Define memory directories in priority order (user first, then project)
442
+ user_memories_dir = Path.home() / ".claude-mpm" / "memories"
443
+ project_memories_dir = Path.cwd() / ".claude-mpm" / "memories"
364
444
 
365
445
  # Check for deployed agents
366
446
  deployed_agents = self._get_deployed_agents()
@@ -369,49 +449,260 @@ class FrameworkLoader:
369
449
  loaded_count = 0
370
450
  skipped_count = 0
371
451
 
452
+ # Dictionary to store aggregated memories
453
+ pm_memories = []
454
+ agent_memories_dict = {}
455
+
456
+ # Load memories from user directory first
457
+ if user_memories_dir.exists():
458
+ self.logger.info(f"Loading user-level memory files from: {user_memories_dir}")
459
+ loaded, skipped = self._load_memories_from_directory(
460
+ user_memories_dir, deployed_agents, pm_memories, agent_memories_dict, "user"
461
+ )
462
+ loaded_count += loaded
463
+ skipped_count += skipped
464
+ else:
465
+ self.logger.debug(f"No user memories directory found at: {user_memories_dir}")
466
+
467
+ # Load memories from project directory (overrides user memories)
468
+ if project_memories_dir.exists():
469
+ self.logger.info(f"Loading project-level memory files from: {project_memories_dir}")
470
+ loaded, skipped = self._load_memories_from_directory(
471
+ project_memories_dir, deployed_agents, pm_memories, agent_memories_dict, "project"
472
+ )
473
+ loaded_count += loaded
474
+ skipped_count += skipped
475
+ else:
476
+ self.logger.debug(f"No project memories directory found at: {project_memories_dir}")
477
+
478
+ # Aggregate PM memories
479
+ if pm_memories:
480
+ aggregated_pm = self._aggregate_memories(pm_memories)
481
+ content["actual_memories"] = aggregated_pm
482
+ memory_size = len(aggregated_pm.encode('utf-8'))
483
+ self.logger.info(f"Aggregated PM memory ({memory_size:,} bytes) from {len(pm_memories)} source(s)")
484
+
485
+ # Store agent memories (already aggregated per agent)
486
+ if agent_memories_dict:
487
+ content["agent_memories"] = agent_memories_dict
488
+ for agent_name, memory_content in agent_memories_dict.items():
489
+ memory_size = len(memory_content.encode('utf-8'))
490
+ self.logger.debug(f"Aggregated {agent_name} memory: {memory_size:,} bytes")
491
+
492
+ # Log summary
493
+ if loaded_count > 0 or skipped_count > 0:
494
+ self.logger.info(f"Memory loading complete: {loaded_count} memories loaded, {skipped_count} skipped")
495
+
496
+ def _load_memories_from_directory(
497
+ self,
498
+ memories_dir: Path,
499
+ deployed_agents: set,
500
+ pm_memories: list,
501
+ agent_memories_dict: dict,
502
+ source: str
503
+ ) -> tuple[int, int]:
504
+ """
505
+ Load memories from a specific directory.
506
+
507
+ Args:
508
+ memories_dir: Directory to load memories from
509
+ deployed_agents: Set of deployed agent names
510
+ pm_memories: List to append PM memories to
511
+ agent_memories_dict: Dict to store agent memories
512
+ source: Source label ("user" or "project")
513
+
514
+ Returns:
515
+ Tuple of (loaded_count, skipped_count)
516
+ """
517
+ loaded_count = 0
518
+ skipped_count = 0
519
+
372
520
  # Load PM memories (always loaded)
373
- pm_memory_path = memories_dir / "PM.md"
521
+ # Support migration from both old formats
522
+ pm_memory_path = memories_dir / "PM_memories.md"
523
+ old_pm_path = memories_dir / "PM.md"
524
+
525
+ # Migrate from old PM.md if needed
526
+ if not pm_memory_path.exists() and old_pm_path.exists():
527
+ try:
528
+ old_pm_path.rename(pm_memory_path)
529
+ self.logger.info(f"Migrated PM.md to PM_memories.md")
530
+ except Exception as e:
531
+ self.logger.error(f"Failed to migrate PM.md: {e}")
532
+ pm_memory_path = old_pm_path # Fall back to old path
374
533
  if pm_memory_path.exists():
375
534
  loaded_content = self._try_load_file(
376
- pm_memory_path, "PM memory"
535
+ pm_memory_path, f"PM memory ({source})"
377
536
  )
378
537
  if loaded_content:
379
- content["actual_memories"] = loaded_content
538
+ pm_memories.append({
539
+ "source": source,
540
+ "content": loaded_content,
541
+ "path": pm_memory_path
542
+ })
380
543
  memory_size = len(loaded_content.encode('utf-8'))
381
- self.logger.info(f"Loaded PM memory: {pm_memory_path} ({memory_size:,} bytes)")
544
+ self.logger.info(f"Loaded {source} PM memory: {pm_memory_path} ({memory_size:,} bytes)")
382
545
  loaded_count += 1
383
- else:
384
- self.logger.debug(f"No PM memory found at: {pm_memory_path}")
385
546
 
386
547
  # Load agent memories (only for deployed agents)
387
548
  for memory_file in memories_dir.glob("*.md"):
388
- # Skip PM.md as we already handled it
389
- if memory_file.name == "PM.md":
549
+ # Skip PM_memories.md and PM.md as we already handled them
550
+ if memory_file.name in ["PM_memories.md", "PM.md"]:
390
551
  continue
391
552
 
392
553
  # Extract agent name from file
554
+ # Support migration from old formats to new {agent_name}_memories.md format
393
555
  agent_name = memory_file.stem
556
+ new_path = None
557
+
558
+ if agent_name.endswith("_agent"):
559
+ # Old format: {agent_name}_agent.md
560
+ agent_name = agent_name[:-6] # Remove "_agent" suffix
561
+ new_path = memories_dir / f"{agent_name}_memories.md"
562
+ elif agent_name.endswith("_memories"):
563
+ # Already in new format: {agent_name}_memories.md
564
+ agent_name = agent_name[:-9] # Remove "_memories" suffix
565
+ elif memory_file.name != "README.md":
566
+ # Intermediate format: {agent_name}.md
567
+ # agent_name already set from stem
568
+ new_path = memories_dir / f"{agent_name}_memories.md"
569
+
570
+ # Skip README.md
571
+ if memory_file.name == "README.md":
572
+ continue
573
+
574
+ # Migrate if needed
575
+ if new_path and not new_path.exists():
576
+ self._migrate_memory_file(memory_file, new_path)
394
577
 
395
578
  # Check if agent is deployed
396
579
  if agent_name in deployed_agents:
397
580
  loaded_content = self._try_load_file(
398
- memory_file, f"agent memory: {agent_name}"
581
+ memory_file, f"agent memory: {agent_name} ({source})"
399
582
  )
400
583
  if loaded_content:
401
- # Store agent memories separately (for future use)
402
- if "agent_memories" not in content:
403
- content["agent_memories"] = {}
404
- content["agent_memories"][agent_name] = loaded_content
584
+ # Store or merge agent memories
585
+ if agent_name not in agent_memories_dict:
586
+ agent_memories_dict[agent_name] = []
587
+
588
+ # If it's a list, append the new memory entry
589
+ if isinstance(agent_memories_dict[agent_name], list):
590
+ agent_memories_dict[agent_name].append({
591
+ "source": source,
592
+ "content": loaded_content,
593
+ "path": memory_file
594
+ })
595
+
405
596
  memory_size = len(loaded_content.encode('utf-8'))
406
- self.logger.info(f"Loaded agent memory: {memory_file.name} (deployed agent found, {memory_size:,} bytes)")
597
+ self.logger.info(f"Loaded {source} memory for {agent_name}: {memory_file.name} ({memory_size:,} bytes)")
407
598
  loaded_count += 1
408
599
  else:
409
- self.logger.info(f"Skipped agent memory: {memory_file.name} (agent not deployed)")
600
+ self.logger.info(f"Skipped {source} memory: {memory_file.name} (agent not deployed)")
410
601
  skipped_count += 1
411
602
 
412
- # Log summary
413
- if loaded_count > 0 or skipped_count > 0:
414
- self.logger.info(f"Memory loading complete: {loaded_count} memories loaded, {skipped_count} skipped")
603
+ # After loading all memories for this directory, aggregate agent memories
604
+ for agent_name in list(agent_memories_dict.keys()):
605
+ if isinstance(agent_memories_dict[agent_name], list) and agent_memories_dict[agent_name]:
606
+ # Aggregate memories for this agent
607
+ aggregated = self._aggregate_memories(agent_memories_dict[agent_name])
608
+ agent_memories_dict[agent_name] = aggregated
609
+
610
+ return loaded_count, skipped_count
611
+
612
+ def _aggregate_memories(self, memory_entries: list) -> str:
613
+ """
614
+ Aggregate multiple memory entries into a single memory string.
615
+
616
+ Strategy:
617
+ - Parse memories by sections
618
+ - Merge sections, with project-level taking precedence
619
+ - Remove exact duplicates within sections
620
+ - Preserve unique entries from both sources
621
+
622
+ Args:
623
+ memory_entries: List of memory entries with source, content, and path
624
+
625
+ Returns:
626
+ Aggregated memory content as a string
627
+ """
628
+ if not memory_entries:
629
+ return ""
630
+
631
+ # If only one entry, return it as-is
632
+ if len(memory_entries) == 1:
633
+ return memory_entries[0]["content"]
634
+
635
+ # Parse all memories into sections
636
+ all_sections = {}
637
+ metadata_lines = []
638
+
639
+ for entry in memory_entries:
640
+ content = entry["content"]
641
+ source = entry["source"]
642
+
643
+ # Parse content into sections
644
+ current_section = None
645
+ current_items = []
646
+
647
+ for line in content.split('\n'):
648
+ # Check for metadata lines
649
+ if line.startswith('<!-- ') and line.endswith(' -->'):
650
+ # Only keep metadata from project source or if not already present
651
+ if source == "project" or line not in metadata_lines:
652
+ metadata_lines.append(line)
653
+ # Check for section headers (## Level 2 headers)
654
+ elif line.startswith('## '):
655
+ # Save previous section if exists
656
+ if current_section and current_items:
657
+ if current_section not in all_sections:
658
+ all_sections[current_section] = {}
659
+ # Store items with their source
660
+ for item in current_items:
661
+ # Use content as key to detect duplicates
662
+ all_sections[current_section][item] = source
663
+
664
+ # Start new section
665
+ current_section = line
666
+ current_items = []
667
+ # Check for content lines (skip empty lines)
668
+ elif line.strip() and current_section:
669
+ current_items.append(line)
670
+
671
+ # Save last section
672
+ if current_section and current_items:
673
+ if current_section not in all_sections:
674
+ all_sections[current_section] = {}
675
+ for item in current_items:
676
+ # Project source overrides user source
677
+ if item not in all_sections[current_section] or source == "project":
678
+ all_sections[current_section][item] = source
679
+
680
+ # Build aggregated content
681
+ lines = []
682
+
683
+ # Add metadata
684
+ if metadata_lines:
685
+ lines.extend(metadata_lines)
686
+ lines.append("")
687
+
688
+ # Add header
689
+ lines.append("# Aggregated Memory")
690
+ lines.append("")
691
+ lines.append("*This memory combines user-level and project-level memories.*")
692
+ lines.append("")
693
+
694
+ # Add sections
695
+ for section_header in sorted(all_sections.keys()):
696
+ lines.append(section_header)
697
+ section_items = all_sections[section_header]
698
+
699
+ # Sort items to ensure consistent output
700
+ for item in sorted(section_items.keys()):
701
+ lines.append(item)
702
+
703
+ lines.append("") # Empty line after section
704
+
705
+ return '\n'.join(lines)
415
706
 
416
707
  def _load_single_agent(
417
708
  self, agent_file: Path
@@ -498,7 +789,7 @@ class FrameworkLoader:
498
789
  "project_workflow": "",
499
790
  "memory_instructions": "",
500
791
  "project_memory": "",
501
- "actual_memories": "", # Add field for actual memories from PM.md
792
+ "actual_memories": "", # Add field for actual memories from PM_memories.md
502
793
  }
503
794
 
504
795
  # Load instructions file from working directory
@@ -551,7 +842,7 @@ class FrameworkLoader:
551
842
  # Load MEMORY.md - check for project-specific first, then system
552
843
  self._load_memory_instructions(content)
553
844
 
554
- # Load actual memories from .claude-mpm/memories/PM.md
845
+ # Load actual memories from .claude-mpm/memories/PM_memories.md
555
846
  self._load_actual_memories(content)
556
847
 
557
848
  # Discover agent directories
@@ -742,6 +1033,17 @@ class FrameworkLoader:
742
1033
  """Format full framework instructions."""
743
1034
  from datetime import datetime
744
1035
 
1036
+ # Initialize output style manager on first use (ensures content is loaded)
1037
+ if self.output_style_manager is None:
1038
+ self._initialize_output_style()
1039
+
1040
+ # Check if we need to inject output style content for older Claude versions
1041
+ inject_output_style = False
1042
+ if self.output_style_manager:
1043
+ inject_output_style = self.output_style_manager.should_inject_content()
1044
+ if inject_output_style:
1045
+ self.logger.info("Injecting output style content into instructions for Claude < 1.0.83")
1046
+
745
1047
  # If we have the full framework INSTRUCTIONS.md, use it
746
1048
  if self.framework_content.get("framework_instructions"):
747
1049
  instructions = self._strip_metadata_comments(
@@ -789,6 +1091,15 @@ class FrameworkLoader:
789
1091
  self.framework_content["base_pm_instructions"]
790
1092
  )
791
1093
  instructions += f"\n\n{base_pm}"
1094
+
1095
+ # Inject output style content if needed (for Claude < 1.0.83)
1096
+ if inject_output_style and self.output_style_manager:
1097
+ output_style_content = self.output_style_manager.get_injectable_content(framework_loader=self)
1098
+ if output_style_content:
1099
+ instructions += "\n\n## Output Style Configuration\n"
1100
+ instructions += "**Note: The following output style is injected for Claude < 1.0.83**\n\n"
1101
+ instructions += output_style_content
1102
+ instructions += "\n"
792
1103
 
793
1104
  # Clean up any trailing whitespace
794
1105
  instructions = instructions.rstrip() + "\n"
@@ -134,6 +134,7 @@ class InteractiveSession:
134
134
 
135
135
  # Deploy project-specific agents
136
136
  self.runner.deploy_project_agents_to_claude()
137
+
137
138
 
138
139
  # Build command
139
140
  cmd = self._build_claude_command()
@@ -296,18 +297,50 @@ class InteractiveSession:
296
297
  def _display_welcome_message(self) -> None:
297
298
  """Display the interactive session welcome message."""
298
299
  version_str = self.runner._get_version()
300
+
301
+ # Get output style status
302
+ output_style_info = self._get_output_style_info()
299
303
 
300
304
  print("\033[32m╭───────────────────────────────────────────────────╮\033[0m")
301
305
  print(
302
306
  "\033[32m│\033[0m ✻ Claude MPM - Interactive Session \033[32m│\033[0m"
303
307
  )
304
308
  print(f"\033[32m│\033[0m Version {version_str:<40}\033[32m│\033[0m")
309
+ if output_style_info:
310
+ print(f"\033[32m│\033[0m {output_style_info:<49}\033[32m│\033[0m")
305
311
  print("\033[32m│ │\033[0m")
306
312
  print(
307
313
  "\033[32m│\033[0m Type '/agents' to see available agents \033[32m│\033[0m"
308
314
  )
309
315
  print("\033[32m╰───────────────────────────────────────────────────╯\033[0m")
310
316
  print("") # Add blank line after box
317
+
318
+
319
+ def _get_output_style_info(self) -> Optional[str]:
320
+ """Get output style status for display."""
321
+ try:
322
+ # Check if output style manager is available through framework loader
323
+ if hasattr(self.runner, 'framework_loader') and self.runner.framework_loader:
324
+ if hasattr(self.runner.framework_loader, 'output_style_manager'):
325
+ osm = self.runner.framework_loader.output_style_manager
326
+ if osm:
327
+ if osm.claude_version and osm.supports_output_styles():
328
+ # Check if claude-mpm style is active
329
+ settings_file = osm.settings_file
330
+ if settings_file.exists():
331
+ import json
332
+ settings = json.loads(settings_file.read_text())
333
+ active_style = settings.get("activeOutputStyle")
334
+ if active_style == "claude-mpm":
335
+ return "Output Style: claude-mpm ✅"
336
+ else:
337
+ return f"Output Style: {active_style or 'none'}"
338
+ return "Output Style: Available"
339
+ else:
340
+ return "Output Style: Injected (legacy)"
341
+ except Exception:
342
+ pass
343
+ return None
311
344
 
312
345
  def _build_claude_command(self) -> list:
313
346
  """Build the Claude command with all necessary arguments."""
@@ -315,8 +348,15 @@ class InteractiveSession:
315
348
 
316
349
  # Add custom arguments
317
350
  if self.runner.claude_args:
351
+ # Enhanced debug logging for --resume flag verification
352
+ self.logger.debug(f"Raw claude_args received: {self.runner.claude_args}")
353
+
354
+ # Check explicitly for --resume flag
355
+ has_resume = "--resume" in self.runner.claude_args
356
+ self.logger.info(f"--resume flag present in claude_args: {has_resume}")
357
+
318
358
  cmd.extend(self.runner.claude_args)
319
-
359
+
320
360
  # Add system instructions
321
361
  from claude_mpm.core.claude_runner import create_simple_context
322
362
 
@@ -324,6 +364,16 @@ class InteractiveSession:
324
364
  if system_prompt and system_prompt != create_simple_context():
325
365
  cmd.extend(["--append-system-prompt", system_prompt])
326
366
 
367
+ # Final command verification
368
+ # self.logger.info(f"Final Claude command built: {' '.join(cmd)}")
369
+
370
+ # Explicit --resume flag verification
371
+ if "--resume" in cmd:
372
+ self.logger.info("✅ VERIFIED: --resume flag IS included in final command")
373
+ self.logger.debug(f"--resume position in command: {cmd.index('--resume')}")
374
+ else:
375
+ self.logger.debug("ℹ️ --resume flag NOT included in final command")
376
+
327
377
  return cmd
328
378
 
329
379
  def _prepare_environment(self) -> dict:
@@ -341,6 +391,10 @@ class InteractiveSession:
341
391
  for var in claude_vars_to_remove:
342
392
  clean_env.pop(var, None)
343
393
 
394
+ # Disable telemetry for Claude Code
395
+ # This ensures Claude Code doesn't send telemetry data during runtime
396
+ clean_env["DISABLE_TELEMETRY"] = "1"
397
+
344
398
  return clean_env
345
399
 
346
400
  def _change_to_user_directory(self, env: dict) -> None:
@@ -267,7 +267,13 @@ class OneshotSession:
267
267
 
268
268
  def _prepare_environment(self) -> Dict[str, str]:
269
269
  """Prepare the execution environment."""
270
- return os.environ.copy()
270
+ env = os.environ.copy()
271
+
272
+ # Disable telemetry for Claude Code
273
+ # This ensures Claude Code doesn't send telemetry data during runtime
274
+ env["DISABLE_TELEMETRY"] = "1"
275
+
276
+ return env
271
277
 
272
278
  def _build_command(self) -> list:
273
279
  """Build the base Claude command."""