claude-mpm 4.2.44__py3-none-any.whl → 4.3.0__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 (153) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +77 -405
  3. claude_mpm/agents/{INSTRUCTIONS.md → INSTRUCTIONS_OLD_DEPRECATED.md} +75 -1
  4. claude_mpm/agents/OUTPUT_STYLE.md +0 -39
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +122 -0
  6. claude_mpm/agents/WORKFLOW.md +74 -323
  7. claude_mpm/agents/frontmatter_validator.py +20 -12
  8. claude_mpm/agents/templates/nextjs_engineer.json +277 -0
  9. claude_mpm/agents/templates/prompt-engineer.json +294 -0
  10. claude_mpm/agents/templates/python_engineer.json +289 -0
  11. claude_mpm/agents/templates/react_engineer.json +11 -3
  12. claude_mpm/agents/templates/security.json +50 -9
  13. claude_mpm/cli/commands/agents.py +2 -2
  14. claude_mpm/cli/commands/uninstall.py +1 -3
  15. claude_mpm/cli/interactive/agent_wizard.py +3 -3
  16. claude_mpm/cli/parsers/agent_manager_parser.py +3 -3
  17. claude_mpm/cli/parsers/agents_parser.py +1 -1
  18. claude_mpm/constants.py +1 -1
  19. claude_mpm/core/error_handler.py +2 -4
  20. claude_mpm/core/file_utils.py +4 -12
  21. claude_mpm/core/framework_loader.py +72 -24
  22. claude_mpm/core/log_manager.py +60 -5
  23. claude_mpm/core/logger.py +1 -1
  24. claude_mpm/core/logging_utils.py +36 -18
  25. claude_mpm/core/unified_agent_registry.py +18 -4
  26. claude_mpm/dashboard/react/components/DataInspector/DataInspector.module.css +188 -0
  27. claude_mpm/dashboard/react/components/EventViewer/EventViewer.module.css +156 -0
  28. claude_mpm/dashboard/react/components/shared/ConnectionStatus.module.css +38 -0
  29. claude_mpm/dashboard/react/components/shared/FilterBar.module.css +92 -0
  30. claude_mpm/dashboard/static/archive/activity_dashboard_fixed.html +248 -0
  31. claude_mpm/dashboard/static/archive/activity_dashboard_test.html +61 -0
  32. claude_mpm/dashboard/static/archive/test_activity_connection.html +179 -0
  33. claude_mpm/dashboard/static/archive/test_claude_tree_tab.html +68 -0
  34. claude_mpm/dashboard/static/archive/test_dashboard.html +409 -0
  35. claude_mpm/dashboard/static/archive/test_dashboard_fixed.html +519 -0
  36. claude_mpm/dashboard/static/archive/test_dashboard_verification.html +181 -0
  37. claude_mpm/dashboard/static/archive/test_file_data.html +315 -0
  38. claude_mpm/dashboard/static/archive/test_file_tree_empty_state.html +243 -0
  39. claude_mpm/dashboard/static/archive/test_file_tree_fix.html +234 -0
  40. claude_mpm/dashboard/static/archive/test_file_tree_rename.html +117 -0
  41. claude_mpm/dashboard/static/archive/test_file_tree_tab.html +115 -0
  42. claude_mpm/dashboard/static/archive/test_file_viewer.html +224 -0
  43. claude_mpm/dashboard/static/archive/test_final_activity.html +220 -0
  44. claude_mpm/dashboard/static/archive/test_tab_fix.html +139 -0
  45. claude_mpm/dashboard/static/built/assets/events.DjpNxWNo.css +1 -0
  46. claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
  47. claude_mpm/dashboard/static/built/components/agent-hierarchy.js +777 -0
  48. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  49. claude_mpm/dashboard/static/built/components/build-tracker.js +333 -0
  50. claude_mpm/dashboard/static/built/components/code-simple.js +857 -0
  51. claude_mpm/dashboard/static/built/components/code-tree/tree-breadcrumb.js +353 -0
  52. claude_mpm/dashboard/static/built/components/code-tree/tree-constants.js +235 -0
  53. claude_mpm/dashboard/static/built/components/code-tree/tree-search.js +409 -0
  54. claude_mpm/dashboard/static/built/components/code-tree/tree-utils.js +435 -0
  55. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -1076
  56. claude_mpm/dashboard/static/built/components/connection-debug.js +654 -0
  57. claude_mpm/dashboard/static/built/components/diff-viewer.js +891 -0
  58. claude_mpm/dashboard/static/built/components/event-processor.js +1 -1
  59. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  60. claude_mpm/dashboard/static/built/components/export-manager.js +1 -1
  61. claude_mpm/dashboard/static/built/components/file-change-tracker.js +443 -0
  62. claude_mpm/dashboard/static/built/components/file-change-viewer.js +690 -0
  63. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  64. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  65. claude_mpm/dashboard/static/built/components/nav-bar.js +145 -0
  66. claude_mpm/dashboard/static/built/components/page-structure.js +429 -0
  67. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  68. claude_mpm/dashboard/static/built/components/ui-state-manager.js +2 -465
  69. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  70. claude_mpm/dashboard/static/built/connection-manager.js +536 -0
  71. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  72. claude_mpm/dashboard/static/built/extension-error-handler.js +164 -0
  73. claude_mpm/dashboard/static/built/react/events.js +30 -0
  74. claude_mpm/dashboard/static/built/shared/dom-helpers.js +396 -0
  75. claude_mpm/dashboard/static/built/shared/event-bus.js +330 -0
  76. claude_mpm/dashboard/static/built/shared/event-filter-service.js +540 -0
  77. claude_mpm/dashboard/static/built/shared/logger.js +385 -0
  78. claude_mpm/dashboard/static/built/shared/page-structure.js +251 -0
  79. claude_mpm/dashboard/static/built/shared/tooltip-service.js +253 -0
  80. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  81. claude_mpm/dashboard/static/built/tab-isolation-fix.js +185 -0
  82. claude_mpm/dashboard/static/css/dashboard.css +28 -5
  83. claude_mpm/dashboard/static/dist/assets/events.DjpNxWNo.css +1 -0
  84. claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
  85. claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
  86. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  87. claude_mpm/dashboard/static/dist/components/event-processor.js +1 -1
  88. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  89. claude_mpm/dashboard/static/dist/components/export-manager.js +1 -1
  90. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
  91. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  92. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  93. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  94. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  95. claude_mpm/dashboard/static/dist/react/events.js +30 -0
  96. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  97. claude_mpm/dashboard/static/events.html +607 -0
  98. claude_mpm/dashboard/static/index.html +713 -0
  99. claude_mpm/dashboard/static/js/components/activity-tree.js +3 -17
  100. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +4 -1
  101. claude_mpm/dashboard/static/js/components/agent-inference.js +3 -0
  102. claude_mpm/dashboard/static/js/components/build-tracker.js +8 -0
  103. claude_mpm/dashboard/static/js/components/code-viewer.js +306 -66
  104. claude_mpm/dashboard/static/js/components/event-processor.js +3 -0
  105. claude_mpm/dashboard/static/js/components/event-viewer.js +39 -2
  106. claude_mpm/dashboard/static/js/components/export-manager.js +3 -0
  107. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +30 -10
  108. claude_mpm/dashboard/static/js/components/socket-manager.js +4 -0
  109. claude_mpm/dashboard/static/js/components/ui-state-manager.js +285 -85
  110. claude_mpm/dashboard/static/js/components/working-directory.js +3 -0
  111. claude_mpm/dashboard/static/js/dashboard.js +61 -33
  112. claude_mpm/dashboard/static/js/socket-client.js +12 -8
  113. claude_mpm/dashboard/static/js/stores/dashboard-store.js +562 -0
  114. claude_mpm/dashboard/static/js/tab-isolation-fix.js +185 -0
  115. claude_mpm/dashboard/static/legacy/activity.html +736 -0
  116. claude_mpm/dashboard/static/legacy/agents.html +786 -0
  117. claude_mpm/dashboard/static/legacy/files.html +747 -0
  118. claude_mpm/dashboard/static/legacy/tools.html +831 -0
  119. claude_mpm/dashboard/static/monitors-index.html +218 -0
  120. claude_mpm/dashboard/static/monitors.html +431 -0
  121. claude_mpm/dashboard/static/production/events.html +659 -0
  122. claude_mpm/dashboard/static/production/main.html +715 -0
  123. claude_mpm/dashboard/static/production/monitors.html +483 -0
  124. claude_mpm/dashboard/static/socket.io.min.js +7 -0
  125. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +7 -0
  126. claude_mpm/dashboard/static/test-archive/dashboard.html +635 -0
  127. claude_mpm/dashboard/static/test-archive/debug-events.html +147 -0
  128. claude_mpm/dashboard/static/test-archive/test-navigation.html +256 -0
  129. claude_mpm/dashboard/static/test-archive/test-react-exports.html +180 -0
  130. claude_mpm/dashboard/templates/index.html +79 -9
  131. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +1 -1
  132. claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -0
  133. claude_mpm/services/agents/deployment/agent_template_builder.py +285 -26
  134. claude_mpm/services/agents/deployment/agent_validator.py +3 -0
  135. claude_mpm/services/agents/deployment/validation/template_validator.py +13 -4
  136. claude_mpm/services/agents/local_template_manager.py +2 -7
  137. claude_mpm/services/monitor/daemon.py +1 -2
  138. claude_mpm/services/monitor/daemon_manager.py +2 -7
  139. claude_mpm/services/monitor/event_emitter.py +6 -2
  140. claude_mpm/services/monitor/handlers/code_analysis.py +4 -6
  141. claude_mpm/services/monitor/handlers/hooks.py +2 -6
  142. claude_mpm/services/monitor/server.py +27 -4
  143. claude_mpm/tools/code_tree_analyzer.py +2 -4
  144. claude_mpm/utils/log_cleanup.py +612 -0
  145. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/METADATA +1 -1
  146. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/RECORD +151 -83
  147. claude_mpm/dashboard/static/test-browser-monitor.html +0 -470
  148. claude_mpm/dashboard/static/test-simple.html +0 -97
  149. /claude_mpm/dashboard/static/{test_debug.html → test-archive/test_debug.html} +0 -0
  150. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/WHEEL +0 -0
  151. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/entry_points.txt +0 -0
  152. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/licenses/LICENSE +0 -0
  153. {claude_mpm-4.2.44.dist-info → claude_mpm-4.3.0.dist-info}/top_level.txt +0 -0
