claude-mpm 5.4.85__py3-none-any.whl → 5.6.76__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
- claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
- claude_mpm/agents/PM_INSTRUCTIONS.md +109 -706
- 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/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +216 -0
- 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 +2 -2
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/executor.py +128 -16
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/parsers/base_parser.py +84 -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/run_parser.py +10 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +345 -40
- claude_mpm/cli/startup_display.py +76 -7
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/startup_migrations.py +236 -0
- 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 +124 -0
- claude_mpm/commander/chat/repl.py +1957 -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 +868 -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 +5 -0
- claude_mpm/core/claude_runner.py +152 -0
- 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 +5 -4
- 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/output_style_manager.py +52 -12
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_config.py +10 -6
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +9 -9
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +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/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/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 +22 -15
- 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/remote_agent_discovery_service.py +3 -0
- 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/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 -2
- claude_mpm/services/monitor/server.py +111 -16
- claude_mpm/services/pm_skills_deployer.py +261 -87
- claude_mpm/services/skills/git_skill_source_manager.py +130 -10
- claude_mpm/services/skills/selective_skill_deployer.py +142 -16
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +31 -5
- 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/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-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/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/registry.py +295 -90
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm-5.6.76.dist-info/METADATA +416 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +312 -175
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
- claude_mpm-5.4.85.dist-info/METADATA +0 -1023
- /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
- /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Message and thread management endpoints for MPM Commander API.
|
|
2
|
+
|
|
3
|
+
This module implements REST endpoints for sending messages and retrieving
|
|
4
|
+
conversation threads for projects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
from fastapi import APIRouter, Request
|
|
11
|
+
|
|
12
|
+
from ...models import ThreadMessage
|
|
13
|
+
from ..errors import ProjectNotFoundError
|
|
14
|
+
from ..schemas import MessageResponse, SendMessageRequest
|
|
15
|
+
|
|
16
|
+
router = APIRouter()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_registry(request: Request):
|
|
20
|
+
"""Get registry instance from app.state."""
|
|
21
|
+
if not hasattr(request.app.state, "registry") or request.app.state.registry is None:
|
|
22
|
+
raise RuntimeError("Registry not initialized")
|
|
23
|
+
return request.app.state.registry
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _message_to_response(message: ThreadMessage) -> MessageResponse:
|
|
27
|
+
"""Convert ThreadMessage model to MessageResponse schema.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
message: ThreadMessage instance
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
MessageResponse with message data
|
|
34
|
+
"""
|
|
35
|
+
return MessageResponse(
|
|
36
|
+
id=message.id,
|
|
37
|
+
role=message.role,
|
|
38
|
+
content=message.content,
|
|
39
|
+
session_id=message.session_id,
|
|
40
|
+
timestamp=message.timestamp,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@router.get("/projects/{project_id}/thread", response_model=List[MessageResponse])
|
|
45
|
+
async def get_thread(request: Request, project_id: str) -> List[MessageResponse]:
|
|
46
|
+
"""Get conversation thread for a project.
|
|
47
|
+
|
|
48
|
+
Returns all messages in chronological order.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
project_id: Unique project identifier
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
List of messages in thread (may be empty)
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ProjectNotFoundError: If project_id doesn't exist
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
GET /api/projects/abc-123/thread
|
|
61
|
+
Response: [
|
|
62
|
+
{
|
|
63
|
+
"id": "msg-1",
|
|
64
|
+
"role": "user",
|
|
65
|
+
"content": "Fix the login bug",
|
|
66
|
+
"session_id": null,
|
|
67
|
+
"timestamp": "2025-01-12T10:00:00Z"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "msg-2",
|
|
71
|
+
"role": "assistant",
|
|
72
|
+
"content": "I'll investigate the login issue",
|
|
73
|
+
"session_id": "sess-456",
|
|
74
|
+
"timestamp": "2025-01-12T10:00:30Z"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
"""
|
|
78
|
+
registry = _get_registry(request)
|
|
79
|
+
project = registry.get(project_id)
|
|
80
|
+
|
|
81
|
+
if project is None:
|
|
82
|
+
raise ProjectNotFoundError(project_id)
|
|
83
|
+
|
|
84
|
+
# Convert thread messages to responses
|
|
85
|
+
return [_message_to_response(m) for m in project.thread]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@router.post(
|
|
89
|
+
"/projects/{project_id}/messages", response_model=MessageResponse, status_code=201
|
|
90
|
+
)
|
|
91
|
+
async def send_message(
|
|
92
|
+
request: Request, project_id: str, req: SendMessageRequest
|
|
93
|
+
) -> MessageResponse:
|
|
94
|
+
"""Send a message to a project's active session.
|
|
95
|
+
|
|
96
|
+
Adds message to conversation thread and sends to specified or active session.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
project_id: Unique project identifier
|
|
100
|
+
req: Message request with content and optional session_id
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Created message information
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
ProjectNotFoundError: If project_id doesn't exist
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
POST /api/projects/abc-123/messages
|
|
110
|
+
Body: {
|
|
111
|
+
"content": "Fix the login bug",
|
|
112
|
+
"session_id": "sess-456"
|
|
113
|
+
}
|
|
114
|
+
Response: {
|
|
115
|
+
"id": "msg-1",
|
|
116
|
+
"role": "user",
|
|
117
|
+
"content": "Fix the login bug",
|
|
118
|
+
"session_id": "sess-456",
|
|
119
|
+
"timestamp": "2025-01-12T10:00:00Z"
|
|
120
|
+
}
|
|
121
|
+
"""
|
|
122
|
+
registry = _get_registry(request)
|
|
123
|
+
project = registry.get(project_id)
|
|
124
|
+
|
|
125
|
+
if project is None:
|
|
126
|
+
raise ProjectNotFoundError(project_id)
|
|
127
|
+
|
|
128
|
+
# Generate message ID
|
|
129
|
+
message_id = str(uuid.uuid4())
|
|
130
|
+
|
|
131
|
+
# Create message object
|
|
132
|
+
message = ThreadMessage(
|
|
133
|
+
id=message_id,
|
|
134
|
+
role="user",
|
|
135
|
+
content=req.content,
|
|
136
|
+
session_id=req.session_id,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Add to project thread
|
|
140
|
+
project.thread.append(message)
|
|
141
|
+
|
|
142
|
+
# Update last activity
|
|
143
|
+
registry.touch(project_id)
|
|
144
|
+
|
|
145
|
+
# TODO: Send to session/runtime adapter (Phase 2)
|
|
146
|
+
# For Phase 1, message is just stored in thread
|
|
147
|
+
|
|
148
|
+
return _message_to_response(message)
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Project management endpoints for MPM Commander API.
|
|
2
|
+
|
|
3
|
+
This module implements REST endpoints for registering, listing, and managing
|
|
4
|
+
projects in the MPM Commander.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
from fastapi import APIRouter, Request, Response
|
|
11
|
+
|
|
12
|
+
from ...models import ProjectState
|
|
13
|
+
from ..errors import InvalidPathError, ProjectAlreadyExistsError, ProjectNotFoundError
|
|
14
|
+
from ..schemas import ProjectResponse, RegisterProjectRequest, SessionResponse
|
|
15
|
+
|
|
16
|
+
router = APIRouter()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_registry(request: Request):
|
|
20
|
+
"""Get registry instance from app.state."""
|
|
21
|
+
if not hasattr(request.app.state, "registry") or request.app.state.registry is None:
|
|
22
|
+
raise RuntimeError("Registry not initialized")
|
|
23
|
+
return request.app.state.registry
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _project_to_response(project) -> ProjectResponse:
|
|
27
|
+
"""Convert Project model to ProjectResponse schema.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
project: Project instance
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
ProjectResponse with all project data
|
|
34
|
+
"""
|
|
35
|
+
# Convert sessions dict to list of SessionResponse
|
|
36
|
+
session_responses = [
|
|
37
|
+
SessionResponse(
|
|
38
|
+
id=session.id,
|
|
39
|
+
project_id=session.project_id,
|
|
40
|
+
runtime=session.runtime,
|
|
41
|
+
tmux_target=session.tmux_target,
|
|
42
|
+
status=session.status,
|
|
43
|
+
created_at=session.created_at,
|
|
44
|
+
)
|
|
45
|
+
for session in project.sessions.values()
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
return ProjectResponse(
|
|
49
|
+
id=project.id,
|
|
50
|
+
path=project.path,
|
|
51
|
+
name=project.name,
|
|
52
|
+
state=project.state.value,
|
|
53
|
+
state_reason=project.state_reason,
|
|
54
|
+
sessions=session_responses,
|
|
55
|
+
pending_events_count=len(project.pending_events),
|
|
56
|
+
last_activity=project.last_activity,
|
|
57
|
+
created_at=project.created_at,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@router.get("/projects", response_model=List[ProjectResponse])
|
|
62
|
+
async def list_projects(request: Request) -> List[ProjectResponse]:
|
|
63
|
+
"""List all registered projects.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List of project information (may be empty)
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
GET /api/projects
|
|
70
|
+
Response: [
|
|
71
|
+
{
|
|
72
|
+
"id": "abc-123",
|
|
73
|
+
"path": "/Users/user/projects/my-app",
|
|
74
|
+
"name": "my-app",
|
|
75
|
+
"state": "idle",
|
|
76
|
+
"state_reason": null,
|
|
77
|
+
"sessions": [],
|
|
78
|
+
"pending_events_count": 0,
|
|
79
|
+
"last_activity": "2025-01-12T10:00:00Z",
|
|
80
|
+
"created_at": "2025-01-12T09:00:00Z"
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
"""
|
|
84
|
+
registry = _get_registry(request)
|
|
85
|
+
projects = registry.list_all()
|
|
86
|
+
return [_project_to_response(p) for p in projects]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@router.get("/projects/{project_id}", response_model=ProjectResponse)
|
|
90
|
+
async def get_project(request: Request, project_id: str) -> ProjectResponse:
|
|
91
|
+
"""Get project details by ID.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
project_id: Unique project identifier
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Project information
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ProjectNotFoundError: If project_id doesn't exist
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
GET /api/projects/abc-123
|
|
104
|
+
Response: {
|
|
105
|
+
"id": "abc-123",
|
|
106
|
+
"path": "/Users/user/projects/my-app",
|
|
107
|
+
"name": "my-app",
|
|
108
|
+
"state": "idle",
|
|
109
|
+
...
|
|
110
|
+
}
|
|
111
|
+
"""
|
|
112
|
+
registry = _get_registry(request)
|
|
113
|
+
project = registry.get(project_id)
|
|
114
|
+
|
|
115
|
+
if project is None:
|
|
116
|
+
raise ProjectNotFoundError(project_id)
|
|
117
|
+
|
|
118
|
+
return _project_to_response(project)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@router.post("/projects", response_model=ProjectResponse, status_code=201)
|
|
122
|
+
async def register_project(
|
|
123
|
+
request: Request, req: RegisterProjectRequest
|
|
124
|
+
) -> ProjectResponse:
|
|
125
|
+
"""Register a new project.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
req: Registration request with path and optional name
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Newly registered project information
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
InvalidPathError: If path doesn't exist or isn't a directory
|
|
135
|
+
ProjectAlreadyExistsError: If path already registered
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
POST /api/projects
|
|
139
|
+
Body: {
|
|
140
|
+
"path": "/Users/user/projects/my-app",
|
|
141
|
+
"name": "My App"
|
|
142
|
+
}
|
|
143
|
+
Response: {
|
|
144
|
+
"id": "abc-123",
|
|
145
|
+
"path": "/Users/user/projects/my-app",
|
|
146
|
+
"name": "My App",
|
|
147
|
+
"state": "idle",
|
|
148
|
+
...
|
|
149
|
+
}
|
|
150
|
+
"""
|
|
151
|
+
registry = _get_registry(request)
|
|
152
|
+
|
|
153
|
+
# Validate path exists and is directory
|
|
154
|
+
path_obj = Path(req.path)
|
|
155
|
+
if not path_obj.exists() or not path_obj.is_dir():
|
|
156
|
+
raise InvalidPathError(req.path)
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
project = registry.register(req.path, req.name, req.project_id)
|
|
160
|
+
return _project_to_response(project)
|
|
161
|
+
except ValueError as e:
|
|
162
|
+
# Registry raises ValueError for duplicate registration
|
|
163
|
+
error_msg = str(e)
|
|
164
|
+
if "already registered" in error_msg.lower():
|
|
165
|
+
raise ProjectAlreadyExistsError(req.path) from e
|
|
166
|
+
# Re-raise as InvalidPathError for other validation errors
|
|
167
|
+
raise InvalidPathError(req.path) from e
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@router.delete("/projects/{project_id}", status_code=204)
|
|
171
|
+
async def unregister_project(request: Request, project_id: str) -> Response:
|
|
172
|
+
"""Unregister a project.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
project_id: Unique project identifier
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Empty response with 204 status
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ProjectNotFoundError: If project_id doesn't exist
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
DELETE /api/projects/abc-123
|
|
185
|
+
Response: 204 No Content
|
|
186
|
+
"""
|
|
187
|
+
registry = _get_registry(request)
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
registry.unregister(project_id)
|
|
191
|
+
return Response(status_code=204)
|
|
192
|
+
except KeyError as e:
|
|
193
|
+
raise ProjectNotFoundError(project_id) from e
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@router.post("/projects/{project_id}/pause", response_model=ProjectResponse)
|
|
197
|
+
async def pause_project(request: Request, project_id: str) -> ProjectResponse:
|
|
198
|
+
"""Pause a project.
|
|
199
|
+
|
|
200
|
+
Sets project state to PAUSED to prevent automatic work processing.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
project_id: Unique project identifier
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Updated project information
|
|
207
|
+
|
|
208
|
+
Raises:
|
|
209
|
+
ProjectNotFoundError: If project_id doesn't exist
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
POST /api/projects/abc-123/pause
|
|
213
|
+
Response: {
|
|
214
|
+
"id": "abc-123",
|
|
215
|
+
"state": "paused",
|
|
216
|
+
"state_reason": "Manually paused via API",
|
|
217
|
+
...
|
|
218
|
+
}
|
|
219
|
+
"""
|
|
220
|
+
registry = _get_registry(request)
|
|
221
|
+
project = registry.get(project_id)
|
|
222
|
+
|
|
223
|
+
if project is None:
|
|
224
|
+
raise ProjectNotFoundError(project_id)
|
|
225
|
+
|
|
226
|
+
registry.update_state(
|
|
227
|
+
project_id,
|
|
228
|
+
ProjectState.PAUSED,
|
|
229
|
+
reason="Manually paused via API",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return _project_to_response(project)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@router.post("/projects/{project_id}/resume", response_model=ProjectResponse)
|
|
236
|
+
async def resume_project(request: Request, project_id: str) -> ProjectResponse:
|
|
237
|
+
"""Resume a paused project.
|
|
238
|
+
|
|
239
|
+
Sets project state back to IDLE to allow work processing.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
project_id: Unique project identifier
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Updated project information
|
|
246
|
+
|
|
247
|
+
Raises:
|
|
248
|
+
ProjectNotFoundError: If project_id doesn't exist
|
|
249
|
+
|
|
250
|
+
Example:
|
|
251
|
+
POST /api/projects/abc-123/resume
|
|
252
|
+
Response: {
|
|
253
|
+
"id": "abc-123",
|
|
254
|
+
"state": "idle",
|
|
255
|
+
"state_reason": "Resumed via API",
|
|
256
|
+
...
|
|
257
|
+
}
|
|
258
|
+
"""
|
|
259
|
+
registry = _get_registry(request)
|
|
260
|
+
project = registry.get(project_id)
|
|
261
|
+
|
|
262
|
+
if project is None:
|
|
263
|
+
raise ProjectNotFoundError(project_id)
|
|
264
|
+
|
|
265
|
+
registry.update_state(
|
|
266
|
+
project_id,
|
|
267
|
+
ProjectState.IDLE,
|
|
268
|
+
reason="Resumed via API",
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return _project_to_response(project)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Session management endpoints for MPM Commander API.
|
|
2
|
+
|
|
3
|
+
This module implements REST endpoints for creating and managing tool sessions
|
|
4
|
+
(Claude Code, Aider, etc.) within projects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import subprocess # nosec B404 - needed for tmux error handling
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
12
|
+
from fastapi import APIRouter, Request, Response
|
|
13
|
+
|
|
14
|
+
from ...models import ToolSession
|
|
15
|
+
from ..errors import (
|
|
16
|
+
InvalidRuntimeError,
|
|
17
|
+
ProjectNotFoundError,
|
|
18
|
+
SessionNotFoundError,
|
|
19
|
+
TmuxNoSpaceError,
|
|
20
|
+
)
|
|
21
|
+
from ..schemas import CreateSessionRequest, SessionResponse
|
|
22
|
+
|
|
23
|
+
router = APIRouter()
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
# Valid runtime adapters (Phase 1: claude-code only)
|
|
27
|
+
VALID_RUNTIMES = {"claude-code"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_registry(request: Request):
|
|
31
|
+
"""Get registry instance from app.state."""
|
|
32
|
+
if not hasattr(request.app.state, "registry") or request.app.state.registry is None:
|
|
33
|
+
raise RuntimeError("Registry not initialized")
|
|
34
|
+
return request.app.state.registry
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_tmux(request: Request):
|
|
38
|
+
"""Get tmux orchestrator instance from app.state."""
|
|
39
|
+
if not hasattr(request.app.state, "tmux") or request.app.state.tmux is None:
|
|
40
|
+
raise RuntimeError("Tmux orchestrator not initialized")
|
|
41
|
+
return request.app.state.tmux
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _session_to_response(session: ToolSession) -> SessionResponse:
|
|
45
|
+
"""Convert ToolSession model to SessionResponse schema.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
session: ToolSession instance
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
SessionResponse with session data
|
|
52
|
+
"""
|
|
53
|
+
return SessionResponse(
|
|
54
|
+
id=session.id,
|
|
55
|
+
project_id=session.project_id,
|
|
56
|
+
runtime=session.runtime,
|
|
57
|
+
tmux_target=session.tmux_target,
|
|
58
|
+
status=session.status,
|
|
59
|
+
created_at=session.created_at,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@router.get("/projects/{project_id}/sessions", response_model=List[SessionResponse])
|
|
64
|
+
async def list_sessions(request: Request, project_id: str) -> List[SessionResponse]:
|
|
65
|
+
"""List all sessions for a project.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
project_id: Unique project identifier
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
List of session information (may be empty)
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ProjectNotFoundError: If project_id doesn't exist
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
GET /api/projects/abc-123/sessions
|
|
78
|
+
Response: [
|
|
79
|
+
{
|
|
80
|
+
"id": "sess-456",
|
|
81
|
+
"project_id": "abc-123",
|
|
82
|
+
"runtime": "claude-code",
|
|
83
|
+
"tmux_target": "%1",
|
|
84
|
+
"status": "running",
|
|
85
|
+
"created_at": "2025-01-12T10:00:00Z"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
"""
|
|
89
|
+
registry = _get_registry(request)
|
|
90
|
+
project = registry.get(project_id)
|
|
91
|
+
|
|
92
|
+
if project is None:
|
|
93
|
+
raise ProjectNotFoundError(project_id)
|
|
94
|
+
|
|
95
|
+
# Convert sessions dict to list of responses
|
|
96
|
+
return [_session_to_response(s) for s in project.sessions.values()]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@router.post(
|
|
100
|
+
"/projects/{project_id}/sessions", response_model=SessionResponse, status_code=201
|
|
101
|
+
)
|
|
102
|
+
async def create_session(
|
|
103
|
+
request: Request, project_id: str, req: CreateSessionRequest
|
|
104
|
+
) -> SessionResponse:
|
|
105
|
+
"""Create a new session for a project.
|
|
106
|
+
|
|
107
|
+
Creates a new tmux pane and initializes the specified runtime adapter.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
project_id: Unique project identifier
|
|
111
|
+
req: Session creation request
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Newly created session information
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
ProjectNotFoundError: If project_id doesn't exist
|
|
118
|
+
InvalidRuntimeError: If runtime is not supported
|
|
119
|
+
TmuxNoSpaceError: If tmux has no space for new pane
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
POST /api/projects/abc-123/sessions
|
|
123
|
+
Body: {
|
|
124
|
+
"runtime": "claude-code",
|
|
125
|
+
"agent_prompt": "You are a helpful coding assistant"
|
|
126
|
+
}
|
|
127
|
+
Response: {
|
|
128
|
+
"id": "sess-456",
|
|
129
|
+
"project_id": "abc-123",
|
|
130
|
+
"runtime": "claude-code",
|
|
131
|
+
"tmux_target": "%1",
|
|
132
|
+
"status": "initializing",
|
|
133
|
+
"created_at": "2025-01-12T10:00:00Z"
|
|
134
|
+
}
|
|
135
|
+
"""
|
|
136
|
+
registry = _get_registry(request)
|
|
137
|
+
tmux_orch = _get_tmux(request)
|
|
138
|
+
|
|
139
|
+
# Validate project exists
|
|
140
|
+
project = registry.get(project_id)
|
|
141
|
+
if project is None:
|
|
142
|
+
raise ProjectNotFoundError(project_id)
|
|
143
|
+
|
|
144
|
+
# Validate runtime
|
|
145
|
+
if req.runtime not in VALID_RUNTIMES:
|
|
146
|
+
raise InvalidRuntimeError(req.runtime)
|
|
147
|
+
|
|
148
|
+
# Generate session ID
|
|
149
|
+
session_id = str(uuid.uuid4())
|
|
150
|
+
|
|
151
|
+
# Create tmux pane for session
|
|
152
|
+
try:
|
|
153
|
+
tmux_target = tmux_orch.create_pane(
|
|
154
|
+
pane_id=f"{project.name}-{req.runtime}",
|
|
155
|
+
working_dir=project.path,
|
|
156
|
+
)
|
|
157
|
+
except subprocess.CalledProcessError as e:
|
|
158
|
+
stderr = e.stderr.decode() if e.stderr else ""
|
|
159
|
+
if "no space for new pane" in stderr.lower():
|
|
160
|
+
raise TmuxNoSpaceError() from None
|
|
161
|
+
raise # Re-raise other subprocess errors
|
|
162
|
+
|
|
163
|
+
# Create session object
|
|
164
|
+
session = ToolSession(
|
|
165
|
+
id=session_id,
|
|
166
|
+
project_id=project_id,
|
|
167
|
+
runtime=req.runtime,
|
|
168
|
+
tmux_target=tmux_target,
|
|
169
|
+
status="initializing",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Add session to project
|
|
173
|
+
registry.add_session(project_id, session)
|
|
174
|
+
|
|
175
|
+
# TODO: Start runtime adapter in pane (Phase 2)
|
|
176
|
+
# For Phase 1, session stays in "initializing" state
|
|
177
|
+
|
|
178
|
+
return _session_to_response(session)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@router.delete("/sessions/{session_id}", status_code=204)
|
|
182
|
+
async def stop_session(request: Request, session_id: str) -> Response:
|
|
183
|
+
"""Stop and remove a session.
|
|
184
|
+
|
|
185
|
+
Kills the tmux pane and removes the session from its project.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
session_id: Unique session identifier
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Empty response with 204 status
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
SessionNotFoundError: If session_id doesn't exist
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
DELETE /api/sessions/sess-456
|
|
198
|
+
Response: 204 No Content
|
|
199
|
+
"""
|
|
200
|
+
registry = _get_registry(request)
|
|
201
|
+
tmux_orch = _get_tmux(request)
|
|
202
|
+
|
|
203
|
+
# Find session across all projects
|
|
204
|
+
session = None
|
|
205
|
+
parent_project_id = None
|
|
206
|
+
|
|
207
|
+
for project in registry.list_all():
|
|
208
|
+
if session_id in project.sessions:
|
|
209
|
+
session = project.sessions[session_id]
|
|
210
|
+
parent_project_id = project.id
|
|
211
|
+
break
|
|
212
|
+
|
|
213
|
+
if session is None or parent_project_id is None:
|
|
214
|
+
raise SessionNotFoundError(session_id)
|
|
215
|
+
|
|
216
|
+
# Kill tmux pane
|
|
217
|
+
try:
|
|
218
|
+
tmux_orch.kill_pane(session.tmux_target)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
# Pane may already be dead, continue with cleanup
|
|
221
|
+
logger.debug("Failed to kill pane (may already be dead): %s", e)
|
|
222
|
+
|
|
223
|
+
# Remove session from project
|
|
224
|
+
registry.remove_session(parent_project_id, session_id)
|
|
225
|
+
|
|
226
|
+
return Response(status_code=204)
|