claude-mpm 5.4.41__py3-none-any.whl → 5.6.23__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
- claude_mpm/agents/CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md +413 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +109 -1925
- claude_mpm/agents/PM_INSTRUCTIONS.md +161 -298
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +2 -4
- claude_mpm/cli/commands/agents_reconcile.py +197 -0
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +216 -0
- claude_mpm/cli/commands/configure.py +620 -21
- claude_mpm/cli/commands/configure_agent_display.py +3 -1
- 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 +15 -8
- claude_mpm/cli/commands/profile.py +9 -10
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +182 -32
- claude_mpm/cli/executor.py +120 -16
- claude_mpm/cli/interactive/__init__.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +30 -50
- claude_mpm/cli/interactive/questionary_styles.py +65 -0
- claude_mpm/cli/interactive/skill_selector.py +481 -0
- claude_mpm/cli/parsers/base_parser.py +76 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/profile_parser.py +0 -1
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +2 -3
- claude_mpm/cli/startup.py +527 -506
- claude_mpm/cli/startup_display.py +74 -6
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +78 -0
- claude_mpm/commander/adapters/__init__.py +60 -0
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +288 -0
- claude_mpm/commander/adapters/claude_code.py +392 -0
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +121 -0
- claude_mpm/commander/api/errors.py +133 -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 +226 -0
- claude_mpm/commander/api/routes/work.py +296 -0
- claude_mpm/commander/api/schemas.py +186 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +146 -0
- claude_mpm/commander/chat/commands.py +96 -0
- claude_mpm/commander/chat/repl.py +310 -0
- claude_mpm/commander/config.py +51 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +603 -0
- claude_mpm/commander/env_loader.py +59 -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 +146 -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 +450 -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/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -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 +410 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +346 -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 +207 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +241 -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/config/skill_sources.py +16 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +154 -2
- claude_mpm/core/config.py +35 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +51 -3
- claude_mpm/core/interactive_session.py +12 -11
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +35 -11
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/optimized_startup.py +3 -1
- claude_mpm/core/output_style_manager.py +63 -18
- claude_mpm/core/shared/config_loader.py +3 -1
- claude_mpm/core/socketio_pool.py +13 -5
- claude_mpm/core/unified_config.py +54 -8
- claude_mpm/core/unified_paths.py +95 -90
- 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/1WZnGYqX.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/67pF3qNn.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/6RxdMKe4.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/8cZrfX0h.js +60 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/9a6T2nm-.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B443AUzu.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B8AwtY2H.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BF15LAsF.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BRcwIQNr.js +4 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uj46x2Wr.js → BSNlmTZj.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BV6nKitt.js +43 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BViJ8lZt.js +128 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BcQ-Q0FE.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bpyvgze_.js +30 -0
- 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/C3rbW_a-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C8WYN38h.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C9I8FlXH.js +61 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIQcWgO2.js +36 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIctN7YN.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CKrS_JZW.js +145 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CR6P9C4A.js +89 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRRR9MD_.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CSXtMOf0.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CT-sbxSk.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWm6DJsp.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CpqQ1Kzn.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D2nGpDRe.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9iCMida.js +267 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9ykgMoY.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DL2Ldur1.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DPfltzjH.js +165 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{N4qtv3Hx.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUliQN2b.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DXlhR01x.js +122 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_lyTybS.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DngoTTgh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DqkmHtDC.js +220 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DsDh8EYs.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DypDmXgd.js +139 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/IPYC-LnN.js +162 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JpevfAFt.js +68 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DjhvlsAc.js → NqQ1dWOy.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/R8CEIRAd.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Zxy7qc-l.js +64 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/qtd3IeO4.js +15 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ulBFON_C.js +65 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/wQVh1CoA.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.Dr7t0z2J.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.CAGBuiOw.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DG-KkbDf.js +1 -0
- 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 +11 -11
- claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
- 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/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +305 -87
- 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 +116 -8
- claude_mpm/hooks/claude_hooks/memory_integration.py +51 -31
- claude_mpm/hooks/claude_hooks/response_tracking.py +42 -59
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +39 -24
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +36 -103
- claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +73 -75
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/hooks/session_resume_hook.py +89 -1
- claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
- claude_mpm/init.py +215 -2
- claude_mpm/scripts/claude-hook-handler.sh +43 -16
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -1
- claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +37 -17
- claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
- claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
- claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +36 -8
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +50 -26
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/git_source_manager.py +21 -2
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/agents/sources/git_source_sync_service.py +116 -5
- claude_mpm/services/agents/startup_sync.py +5 -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 -3
- claude_mpm/services/monitor/server.py +106 -16
- claude_mpm/services/pm_skills_deployer.py +302 -94
- claude_mpm/services/profile_manager.py +10 -4
- claude_mpm/services/skills/git_skill_source_manager.py +192 -29
- claude_mpm/services/skills/selective_skill_deployer.py +211 -46
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +192 -70
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- 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-bug-reporting/SKILL.md +248 -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-delegation-patterns/SKILL.md +167 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-git-file-tracking/SKILL.md +113 -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-pr-workflow/SKILL.md +124 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -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/mpm-teaching-mode/SKILL.md +657 -0
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-ticketing-integration/SKILL.md +154 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-verification-protocols/SKILL.md +198 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/security-scanning.md +112 -0
- claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
- claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
- claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
- claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
- claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
- claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
- claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
- claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
- claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
- claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
- claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/skills/registry.py +295 -90
- claude_mpm/skills/skill_manager.py +29 -23
- claude_mpm/templates/.pre-commit-config.yaml +112 -0
- claude_mpm/utils/agent_dependency_loader.py +103 -4
- claude_mpm/utils/robust_installer.py +45 -24
- claude_mpm-5.6.23.dist-info/METADATA +393 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/RECORD +447 -149
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +0 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +0 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +0 -10
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm-5.4.41.dist-info/METADATA +0 -998
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.23.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"""Commander daemon for autonomous multi-project orchestration.
|
|
2
|
+
|
|
3
|
+
This module implements the main daemon process that coordinates multiple
|
|
4
|
+
projects, manages their lifecycles, and handles graceful shutdown.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import signal
|
|
10
|
+
from typing import Dict, Optional
|
|
11
|
+
|
|
12
|
+
import uvicorn
|
|
13
|
+
|
|
14
|
+
from .api.app import (
|
|
15
|
+
app,
|
|
16
|
+
)
|
|
17
|
+
from .config import DaemonConfig
|
|
18
|
+
from .core.block_manager import BlockManager
|
|
19
|
+
from .env_loader import load_env
|
|
20
|
+
from .events.manager import EventManager
|
|
21
|
+
from .inbox import Inbox
|
|
22
|
+
from .models.events import EventStatus
|
|
23
|
+
from .parsing.output_parser import OutputParser
|
|
24
|
+
from .persistence import EventStore, StateStore
|
|
25
|
+
from .project_session import ProjectSession, SessionState
|
|
26
|
+
from .registry import ProjectRegistry
|
|
27
|
+
from .runtime.monitor import RuntimeMonitor
|
|
28
|
+
from .tmux_orchestrator import TmuxOrchestrator
|
|
29
|
+
from .work.executor import WorkExecutor
|
|
30
|
+
from .work.queue import WorkQueue
|
|
31
|
+
from .workflow.event_handler import EventHandler
|
|
32
|
+
|
|
33
|
+
# Load environment variables at module import
|
|
34
|
+
load_env()
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CommanderDaemon:
|
|
40
|
+
"""Main daemon process for MPM Commander.
|
|
41
|
+
|
|
42
|
+
Orchestrates multiple projects, manages their sessions, handles events,
|
|
43
|
+
and provides REST API for external control.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
config: Daemon configuration
|
|
47
|
+
registry: Project registry
|
|
48
|
+
orchestrator: Tmux orchestrator
|
|
49
|
+
event_manager: Event manager
|
|
50
|
+
inbox: Event inbox
|
|
51
|
+
sessions: Active project sessions by project_id
|
|
52
|
+
work_queues: Work queues by project_id
|
|
53
|
+
work_executors: Work executors by project_id
|
|
54
|
+
block_manager: Block manager for automatic work blocking
|
|
55
|
+
runtime_monitor: Runtime monitor for output monitoring
|
|
56
|
+
event_handler: Event handler for blocking event workflow
|
|
57
|
+
state_store: StateStore for project/session persistence
|
|
58
|
+
event_store: EventStore for event queue persistence
|
|
59
|
+
running: Whether daemon is currently running
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
>>> config = DaemonConfig(port=8765)
|
|
63
|
+
>>> daemon = CommanderDaemon(config)
|
|
64
|
+
>>> await daemon.start()
|
|
65
|
+
>>> # Daemon runs until stopped
|
|
66
|
+
>>> await daemon.stop()
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(self, config: DaemonConfig):
|
|
70
|
+
"""Initialize Commander daemon.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
config: Daemon configuration
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValueError: If config is invalid
|
|
77
|
+
"""
|
|
78
|
+
if config is None:
|
|
79
|
+
raise ValueError("Config cannot be None")
|
|
80
|
+
|
|
81
|
+
self.config = config
|
|
82
|
+
self.registry = ProjectRegistry()
|
|
83
|
+
self.orchestrator = TmuxOrchestrator()
|
|
84
|
+
self.event_manager = EventManager()
|
|
85
|
+
self.inbox = Inbox(self.event_manager, self.registry)
|
|
86
|
+
self.sessions: Dict[str, ProjectSession] = {}
|
|
87
|
+
self.work_queues: Dict[str, WorkQueue] = {}
|
|
88
|
+
self.work_executors: Dict[str, WorkExecutor] = {}
|
|
89
|
+
self._running = False
|
|
90
|
+
self._server_task: Optional[asyncio.Task] = None
|
|
91
|
+
self._main_loop_task: Optional[asyncio.Task] = None
|
|
92
|
+
|
|
93
|
+
# Initialize persistence stores
|
|
94
|
+
self.state_store = StateStore(config.state_dir)
|
|
95
|
+
self.event_store = EventStore(config.state_dir)
|
|
96
|
+
|
|
97
|
+
# Initialize BlockManager with work queues and executors
|
|
98
|
+
self.block_manager = BlockManager(
|
|
99
|
+
event_manager=self.event_manager,
|
|
100
|
+
work_queues=self.work_queues,
|
|
101
|
+
work_executors=self.work_executors,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Initialize RuntimeMonitor with BlockManager
|
|
105
|
+
parser = OutputParser(self.event_manager)
|
|
106
|
+
self.runtime_monitor = RuntimeMonitor(
|
|
107
|
+
orchestrator=self.orchestrator,
|
|
108
|
+
parser=parser,
|
|
109
|
+
event_manager=self.event_manager,
|
|
110
|
+
poll_interval=config.poll_interval,
|
|
111
|
+
block_manager=self.block_manager,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Initialize EventHandler with BlockManager
|
|
115
|
+
self.event_handler = EventHandler(
|
|
116
|
+
inbox=self.inbox,
|
|
117
|
+
session_manager=self.sessions,
|
|
118
|
+
block_manager=self.block_manager,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Configure logging
|
|
122
|
+
logging.basicConfig(
|
|
123
|
+
level=getattr(logging, config.log_level.upper()),
|
|
124
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
125
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
logger.info(
|
|
129
|
+
f"Initialized CommanderDaemon (host={config.host}, "
|
|
130
|
+
f"port={config.port}, state_dir={config.state_dir})"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def is_running(self) -> bool:
|
|
135
|
+
"""Check if daemon is running.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
True if daemon main loop is active
|
|
139
|
+
"""
|
|
140
|
+
return self._running
|
|
141
|
+
|
|
142
|
+
async def start(self) -> None:
|
|
143
|
+
"""Start daemon and all subsystems.
|
|
144
|
+
|
|
145
|
+
Initializes:
|
|
146
|
+
- Load state from disk (projects, sessions, events)
|
|
147
|
+
- Signal handlers for graceful shutdown
|
|
148
|
+
- REST API server
|
|
149
|
+
- Main daemon loop
|
|
150
|
+
- Tmux session for project management
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
RuntimeError: If daemon already running
|
|
154
|
+
"""
|
|
155
|
+
if self._running:
|
|
156
|
+
raise RuntimeError("Daemon already running")
|
|
157
|
+
|
|
158
|
+
logger.info("Starting Commander daemon...")
|
|
159
|
+
self._running = True
|
|
160
|
+
|
|
161
|
+
# Load state from disk
|
|
162
|
+
await self._load_state()
|
|
163
|
+
|
|
164
|
+
# Set up signal handlers
|
|
165
|
+
self._setup_signal_handlers()
|
|
166
|
+
|
|
167
|
+
# Inject daemon instances into API app.state (BEFORE lifespan runs)
|
|
168
|
+
app.state.registry = self.registry
|
|
169
|
+
app.state.tmux = self.orchestrator
|
|
170
|
+
app.state.event_manager = self.event_manager
|
|
171
|
+
app.state.inbox = self.inbox
|
|
172
|
+
app.state.work_queues = self.work_queues
|
|
173
|
+
app.state.daemon_instance = self
|
|
174
|
+
app.state.session_manager = self.sessions
|
|
175
|
+
app.state.event_handler = self.event_handler
|
|
176
|
+
logger.info(f"Injected work_queues dict id: {id(self.work_queues)}")
|
|
177
|
+
|
|
178
|
+
# Start API server in background
|
|
179
|
+
logger.info(f"Starting API server on {self.config.host}:{self.config.port}")
|
|
180
|
+
config_uvicorn = uvicorn.Config(
|
|
181
|
+
app,
|
|
182
|
+
host=self.config.host,
|
|
183
|
+
port=self.config.port,
|
|
184
|
+
log_level=self.config.log_level.lower(),
|
|
185
|
+
)
|
|
186
|
+
server = uvicorn.Server(config_uvicorn)
|
|
187
|
+
self._server_task = asyncio.create_task(server.serve())
|
|
188
|
+
|
|
189
|
+
# Create tmux session for projects
|
|
190
|
+
if not self.orchestrator.session_exists():
|
|
191
|
+
self.orchestrator.create_session()
|
|
192
|
+
logger.info("Created tmux session for project management")
|
|
193
|
+
|
|
194
|
+
# Start main daemon loop
|
|
195
|
+
logger.info("Starting main daemon loop")
|
|
196
|
+
self._main_loop_task = asyncio.create_task(self.run())
|
|
197
|
+
|
|
198
|
+
logger.info("Commander daemon started successfully")
|
|
199
|
+
|
|
200
|
+
async def stop(self) -> None:
|
|
201
|
+
"""Graceful shutdown with cleanup.
|
|
202
|
+
|
|
203
|
+
Stops all active sessions, persists state, and shuts down API server.
|
|
204
|
+
"""
|
|
205
|
+
if not self._running:
|
|
206
|
+
logger.warning("Daemon not running, nothing to stop")
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
logger.info("Stopping Commander daemon...")
|
|
210
|
+
self._running = False
|
|
211
|
+
|
|
212
|
+
# Stop all project sessions
|
|
213
|
+
for project_id, session in list(self.sessions.items()):
|
|
214
|
+
try:
|
|
215
|
+
logger.info(f"Stopping session for project {project_id}")
|
|
216
|
+
await session.stop()
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.error(f"Error stopping session {project_id}: {e}")
|
|
219
|
+
|
|
220
|
+
# Clear BlockManager project mappings
|
|
221
|
+
for project_id in list(self.work_queues.keys()):
|
|
222
|
+
try:
|
|
223
|
+
removed = self.block_manager.clear_project_mappings(project_id)
|
|
224
|
+
logger.debug(
|
|
225
|
+
f"Cleared {removed} work mappings for project {project_id}"
|
|
226
|
+
)
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.error(f"Error clearing mappings for {project_id}: {e}")
|
|
229
|
+
|
|
230
|
+
# Cancel main loop task
|
|
231
|
+
if self._main_loop_task and not self._main_loop_task.done():
|
|
232
|
+
self._main_loop_task.cancel()
|
|
233
|
+
try:
|
|
234
|
+
await self._main_loop_task
|
|
235
|
+
except asyncio.CancelledError:
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
# Persist state to disk
|
|
239
|
+
await self._save_state()
|
|
240
|
+
|
|
241
|
+
# Stop API server
|
|
242
|
+
if self._server_task and not self._server_task.done():
|
|
243
|
+
self._server_task.cancel()
|
|
244
|
+
try:
|
|
245
|
+
await self._server_task
|
|
246
|
+
except asyncio.CancelledError:
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
logger.info("Commander daemon stopped")
|
|
250
|
+
|
|
251
|
+
async def run(self) -> None:
|
|
252
|
+
"""Main daemon loop.
|
|
253
|
+
|
|
254
|
+
Continuously polls for:
|
|
255
|
+
- Resolved events to resume paused sessions
|
|
256
|
+
- New work items to execute
|
|
257
|
+
- Project state changes
|
|
258
|
+
- Periodic state persistence
|
|
259
|
+
|
|
260
|
+
Runs until _running flag is set to False.
|
|
261
|
+
"""
|
|
262
|
+
logger.info("Main daemon loop starting")
|
|
263
|
+
|
|
264
|
+
# Track last save time for periodic persistence
|
|
265
|
+
last_save_time = asyncio.get_event_loop().time()
|
|
266
|
+
|
|
267
|
+
while self._running:
|
|
268
|
+
try:
|
|
269
|
+
logger.info(f"🔄 Main loop iteration (running={self._running})")
|
|
270
|
+
logger.info(
|
|
271
|
+
f"work_queues dict id: {id(self.work_queues)}, keys: {list(self.work_queues.keys())}"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Check for resolved events and resume sessions
|
|
275
|
+
await self._check_and_resume_sessions()
|
|
276
|
+
|
|
277
|
+
# Check each ProjectSession for runnable work
|
|
278
|
+
logger.info(
|
|
279
|
+
f"Checking for pending work across {len(self.work_queues)} queues"
|
|
280
|
+
)
|
|
281
|
+
await self._execute_pending_work()
|
|
282
|
+
|
|
283
|
+
# Periodic state persistence
|
|
284
|
+
current_time = asyncio.get_event_loop().time()
|
|
285
|
+
if current_time - last_save_time >= self.config.save_interval:
|
|
286
|
+
try:
|
|
287
|
+
await self._save_state()
|
|
288
|
+
last_save_time = current_time
|
|
289
|
+
except Exception as e:
|
|
290
|
+
logger.error(f"Error during periodic save: {e}", exc_info=True)
|
|
291
|
+
|
|
292
|
+
# Sleep to prevent tight loop
|
|
293
|
+
await asyncio.sleep(self.config.poll_interval)
|
|
294
|
+
|
|
295
|
+
except asyncio.CancelledError:
|
|
296
|
+
logger.info("Main loop cancelled")
|
|
297
|
+
break
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.error(f"Error in main loop: {e}", exc_info=True)
|
|
300
|
+
# Continue running despite errors
|
|
301
|
+
await asyncio.sleep(self.config.poll_interval)
|
|
302
|
+
|
|
303
|
+
logger.info("Main daemon loop stopped")
|
|
304
|
+
|
|
305
|
+
def _setup_signal_handlers(self) -> None:
|
|
306
|
+
"""Set up signal handlers for graceful shutdown.
|
|
307
|
+
|
|
308
|
+
Registers handlers for SIGINT and SIGTERM that trigger
|
|
309
|
+
daemon shutdown via asyncio event loop.
|
|
310
|
+
|
|
311
|
+
Note: Signal handlers can only be registered from the main thread.
|
|
312
|
+
If called from a background thread, registration is skipped.
|
|
313
|
+
"""
|
|
314
|
+
import threading
|
|
315
|
+
|
|
316
|
+
# Signal handlers can only be registered from the main thread
|
|
317
|
+
if threading.current_thread() is not threading.main_thread():
|
|
318
|
+
logger.info("Running in background thread - signal handlers skipped")
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
def handle_signal(signum: int, frame) -> None:
|
|
322
|
+
"""Handle shutdown signal.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
signum: Signal number
|
|
326
|
+
frame: Current stack frame
|
|
327
|
+
"""
|
|
328
|
+
sig_name = signal.Signals(signum).name
|
|
329
|
+
logger.info(f"Received {sig_name}, initiating graceful shutdown...")
|
|
330
|
+
|
|
331
|
+
# Schedule shutdown in event loop
|
|
332
|
+
if self._running:
|
|
333
|
+
asyncio.create_task(self.stop())
|
|
334
|
+
|
|
335
|
+
# Register signal handlers
|
|
336
|
+
signal.signal(signal.SIGINT, handle_signal)
|
|
337
|
+
signal.signal(signal.SIGTERM, handle_signal)
|
|
338
|
+
|
|
339
|
+
logger.debug("Signal handlers configured (SIGINT, SIGTERM)")
|
|
340
|
+
|
|
341
|
+
def get_or_create_session(self, project_id: str) -> ProjectSession:
|
|
342
|
+
"""Get existing session or create new one for project.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
project_id: Project identifier
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
ProjectSession for the project
|
|
349
|
+
|
|
350
|
+
Raises:
|
|
351
|
+
ValueError: If project not found in registry
|
|
352
|
+
"""
|
|
353
|
+
if project_id in self.sessions:
|
|
354
|
+
return self.sessions[project_id]
|
|
355
|
+
|
|
356
|
+
project = self.registry.get(project_id)
|
|
357
|
+
if project is None:
|
|
358
|
+
raise ValueError(f"Project not found: {project_id}")
|
|
359
|
+
|
|
360
|
+
# Create work queue for project if not exists
|
|
361
|
+
if project_id not in self.work_queues:
|
|
362
|
+
self.work_queues[project_id] = WorkQueue(project_id)
|
|
363
|
+
logger.debug(f"Created work queue for project {project_id}")
|
|
364
|
+
|
|
365
|
+
# Create work executor for project if not exists
|
|
366
|
+
if project_id not in self.work_executors:
|
|
367
|
+
from .runtime.executor import RuntimeExecutor
|
|
368
|
+
|
|
369
|
+
runtime_executor = RuntimeExecutor(self.orchestrator)
|
|
370
|
+
self.work_executors[project_id] = WorkExecutor(
|
|
371
|
+
runtime=runtime_executor, queue=self.work_queues[project_id]
|
|
372
|
+
)
|
|
373
|
+
logger.debug(f"Created work executor for project {project_id}")
|
|
374
|
+
|
|
375
|
+
session = ProjectSession(
|
|
376
|
+
project=project,
|
|
377
|
+
orchestrator=self.orchestrator,
|
|
378
|
+
monitor=self.runtime_monitor,
|
|
379
|
+
)
|
|
380
|
+
self.sessions[project_id] = session
|
|
381
|
+
|
|
382
|
+
logger.info(f"Created new session for project {project_id}")
|
|
383
|
+
return session
|
|
384
|
+
|
|
385
|
+
async def _load_state(self) -> None:
|
|
386
|
+
"""Load state from disk (projects, sessions, events).
|
|
387
|
+
|
|
388
|
+
Called on daemon startup to restore previous state.
|
|
389
|
+
Handles missing or corrupt files gracefully.
|
|
390
|
+
"""
|
|
391
|
+
logger.info("Loading state from disk...")
|
|
392
|
+
|
|
393
|
+
# Load projects
|
|
394
|
+
try:
|
|
395
|
+
projects = await self.state_store.load_projects()
|
|
396
|
+
for project in projects:
|
|
397
|
+
# Re-register projects (bypassing validation for already-registered paths)
|
|
398
|
+
self.registry._projects[project.id] = project
|
|
399
|
+
self.registry._path_index[project.path] = project.id
|
|
400
|
+
logger.info(f"Restored {len(projects)} projects")
|
|
401
|
+
except Exception as e:
|
|
402
|
+
logger.error(f"Failed to load projects: {e}", exc_info=True)
|
|
403
|
+
|
|
404
|
+
# Load sessions
|
|
405
|
+
try:
|
|
406
|
+
session_states = await self.state_store.load_sessions()
|
|
407
|
+
for project_id, state_dict in session_states.items():
|
|
408
|
+
# Only restore sessions for projects we have
|
|
409
|
+
if project_id in self.registry._projects:
|
|
410
|
+
project = self.registry.get(project_id)
|
|
411
|
+
session = ProjectSession(project, self.orchestrator)
|
|
412
|
+
|
|
413
|
+
# Restore session state (but don't restart runtime - manual resume)
|
|
414
|
+
try:
|
|
415
|
+
session._state = SessionState(state_dict.get("state", "idle"))
|
|
416
|
+
session.active_pane = state_dict.get("pane_target")
|
|
417
|
+
session.pause_reason = state_dict.get("paused_event_id")
|
|
418
|
+
self.sessions[project_id] = session
|
|
419
|
+
except Exception as e:
|
|
420
|
+
logger.warning(
|
|
421
|
+
f"Failed to restore session for {project_id}: {e}"
|
|
422
|
+
)
|
|
423
|
+
logger.info(f"Restored {len(self.sessions)} sessions")
|
|
424
|
+
except Exception as e:
|
|
425
|
+
logger.error(f"Failed to load sessions: {e}", exc_info=True)
|
|
426
|
+
|
|
427
|
+
# Load events
|
|
428
|
+
try:
|
|
429
|
+
events = await self.event_store.load_events()
|
|
430
|
+
for event in events:
|
|
431
|
+
self.event_manager.add_event(event)
|
|
432
|
+
logger.info(f"Restored {len(events)} events")
|
|
433
|
+
except Exception as e:
|
|
434
|
+
logger.error(f"Failed to load events: {e}", exc_info=True)
|
|
435
|
+
|
|
436
|
+
logger.info("State loading complete")
|
|
437
|
+
|
|
438
|
+
async def _save_state(self) -> None:
|
|
439
|
+
"""Save state to disk (projects, sessions, events).
|
|
440
|
+
|
|
441
|
+
Called on daemon shutdown and periodically during runtime.
|
|
442
|
+
Uses atomic writes to prevent corruption.
|
|
443
|
+
"""
|
|
444
|
+
logger.debug("Saving state to disk...")
|
|
445
|
+
|
|
446
|
+
try:
|
|
447
|
+
# Save projects
|
|
448
|
+
await self.state_store.save_projects(self.registry)
|
|
449
|
+
|
|
450
|
+
# Save sessions
|
|
451
|
+
await self.state_store.save_sessions(self.sessions)
|
|
452
|
+
|
|
453
|
+
# Save events
|
|
454
|
+
await self.event_store.save_events(self.inbox)
|
|
455
|
+
|
|
456
|
+
logger.debug("State saved successfully")
|
|
457
|
+
except Exception as e:
|
|
458
|
+
logger.error(f"Failed to save state: {e}", exc_info=True)
|
|
459
|
+
|
|
460
|
+
async def _check_and_resume_sessions(self) -> None:
|
|
461
|
+
"""Check for resolved events and resume paused sessions.
|
|
462
|
+
|
|
463
|
+
Iterates through all paused sessions, checks if their blocking events
|
|
464
|
+
have been resolved, and resumes execution if ready.
|
|
465
|
+
"""
|
|
466
|
+
for project_id, session in list(self.sessions.items()):
|
|
467
|
+
# Skip non-paused sessions
|
|
468
|
+
if session.state != SessionState.PAUSED:
|
|
469
|
+
continue
|
|
470
|
+
|
|
471
|
+
# Check if pause reason (event ID) is resolved
|
|
472
|
+
if not session.pause_reason:
|
|
473
|
+
logger.warning(f"Session {project_id} paused with no reason, resuming")
|
|
474
|
+
await session.resume()
|
|
475
|
+
continue
|
|
476
|
+
|
|
477
|
+
# Check if event is resolved
|
|
478
|
+
event = self.event_manager.get(session.pause_reason)
|
|
479
|
+
if event and event.status == EventStatus.RESOLVED:
|
|
480
|
+
logger.info(
|
|
481
|
+
f"Event {event.id} resolved, resuming session for {project_id}"
|
|
482
|
+
)
|
|
483
|
+
await session.resume()
|
|
484
|
+
|
|
485
|
+
# Unblock any work items that were blocked by this event
|
|
486
|
+
if project_id in self.work_executors:
|
|
487
|
+
executor = self.work_executors[project_id]
|
|
488
|
+
queue = self.work_queues[project_id]
|
|
489
|
+
|
|
490
|
+
# Find work items blocked by this event
|
|
491
|
+
blocked_items = [
|
|
492
|
+
item
|
|
493
|
+
for item in queue.list()
|
|
494
|
+
if item.state.value == "blocked"
|
|
495
|
+
and item.metadata.get("block_reason") == event.id
|
|
496
|
+
]
|
|
497
|
+
|
|
498
|
+
for item in blocked_items:
|
|
499
|
+
await executor.handle_unblock(item.id)
|
|
500
|
+
logger.info(f"Unblocked work item {item.id}")
|
|
501
|
+
|
|
502
|
+
async def _execute_pending_work(self) -> None:
|
|
503
|
+
"""Execute pending work for all ready sessions.
|
|
504
|
+
|
|
505
|
+
Scans all work queues for pending work. For projects with work but no session,
|
|
506
|
+
auto-creates a session. Then executes the next available work item via WorkExecutor.
|
|
507
|
+
"""
|
|
508
|
+
# First pass: Auto-create and start sessions for projects with pending work
|
|
509
|
+
for project_id, queue in list(self.work_queues.items()):
|
|
510
|
+
logger.info(
|
|
511
|
+
f"Checking queue for {project_id}: pending={queue.pending_count}"
|
|
512
|
+
)
|
|
513
|
+
# Skip if no pending work
|
|
514
|
+
if queue.pending_count == 0:
|
|
515
|
+
continue
|
|
516
|
+
|
|
517
|
+
# Auto-create session if needed
|
|
518
|
+
if project_id not in self.sessions:
|
|
519
|
+
try:
|
|
520
|
+
logger.info(
|
|
521
|
+
f"Auto-creating session for project {project_id} with pending work"
|
|
522
|
+
)
|
|
523
|
+
session = self.get_or_create_session(project_id)
|
|
524
|
+
|
|
525
|
+
# Start the session so it's ready for work
|
|
526
|
+
if session.state.value == "idle":
|
|
527
|
+
logger.info(f"Auto-starting session for {project_id}")
|
|
528
|
+
await session.start()
|
|
529
|
+
except Exception as e:
|
|
530
|
+
logger.error(
|
|
531
|
+
f"Failed to auto-create/start session for {project_id}: {e}",
|
|
532
|
+
exc_info=True,
|
|
533
|
+
)
|
|
534
|
+
continue
|
|
535
|
+
|
|
536
|
+
# Second pass: Execute work for ready sessions
|
|
537
|
+
for project_id, session in list(self.sessions.items()):
|
|
538
|
+
# Skip sessions that aren't ready for work
|
|
539
|
+
if not session.is_ready():
|
|
540
|
+
continue
|
|
541
|
+
|
|
542
|
+
# Skip if no work queue exists
|
|
543
|
+
if project_id not in self.work_queues:
|
|
544
|
+
continue
|
|
545
|
+
|
|
546
|
+
# Get work executor for project
|
|
547
|
+
executor = self.work_executors.get(project_id)
|
|
548
|
+
if not executor:
|
|
549
|
+
logger.warning(
|
|
550
|
+
f"No work executor found for project {project_id}, skipping"
|
|
551
|
+
)
|
|
552
|
+
continue
|
|
553
|
+
|
|
554
|
+
# Check if there's work available
|
|
555
|
+
queue = self.work_queues[project_id]
|
|
556
|
+
if queue.pending_count == 0:
|
|
557
|
+
continue
|
|
558
|
+
|
|
559
|
+
# Try to execute next work item
|
|
560
|
+
try:
|
|
561
|
+
# Pass the session's active pane for execution
|
|
562
|
+
executed = await executor.execute_next(pane_target=session.active_pane)
|
|
563
|
+
if executed:
|
|
564
|
+
logger.info(f"Started work execution for project {project_id}")
|
|
565
|
+
except Exception as e:
|
|
566
|
+
logger.error(
|
|
567
|
+
f"Error executing work for project {project_id}: {e}",
|
|
568
|
+
exc_info=True,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
async def main(config: Optional[DaemonConfig] = None) -> None:
|
|
573
|
+
"""Main entry point for running the daemon.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
config: Optional daemon configuration (uses defaults if None)
|
|
577
|
+
|
|
578
|
+
Example:
|
|
579
|
+
>>> import asyncio
|
|
580
|
+
>>> asyncio.run(main())
|
|
581
|
+
"""
|
|
582
|
+
if config is None:
|
|
583
|
+
config = DaemonConfig()
|
|
584
|
+
|
|
585
|
+
daemon = CommanderDaemon(config)
|
|
586
|
+
|
|
587
|
+
try:
|
|
588
|
+
await daemon.start()
|
|
589
|
+
|
|
590
|
+
# Keep daemon running until stopped
|
|
591
|
+
while daemon.is_running:
|
|
592
|
+
await asyncio.sleep(1)
|
|
593
|
+
|
|
594
|
+
except KeyboardInterrupt:
|
|
595
|
+
logger.info("Received KeyboardInterrupt")
|
|
596
|
+
except Exception as e:
|
|
597
|
+
logger.error(f"Daemon error: {e}", exc_info=True)
|
|
598
|
+
finally:
|
|
599
|
+
await daemon.stop()
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
if __name__ == "__main__":
|
|
603
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Environment variable loader for Commander.
|
|
2
|
+
|
|
3
|
+
This module handles automatic loading of .env and .env.local files
|
|
4
|
+
at Commander startup. Environment files are loaded with the following precedence:
|
|
5
|
+
1. Existing environment variables (not overridden)
|
|
6
|
+
2. .env.local (local overrides)
|
|
7
|
+
3. .env (defaults)
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
>>> from claude_mpm.commander.env_loader import load_env
|
|
11
|
+
>>> load_env()
|
|
12
|
+
# Automatically loads .env.local and .env from project root
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from dotenv import load_dotenv
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_env() -> None:
|
|
24
|
+
"""Load environment variables from .env and .env.local files.
|
|
25
|
+
|
|
26
|
+
Searches for .env and .env.local in the project root directory
|
|
27
|
+
(parent of src/claude_mpm). Files are loaded with override=False,
|
|
28
|
+
meaning existing environment variables take precedence.
|
|
29
|
+
|
|
30
|
+
Precedence (highest to lowest):
|
|
31
|
+
1. Existing environment variables
|
|
32
|
+
2. .env.local
|
|
33
|
+
3. .env
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> load_env()
|
|
37
|
+
# Loads .env.local and .env if they exist
|
|
38
|
+
"""
|
|
39
|
+
# Find project root (parent of src/claude_mpm)
|
|
40
|
+
# Current file: src/claude_mpm/commander/env_loader.py
|
|
41
|
+
# Project root: ../../../ (3 levels up)
|
|
42
|
+
current_file = Path(__file__)
|
|
43
|
+
project_root = current_file.parent.parent.parent.parent
|
|
44
|
+
|
|
45
|
+
# Try loading .env.local first (higher priority)
|
|
46
|
+
env_local = project_root / ".env.local"
|
|
47
|
+
if env_local.exists():
|
|
48
|
+
load_dotenv(env_local, override=False)
|
|
49
|
+
logger.debug(f"Loaded environment from {env_local}")
|
|
50
|
+
|
|
51
|
+
# Then load .env (lower priority)
|
|
52
|
+
env_file = project_root / ".env"
|
|
53
|
+
if env_file.exists():
|
|
54
|
+
load_dotenv(env_file, override=False)
|
|
55
|
+
logger.debug(f"Loaded environment from {env_file}")
|
|
56
|
+
|
|
57
|
+
# Log if neither file exists
|
|
58
|
+
if not env_local.exists() and not env_file.exists():
|
|
59
|
+
logger.debug("No .env or .env.local files found in project root")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Event management for MPM Commander.
|
|
2
|
+
|
|
3
|
+
Exports:
|
|
4
|
+
- Event model and enums from models.events
|
|
5
|
+
- EventManager for event lifecycle management
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ..models.events import (
|
|
9
|
+
BLOCKING_EVENTS,
|
|
10
|
+
DEFAULT_PRIORITIES,
|
|
11
|
+
Event,
|
|
12
|
+
EventPriority,
|
|
13
|
+
EventStatus,
|
|
14
|
+
EventType,
|
|
15
|
+
)
|
|
16
|
+
from .manager import EventManager
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"BLOCKING_EVENTS",
|
|
20
|
+
"DEFAULT_PRIORITIES",
|
|
21
|
+
"Event",
|
|
22
|
+
"EventManager",
|
|
23
|
+
"EventPriority",
|
|
24
|
+
"EventStatus",
|
|
25
|
+
"EventType",
|
|
26
|
+
]
|