@@ -150,6 +150,23 @@
150
150
  <div class="header-row">
151
151
  <div class="header-title">
152
152
  <h1>🚀 Claude MPM Monitor</h1>
153
+ <a href="/static/monitors-index.html" style="
154
+ display: inline-block;
155
+ margin-left: 20px;
156
+ padding: 8px 16px;
157
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
158
+ color: white;
159
+ text-decoration: none;
160
+ border-radius: 8px;
161
+ font-size: 14px;
162
+ font-weight: 500;
163
+ transition: transform 0.2s, box-shadow 0.2s;
164
+ vertical-align: middle;
165
+ "
166
+ onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 5px 15px rgba(102, 126, 234, 0.4)';"
167
+ onmouseout="this.style.transform=''; this.style.boxShadow='';">
168
+ Try New Dashboard →
169
+ </a>
153
170
  <div id="connection-status" class="status-badge status-disconnected">
154
171
  <span>●</span> Disconnected
155
172
  </div>
@@ -179,6 +196,15 @@
179
196
  <div class="metric-mini-value" id="error-count">0</div>
180
197
  <div class="metric-mini-label">Errors</div>
181
198
  </div>
199
+ <!-- Debug metrics for troubleshooting -->
200
+ <div class="metric-mini" style="background: rgba(255,255,255,0.05);">
201
+ <div class="metric-mini-value" id="debug-events-received">0</div>
202
+ <div class="metric-mini-label">Received</div>
203
+ </div>
204
+ <div class="metric-mini" style="background: rgba(255,255,255,0.05);">
205
+ <div class="metric-mini-value" id="debug-events-rendered">0</div>
206
+ <div class="metric-mini-label">Rendered</div>
207
+ </div>
182
208
  </div>
