claude-mpm 5.4.85__py3-none-any.whl → 5.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
- claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
- claude_mpm/agents/PM_INSTRUCTIONS.md +101 -703
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +46 -0
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +2 -2
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/executor.py +119 -16
- claude_mpm/cli/parsers/base_parser.py +71 -1
- claude_mpm/cli/parsers/commander_parser.py +83 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/startup.py +54 -16
- claude_mpm/cli/startup_display.py +72 -5
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +72 -0
- claude_mpm/commander/adapters/__init__.py +31 -0
- claude_mpm/commander/adapters/base.py +191 -0
- claude_mpm/commander/adapters/claude_code.py +361 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +105 -0
- claude_mpm/commander/api/errors.py +112 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +215 -0
- claude_mpm/commander/api/routes/work.py +260 -0
- claude_mpm/commander/api/schemas.py +182 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +107 -0
- claude_mpm/commander/chat/commands.py +96 -0
- claude_mpm/commander/chat/repl.py +310 -0
- claude_mpm/commander/config.py +49 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/daemon.py +398 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +332 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +143 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +62 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +337 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +121 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +309 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +404 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +316 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +361 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +189 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +219 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +8 -0
- claude_mpm/commands/mpm-doctor.md +8 -0
- claude_mpm/commands/mpm-help.md +8 -0
- claude_mpm/commands/mpm-init.md +8 -0
- claude_mpm/commands/mpm-monitor.md +8 -0
- claude_mpm/commands/mpm-organize.md +8 -0
- claude_mpm/commands/mpm-postmortem.md +8 -0
- claude_mpm/commands/mpm-session-resume.md +9 -1
- claude_mpm/commands/mpm-status.md +8 -0
- claude_mpm/commands/mpm-ticket-view.md +8 -0
- claude_mpm/commands/mpm-version.md +8 -0
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/core/config.py +5 -0
- claude_mpm/core/hook_manager.py +51 -3
- claude_mpm/core/logger.py +10 -7
- claude_mpm/core/logging_utils.py +4 -2
- claude_mpm/core/output_style_manager.py +15 -5
- claude_mpm/core/unified_config.py +10 -6
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +9 -9
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +250 -11
- claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +69 -5
- claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +14 -77
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
- claude_mpm/hooks/session_resume_hook.py +85 -1
- claude_mpm/init.py +1 -1
- claude_mpm/scripts/claude-hook-handler.sh +36 -10
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/event_log.py +325 -0
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +106 -16
- claude_mpm/services/pm_skills_deployer.py +259 -87
- claude_mpm/services/skills/git_skill_source_manager.py +51 -2
- claude_mpm/services/skills/selective_skill_deployer.py +114 -16
- claude_mpm/services/skills/skill_discovery_service.py +57 -3
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm-5.6.1.dist-info/METADATA +391 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/RECORD +244 -145
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
- claude_mpm-5.4.85.dist-info/METADATA +0 -1023
- /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
- /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/top_level.txt +0 -0
|
@@ -7,9 +7,14 @@ This service manages:
|
|
|
7
7
|
DESIGN DECISION: Use stateless HTTP POST instead of persistent SocketIO
|
|
8
8
|
connections because hook handlers are ephemeral processes (< 1 second lifetime).
|
|
9
9
|
This eliminates disconnection issues and matches the process lifecycle.
|
|
10
|
+
|
|
11
|
+
DESIGN DECISION: Synchronous HTTP POST only (no async)
|
|
12
|
+
Hook handlers are too short-lived (~25ms lifecycle) to benefit from async.
|
|
13
|
+
Using asyncio.run() creates event loops that close before HTTP operations complete,
|
|
14
|
+
causing "Event loop is closed" errors. Synchronous HTTP POST in a thread pool
|
|
15
|
+
is simpler and more reliable for ephemeral processes.
|
|
10
16
|
"""
|
|
11
17
|
|
|
12
|
-
import asyncio
|
|
13
18
|
import os
|
|
14
19
|
import sys
|
|
15
20
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -27,9 +32,6 @@ except ImportError:
|
|
|
27
32
|
REQUESTS_AVAILABLE = False
|
|
28
33
|
requests = None
|
|
29
34
|
|
|
30
|
-
# Import high-performance event emitter - lazy loaded in _async_emit()
|
|
31
|
-
# to reduce hook handler initialization time by ~85% (792ms -> minimal)
|
|
32
|
-
|
|
33
35
|
# Import EventNormalizer for consistent event formatting
|
|
34
36
|
try:
|
|
35
37
|
from claude_mpm.services.socketio.event_normalizer import EventNormalizer
|
|
@@ -55,10 +57,6 @@ except ImportError:
|
|
|
55
57
|
)
|
|
56
58
|
|
|
57
59
|
|
|
58
|
-
# EventBus removed - using direct HTTP POST only
|
|
59
|
-
# This eliminates duplicate events and simplifies the architecture
|
|
60
|
-
|
|
61
|
-
|
|
62
60
|
class ConnectionManagerService:
|
|
63
61
|
"""Manages connections for the Claude hook handler using HTTP POST."""
|
|
64
62
|
|
|
@@ -72,17 +70,9 @@ class ConnectionManagerService:
|
|
|
72
70
|
self.server_port = int(os.environ.get("CLAUDE_MPM_SERVER_PORT", "8765"))
|
|
73
71
|
self.http_endpoint = f"http://{self.server_host}:{self.server_port}/api/events"
|
|
74
72
|
|
|
75
|
-
# EventBus removed - using direct HTTP POST only
|
|
76
|
-
|
|
77
|
-
# For backward compatibility with tests
|
|
78
|
-
self.connection_pool = None # No longer used
|
|
79
|
-
|
|
80
|
-
# Track async emit tasks to prevent garbage collection
|
|
81
|
-
self._emit_tasks: set = set()
|
|
82
|
-
|
|
83
73
|
# Thread pool for non-blocking HTTP requests
|
|
84
74
|
# WHY: Prevents HTTP POST from blocking hook processing (2s timeout → 0ms blocking)
|
|
85
|
-
# max_workers=2: Sufficient for low-frequency
|
|
75
|
+
# max_workers=2: Sufficient for low-frequency hook events
|
|
86
76
|
self._http_executor = ThreadPoolExecutor(
|
|
87
77
|
max_workers=2, thread_name_prefix="http-emit"
|
|
88
78
|
)
|
|
@@ -94,13 +84,13 @@ class ConnectionManagerService:
|
|
|
94
84
|
)
|
|
95
85
|
|
|
96
86
|
def emit_event(self, namespace: str, event: str, data: dict):
|
|
97
|
-
"""Emit event using
|
|
87
|
+
"""Emit event using HTTP POST.
|
|
98
88
|
|
|
99
|
-
WHY
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
89
|
+
WHY HTTP POST only:
|
|
90
|
+
- Hook handlers are ephemeral (~25ms lifecycle)
|
|
91
|
+
- Async emission causes "Event loop is closed" errors
|
|
92
|
+
- HTTP POST in thread pool is simpler and more reliable
|
|
93
|
+
- Completes in 20-50ms, which is acceptable for hook handlers
|
|
104
94
|
"""
|
|
105
95
|
# Create event data for normalization
|
|
106
96
|
raw_event = {
|
|
@@ -132,62 +122,9 @@ class ConnectionManagerService:
|
|
|
132
122
|
file=sys.stderr,
|
|
133
123
|
)
|
|
134
124
|
|
|
135
|
-
#
|
|
136
|
-
success = self._try_async_emit(namespace, event, claude_event_data)
|
|
137
|
-
if success:
|
|
138
|
-
return
|
|
139
|
-
|
|
140
|
-
# Fallback to HTTP POST for cross-process communication
|
|
125
|
+
# Emit via HTTP POST (non-blocking, runs in thread pool)
|
|
141
126
|
self._try_http_emit(namespace, event, claude_event_data)
|
|
142
127
|
|
|
143
|
-
def _try_async_emit(self, namespace: str, event: str, data: dict) -> bool:
|
|
144
|
-
"""Try to emit event using high-performance async emitter."""
|
|
145
|
-
try:
|
|
146
|
-
# Run async emission in the current event loop or create one
|
|
147
|
-
loop = None
|
|
148
|
-
try:
|
|
149
|
-
loop = asyncio.get_running_loop()
|
|
150
|
-
except RuntimeError:
|
|
151
|
-
# No running loop, create a new one
|
|
152
|
-
pass
|
|
153
|
-
|
|
154
|
-
if loop:
|
|
155
|
-
# We're in an async context, create a task with tracking
|
|
156
|
-
task = loop.create_task(self._async_emit(namespace, event, data))
|
|
157
|
-
self._emit_tasks.add(task)
|
|
158
|
-
task.add_done_callback(self._emit_tasks.discard)
|
|
159
|
-
# Don't wait for completion to maintain low latency
|
|
160
|
-
if DEBUG:
|
|
161
|
-
print(f"✅ Async emit scheduled: {event}", file=sys.stderr)
|
|
162
|
-
return True
|
|
163
|
-
# No event loop, run synchronously
|
|
164
|
-
success = asyncio.run(self._async_emit(namespace, event, data))
|
|
165
|
-
if DEBUG and success:
|
|
166
|
-
print(f"✅ Async emit successful: {event}", file=sys.stderr)
|
|
167
|
-
return success
|
|
168
|
-
|
|
169
|
-
except Exception as e:
|
|
170
|
-
if DEBUG:
|
|
171
|
-
print(f"⚠️ Async emit failed: {e}", file=sys.stderr)
|
|
172
|
-
return False
|
|
173
|
-
|
|
174
|
-
async def _async_emit(self, namespace: str, event: str, data: dict) -> bool:
|
|
175
|
-
"""Async helper for event emission."""
|
|
176
|
-
try:
|
|
177
|
-
# Lazy load event emitter to reduce initialization overhead
|
|
178
|
-
from claude_mpm.services.monitor.event_emitter import get_event_emitter
|
|
179
|
-
|
|
180
|
-
emitter = await get_event_emitter()
|
|
181
|
-
return await emitter.emit_event(namespace, "claude_event", data)
|
|
182
|
-
except ImportError:
|
|
183
|
-
if DEBUG:
|
|
184
|
-
print("⚠️ Event emitter not available", file=sys.stderr)
|
|
185
|
-
return False
|
|
186
|
-
except Exception as e:
|
|
187
|
-
if DEBUG:
|
|
188
|
-
print(f"⚠️ Async emitter error: {e}", file=sys.stderr)
|
|
189
|
-
return False
|
|
190
|
-
|
|
191
128
|
def _try_http_emit(self, namespace: str, event: str, data: dict):
|
|
192
129
|
"""Try to emit event using HTTP POST fallback (non-blocking).
|
|
193
130
|
|
|
@@ -67,7 +67,9 @@ class SubagentResponseProcessor:
|
|
|
67
67
|
print(" - No stored sessions in delegation_requests!", file=sys.stderr)
|
|
68
68
|
|
|
69
69
|
# Get agent type and other basic info
|
|
70
|
-
agent_type, agent_id, reason = self._extract_basic_info(
|
|
70
|
+
agent_type, agent_id, reason, agent_type_inferred = self._extract_basic_info(
|
|
71
|
+
event, session_id
|
|
72
|
+
)
|
|
71
73
|
|
|
72
74
|
# Always log SubagentStop events for debugging
|
|
73
75
|
if DEBUG or agent_type != "unknown":
|
|
@@ -108,6 +110,7 @@ class SubagentResponseProcessor:
|
|
|
108
110
|
working_dir,
|
|
109
111
|
git_branch,
|
|
110
112
|
structured_response,
|
|
113
|
+
agent_type_inferred,
|
|
111
114
|
)
|
|
112
115
|
|
|
113
116
|
# Debug log the processed data
|
|
@@ -117,11 +120,20 @@ class SubagentResponseProcessor:
|
|
|
117
120
|
file=sys.stderr,
|
|
118
121
|
)
|
|
119
122
|
|
|
120
|
-
# Emit to
|
|
121
|
-
self.connection_manager.emit_event("
|
|
123
|
+
# Emit to default namespace (consistent with subagent_start)
|
|
124
|
+
self.connection_manager.emit_event("", "subagent_stop", subagent_stop_data)
|
|
125
|
+
|
|
126
|
+
def _extract_basic_info(
|
|
127
|
+
self, event: dict, session_id: str
|
|
128
|
+
) -> Tuple[str, str, str, bool]:
|
|
129
|
+
"""Extract basic info from the event.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Tuple of (agent_type, agent_id, reason, agent_type_inferred)
|
|
133
|
+
- agent_type_inferred is True when defaulted to "pm"
|
|
134
|
+
"""
|
|
135
|
+
agent_type_inferred = False
|
|
122
136
|
|
|
123
|
-
def _extract_basic_info(self, event: dict, session_id: str) -> Tuple[str, str, str]:
|
|
124
|
-
"""Extract basic info from the event."""
|
|
125
137
|
# First try to get agent type from our tracking
|
|
126
138
|
agent_type = (
|
|
127
139
|
self.state_manager.get_delegation_agent_type(session_id)
|
|
@@ -146,7 +158,17 @@ class SubagentResponseProcessor:
|
|
|
146
158
|
elif "pm" in task_desc or "project" in task_desc:
|
|
147
159
|
agent_type = "pm"
|
|
148
160
|
|
|
149
|
-
|
|
161
|
+
# Default to "pm" if still unknown (main conversation doesn't use Task tool)
|
|
162
|
+
if agent_type == "unknown":
|
|
163
|
+
agent_type = "pm"
|
|
164
|
+
agent_type_inferred = True
|
|
165
|
+
if DEBUG:
|
|
166
|
+
print(
|
|
167
|
+
" - Inferred agent_type='pm' (no explicit type found)",
|
|
168
|
+
file=sys.stderr,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return agent_type, agent_id, reason, agent_type_inferred
|
|
150
172
|
|
|
151
173
|
def _extract_structured_response(
|
|
152
174
|
self, output: str, agent_type: str
|
|
@@ -338,10 +360,12 @@ class SubagentResponseProcessor:
|
|
|
338
360
|
working_dir: str,
|
|
339
361
|
git_branch: str,
|
|
340
362
|
structured_response: Optional[dict],
|
|
363
|
+
agent_type_inferred: bool,
|
|
341
364
|
) -> dict:
|
|
342
365
|
"""Build the subagent stop data for event emission."""
|
|
343
366
|
subagent_stop_data = {
|
|
344
367
|
"agent_type": agent_type,
|
|
368
|
+
"agent_type_inferred": agent_type_inferred,
|
|
345
369
|
"agent_id": agent_id,
|
|
346
370
|
"reason": reason,
|
|
347
371
|
"session_id": session_id,
|
|
@@ -8,8 +8,11 @@ DESIGN DECISIONS:
|
|
|
8
8
|
- Non-blocking: doesn't prevent PM from starting if check fails
|
|
9
9
|
- Displays context to stdout for user visibility
|
|
10
10
|
- Integrates with existing session pause/resume infrastructure
|
|
11
|
+
- Checks for ACTIVE-PAUSE.jsonl (incremental auto-pause) before regular paused sessions
|
|
11
12
|
"""
|
|
12
13
|
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
13
16
|
from pathlib import Path
|
|
14
17
|
from typing import Any, Dict, Optional
|
|
15
18
|
|
|
@@ -31,10 +34,83 @@ class SessionResumeStartupHook:
|
|
|
31
34
|
self.project_path = project_path or Path.cwd()
|
|
32
35
|
self.resume_helper = SessionResumeHelper(self.project_path)
|
|
33
36
|
self._session_displayed = False
|
|
37
|
+
self.sessions_dir = self.project_path / ".claude-mpm" / "sessions"
|
|
38
|
+
|
|
39
|
+
def check_for_active_pause(self) -> Optional[Dict[str, Any]]:
|
|
40
|
+
"""Check for an active incremental pause session.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Pause session metadata if ACTIVE-PAUSE.jsonl exists, None otherwise
|
|
44
|
+
"""
|
|
45
|
+
active_pause_path = self.sessions_dir / "ACTIVE-PAUSE.jsonl"
|
|
46
|
+
|
|
47
|
+
if not active_pause_path.exists():
|
|
48
|
+
logger.debug("No ACTIVE-PAUSE.jsonl found")
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
# Read JSONL file to get first and last actions
|
|
53
|
+
with active_pause_path.open("r") as f:
|
|
54
|
+
lines = f.readlines()
|
|
55
|
+
|
|
56
|
+
if not lines:
|
|
57
|
+
logger.warning("ACTIVE-PAUSE.jsonl is empty")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
# Parse first action (session start)
|
|
61
|
+
first_action = json.loads(lines[0])
|
|
62
|
+
|
|
63
|
+
# Parse last action (most recent)
|
|
64
|
+
last_action = json.loads(lines[-1]) if len(lines) > 1 else first_action
|
|
65
|
+
|
|
66
|
+
# Extract metadata
|
|
67
|
+
return {
|
|
68
|
+
"is_incremental": True,
|
|
69
|
+
"session_id": first_action.get("session_id"),
|
|
70
|
+
"started_at": first_action.get("timestamp"),
|
|
71
|
+
"context_at_start": first_action.get("data", {}).get(
|
|
72
|
+
"context_percentage", 0
|
|
73
|
+
),
|
|
74
|
+
"current_context": last_action.get("context_percentage", 0),
|
|
75
|
+
"action_count": len(lines),
|
|
76
|
+
"file_path": str(active_pause_path),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
except (json.JSONDecodeError, OSError, KeyError) as e:
|
|
80
|
+
logger.error(f"Failed to parse ACTIVE-PAUSE.jsonl: {e}", exc_info=True)
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def display_active_pause_warning(self, pause_info: Dict[str, Any]) -> None:
|
|
84
|
+
"""Display warning about active incremental pause session.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
pause_info: Pause session metadata from check_for_active_pause()
|
|
88
|
+
"""
|
|
89
|
+
print("\n" + "=" * 60, file=sys.stderr)
|
|
90
|
+
print("⚠️ ACTIVE AUTO-PAUSE SESSION DETECTED", file=sys.stderr)
|
|
91
|
+
print("=" * 60, file=sys.stderr)
|
|
92
|
+
print(f"Session ID: {pause_info['session_id']}", file=sys.stderr)
|
|
93
|
+
print(f"Started at: {pause_info['started_at']}", file=sys.stderr)
|
|
94
|
+
print(
|
|
95
|
+
f"Context at pause: {pause_info['context_at_start']:.1%}", file=sys.stderr
|
|
96
|
+
)
|
|
97
|
+
print(f"Actions recorded: {pause_info['action_count']}", file=sys.stderr)
|
|
98
|
+
print(
|
|
99
|
+
"\nThis session was auto-paused due to high context usage.", file=sys.stderr
|
|
100
|
+
)
|
|
101
|
+
print("Options:", file=sys.stderr)
|
|
102
|
+
print(" 1. Continue (actions will be appended)", file=sys.stderr)
|
|
103
|
+
print(" 2. Use /mpm-init pause --finalize to create snapshot", file=sys.stderr)
|
|
104
|
+
print(" 3. Use /mpm-init pause --discard to abandon", file=sys.stderr)
|
|
105
|
+
print("=" * 60 + "\n", file=sys.stderr)
|
|
34
106
|
|
|
35
107
|
def on_pm_startup(self) -> Optional[Dict[str, Any]]:
|
|
36
108
|
"""Execute on PM startup to check for paused sessions.
|
|
37
109
|
|
|
110
|
+
Checks in priority order:
|
|
111
|
+
1. ACTIVE-PAUSE.jsonl (incremental auto-pause)
|
|
112
|
+
2. Regular paused sessions (session-*.json)
|
|
113
|
+
|
|
38
114
|
Returns:
|
|
39
115
|
Session data if paused session found, None otherwise
|
|
40
116
|
"""
|
|
@@ -44,7 +120,15 @@ class SessionResumeStartupHook:
|
|
|
44
120
|
logger.debug("Session already displayed, skipping")
|
|
45
121
|
return None
|
|
46
122
|
|
|
47
|
-
# Check for
|
|
123
|
+
# PRIORITY 1: Check for active incremental pause FIRST
|
|
124
|
+
active_pause_info = self.check_for_active_pause()
|
|
125
|
+
if active_pause_info:
|
|
126
|
+
self.display_active_pause_warning(active_pause_info)
|
|
127
|
+
self._session_displayed = True
|
|
128
|
+
logger.info("Active pause session detected and displayed")
|
|
129
|
+
return active_pause_info
|
|
130
|
+
|
|
131
|
+
# PRIORITY 2: Fall back to regular paused sessions
|
|
48
132
|
session_data = self.resume_helper.check_and_display_resume_prompt()
|
|
49
133
|
|
|
50
134
|
if session_data:
|
claude_mpm/init.py
CHANGED
|
@@ -62,8 +62,13 @@ set -e
|
|
|
62
62
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
63
63
|
|
|
64
64
|
# Determine the claude-mpm root based on installation type
|
|
65
|
+
# Check if we're in a UV tools installation
|
|
66
|
+
if [[ "$SCRIPT_DIR" == *"/.local/share/uv/tools/"* ]]; then
|
|
67
|
+
# UV tools installation - script is at lib/python*/site-packages/claude_mpm/scripts/
|
|
68
|
+
# The tool root is what we need for Python detection
|
|
69
|
+
CLAUDE_MPM_ROOT="$(echo "$SCRIPT_DIR" | sed 's|/lib/python.*/site-packages.*||')"
|
|
65
70
|
# Check if we're in a pipx installation
|
|
66
|
-
|
|
71
|
+
elif [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
|
|
67
72
|
# pipx installation - script is at lib/python*/site-packages/claude_mpm/scripts/
|
|
68
73
|
# The venv root is what we need for Python detection
|
|
69
74
|
CLAUDE_MPM_ROOT="$(echo "$SCRIPT_DIR" | sed 's|/lib/python.*/site-packages/.*||')"
|
|
@@ -89,11 +94,12 @@ fi
|
|
|
89
94
|
# STRATEGY:
|
|
90
95
|
# This function implements a fallback chain to find Python with claude-mpm dependencies:
|
|
91
96
|
# 1. UV-managed projects (uv.lock detected) - uses "uv run python"
|
|
92
|
-
# 2.
|
|
93
|
-
# 3.
|
|
94
|
-
# 4.
|
|
95
|
-
# 5.
|
|
96
|
-
# 6. System
|
|
97
|
+
# 2. UV tools installations (~/.local/share/uv/tools/) - uses tool's venv Python
|
|
98
|
+
# 3. pipx installations - uses pipx venv Python
|
|
99
|
+
# 4. Project-specific virtual environments (venv, .venv)
|
|
100
|
+
# 5. Currently active virtual environment ($VIRTUAL_ENV)
|
|
101
|
+
# 6. System python3 (may lack dependencies)
|
|
102
|
+
# 7. System python (last resort)
|
|
97
103
|
#
|
|
98
104
|
# WHY THIS APPROACH:
|
|
99
105
|
# - Claude MPM requires specific packages (socketio, eventlet) not in system Python
|
|
@@ -124,7 +130,21 @@ find_python_command() {
|
|
|
124
130
|
fi
|
|
125
131
|
fi
|
|
126
132
|
|
|
127
|
-
# 2. Check if we're in a
|
|
133
|
+
# 2. Check if we're in a UV tools installation
|
|
134
|
+
if [[ "$SCRIPT_DIR" == *"/.local/share/uv/tools/"* ]]; then
|
|
135
|
+
# UV tools installation - extract the tool root directory
|
|
136
|
+
CLAUDE_MPM_ROOT="$(echo "$SCRIPT_DIR" | sed 's|/lib/python.*/site-packages.*||')"
|
|
137
|
+
local uv_python="$CLAUDE_MPM_ROOT/bin/python"
|
|
138
|
+
if [ -x "$uv_python" ]; then
|
|
139
|
+
if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
|
|
140
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] UV tools Python found: $uv_python" >> /tmp/claude-mpm-hook.log
|
|
141
|
+
fi
|
|
142
|
+
echo "$uv_python"
|
|
143
|
+
return
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# 3. Check if we're in a pipx installation
|
|
128
148
|
if [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
|
|
129
149
|
# pipx installation - use the pipx venv's Python directly
|
|
130
150
|
if [ -f "$CLAUDE_MPM_ROOT/bin/python" ]; then
|
|
@@ -133,7 +153,7 @@ find_python_command() {
|
|
|
133
153
|
fi
|
|
134
154
|
fi
|
|
135
155
|
|
|
136
|
-
#
|
|
156
|
+
# 4. Check for project-local virtual environment (common in development)
|
|
137
157
|
if [ -f "$CLAUDE_MPM_ROOT/venv/bin/activate" ]; then
|
|
138
158
|
source "$CLAUDE_MPM_ROOT/venv/bin/activate"
|
|
139
159
|
echo "$CLAUDE_MPM_ROOT/venv/bin/python"
|
|
@@ -154,7 +174,13 @@ find_python_command() {
|
|
|
154
174
|
PYTHON_CMD=$(find_python_command)
|
|
155
175
|
|
|
156
176
|
# Check installation type and set PYTHONPATH accordingly
|
|
157
|
-
if [[ "$SCRIPT_DIR" == *"/.local/
|
|
177
|
+
if [[ "$SCRIPT_DIR" == *"/.local/share/uv/tools/"* ]]; then
|
|
178
|
+
# UV tools installation - claude_mpm is already in the tool's site-packages
|
|
179
|
+
# No need to modify PYTHONPATH
|
|
180
|
+
if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
|
|
181
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] UV tools installation detected" >> /tmp/claude-mpm-hook.log
|
|
182
|
+
fi
|
|
183
|
+
elif [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
|
|
158
184
|
# pipx installation - claude_mpm is already in the venv's site-packages
|
|
159
185
|
# No need to modify PYTHONPATH
|
|
160
186
|
if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
|
|
@@ -224,4 +250,4 @@ if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
|
|
|
224
250
|
fi
|
|
225
251
|
# Return continue action to prevent blocking Claude Code
|
|
226
252
|
echo '{"action": "continue"}'
|
|
227
|
-
exit 0
|
|
253
|
+
exit 0
|
|
@@ -30,16 +30,16 @@ class AgentRecommendationService:
|
|
|
30
30
|
Can be used by CLI, API, or future auto-configuration features.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
# Core agents always included -
|
|
33
|
+
# Core agents always included - Standard 6 core agents for essential PM workflow
|
|
34
|
+
# These agents are auto-deployed when no configuration exists
|
|
34
35
|
# Uses exact agent IDs from repository for consistency
|
|
35
36
|
CORE_AGENTS = {
|
|
36
|
-
"engineer",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"security-agent",
|
|
37
|
+
"engineer", # General-purpose implementation
|
|
38
|
+
"research", # Codebase exploration and analysis
|
|
39
|
+
"qa", # Testing and quality assurance
|
|
40
|
+
"documentation", # Documentation generation
|
|
41
|
+
"ops", # Basic deployment operations
|
|
42
|
+
"ticketing", # Ticket tracking (essential for PM workflow)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
# Map detected languages to recommended engineer agents
|
|
@@ -88,7 +88,7 @@ class CacheGitManager:
|
|
|
88
88
|
if self.repo_path:
|
|
89
89
|
logger.debug(f"Initialized CacheGitManager for repo: {self.repo_path}")
|
|
90
90
|
else:
|
|
91
|
-
logger.
|
|
91
|
+
logger.debug(f"Cache path is not a git repository: {cache_path}")
|
|
92
92
|
|
|
93
93
|
def _find_git_root(self) -> Optional[Path]:
|
|
94
94
|
"""
|
|
@@ -491,6 +491,9 @@ class RemoteAgentDiscoveryService:
|
|
|
491
491
|
"SKILL.md",
|
|
492
492
|
"SKILLS.md",
|
|
493
493
|
"skill-template.md",
|
|
494
|
+
# Legacy agents superseded by newer versions
|
|
495
|
+
# TODO: Remove after bobmatnyc/claude-mpm-agents#XXX is merged
|
|
496
|
+
"memory-manager.md", # Superseded by memory-manager-agent.md (v1.2.0)
|
|
494
497
|
}
|
|
495
498
|
md_files = [f for f in md_files if f.name not in excluded_files]
|
|
496
499
|
|
|
@@ -10,10 +10,19 @@ Loading precedence: Project → User → System
|
|
|
10
10
|
|
|
11
11
|
This service integrates with the main agent_loader.py to provide
|
|
12
12
|
markdown-based agent profiles alongside JSON-based templates.
|
|
13
|
+
|
|
14
|
+
Auto-Deployment: When no agents are configured, the standard 6 core agents
|
|
15
|
+
are automatically deployed:
|
|
16
|
+
- engineer: General-purpose implementation
|
|
17
|
+
- research: Codebase exploration and analysis
|
|
18
|
+
- qa: Testing and quality assurance
|
|
19
|
+
- documentation: Documentation generation
|
|
20
|
+
- ops: Basic deployment operations
|
|
21
|
+
- ticketing: Ticket tracking (essential for PM workflow)
|
|
13
22
|
"""
|
|
14
23
|
|
|
15
24
|
from pathlib import Path
|
|
16
|
-
from typing import Any, Dict, Optional
|
|
25
|
+
from typing import Any, Dict, List, Optional
|
|
17
26
|
|
|
18
27
|
from claude_mpm.agents.agent_loader import AgentTier, list_agents_by_tier
|
|
19
28
|
from claude_mpm.core.logging_utils import get_logger
|
|
@@ -21,6 +30,17 @@ from claude_mpm.core.unified_paths import get_path_manager
|
|
|
21
30
|
|
|
22
31
|
logger = get_logger(__name__)
|
|
23
32
|
|
|
33
|
+
# Standard 6 core agents that are auto-deployed when no agents are specified
|
|
34
|
+
# This list is the canonical source - other modules should import from here
|
|
35
|
+
CORE_AGENTS: List[str] = [
|
|
36
|
+
"engineer", # General-purpose implementation
|
|
37
|
+
"research", # Codebase exploration and analysis
|
|
38
|
+
"qa", # Testing and quality assurance
|
|
39
|
+
"documentation", # Documentation generation
|
|
40
|
+
"ops", # Basic deployment operations
|
|
41
|
+
"ticketing", # Ticket tracking (essential for PM workflow)
|
|
42
|
+
]
|
|
43
|
+
|
|
24
44
|
|
|
25
45
|
class FrameworkAgentLoader:
|
|
26
46
|
"""Loads agent profiles from project, user, and system directories with proper precedence"""
|
|
@@ -86,7 +106,7 @@ class FrameworkAgentLoader:
|
|
|
86
106
|
data_claude = package_path / "data" / "agents" / "CLAUDE.md"
|
|
87
107
|
if data_instructions.exists() or data_claude.exists():
|
|
88
108
|
return package_path / "data"
|
|
89
|
-
except Exception:
|
|
109
|
+
except Exception: # nosec B110 - intentional fallthrough to next location
|
|
90
110
|
pass
|
|
91
111
|
|
|
92
112
|
current = Path.cwd()
|
|
@@ -431,3 +451,56 @@ Please operate according to your profile specifications and maintain quality sta
|
|
|
431
451
|
"""
|
|
432
452
|
|
|
433
453
|
return instruction.strip()
|
|
454
|
+
|
|
455
|
+
def get_core_agents(self) -> List[str]:
|
|
456
|
+
"""
|
|
457
|
+
Get the standard 6 core agents for auto-deployment.
|
|
458
|
+
|
|
459
|
+
These agents are automatically deployed when no agents are specified
|
|
460
|
+
in the configuration. They provide essential PM workflow functionality.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
List of core agent IDs
|
|
464
|
+
|
|
465
|
+
Example:
|
|
466
|
+
>>> loader = FrameworkAgentLoader()
|
|
467
|
+
>>> core = loader.get_core_agents()
|
|
468
|
+
>>> 'engineer' in core
|
|
469
|
+
True
|
|
470
|
+
>>> len(core)
|
|
471
|
+
6
|
|
472
|
+
"""
|
|
473
|
+
return CORE_AGENTS.copy()
|
|
474
|
+
|
|
475
|
+
def get_agents_with_fallback(self) -> Dict[str, list]:
|
|
476
|
+
"""
|
|
477
|
+
Get available agents, falling back to core agents if none found.
|
|
478
|
+
|
|
479
|
+
This method implements the auto-deployment logic: when no agents
|
|
480
|
+
are found in any tier (project, user, system), it returns the
|
|
481
|
+
standard 6 core agents as a fallback.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
Dictionary with agent lists by tier. If no agents found in any tier,
|
|
485
|
+
returns core agents under 'fallback' key.
|
|
486
|
+
|
|
487
|
+
Example:
|
|
488
|
+
>>> loader = FrameworkAgentLoader()
|
|
489
|
+
>>> loader.initialize()
|
|
490
|
+
>>> agents = loader.get_agents_with_fallback()
|
|
491
|
+
>>> if 'fallback' in agents:
|
|
492
|
+
... print("Using core agents as fallback")
|
|
493
|
+
"""
|
|
494
|
+
available = self.get_available_agents()
|
|
495
|
+
|
|
496
|
+
# Check if any agents are found
|
|
497
|
+
total_agents = sum(len(agents) for agents in available.values())
|
|
498
|
+
|
|
499
|
+
if total_agents == 0:
|
|
500
|
+
logger.info(
|
|
501
|
+
"No agents found in configuration. "
|
|
502
|
+
"Auto-deploying standard 6 core agents."
|
|
503
|
+
)
|
|
504
|
+
return {"fallback": CORE_AGENTS.copy()}
|
|
505
|
+
|
|
506
|
+
return available
|
|
@@ -5,6 +5,7 @@ Services specifically for CLI command support and utilities.
|
|
|
5
5
|
|
|
6
6
|
from .agent_dependency_service import AgentDependencyService, IAgentDependencyService
|
|
7
7
|
from .agent_validation_service import AgentValidationService, IAgentValidationService
|
|
8
|
+
from .incremental_pause_manager import IncrementalPauseManager, PauseAction
|
|
8
9
|
from .startup_checker import IStartupChecker, StartupCheckerService, StartupWarning
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
@@ -13,6 +14,8 @@ __all__ = [
|
|
|
13
14
|
"IAgentDependencyService",
|
|
14
15
|
"IAgentValidationService",
|
|
15
16
|
"IStartupChecker",
|
|
17
|
+
"IncrementalPauseManager",
|
|
18
|
+
"PauseAction",
|
|
16
19
|
"StartupCheckerService",
|
|
17
20
|
"StartupWarning",
|
|
18
21
|
]
|