claude-mpm 5.4.41__py3-none-any.whl → 5.6.72__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/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- 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/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +15 -8
- claude_mpm/cli/commands/oauth.py +481 -0
- 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 +129 -16
- claude_mpm/cli/helpers.py +1 -1
- 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 +89 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -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 +662 -524
- claude_mpm/cli/startup_display.py +76 -7
- 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 +149 -0
- claude_mpm/commander/chat/commands.py +122 -0
- claude_mpm/commander/chat/repl.py +1821 -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 +392 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +233 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +57 -0
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -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 +865 -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 +127 -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 +403 -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 +362 -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 +6 -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 +53 -4
- claude_mpm/core/interactive_session.py +12 -11
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +39 -13
- 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 +66 -18
- claude_mpm/core/shared/config_loader.py +3 -1
- claude_mpm/core/socketio_pool.py +47 -15
- 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 +466 -136
- claude_mpm/hooks/claude_hooks/hook_handler.py +204 -104
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +291 -59
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/container.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/protocols.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 +41 -26
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
- claude_mpm/hooks/claude_hooks/services/container.py +326 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
- 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_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- claude_mpm/init.py +224 -4
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +46 -19
- 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/command_deployment_service.py +44 -26
- 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/hook_installer_service.py +77 -8
- 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/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -3
- claude_mpm/services/monitor/server.py +111 -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.72.dist-info/METADATA +416 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/RECORD +477 -159
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/WHEEL +1 -1
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/entry_points.txt +2 -0
- 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.72.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.41.dist-info → claude_mpm-5.6.72.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
"""Manages running Claude Code/MPM instances."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Optional
|
|
9
|
+
|
|
10
|
+
from claude_mpm.commander.adapters import (
|
|
11
|
+
AdapterResponse,
|
|
12
|
+
ClaudeCodeAdapter,
|
|
13
|
+
ClaudeCodeCommunicationAdapter,
|
|
14
|
+
)
|
|
15
|
+
from claude_mpm.commander.frameworks.base import (
|
|
16
|
+
BaseFramework,
|
|
17
|
+
InstanceInfo,
|
|
18
|
+
RegisteredInstance,
|
|
19
|
+
)
|
|
20
|
+
from claude_mpm.commander.frameworks.claude_code import ClaudeCodeFramework
|
|
21
|
+
from claude_mpm.commander.frameworks.mpm import MPMFramework
|
|
22
|
+
from claude_mpm.commander.git.worktree_manager import WorktreeManager
|
|
23
|
+
from claude_mpm.commander.models.events import EventType
|
|
24
|
+
from claude_mpm.commander.tmux_orchestrator import TmuxOrchestrator
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from claude_mpm.commander.events.manager import EventManager
|
|
28
|
+
from claude_mpm.commander.persistence.state_store import StateStore
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class InstanceNotFoundError(Exception):
|
|
34
|
+
"""Raised when an instance is not found."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, name: str):
|
|
37
|
+
super().__init__(f"Instance not found: {name}")
|
|
38
|
+
self.name = name
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FrameworkNotFoundError(Exception):
|
|
42
|
+
"""Raised when a framework is not found or not available."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, framework: str):
|
|
45
|
+
super().__init__(f"Framework not found or not available: {framework}")
|
|
46
|
+
self.framework = framework
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class InstanceAlreadyExistsError(Exception):
|
|
50
|
+
"""Raised when trying to start an instance that already exists."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, name: str):
|
|
53
|
+
super().__init__(f"Instance already exists: {name}")
|
|
54
|
+
self.name = name
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class InstanceManager:
|
|
58
|
+
"""Manages lifecycle of Claude instances.
|
|
59
|
+
|
|
60
|
+
The InstanceManager coordinates framework selection, instance startup,
|
|
61
|
+
and tracking of running instances across the TmuxOrchestrator.
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
orchestrator: TmuxOrchestrator for managing tmux sessions/panes
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> orchestrator = TmuxOrchestrator()
|
|
68
|
+
>>> manager = InstanceManager(orchestrator)
|
|
69
|
+
>>> frameworks = manager.list_frameworks()
|
|
70
|
+
>>> print(frameworks)
|
|
71
|
+
['cc', 'mpm']
|
|
72
|
+
>>> instance = await manager.start_instance(
|
|
73
|
+
... "myapp",
|
|
74
|
+
... Path("/Users/user/myapp"),
|
|
75
|
+
... framework="cc"
|
|
76
|
+
... )
|
|
77
|
+
>>> print(instance.name, instance.framework)
|
|
78
|
+
myapp cc
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, orchestrator: TmuxOrchestrator):
|
|
82
|
+
"""Initialize the instance manager.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
orchestrator: TmuxOrchestrator for managing tmux sessions/panes
|
|
86
|
+
"""
|
|
87
|
+
self.orchestrator = orchestrator
|
|
88
|
+
self._instances: dict[str, InstanceInfo] = {}
|
|
89
|
+
self._frameworks = self._load_frameworks()
|
|
90
|
+
self._adapters: dict[str, ClaudeCodeCommunicationAdapter] = {}
|
|
91
|
+
self._event_manager: Optional[EventManager] = None
|
|
92
|
+
self._state_store: Optional[StateStore] = None
|
|
93
|
+
|
|
94
|
+
def set_event_manager(self, event_manager: "EventManager") -> None:
|
|
95
|
+
"""Set the event manager for emitting instance events.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
event_manager: EventManager instance for event emission
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
>>> manager = InstanceManager(orchestrator)
|
|
102
|
+
>>> manager.set_event_manager(event_manager)
|
|
103
|
+
"""
|
|
104
|
+
self._event_manager = event_manager
|
|
105
|
+
|
|
106
|
+
def set_state_store(self, state_store: "StateStore") -> None:
|
|
107
|
+
"""Set state store for persistence.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
state_store: StateStore instance for persisting registered instances
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
>>> manager = InstanceManager(orchestrator)
|
|
114
|
+
>>> manager.set_state_store(state_store)
|
|
115
|
+
"""
|
|
116
|
+
self._state_store = state_store
|
|
117
|
+
|
|
118
|
+
def _load_frameworks(self) -> dict[str, BaseFramework]:
|
|
119
|
+
"""Load available frameworks.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Dict mapping framework name to framework instance
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> manager = InstanceManager(orchestrator)
|
|
126
|
+
>>> frameworks = manager._load_frameworks()
|
|
127
|
+
>>> print(frameworks.keys())
|
|
128
|
+
dict_keys(['cc', 'mpm'])
|
|
129
|
+
"""
|
|
130
|
+
frameworks = {}
|
|
131
|
+
for framework_class in [ClaudeCodeFramework, MPMFramework]:
|
|
132
|
+
framework = framework_class()
|
|
133
|
+
if framework.is_available():
|
|
134
|
+
frameworks[framework.name] = framework
|
|
135
|
+
logger.info(
|
|
136
|
+
f"Loaded framework: {framework.name} ({framework.display_name})"
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
logger.warning(
|
|
140
|
+
f"Framework not available: {framework.name} ({framework.display_name})"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return frameworks
|
|
144
|
+
|
|
145
|
+
def list_frameworks(self) -> list[str]:
|
|
146
|
+
"""List available framework names.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of framework names (e.g., ["cc", "mpm"])
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> manager = InstanceManager(orchestrator)
|
|
153
|
+
>>> frameworks = manager.list_frameworks()
|
|
154
|
+
>>> print(frameworks)
|
|
155
|
+
['cc', 'mpm']
|
|
156
|
+
"""
|
|
157
|
+
return list(self._frameworks.keys())
|
|
158
|
+
|
|
159
|
+
async def start_instance(
|
|
160
|
+
self, name: str, project_path: Path, framework: str = "cc"
|
|
161
|
+
) -> InstanceInfo:
|
|
162
|
+
"""Start a new instance.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
name: Instance name (e.g., "myapp")
|
|
166
|
+
project_path: Path to project directory
|
|
167
|
+
framework: Framework to use ("cc" or "mpm")
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
InstanceInfo with tmux session details
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
FrameworkNotFoundError: If framework is not available
|
|
174
|
+
InstanceAlreadyExistsError: If instance already exists
|
|
175
|
+
|
|
176
|
+
Example:
|
|
177
|
+
>>> manager = InstanceManager(orchestrator)
|
|
178
|
+
>>> instance = await manager.start_instance(
|
|
179
|
+
... "myapp",
|
|
180
|
+
... Path("/Users/user/myapp"),
|
|
181
|
+
... framework="cc"
|
|
182
|
+
... )
|
|
183
|
+
>>> print(instance.name, instance.framework)
|
|
184
|
+
myapp cc
|
|
185
|
+
"""
|
|
186
|
+
# Check if instance already exists
|
|
187
|
+
if name in self._instances:
|
|
188
|
+
raise InstanceAlreadyExistsError(name)
|
|
189
|
+
|
|
190
|
+
# Get framework
|
|
191
|
+
if framework not in self._frameworks:
|
|
192
|
+
raise FrameworkNotFoundError(framework)
|
|
193
|
+
|
|
194
|
+
framework_obj = self._frameworks[framework]
|
|
195
|
+
|
|
196
|
+
# Get git info
|
|
197
|
+
git_branch, git_status = framework_obj.get_git_info(project_path)
|
|
198
|
+
|
|
199
|
+
# Ensure tmux session exists
|
|
200
|
+
self.orchestrator.create_session()
|
|
201
|
+
|
|
202
|
+
# Create pane
|
|
203
|
+
pane_target = self.orchestrator.create_pane(name, str(project_path))
|
|
204
|
+
|
|
205
|
+
# Start framework in pane
|
|
206
|
+
startup_cmd = framework_obj.get_startup_command(project_path)
|
|
207
|
+
self.orchestrator.send_keys(pane_target, startup_cmd)
|
|
208
|
+
|
|
209
|
+
# Create communication adapter for the instance (only for Claude Code for now)
|
|
210
|
+
# Do this BEFORE creating InstanceInfo so we can set connected=True
|
|
211
|
+
has_adapter = False
|
|
212
|
+
if framework == "cc":
|
|
213
|
+
runtime_adapter = ClaudeCodeAdapter()
|
|
214
|
+
comm_adapter = ClaudeCodeCommunicationAdapter(
|
|
215
|
+
orchestrator=self.orchestrator,
|
|
216
|
+
pane_target=pane_target,
|
|
217
|
+
runtime_adapter=runtime_adapter,
|
|
218
|
+
)
|
|
219
|
+
self._adapters[name] = comm_adapter
|
|
220
|
+
has_adapter = True
|
|
221
|
+
logger.debug(f"Created communication adapter for instance '{name}'")
|
|
222
|
+
|
|
223
|
+
# Create instance info
|
|
224
|
+
instance = InstanceInfo(
|
|
225
|
+
name=name,
|
|
226
|
+
project_path=project_path,
|
|
227
|
+
framework=framework,
|
|
228
|
+
tmux_session=self.orchestrator.session_name,
|
|
229
|
+
pane_target=pane_target,
|
|
230
|
+
git_branch=git_branch,
|
|
231
|
+
git_status=git_status,
|
|
232
|
+
connected=has_adapter,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Track instance
|
|
236
|
+
self._instances[name] = instance
|
|
237
|
+
|
|
238
|
+
logger.info(
|
|
239
|
+
f"Started instance '{name}' with framework '{framework}' at {project_path}"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Emit starting event and start background ready detection
|
|
243
|
+
if self._event_manager:
|
|
244
|
+
event = self._event_manager.create(
|
|
245
|
+
project_id=name,
|
|
246
|
+
event_type=EventType.INSTANCE_STARTING,
|
|
247
|
+
title=f"Starting instance '{name}'",
|
|
248
|
+
content=f"Instance {name} is starting at {project_path}",
|
|
249
|
+
context={"instance_name": name, "working_dir": str(project_path)},
|
|
250
|
+
)
|
|
251
|
+
await self._event_manager.emit(event)
|
|
252
|
+
|
|
253
|
+
# Start background ready detection (always, not just with event_manager)
|
|
254
|
+
asyncio.create_task(self._detect_ready(name, instance))
|
|
255
|
+
|
|
256
|
+
return instance
|
|
257
|
+
|
|
258
|
+
async def _detect_ready(
|
|
259
|
+
self, name: str, instance_info: InstanceInfo, timeout: int = 30
|
|
260
|
+
) -> None:
|
|
261
|
+
"""Background task to detect when instance is ready.
|
|
262
|
+
|
|
263
|
+
Monitors the pane output for patterns indicating the instance
|
|
264
|
+
is ready to accept commands.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
name: Instance name
|
|
268
|
+
instance_info: InstanceInfo with pane details
|
|
269
|
+
timeout: Maximum seconds to wait for ready state
|
|
270
|
+
|
|
271
|
+
Example:
|
|
272
|
+
>>> # Called internally by start_instance
|
|
273
|
+
>>> asyncio.create_task(self._detect_ready(name, instance))
|
|
274
|
+
"""
|
|
275
|
+
# TIER 1: Startup markers (most reliable, appear at T+0-2s)
|
|
276
|
+
startup_patterns = [
|
|
277
|
+
r"Claude Code v[\d.]+", # Version marker
|
|
278
|
+
r"Claude Code", # Product identification
|
|
279
|
+
r"Opus 4\.5", # Model identifier
|
|
280
|
+
r"Claude Max|Claude Pro", # Tier indicator
|
|
281
|
+
]
|
|
282
|
+
|
|
283
|
+
# TIER 2: Ready state indicators (appear at T+5-10s)
|
|
284
|
+
ready_patterns = [
|
|
285
|
+
r">\s*$", # CLI prompt (end of line)
|
|
286
|
+
r"^>\s*", # CLI prompt (start of line)
|
|
287
|
+
r"What can I.*help", # Specific greeting
|
|
288
|
+
r"How can I.*assist", # Specific greeting
|
|
289
|
+
r"(Human|User):\s*$", # Anthropic format
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
# TIER 3: Secondary indicators (confirmation)
|
|
293
|
+
secondary_patterns = [
|
|
294
|
+
r"Ready for input", # Explicit ready
|
|
295
|
+
r"Tips for getting", # Claude CLI tips
|
|
296
|
+
r"Use /help", # Help hint
|
|
297
|
+
r"claudeMd", # CLAUDE.md loaded
|
|
298
|
+
r"Agent Memory", # Agent initialized
|
|
299
|
+
r"PM_INSTRUCTIONS", # MPM initialized
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
# Combine all patterns (TIER 1 most reliable)
|
|
303
|
+
all_patterns = startup_patterns + ready_patterns + secondary_patterns
|
|
304
|
+
|
|
305
|
+
start_time = asyncio.get_event_loop().time()
|
|
306
|
+
check_count = 0
|
|
307
|
+
|
|
308
|
+
while asyncio.get_event_loop().time() - start_time < timeout:
|
|
309
|
+
elapsed = asyncio.get_event_loop().time() - start_time
|
|
310
|
+
check_count += 1
|
|
311
|
+
|
|
312
|
+
# Progress logging at key milestones
|
|
313
|
+
if elapsed > 10 and check_count == 11:
|
|
314
|
+
logger.info(
|
|
315
|
+
f"Ready detection for '{name}' still waiting after 10s... "
|
|
316
|
+
f"(Claude Code startup may be slow)"
|
|
317
|
+
)
|
|
318
|
+
elif elapsed > 20 and check_count == 21:
|
|
319
|
+
logger.info(
|
|
320
|
+
f"Ready detection for '{name}' still waiting after 20s... "
|
|
321
|
+
f"(system may be overloaded)"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
await asyncio.sleep(1)
|
|
325
|
+
try:
|
|
326
|
+
# IMPROVED: Increased buffer from 50 to 100 lines
|
|
327
|
+
output = self.orchestrator.capture_output(
|
|
328
|
+
instance_info.pane_target, lines=100
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
if output:
|
|
332
|
+
# Check all patterns (added re.IGNORECASE flag)
|
|
333
|
+
# IMPROVED: Use MULTILINE | IGNORECASE flags
|
|
334
|
+
for pattern in all_patterns:
|
|
335
|
+
if re.search(pattern, output, re.MULTILINE | re.IGNORECASE):
|
|
336
|
+
# Instance is ready!
|
|
337
|
+
if name in self._instances:
|
|
338
|
+
self._instances[name].ready = True
|
|
339
|
+
|
|
340
|
+
# Emit ready event
|
|
341
|
+
if self._event_manager:
|
|
342
|
+
event = self._event_manager.create(
|
|
343
|
+
project_id=name,
|
|
344
|
+
event_type=EventType.INSTANCE_READY,
|
|
345
|
+
title=f"Instance '{name}' ready",
|
|
346
|
+
content=(
|
|
347
|
+
f"Instance {name} is ready for commands "
|
|
348
|
+
f"(detected in {elapsed:.1f}s)"
|
|
349
|
+
),
|
|
350
|
+
context={
|
|
351
|
+
"instance_name": name,
|
|
352
|
+
"detection_time": elapsed,
|
|
353
|
+
"pattern_matched": pattern,
|
|
354
|
+
},
|
|
355
|
+
)
|
|
356
|
+
await self._event_manager.emit(event)
|
|
357
|
+
|
|
358
|
+
logger.info(
|
|
359
|
+
f"Instance '{name}' is ready "
|
|
360
|
+
f"(detected in {elapsed:.1f}s via pattern: {pattern})"
|
|
361
|
+
)
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
except Exception as e:
|
|
365
|
+
logger.debug(
|
|
366
|
+
f"Error checking ready state for '{name}': {e}. "
|
|
367
|
+
f"Elapsed: {elapsed:.1f}s"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Timeout - mark as ready anyway since instance might still work
|
|
371
|
+
logger.warning(
|
|
372
|
+
f"Instance '{name}' ready detection timed out after {timeout}s. "
|
|
373
|
+
f"Instance may still be functional. Check logs for issues."
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
if name in self._instances:
|
|
377
|
+
self._instances[name].ready = True
|
|
378
|
+
|
|
379
|
+
if self._event_manager:
|
|
380
|
+
event = self._event_manager.create(
|
|
381
|
+
project_id=name,
|
|
382
|
+
event_type=EventType.INSTANCE_READY,
|
|
383
|
+
title=f"Instance '{name}' started",
|
|
384
|
+
content=(
|
|
385
|
+
f"Instance {name} startup timeout. "
|
|
386
|
+
f"May be ready, or startup may be slow/blocked. "
|
|
387
|
+
f"Check instance manually if issues occur."
|
|
388
|
+
),
|
|
389
|
+
context={
|
|
390
|
+
"instance_name": name,
|
|
391
|
+
"timeout": True,
|
|
392
|
+
"timeout_seconds": timeout,
|
|
393
|
+
},
|
|
394
|
+
)
|
|
395
|
+
await self._event_manager.emit(event)
|
|
396
|
+
|
|
397
|
+
async def wait_for_ready(self, name: str, timeout: int = 30) -> bool:
|
|
398
|
+
"""Wait for an instance to be ready.
|
|
399
|
+
|
|
400
|
+
Polls the instance's ready flag until it becomes True or timeout.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
name: Instance name
|
|
404
|
+
timeout: Maximum seconds to wait
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
True if instance is ready, False if timeout
|
|
408
|
+
|
|
409
|
+
Example:
|
|
410
|
+
>>> manager = InstanceManager(orchestrator)
|
|
411
|
+
>>> await manager.start_instance("myapp", "/path/to/app", "mpm")
|
|
412
|
+
>>> if await manager.wait_for_ready("myapp", timeout=30):
|
|
413
|
+
... print("Ready!")
|
|
414
|
+
"""
|
|
415
|
+
start_time = asyncio.get_event_loop().time()
|
|
416
|
+
while asyncio.get_event_loop().time() - start_time < timeout:
|
|
417
|
+
inst = self._instances.get(name)
|
|
418
|
+
if inst and inst.ready:
|
|
419
|
+
return True
|
|
420
|
+
await asyncio.sleep(0.5)
|
|
421
|
+
return False
|
|
422
|
+
|
|
423
|
+
async def stop_instance(self, name: str) -> bool:
|
|
424
|
+
"""Stop an instance.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
name: Instance name
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
True if instance was stopped
|
|
431
|
+
|
|
432
|
+
Raises:
|
|
433
|
+
InstanceNotFoundError: If instance not found
|
|
434
|
+
|
|
435
|
+
Example:
|
|
436
|
+
>>> manager = InstanceManager(orchestrator)
|
|
437
|
+
>>> await manager.stop_instance("myapp")
|
|
438
|
+
True
|
|
439
|
+
"""
|
|
440
|
+
if name not in self._instances:
|
|
441
|
+
raise InstanceNotFoundError(name)
|
|
442
|
+
|
|
443
|
+
instance = self._instances[name]
|
|
444
|
+
|
|
445
|
+
# Kill tmux pane
|
|
446
|
+
self.orchestrator.kill_pane(instance.pane_target)
|
|
447
|
+
|
|
448
|
+
# Remove adapter if exists
|
|
449
|
+
if name in self._adapters:
|
|
450
|
+
del self._adapters[name]
|
|
451
|
+
instance.connected = False
|
|
452
|
+
logger.debug(f"Removed adapter for instance '{name}'")
|
|
453
|
+
|
|
454
|
+
# Remove from tracking
|
|
455
|
+
del self._instances[name]
|
|
456
|
+
|
|
457
|
+
logger.info(f"Stopped instance '{name}'")
|
|
458
|
+
|
|
459
|
+
return True
|
|
460
|
+
|
|
461
|
+
def get_instance(self, name: str) -> Optional[InstanceInfo]:
|
|
462
|
+
"""Get instance by name.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
name: Instance name
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
InstanceInfo if found, None otherwise
|
|
469
|
+
|
|
470
|
+
Example:
|
|
471
|
+
>>> manager = InstanceManager(orchestrator)
|
|
472
|
+
>>> instance = manager.get_instance("myapp")
|
|
473
|
+
>>> if instance:
|
|
474
|
+
... print(instance.name, instance.framework)
|
|
475
|
+
myapp cc
|
|
476
|
+
"""
|
|
477
|
+
return self._instances.get(name)
|
|
478
|
+
|
|
479
|
+
def list_instances(self) -> list[InstanceInfo]:
|
|
480
|
+
"""List all running instances.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
List of InstanceInfo for all running instances
|
|
484
|
+
|
|
485
|
+
Example:
|
|
486
|
+
>>> manager = InstanceManager(orchestrator)
|
|
487
|
+
>>> instances = manager.list_instances()
|
|
488
|
+
>>> for instance in instances:
|
|
489
|
+
... print(instance.name, instance.framework)
|
|
490
|
+
myapp cc
|
|
491
|
+
otherapp mpm
|
|
492
|
+
"""
|
|
493
|
+
return list(self._instances.values())
|
|
494
|
+
|
|
495
|
+
async def send_to_instance(
|
|
496
|
+
self, name: str, message: str, wait_for_response: bool = False
|
|
497
|
+
) -> Optional[AdapterResponse]:
|
|
498
|
+
"""Send a message/command to an instance.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
name: Instance name
|
|
502
|
+
message: Message to send
|
|
503
|
+
wait_for_response: If True, wait for and return response
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
AdapterResponse if wait_for_response=True, None otherwise
|
|
507
|
+
|
|
508
|
+
Raises:
|
|
509
|
+
InstanceNotFoundError: If instance not found
|
|
510
|
+
|
|
511
|
+
Example:
|
|
512
|
+
>>> manager = InstanceManager(orchestrator)
|
|
513
|
+
>>> # Send without waiting
|
|
514
|
+
>>> await manager.send_to_instance("myapp", "Fix the bug in main.py")
|
|
515
|
+
>>> # Send and wait for response
|
|
516
|
+
>>> response = await manager.send_to_instance(
|
|
517
|
+
... "myapp", "Fix the bug", wait_for_response=True
|
|
518
|
+
... )
|
|
519
|
+
>>> print(response.content)
|
|
520
|
+
"""
|
|
521
|
+
if name not in self._instances:
|
|
522
|
+
raise InstanceNotFoundError(name)
|
|
523
|
+
|
|
524
|
+
instance = self._instances[name]
|
|
525
|
+
|
|
526
|
+
# Use adapter if available
|
|
527
|
+
if name in self._adapters:
|
|
528
|
+
adapter = self._adapters[name]
|
|
529
|
+
await adapter.send(message)
|
|
530
|
+
logger.info(
|
|
531
|
+
f"Sent message via adapter to instance '{name}': {message[:50]}..."
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
if wait_for_response:
|
|
535
|
+
return await adapter.receive()
|
|
536
|
+
return None
|
|
537
|
+
|
|
538
|
+
# Fallback to direct tmux if no adapter
|
|
539
|
+
self.orchestrator.send_keys(instance.pane_target, message)
|
|
540
|
+
logger.info(f"Sent message to instance '{name}': {message[:50]}...")
|
|
541
|
+
return None
|
|
542
|
+
|
|
543
|
+
def get_adapter(self, name: str) -> Optional[ClaudeCodeCommunicationAdapter]:
|
|
544
|
+
"""Get communication adapter for an instance.
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
name: Instance name
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
ClaudeCodeCommunicationAdapter if exists, None otherwise
|
|
551
|
+
|
|
552
|
+
Example:
|
|
553
|
+
>>> manager = InstanceManager(orchestrator)
|
|
554
|
+
>>> adapter = manager.get_adapter("myapp")
|
|
555
|
+
>>> if adapter:
|
|
556
|
+
... await adapter.send("Create a new file")
|
|
557
|
+
... async for chunk in adapter.stream_response():
|
|
558
|
+
... print(chunk, end='')
|
|
559
|
+
"""
|
|
560
|
+
return self._adapters.get(name)
|
|
561
|
+
|
|
562
|
+
async def rename_instance(self, old_name: str, new_name: str) -> bool:
|
|
563
|
+
"""Rename an instance.
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
old_name: Current instance name
|
|
567
|
+
new_name: New instance name
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
True if renamed successfully
|
|
571
|
+
|
|
572
|
+
Raises:
|
|
573
|
+
InstanceNotFoundError: If old_name doesn't exist
|
|
574
|
+
InstanceAlreadyExistsError: If new_name already exists
|
|
575
|
+
|
|
576
|
+
Example:
|
|
577
|
+
>>> manager = InstanceManager(orchestrator)
|
|
578
|
+
>>> await manager.rename_instance("myapp", "myapp-v2")
|
|
579
|
+
True
|
|
580
|
+
"""
|
|
581
|
+
# Validate old_name exists
|
|
582
|
+
if old_name not in self._instances:
|
|
583
|
+
raise InstanceNotFoundError(old_name)
|
|
584
|
+
|
|
585
|
+
# Validate new_name doesn't exist
|
|
586
|
+
if new_name in self._instances:
|
|
587
|
+
raise InstanceAlreadyExistsError(new_name)
|
|
588
|
+
|
|
589
|
+
# Get instance and update name
|
|
590
|
+
instance = self._instances[old_name]
|
|
591
|
+
instance.name = new_name
|
|
592
|
+
|
|
593
|
+
# Update _instances dict (remove old key, add new)
|
|
594
|
+
del self._instances[old_name]
|
|
595
|
+
self._instances[new_name] = instance
|
|
596
|
+
|
|
597
|
+
# Update _adapters dict if exists
|
|
598
|
+
if old_name in self._adapters:
|
|
599
|
+
adapter = self._adapters[old_name]
|
|
600
|
+
del self._adapters[old_name]
|
|
601
|
+
self._adapters[new_name] = adapter
|
|
602
|
+
logger.debug(f"Moved adapter from '{old_name}' to '{new_name}'")
|
|
603
|
+
|
|
604
|
+
logger.info(f"Renamed instance from '{old_name}' to '{new_name}'")
|
|
605
|
+
|
|
606
|
+
return True
|
|
607
|
+
|
|
608
|
+
async def close_instance(self, name: str, merge: bool = True) -> tuple[bool, str]:
|
|
609
|
+
"""Close instance: stop tmux, optionally merge worktree, cleanup.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
name: Instance name to close
|
|
613
|
+
merge: Whether to merge worktree to main (default True)
|
|
614
|
+
|
|
615
|
+
Returns:
|
|
616
|
+
Tuple of (success, message)
|
|
617
|
+
|
|
618
|
+
Example:
|
|
619
|
+
>>> manager = InstanceManager(orchestrator)
|
|
620
|
+
>>> success, msg = await manager.close_instance("myapp")
|
|
621
|
+
>>> print(success, msg)
|
|
622
|
+
True Closed 'myapp'
|
|
623
|
+
"""
|
|
624
|
+
registered = (
|
|
625
|
+
self._state_store.get_registered_instance(name)
|
|
626
|
+
if self._state_store
|
|
627
|
+
else None
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
# Stop the tmux session
|
|
631
|
+
try:
|
|
632
|
+
await self.stop_instance(name)
|
|
633
|
+
except InstanceNotFoundError:
|
|
634
|
+
# Instance not running, but may still have worktree to clean up
|
|
635
|
+
pass
|
|
636
|
+
|
|
637
|
+
# Merge and cleanup worktree if enabled
|
|
638
|
+
if registered and registered.use_worktree and registered.worktree_path:
|
|
639
|
+
wt_manager = WorktreeManager(Path(registered.path))
|
|
640
|
+
if merge:
|
|
641
|
+
success, msg = wt_manager.merge_to_main(name, delete_after=True)
|
|
642
|
+
if not success:
|
|
643
|
+
return False, msg
|
|
644
|
+
else:
|
|
645
|
+
wt_manager.remove(name, force=True)
|
|
646
|
+
|
|
647
|
+
# Remove from registry
|
|
648
|
+
if self._state_store:
|
|
649
|
+
self._state_store.unregister_instance(name)
|
|
650
|
+
|
|
651
|
+
return True, f"Closed '{name}'"
|
|
652
|
+
|
|
653
|
+
async def disconnect_instance(self, name: str) -> bool:
|
|
654
|
+
"""Disconnect from an instance without closing it.
|
|
655
|
+
|
|
656
|
+
The instance keeps running but we stop communication.
|
|
657
|
+
Removes the adapter while keeping the instance tracked.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
name: Instance name to disconnect from
|
|
661
|
+
|
|
662
|
+
Returns:
|
|
663
|
+
True if disconnected successfully
|
|
664
|
+
|
|
665
|
+
Raises:
|
|
666
|
+
InstanceNotFoundError: If instance not found
|
|
667
|
+
|
|
668
|
+
Example:
|
|
669
|
+
>>> manager = InstanceManager(orchestrator)
|
|
670
|
+
>>> await manager.disconnect_instance("myapp")
|
|
671
|
+
True
|
|
672
|
+
>>> # Instance still running, but no adapter connection
|
|
673
|
+
>>> adapter = manager.get_adapter("myapp")
|
|
674
|
+
>>> print(adapter)
|
|
675
|
+
None
|
|
676
|
+
"""
|
|
677
|
+
# Validate instance exists
|
|
678
|
+
if name not in self._instances:
|
|
679
|
+
raise InstanceNotFoundError(name)
|
|
680
|
+
|
|
681
|
+
instance = self._instances[name]
|
|
682
|
+
|
|
683
|
+
# Remove adapter if exists (but keep instance)
|
|
684
|
+
if name in self._adapters:
|
|
685
|
+
# Could add cleanup here if adapter has resources to close
|
|
686
|
+
del self._adapters[name]
|
|
687
|
+
instance.connected = False
|
|
688
|
+
logger.info(f"Disconnected from instance '{name}' (instance still running)")
|
|
689
|
+
else:
|
|
690
|
+
logger.debug(f"No adapter to disconnect for instance '{name}'")
|
|
691
|
+
|
|
692
|
+
return True
|
|
693
|
+
|
|
694
|
+
async def register_instance(
|
|
695
|
+
self,
|
|
696
|
+
path: str,
|
|
697
|
+
framework: str,
|
|
698
|
+
name: str,
|
|
699
|
+
use_worktree: bool = True,
|
|
700
|
+
branch: Optional[str] = None,
|
|
701
|
+
) -> InstanceInfo:
|
|
702
|
+
"""Register an instance and start it.
|
|
703
|
+
|
|
704
|
+
Registers the instance in persistent storage so it can be started
|
|
705
|
+
by name in future sessions, then starts the instance.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
path: Project directory path
|
|
709
|
+
framework: Framework to use ("cc" or "mpm")
|
|
710
|
+
name: Instance name (also worktree name if enabled)
|
|
711
|
+
use_worktree: Create isolated git worktree (default True)
|
|
712
|
+
branch: Branch for worktree (default: session-{name})
|
|
713
|
+
|
|
714
|
+
Returns:
|
|
715
|
+
InstanceInfo with tmux session details
|
|
716
|
+
|
|
717
|
+
Raises:
|
|
718
|
+
FrameworkNotFoundError: If framework is not available
|
|
719
|
+
InstanceAlreadyExistsError: If instance already exists
|
|
720
|
+
|
|
721
|
+
Example:
|
|
722
|
+
>>> manager = InstanceManager(orchestrator)
|
|
723
|
+
>>> manager.set_state_store(state_store)
|
|
724
|
+
>>> instance = await manager.register_instance(
|
|
725
|
+
... "/Users/user/myapp", "cc", "myapp"
|
|
726
|
+
... )
|
|
727
|
+
>>> print(instance.name, instance.framework)
|
|
728
|
+
myapp cc
|
|
729
|
+
"""
|
|
730
|
+
project_path = Path(path).expanduser().resolve()
|
|
731
|
+
|
|
732
|
+
worktree_path = None
|
|
733
|
+
worktree_branch = None
|
|
734
|
+
working_path = project_path
|
|
735
|
+
|
|
736
|
+
# Create worktree if enabled and it's a git repo
|
|
737
|
+
if use_worktree and (project_path / ".git").exists():
|
|
738
|
+
try:
|
|
739
|
+
wt_manager = WorktreeManager(project_path)
|
|
740
|
+
wt_info = wt_manager.create(name, branch)
|
|
741
|
+
worktree_path = str(wt_info.path)
|
|
742
|
+
worktree_branch = wt_info.branch
|
|
743
|
+
working_path = wt_info.path
|
|
744
|
+
logger.info(
|
|
745
|
+
f"Created worktree for '{name}' at {worktree_path} "
|
|
746
|
+
f"on branch {worktree_branch}"
|
|
747
|
+
)
|
|
748
|
+
except Exception as e:
|
|
749
|
+
logger.warning(f"Could not create worktree: {e}. Using original path.")
|
|
750
|
+
|
|
751
|
+
# Create registered instance with worktree info
|
|
752
|
+
registered = RegisteredInstance(
|
|
753
|
+
name=name,
|
|
754
|
+
path=str(project_path),
|
|
755
|
+
framework=framework,
|
|
756
|
+
registered_at=datetime.now(timezone.utc).isoformat(),
|
|
757
|
+
worktree_path=worktree_path,
|
|
758
|
+
worktree_branch=worktree_branch,
|
|
759
|
+
use_worktree=use_worktree and worktree_path is not None,
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# Save to persistent storage
|
|
763
|
+
if self._state_store:
|
|
764
|
+
self._state_store.register_instance(registered)
|
|
765
|
+
|
|
766
|
+
# Start the instance in worktree path
|
|
767
|
+
return await self.start_instance(name, working_path, framework)
|
|
768
|
+
|
|
769
|
+
async def start_by_name(self, name: str) -> Optional[InstanceInfo]:
|
|
770
|
+
"""Start a previously registered instance by name.
|
|
771
|
+
|
|
772
|
+
Looks up the instance registration and starts it with the
|
|
773
|
+
stored path and framework. Uses the worktree path if configured.
|
|
774
|
+
|
|
775
|
+
Args:
|
|
776
|
+
name: Instance name (must have been previously registered)
|
|
777
|
+
|
|
778
|
+
Returns:
|
|
779
|
+
InstanceInfo if instance was found and started, None if not registered
|
|
780
|
+
|
|
781
|
+
Example:
|
|
782
|
+
>>> manager = InstanceManager(orchestrator)
|
|
783
|
+
>>> manager.set_state_store(state_store)
|
|
784
|
+
>>> # After previous registration
|
|
785
|
+
>>> instance = await manager.start_by_name("myapp")
|
|
786
|
+
>>> if instance:
|
|
787
|
+
... print(instance.name, instance.project_path)
|
|
788
|
+
myapp /Users/user/myapp
|
|
789
|
+
"""
|
|
790
|
+
if not self._state_store:
|
|
791
|
+
return None
|
|
792
|
+
|
|
793
|
+
registered = self._state_store.get_registered_instance(name)
|
|
794
|
+
if not registered:
|
|
795
|
+
return None
|
|
796
|
+
|
|
797
|
+
# Use working_path which respects worktree setting
|
|
798
|
+
return await self.start_instance(
|
|
799
|
+
registered.name,
|
|
800
|
+
Path(registered.working_path),
|
|
801
|
+
registered.framework,
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
def list_registered(self) -> dict[str, RegisteredInstance]:
|
|
805
|
+
"""List all registered instances.
|
|
806
|
+
|
|
807
|
+
Returns:
|
|
808
|
+
Dict mapping instance name to RegisteredInstance
|
|
809
|
+
|
|
810
|
+
Example:
|
|
811
|
+
>>> manager = InstanceManager(orchestrator)
|
|
812
|
+
>>> manager.set_state_store(state_store)
|
|
813
|
+
>>> registered = manager.list_registered()
|
|
814
|
+
>>> for name, instance in registered.items():
|
|
815
|
+
... print(f"{name}: {instance.path} ({instance.framework})")
|
|
816
|
+
myapp: /Users/user/myapp (cc)
|
|
817
|
+
"""
|
|
818
|
+
if not self._state_store:
|
|
819
|
+
return {}
|
|
820
|
+
return self._state_store.load_instances()
|
|
821
|
+
|
|
822
|
+
def unregister(self, name: str) -> bool:
|
|
823
|
+
"""Unregister an instance.
|
|
824
|
+
|
|
825
|
+
Removes the instance from persistent storage. Does not stop
|
|
826
|
+
any running instance with this name.
|
|
827
|
+
|
|
828
|
+
Args:
|
|
829
|
+
name: Instance name to unregister
|
|
830
|
+
|
|
831
|
+
Returns:
|
|
832
|
+
True if instance was found and unregistered, False if not found
|
|
833
|
+
|
|
834
|
+
Example:
|
|
835
|
+
>>> manager = InstanceManager(orchestrator)
|
|
836
|
+
>>> manager.set_state_store(state_store)
|
|
837
|
+
>>> success = manager.unregister("myapp")
|
|
838
|
+
>>> print(success)
|
|
839
|
+
True
|
|
840
|
+
"""
|
|
841
|
+
if not self._state_store:
|
|
842
|
+
return False
|
|
843
|
+
return self._state_store.unregister_instance(name)
|
|
844
|
+
|
|
845
|
+
def list_worktrees(self, path: str) -> list:
|
|
846
|
+
"""List worktrees for a project.
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
path: Project directory path
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
List of WorktreeInfo for all worktrees associated with the project
|
|
853
|
+
|
|
854
|
+
Example:
|
|
855
|
+
>>> manager = InstanceManager(orchestrator)
|
|
856
|
+
>>> worktrees = manager.list_worktrees("/Users/user/myapp")
|
|
857
|
+
>>> for wt in worktrees:
|
|
858
|
+
... print(f"{wt.name}: {wt.path} ({wt.branch})")
|
|
859
|
+
myapp: /Users/user/.worktrees-myapp/myapp (session-myapp)
|
|
860
|
+
"""
|
|
861
|
+
try:
|
|
862
|
+
wt_manager = WorktreeManager(Path(path))
|
|
863
|
+
return wt_manager.list()
|
|
864
|
+
except Exception:
|
|
865
|
+
return []
|