183
209
  </div>
184
210
 
@@ -390,8 +416,9 @@
390
416
 
391
417
  <!-- File Tree Tab -->
392
418
  <div class="tab-content" id="claude-tree-tab">
393
- <div id="claude-tree-container" style="width: 100%; height: 100%;">
419
+ <div id="claude-tree-container" style="width: 100%; height: 100%; position: relative;">
394
420
  <!-- File activity tree will be rendered here by code-viewer.js -->
421
+ <!-- This container is ISOLATED from other tabs -->
395
422
  </div>
396
423
  </div>
397
424
 
@@ -490,27 +517,68 @@
490
517
  .then(() => loadModule('/static/js/components/code-tree/tree-search.js'))
491
518
  .then(() => loadModule('/static/js/components/code-tree/tree-breadcrumb.js'))
492
519
  .then(() => {
493
- // Load UI State Manager v2 from source with nuclear browser log handling
494
- return loadModule('/static/js/components/ui-state-manager.js?v=2.0-NUCLEAR');
520
+ // CRITICAL: Load socket-client.js FIRST (dependency of socket-manager)
521
+ return loadModule('/static/js/socket-client.js');
522
+ })
523
+ .then(() => {
524
+ // CRITICAL: Load socket-manager BEFORE dashboard.js
525
+ // The source version must be loaded to make SocketManager globally available
526
+ return loadModule('/static/js/components/socket-manager.js');
495
527
  })
496
528
  .then(() => {
497
- // Now load main components in parallel
529
+ // Load all required dashboard dependencies BEFORE dashboard.js
530
+ // These must be available globally for the dist/dashboard.js to work
498
531
  return Promise.all([
499
- loadModule('/static/dist/dashboard.js'),
532
+ loadModule('/static/js/components/event-viewer.js'),
533
+ loadModule('/static/js/components/module-viewer.js'),
534
+ loadModule('/static/js/components/session-manager.js'),
535
+ loadModule('/static/js/components/agent-inference.js'),
536
+ loadModule('/static/js/components/agent-hierarchy.js'),
537
+ loadModule('/static/js/components/ui-state-manager.js'),
538
+ loadModule('/static/js/components/event-processor.js'),
539
+ loadModule('/static/js/components/export-manager.js'),
540
+ loadModule('/static/js/components/working-directory.js'),
541
+ loadModule('/static/js/components/file-tool-tracker.js'),
542
+ loadModule('/static/js/components/build-tracker.js')
543
+ ]);
544
+ })
545
+ .then(() => {
546
+ // Now load main components including dashboard.js which depends on the above
547
+ return Promise.all([
548
+ loadModule('/static/dist/dashboard.js'), // Use dist version that requires above components
500
549
  loadModule('/static/dist/components/activity-tree.js'),
501
550
  loadModule('/static/js/components/code-tree.js'), // TEMPORARY: Direct source for debugging
502
- loadModule('/static/js/components/code-viewer.js'), // Code viewer now includes file change tracking
551
+ loadModule('/static/js/components/code-viewer.js').catch(err => {
552
+ console.error('[CRITICAL] Failed to load code-viewer.js:', err);
553
+ throw err;
554
+ }), // Code viewer now includes file change tracking
503
555
  loadModule('/static/dist/components/file-viewer.js') // File viewer for viewing file contents
504
556
  ]);
505
557
  })
506
558
  .then(() => {
507
559
  console.log('All dashboard modules loaded successfully');
508
560
 
561
+ // Debug: Check if CodeViewer loaded
562
+ if (window.CodeViewer) {
563
+ console.log('[DEBUG] CodeViewer is available on window object');
564
+ } else {
565
+ console.error('[ERROR] CodeViewer NOT FOUND on window object!');
566
+ }
567
+
509
568
  // CodeViewer will auto-initialize and handle tab switching internally
510
569
 
511
570
  // Browser Log Viewer initialization is now handled by UIStateManager
512
571
  // This prevents duplicate event handlers and tab selection conflicts
513
572
 
573
+ // Load bulletproof tab isolation fix
574
+ loadModule('/static/js/tab-isolation-fix.js')
575
+ .then(() => {
576
+ console.log('✅ Tab isolation fix loaded successfully');
577
+ })
578
+ .catch(err => {
579
+ console.error('❌ Failed to load tab isolation fix:', err);
580
+ });
581
+
514
582
  // Hash navigation will handle default tab based on URL
515
583
  // If no hash, default will be 'events' as per hashToTab mapping
516
584
  // To start with File Tree, we can set hash if not present
@@ -522,6 +590,11 @@
522
590
  console.log('Hash present:', window.location.hash);
523
591
  }
524
592
  }, 500);
593
+ })
594
+ .catch(error => {
595
+ console.error('[CRITICAL] Error loading dashboard modules:', error);
596
+ console.error('Stack trace:', error.stack);
597
+ });
525
598
 
526
599
  // Give modules time to execute and initialize dashboard
527
600
  setTimeout(() => {
@@ -583,9 +656,6 @@
583
656
  console.error('Connection toggle button not found in DOM');
584
657
  }
585
658
  }, 1000);
586
- }).catch(error => {
587
- console.error('Failed to load dashboard modules:', error);
588
- });
589
659
  </script>
590
660
  </body>
591
661
  </html>
@@ -151,7 +151,7 @@ class ConnectionManagerService:
151
151
 
152
152
  if loop:
153
153
  # We're in an async context, create a task
154
- task = loop.create_task(self._async_emit(namespace, event, data))
154
+ loop.create_task(self._async_emit(namespace, event, data))
155
155
  # Don't wait for completion to maintain low latency
156
156
  if DEBUG:
157
157
  print(f"✅ Async emit scheduled: {event}", file=sys.stderr)
@@ -219,6 +219,9 @@ class AgentDiscoveryService:
219
219
  agent_info = {
220
220
  "name": metadata.get("name", template_file.stem),
221
221
  "description": metadata.get("description", "No description available"),
222
+ "type": template_data.get(
223
+ "agent_type", metadata.get("category", "agent")
224
+ ), # Extract agent type
222
225
  "version": template_data.get(
223
226
  "agent_version",
224
227
  template_data.get("version", metadata.get("version", "1.0.0")),
@@ -30,6 +30,52 @@ class AgentTemplateBuilder:
30
30
  """Initialize the template builder."""
31
31
  self.logger = get_logger(__name__)
32
32
 
33
+ def normalize_tools_input(self, tools):
34
+ """Normalize various tool input formats to a consistent list.
35
+
36
+ Handles multiple input formats:
37
+ - None/empty: Returns default tools
38
+ - String: Splits by comma and strips whitespace
39
+ - List: Ensures all items are strings and strips whitespace
40
+ - Dict: Takes enabled tools (where value is True)
41
+
42
+ Args:
43
+ tools: Tools input in various formats (str, list, dict, or None)
44
+
45
+ Returns:
46
+ List of tool names, normalized and cleaned
47
+ """
48
+ default_tools = ["Read", "Write", "Edit", "Grep", "Glob", "Bash"]
49
+
50
+ # Handle None or empty
51
+ if not tools:
52
+ self.logger.debug("No tools provided, using defaults")
53
+ return default_tools
54
+
55
+ # Convert to list format
56
+ if isinstance(tools, str):
57
+ # Split by comma, strip whitespace
58
+ tool_list = [t.strip() for t in tools.split(",") if t.strip()]
59
+ self.logger.debug(f"Converted string tools '{tools}' to list: {tool_list}")
60
+ elif isinstance(tools, list):
61
+ # Ensure all items are strings and strip whitespace
62
+ tool_list = [str(t).strip() for t in tools if t and str(t).strip()]
63
+ self.logger.debug(f"Normalized list tools: {tool_list}")
64
+ elif isinstance(tools, dict):
65
+ # Handle dict format - take enabled tools
66
+ tool_list = [k for k, v in tools.items() if v]
67
+ self.logger.info(f"Converting dict tools format: {tools} -> {tool_list}")
68
+ else:
69
+ self.logger.warning(f"Unknown tools format: {type(tools)}, using defaults")
70
+ return default_tools
71
+
72
+ # Return processed list or defaults if empty
73
+ if not tool_list:
74
+ self.logger.debug("Tools list empty after processing, using defaults")
75
+ return default_tools
76
+
77
+ return tool_list
78
+
33
79
  def _load_base_agent_instructions(self, agent_type: str) -> str:
34
80
  """Load BASE instructions for a specific agent type.
35
81
 
@@ -138,13 +184,39 @@ class AgentTemplateBuilder:
138
184
  capabilities.get("tools") if isinstance(capabilities, dict) else None
139
185
  )
140
186
 
141
- tools = (
187
+ # Get raw tools from various possible locations
188
+ raw_tools = (
142
189
  template_data.get("tools")
143
190
  or capabilities_tools
144
191
  or template_data.get("configuration_fields", {}).get("tools")
145
- or ["Read", "Write", "Edit", "Grep", "Glob", "LS"] # Default fallback
146
192
  )
147
193
 
194
+ # Normalize tools to a consistent list format
195
+ tools = self.normalize_tools_input(raw_tools)
196
+
197
+ # Log if we see non-standard tool names (info level, not warning)
198
+ standard_tools = {
199
+ "Read",
200
+ "Write",
201
+ "Edit",
202
+ "MultiEdit", # File operations
203
+ "Grep",
204
+ "Glob",
205
+ "LS", # Search and navigation
206
+ "Bash",
207
+ "BashOutput",
208
+ "KillShell", # Command execution
209
+ "TodoWrite",
210
+ "ExitPlanMode", # Task management
211
+ "WebSearch",
212
+ "WebFetch", # Web operations
213
+ "NotebookRead",
214
+ "NotebookEdit", # Jupyter notebook support
215
+ }
216
+ non_standard = [t for t in tools if t not in standard_tools]
217
+ if non_standard:
218
+ self.logger.info(f"Using non-standard tools: {non_standard}")
219
+
148
220
  # Extract model from template with fallback
149
221
  capabilities_model = (
150
222
  capabilities.get("model") if isinstance(capabilities, dict) else None
@@ -157,15 +229,8 @@ class AgentTemplateBuilder:
157
229
  or "sonnet" # Default fallback
158
230
  )
159
231
 
160
- # Convert tools list to comma-separated string (no spaces!)
161
- tools_str = ",".join(tools) if isinstance(tools, list) else str(tools)
162
-
163
- # Validate tools format - CRITICAL: No spaces allowed!
164
- if ", " in tools_str:
165
- self.logger.error(f"Tools contain spaces: '{tools_str}'")
166
- raise ValueError(
167
- f"Tools must be comma-separated WITHOUT spaces: {tools_str}"
168
- )
232
+ # Convert tools list to comma-separated string (without spaces for compatibility)
233
+ tools_str = ",".join(tools)
169
234
 
170
235
  # Map model names to Claude Code format (as required)
171
236
  model_map = {
@@ -267,17 +332,21 @@ class AgentTemplateBuilder:
267
332
  # Include tools field only if agent is clearly restricted (missing core tools or very few tools)
268
333
  not has_core_tools or len(agent_tools) < 6
269
334
 
270
- # Build YAML frontmatter using Claude Code's compatible format
271
- # ONLY include fields that Claude Code recognizes
335
+ # Build YAML frontmatter with all relevant metadata from JSON template
336
+ # Include all fields that are useful for agent management and functionality
272
337
  #
273
- # CLAUDE CODE COMPATIBLE FORMAT:
338
+ # COMPREHENSIVE AGENT FRONTMATTER FORMAT:
274
339
  # - name: kebab-case agent name (required)
275
340
  # - description: when/why to use this agent with examples (required, multiline)
276
341
  # - model: mapped model name (required)
342
+ # - type: agent type for categorization and functionality (optional but important)
343
+ # - category: organizational category (optional)
277
344
  # - color: visual identifier (optional)
278
345
  # - version: agent version for update tracking (optional)
279
346
  # - author: creator information (optional)
280
- # NOTE: tags field REMOVED - not supported by Claude Code
347
+ # - created_at: creation timestamp (optional)
348
+ # - updated_at: last update timestamp (optional)
349
+ # - tags: list of tags for search and categorization (optional)
281
350
  frontmatter_lines = [
282
351
  "---",
283
352
  f"name: {claude_code_name}",
@@ -291,15 +360,28 @@ class AgentTemplateBuilder:
291
360
  # Add model field (required for Claude Code)
292
361
  frontmatter_lines.append(f"model: {model}")
293
362
 
294
- # Add optional metadata (excluding tags field completely)
363
+ # Add type field (important for agent categorization)
364
+ if agent_type and agent_type != "general":
365
+ frontmatter_lines.append(f"type: {agent_type}")
366
+
367
+ # Add optional metadata fields
295
368
  if metadata.get("color"):
296
369
  frontmatter_lines.append(f"color: {metadata['color']}")
297
- if (
298
- agent_version and agent_version != "1.0.0"
299
- ): # Only include non-default versions
370
+ if metadata.get("category"):
371
+ frontmatter_lines.append(f"category: {metadata['category']}")
372
+ # Always include version field to prevent deployment comparison issues
373
+ if agent_version:
300
374
  frontmatter_lines.append(f'version: "{agent_version}"')
301
375
  if metadata.get("author"):
302
376
  frontmatter_lines.append(f'author: "{metadata["author"]}"')
377
+ if metadata.get("created_at"):
378
+ frontmatter_lines.append(f"created_at: {metadata['created_at']}")
379
+ if metadata.get("updated_at"):
380
+ frontmatter_lines.append(f"updated_at: {metadata['updated_at']}")
381
+ # Add tags as comma-separated string if they exist (consistent with tools format)
382
+ if metadata.get("tags") and isinstance(metadata["tags"], list):
383
+ tags_str = ",".join(metadata["tags"])
384
+ frontmatter_lines.append(f"tags: {tags_str}")
303
385
 
304
386
  frontmatter_lines.extend(
305
387
  [
@@ -314,12 +396,20 @@ class AgentTemplateBuilder:
314
396
  base_instructions = self._load_base_agent_instructions(agent_type)
315
397
 
316
398
  # Get agent instructions from template data (primary) or base agent data (fallback)
317
- agent_specific_instructions = (
318
- template_data.get("instructions")
319
- or base_agent_data.get("content")
320
- or base_agent_data.get("instructions")
321
- or "# Agent Instructions\n\nThis agent provides specialized assistance."
322
- )
399
+ raw_instructions = template_data.get("instructions")
400
+
401
+ # Handle dictionary instructions format
402
+ if isinstance(raw_instructions, dict):
403
+ agent_specific_instructions = self._convert_instructions_dict_to_markdown(
404
+ raw_instructions
405
+ )
406
+ else:
407
+ agent_specific_instructions = (
408
+ raw_instructions
409
+ or base_agent_data.get("content")
410
+ or base_agent_data.get("instructions")
411
+ or "# Agent Instructions\n\nThis agent provides specialized assistance."
412
+ )
323
413
 
324
414
  # Combine BASE instructions with agent-specific instructions
325
415
  if base_instructions:
@@ -406,7 +496,8 @@ Only include memories that are:
406
496
  )
407
497
 
408
498
  # Get tools and model with fallbacks
409
- tools = merged_config.get("tools", ["Read", "Write", "Edit"])
499
+ raw_tools = merged_config.get("tools")
500
+ tools = self.normalize_tools_input(raw_tools)
410
501
  model = merged_config.get("model", "sonnet")
411
502
 
412
503
  # Format tools as YAML list
@@ -873,3 +964,171 @@ tools:
873
964
 
874
965
  # Return as quoted string
875
966
  return f'"{escaped}"'
967
+
968
+ def _convert_instructions_dict_to_markdown(self, instructions_dict: dict) -> str:
969
+ """Convert complex instructions dictionary to markdown format.
970
+
971
+ Args:
972
+ instructions_dict: Dictionary containing structured instructions
973
+
974
+ Returns:
975
+ Formatted markdown string representing the instructions
976
+ """
977
+ if not instructions_dict:
978
+ return "# Agent Instructions\n\nThis agent provides specialized assistance."
979
+
980
+ markdown_parts = []
981
+
982
+ # Add primary role
983
+ if "primary_role" in instructions_dict:
984
+ markdown_parts.extend(["# Role", "", instructions_dict["primary_role"], ""])
985
+
986
+ # Add core identity
987
+ if "core_identity" in instructions_dict:
988
+ markdown_parts.extend(
989
+ ["## Core Identity", "", instructions_dict["core_identity"], ""]
990
+ )
991
+
992
+ # Add responsibilities
993
+ if "responsibilities" in instructions_dict:
994
+ markdown_parts.extend(["## Responsibilities", ""])
995
+
996
+ responsibilities = instructions_dict["responsibilities"]
997
+ if isinstance(responsibilities, list):
998
+ for resp in responsibilities:
999
+ if isinstance(resp, dict):
1000
+ area = resp.get("area", "Unknown Area")
1001
+ tasks = resp.get("tasks", [])
1002
+
1003
+ markdown_parts.extend([f"### {area}", ""])
1004
+
1005
+ if isinstance(tasks, list):
1006
+ for task in tasks:
1007
+ markdown_parts.append(f"- {task}")
1008
+
1009
+ markdown_parts.append("")
1010
+ else:
1011
+ markdown_parts.append(f"- {resp}")
1012
+
1013
+ markdown_parts.append("")
1014
+
1015
+ # Add analytical framework
1016
+ if "analytical_framework" in instructions_dict:
1017
+ framework = instructions_dict["analytical_framework"]
1018
+ if isinstance(framework, dict):
1019
+ markdown_parts.extend(["## Analytical Framework", ""])
1020
+
1021
+ for framework_area, framework_data in framework.items():
1022
+ markdown_parts.extend(
1023
+ [f"### {framework_area.replace('_', ' ').title()}", ""]
1024
+ )
1025
+
1026
+ if isinstance(framework_data, dict):
1027
+ for category, items in framework_data.items():
1028
+ markdown_parts.extend(
1029
+ [f"#### {category.replace('_', ' ').title()}", ""]
1030
+ )
1031
+
1032
+ if isinstance(items, list):
1033
+ for item in items:
1034
+ markdown_parts.append(f"- {item}")
1035
+ elif isinstance(items, str):
1036
+ markdown_parts.append(items)
1037
+
1038
+ markdown_parts.append("")
1039
+ elif isinstance(framework_data, list):
1040
+ for item in framework_data:
1041
+ markdown_parts.append(f"- {item}")
1042
+ markdown_parts.append("")
1043
+
1044
+ # Add methodologies
1045
+ if "methodologies" in instructions_dict:
1046
+ methodologies = instructions_dict["methodologies"]
1047
+ if isinstance(methodologies, dict):
1048
+ markdown_parts.extend(["## Methodologies", ""])
1049
+
1050
+ for method_name, method_data in methodologies.items():
1051
+ markdown_parts.extend(
1052
+ [f"### {method_name.replace('_', ' ').title()}", ""]
1053
+ )
1054
+
1055
+ if isinstance(method_data, dict):
1056
+ for key, value in method_data.items():
1057
+ if isinstance(value, list):
1058
+ markdown_parts.extend(
1059
+ [f"#### {key.replace('_', ' ').title()}", ""]
1060
+ )
1061
+ for item in value:
1062
+ markdown_parts.append(f"- {item}")
1063
+ markdown_parts.append("")
1064
+ elif isinstance(value, str):
1065
+ markdown_parts.extend(
1066
+ [
1067
+ f"**{key.replace('_', ' ').title()}**: {value}",
1068
+ "",
1069
+ ]
1070
+ )
1071
+
1072
+ # Add quality standards
1073
+ if "quality_standards" in instructions_dict:
1074
+ standards = instructions_dict["quality_standards"]
1075
+ if isinstance(standards, dict):
1076
+ markdown_parts.extend(["## Quality Standards", ""])
1077
+
1078
+ for standard_area, standard_items in standards.items():
1079
+ markdown_parts.extend(
1080
+ [f"### {standard_area.replace('_', ' ').title()}", ""]
1081
+ )
1082
+
1083
+ if isinstance(standard_items, list):
1084
+ for item in standard_items:
1085
+ markdown_parts.append(f"- {item}")
1086
+ elif isinstance(standard_items, str):
1087
+ markdown_parts.append(standard_items)
1088
+
1089
+ markdown_parts.append("")
1090
+
1091
+ # Add communication style
1092
+ if "communication_style" in instructions_dict:
1093
+ comm_style = instructions_dict["communication_style"]
1094
+ if isinstance(comm_style, dict):
1095
+ markdown_parts.extend(["## Communication Style", ""])
1096
+
1097
+ for style_area, style_items in comm_style.items():
1098
+ markdown_parts.extend(
1099
+ [f"### {style_area.replace('_', ' ').title()}", ""]
1100
+ )
1101
+
1102
+ if isinstance(style_items, list):
1103
+ for item in style_items:
1104
+ markdown_parts.append(f"- {item}")
1105
+ elif isinstance(style_items, str):
1106
+ markdown_parts.append(style_items)
1107
+
1108
+ markdown_parts.append("")
1109
+
1110
+ # If no specific sections were found, convert as generic dict
1111
+ if not markdown_parts:
1112
+ markdown_parts = ["# Agent Instructions", ""]
1113
+ for key, value in instructions_dict.items():
1114
+ key_title = key.replace("_", " ").title()
1115
+ if isinstance(value, str):
1116
+ markdown_parts.extend([f"## {key_title}", "", value, ""])
1117
+ elif isinstance(value, list):
1118
+ markdown_parts.extend([f"## {key_title}", ""])
1119
+ for item in value:
1120
+ markdown_parts.append(f"- {item}")
1121
+ markdown_parts.append("")
1122
+ elif isinstance(value, dict):
1123
+ markdown_parts.extend([f"## {key_title}", ""])
1124
+ # Simple dict formatting
1125
+ for subkey, subvalue in value.items():
1126
+ if isinstance(subvalue, str):
1127
+ markdown_parts.extend(
1128
+ [
1129
+ f"**{subkey.replace('_', ' ').title()}**: {subvalue}",
1130
+ "",
1131
+ ]
1132
+ )
1133
+
1134
+ return "\n".join(markdown_parts).strip()
@@ -326,6 +326,7 @@ class AgentValidator:
326
326
  "path": str(agent_file),
327
327
  "description": "No description",
328
328
  "version": "unknown",
329
+ "type": "agent", # Default type
329
330
  }
330
331
 
331
332
  # Extract from YAML frontmatter
@@ -342,6 +343,8 @@ class AgentValidator:
342
343
  agent_info["version"] = (
343
344
  stripped_line.split(":", 1)[1].strip().strip("\"'")
344
345
  )
346
+ elif stripped_line.startswith("type:"):
347
+ agent_info["type"] = stripped_line.split(":", 1)[1].strip().strip("\"'")
345
348
 
346
349
  return agent_info
347
350
 
@@ -190,10 +190,19 @@ class TemplateValidator:
190
190
 
191
191
  if "tags" in metadata:
192
192
  tags = metadata["tags"]
193
- if not isinstance(tags, list):
194
- result.add_error("Tags should be a list", field_name="metadata.tags")
195
- elif len(tags) == 0:
196
- result.add_warning("No tags specified", field_name="metadata.tags")
193
+ if isinstance(tags, str):
194
+ # Convert comma-separated string to list for validation
195
+ tag_list = [tag.strip() for tag in tags.split(",") if tag.strip()]
196
+ if len(tag_list) == 0:
197
+ result.add_warning("No tags specified", field_name="metadata.tags")
198
+ elif isinstance(tags, list):
199
+ if len(tags) == 0:
200
+ result.add_warning("No tags specified", field_name="metadata.tags")
201
+ else:
202
+ result.add_error(
203
+ "Tags should be a list or comma-separated string",
204
+ field_name="metadata.tags",
205
+ )
197
206
 
198
207
  def _validate_capabilities(
199
208
  self, capabilities: Dict[str, Any], result: ValidationResult
@@ -250,13 +250,10 @@ class LocalAgentTemplateManager:
250
250
  Created LocalAgentTemplate object
251
251
  """
252
252
  # Determine author based on tier
253
- if tier == "project":
254
- author = self.get_project_name()
255
- else:
256
- author = Path.home().name
253
+ author = self.get_project_name() if tier == "project" else Path.home().name
257
254
 
258
255
  # Create template
259
- template = LocalAgentTemplate(
256
+ return LocalAgentTemplate(
260
257
  agent_id=agent_id,
261
258
  agent_version="1.0.0",
262
259
  author=author,
@@ -283,8 +280,6 @@ class LocalAgentTemplateManager:
283
280
  parent_agent=parent_agent,
284
281
  )
285
282
 
286
- return template
287
-
288
283
  def save_local_template(
289
284
  self, template: LocalAgentTemplate, tier: Optional[str] = None
290
285
  ) -> Path:
@@ -163,8 +163,7 @@ class UnifiedMonitorDaemon:
163
163
  # environment variable to prevent infinite recursion
164
164
  if self.daemon_manager.use_subprocess_daemon():
165
165
  # Start using subprocess - this returns immediately in parent
166
- success = self.daemon_manager.start_daemon_subprocess()
167
- return success
166
+ return self.daemon_manager.start_daemon_subprocess()
168
167
 
169
168
  # Check if we're in subprocess mode (environment variable set)
170
169
  if os.environ.get("CLAUDE_MPM_SUBPROCESS_DAEMON") == "1":
@@ -183,10 +183,7 @@ class DaemonManager:
183
183
  return True
184
184
 
185
185
  # Try to identify claude-mpm processes
186
- if self._kill_claude_mpm_processes():
187
- return True
188
-
189
- return False
186
+ return bool(self._kill_claude_mpm_processes())
190
187
 
191
188
  except Exception as e:
192
189
  self.logger.error(f"Error killing processes on port: {e}")
@@ -578,9 +575,7 @@ class DaemonManager:
578
575
  stdout=log_file,
579
576
  stderr=subprocess.STDOUT if self.log_file else subprocess.DEVNULL,
580
577
  start_new_session=True, # Create new process group
581
- close_fds=(
582
- False if self.log_file else True
583
- ), # Keep log file open if redirecting
578
+ close_fds=(not self.log_file), # Keep log file open if redirecting
584
579
  env=env, # Pass modified environment
585
580
  )
586
581