claude-mpm 5.4.85__py3-none-any.whl → 5.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
- claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
- claude_mpm/agents/PM_INSTRUCTIONS.md +101 -703
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +46 -0
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +2 -2
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/executor.py +119 -16
- claude_mpm/cli/parsers/base_parser.py +71 -1
- claude_mpm/cli/parsers/commander_parser.py +83 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/startup.py +54 -16
- claude_mpm/cli/startup_display.py +72 -5
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +72 -0
- claude_mpm/commander/adapters/__init__.py +31 -0
- claude_mpm/commander/adapters/base.py +191 -0
- claude_mpm/commander/adapters/claude_code.py +361 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +105 -0
- claude_mpm/commander/api/errors.py +112 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +215 -0
- claude_mpm/commander/api/routes/work.py +260 -0
- claude_mpm/commander/api/schemas.py +182 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +107 -0
- claude_mpm/commander/chat/commands.py +96 -0
- claude_mpm/commander/chat/repl.py +310 -0
- claude_mpm/commander/config.py +49 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/daemon.py +398 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +332 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +143 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +62 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +337 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +121 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +309 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +404 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +316 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +361 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +189 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +219 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +8 -0
- claude_mpm/commands/mpm-doctor.md +8 -0
- claude_mpm/commands/mpm-help.md +8 -0
- claude_mpm/commands/mpm-init.md +8 -0
- claude_mpm/commands/mpm-monitor.md +8 -0
- claude_mpm/commands/mpm-organize.md +8 -0
- claude_mpm/commands/mpm-postmortem.md +8 -0
- claude_mpm/commands/mpm-session-resume.md +9 -1
- claude_mpm/commands/mpm-status.md +8 -0
- claude_mpm/commands/mpm-ticket-view.md +8 -0
- claude_mpm/commands/mpm-version.md +8 -0
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/core/config.py +5 -0
- claude_mpm/core/hook_manager.py +51 -3
- claude_mpm/core/logger.py +10 -7
- claude_mpm/core/logging_utils.py +4 -2
- claude_mpm/core/output_style_manager.py +15 -5
- claude_mpm/core/unified_config.py +10 -6
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +9 -9
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +250 -11
- claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +69 -5
- claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +14 -77
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
- claude_mpm/hooks/session_resume_hook.py +85 -1
- claude_mpm/init.py +1 -1
- claude_mpm/scripts/claude-hook-handler.sh +36 -10
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/event_log.py +325 -0
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +106 -16
- claude_mpm/services/pm_skills_deployer.py +259 -87
- claude_mpm/services/skills/git_skill_source_manager.py +51 -2
- claude_mpm/services/skills/selective_skill_deployer.py +114 -16
- claude_mpm/services/skills/skill_discovery_service.py +57 -3
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm-5.6.1.dist-info/METADATA +391 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/RECORD +244 -145
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
- claude_mpm-5.4.85.dist-info/METADATA +0 -1023
- /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
- /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""Communication adapters for managing async I/O with AI coding assistants.
|
|
2
|
+
|
|
3
|
+
This module provides the async communication layer that sits between
|
|
4
|
+
InstanceManager and TmuxOrchestrator, using RuntimeAdapter for parsing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import TYPE_CHECKING, AsyncIterator, Optional
|
|
13
|
+
|
|
14
|
+
from claude_mpm.commander.tmux_orchestrator import TmuxOrchestrator
|
|
15
|
+
|
|
16
|
+
from .base import RuntimeAdapter
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .base import ParsedResponse
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AdapterState(Enum):
|
|
25
|
+
"""States that a communication adapter can be in."""
|
|
26
|
+
|
|
27
|
+
IDLE = "idle" # Ready for input
|
|
28
|
+
PROCESSING = "processing" # Working on request
|
|
29
|
+
WAITING = "waiting" # Waiting for user input (e.g., permission)
|
|
30
|
+
ERROR = "error" # Error state
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class AdapterResponse:
|
|
35
|
+
"""Response from a communication adapter.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
content: The response content
|
|
39
|
+
state: Current adapter state
|
|
40
|
+
tool_uses: List of tools used in response
|
|
41
|
+
files_modified: List of files edited
|
|
42
|
+
is_complete: True if response is complete (prompt returned)
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> response = AdapterResponse(
|
|
46
|
+
... content="File created: test.py",
|
|
47
|
+
... state=AdapterState.IDLE,
|
|
48
|
+
... tool_uses=["Write"],
|
|
49
|
+
... files_modified=["test.py"],
|
|
50
|
+
... is_complete=True
|
|
51
|
+
... )
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
content: str
|
|
55
|
+
state: AdapterState
|
|
56
|
+
tool_uses: Optional[list[str]] = None
|
|
57
|
+
files_modified: Optional[list[str]] = None
|
|
58
|
+
is_complete: bool = False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BaseCommunicationAdapter(ABC):
|
|
62
|
+
"""Abstract base class for communication adapters.
|
|
63
|
+
|
|
64
|
+
A communication adapter manages the async I/O with an AI coding assistant
|
|
65
|
+
via TmuxOrchestrator, maintaining state and handling streaming responses.
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
>>> orchestrator = TmuxOrchestrator()
|
|
69
|
+
>>> adapter = ClaudeCodeCommunicationAdapter(orchestrator, "%0")
|
|
70
|
+
>>> await adapter.send("Fix the bug in main.py")
|
|
71
|
+
>>> response = await adapter.receive()
|
|
72
|
+
>>> print(response.content)
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
async def send(self, message: str) -> None:
|
|
77
|
+
"""Send a message to the assistant.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
message: The message to send
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> await adapter.send("Fix the bug in main.py")
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
async def receive(self, timeout: float = 30.0) -> AdapterResponse:
|
|
88
|
+
"""Wait for and return response.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
timeout: Maximum time to wait for response
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
AdapterResponse with parsed content and state
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
>>> response = await adapter.receive(timeout=60.0)
|
|
98
|
+
>>> print(response.content)
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
async def interrupt(self) -> bool:
|
|
103
|
+
"""Send interrupt signal (Ctrl+C).
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if interrupt was successful
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
>>> success = await adapter.interrupt()
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def is_ready(self) -> bool:
|
|
114
|
+
"""Check if adapter is ready for input.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
True if adapter is in IDLE state
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
>>> if adapter.is_ready():
|
|
121
|
+
... await adapter.send("Next task")
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
@abstractmethod
|
|
125
|
+
async def stream_response(self) -> AsyncIterator[str]:
|
|
126
|
+
"""Stream response chunks as they arrive.
|
|
127
|
+
|
|
128
|
+
Yields:
|
|
129
|
+
Response chunks as they become available
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
>>> async for chunk in adapter.stream_response():
|
|
133
|
+
... print(chunk, end='')
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ClaudeCodeCommunicationAdapter(BaseCommunicationAdapter):
|
|
138
|
+
"""Communication adapter for Claude Code CLI.
|
|
139
|
+
|
|
140
|
+
This adapter manages async I/O with Claude Code via TmuxOrchestrator,
|
|
141
|
+
using ClaudeCodeAdapter (RuntimeAdapter) for output parsing.
|
|
142
|
+
|
|
143
|
+
Attributes:
|
|
144
|
+
orchestrator: TmuxOrchestrator for tmux operations
|
|
145
|
+
pane_target: Tmux pane target (e.g., "%0")
|
|
146
|
+
poll_interval: Polling interval for output capture (seconds)
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
>>> orchestrator = TmuxOrchestrator()
|
|
150
|
+
>>> adapter = ClaudeCodeCommunicationAdapter(orchestrator, "%0")
|
|
151
|
+
>>> await adapter.send("Create a new Python file")
|
|
152
|
+
>>> response = await adapter.receive()
|
|
153
|
+
>>> print(response.files_modified)
|
|
154
|
+
['new_file.py']
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
def __init__(
|
|
158
|
+
self,
|
|
159
|
+
orchestrator: TmuxOrchestrator,
|
|
160
|
+
pane_target: str,
|
|
161
|
+
runtime_adapter: RuntimeAdapter,
|
|
162
|
+
poll_interval: float = 0.2,
|
|
163
|
+
):
|
|
164
|
+
"""Initialize the communication adapter.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
orchestrator: TmuxOrchestrator for tmux operations
|
|
168
|
+
pane_target: Tmux pane target (e.g., "%0")
|
|
169
|
+
runtime_adapter: RuntimeAdapter for parsing output
|
|
170
|
+
poll_interval: Polling interval for output capture (seconds)
|
|
171
|
+
"""
|
|
172
|
+
self.orchestrator = orchestrator
|
|
173
|
+
self.pane_target = pane_target
|
|
174
|
+
self.runtime_adapter = runtime_adapter
|
|
175
|
+
self.poll_interval = poll_interval
|
|
176
|
+
self._state = AdapterState.IDLE
|
|
177
|
+
self._last_output = ""
|
|
178
|
+
self._output_buffer = ""
|
|
179
|
+
|
|
180
|
+
async def send(self, message: str) -> None:
|
|
181
|
+
"""Send message to Claude Code.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
message: The message to send
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
>>> await adapter.send("Fix the bug in main.py")
|
|
188
|
+
"""
|
|
189
|
+
logger.debug(f"Sending message to {self.pane_target}: {message[:50]}...")
|
|
190
|
+
self._state = AdapterState.PROCESSING
|
|
191
|
+
self._output_buffer = ""
|
|
192
|
+
|
|
193
|
+
# Format message using RuntimeAdapter
|
|
194
|
+
formatted = self.runtime_adapter.format_input(message)
|
|
195
|
+
|
|
196
|
+
# Send via tmux
|
|
197
|
+
self.orchestrator.send_keys(self.pane_target, formatted, enter=True)
|
|
198
|
+
|
|
199
|
+
async def receive(self, timeout: float = 30.0) -> AdapterResponse:
|
|
200
|
+
"""Wait for complete response from Claude Code.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
timeout: Maximum time to wait for response
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
AdapterResponse with parsed content and state
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
>>> response = await adapter.receive(timeout=60.0)
|
|
210
|
+
>>> if response.is_complete:
|
|
211
|
+
... print("Task complete!")
|
|
212
|
+
"""
|
|
213
|
+
start = asyncio.get_event_loop().time()
|
|
214
|
+
|
|
215
|
+
while asyncio.get_event_loop().time() - start < timeout:
|
|
216
|
+
# Capture output from tmux pane
|
|
217
|
+
output = self.orchestrator.capture_output(self.pane_target, lines=100)
|
|
218
|
+
|
|
219
|
+
# Get only new output
|
|
220
|
+
new_output = self._get_new_output(output)
|
|
221
|
+
if new_output:
|
|
222
|
+
self._output_buffer += new_output
|
|
223
|
+
|
|
224
|
+
# Parse response using RuntimeAdapter
|
|
225
|
+
parsed = self.runtime_adapter.parse_response(output)
|
|
226
|
+
|
|
227
|
+
# Check if error occurred (prioritize error state)
|
|
228
|
+
if parsed.is_error:
|
|
229
|
+
self._state = AdapterState.ERROR
|
|
230
|
+
logger.warning(f"Error detected: {parsed.error_message}")
|
|
231
|
+
return self._build_response(parsed, is_complete=True)
|
|
232
|
+
|
|
233
|
+
# Check if waiting for user input (question)
|
|
234
|
+
if parsed.is_question:
|
|
235
|
+
self._state = AdapterState.WAITING
|
|
236
|
+
logger.debug(f"Question detected: {parsed.question_text}")
|
|
237
|
+
return self._build_response(parsed, is_complete=False)
|
|
238
|
+
|
|
239
|
+
# Check if response is complete (idle state)
|
|
240
|
+
if parsed.is_complete:
|
|
241
|
+
self._state = AdapterState.IDLE
|
|
242
|
+
logger.debug("Response complete (idle detected)")
|
|
243
|
+
return self._build_response(parsed, is_complete=True)
|
|
244
|
+
|
|
245
|
+
# Continue polling
|
|
246
|
+
await asyncio.sleep(self.poll_interval)
|
|
247
|
+
|
|
248
|
+
# Timeout - return partial response
|
|
249
|
+
logger.warning(f"Timeout after {timeout}s")
|
|
250
|
+
parsed = self.runtime_adapter.parse_response(self._output_buffer)
|
|
251
|
+
return self._build_response(parsed, is_complete=False)
|
|
252
|
+
|
|
253
|
+
async def interrupt(self) -> bool:
|
|
254
|
+
"""Send Ctrl+C to interrupt Claude Code.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
True if interrupt was successful
|
|
258
|
+
|
|
259
|
+
Example:
|
|
260
|
+
>>> success = await adapter.interrupt()
|
|
261
|
+
>>> if success:
|
|
262
|
+
... print("Interrupted successfully")
|
|
263
|
+
"""
|
|
264
|
+
try:
|
|
265
|
+
logger.info(f"Sending interrupt to {self.pane_target}")
|
|
266
|
+
self.orchestrator.send_keys(self.pane_target, "C-c", enter=False)
|
|
267
|
+
self._state = AdapterState.IDLE
|
|
268
|
+
return True
|
|
269
|
+
except Exception as e:
|
|
270
|
+
logger.error(f"Failed to interrupt: {e}")
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
def is_ready(self) -> bool:
|
|
274
|
+
"""Check if adapter is ready for input.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
True if adapter is in IDLE state
|
|
278
|
+
|
|
279
|
+
Example:
|
|
280
|
+
>>> if adapter.is_ready():
|
|
281
|
+
... await adapter.send("Next task")
|
|
282
|
+
"""
|
|
283
|
+
return self._state == AdapterState.IDLE
|
|
284
|
+
|
|
285
|
+
async def stream_response(self) -> AsyncIterator[str]:
|
|
286
|
+
"""Stream response chunks from Claude Code.
|
|
287
|
+
|
|
288
|
+
Yields:
|
|
289
|
+
Response chunks as they become available
|
|
290
|
+
|
|
291
|
+
Example:
|
|
292
|
+
>>> async for chunk in adapter.stream_response():
|
|
293
|
+
... print(chunk, end='', flush=True)
|
|
294
|
+
"""
|
|
295
|
+
last_len = 0
|
|
296
|
+
|
|
297
|
+
while self._state == AdapterState.PROCESSING:
|
|
298
|
+
# Capture current output
|
|
299
|
+
output = self.orchestrator.capture_output(self.pane_target, lines=100)
|
|
300
|
+
|
|
301
|
+
# Get new output since last check and add to buffer
|
|
302
|
+
new_output = self._get_new_output(output)
|
|
303
|
+
if new_output:
|
|
304
|
+
self._output_buffer += new_output
|
|
305
|
+
|
|
306
|
+
# Yield new chunk if buffer grew
|
|
307
|
+
if len(self._output_buffer) > last_len:
|
|
308
|
+
chunk = self._output_buffer[last_len:]
|
|
309
|
+
last_len = len(self._output_buffer)
|
|
310
|
+
yield chunk
|
|
311
|
+
|
|
312
|
+
# Check if complete using RuntimeAdapter
|
|
313
|
+
parsed = self.runtime_adapter.parse_response(output)
|
|
314
|
+
if parsed.is_complete:
|
|
315
|
+
self._state = AdapterState.IDLE
|
|
316
|
+
logger.debug("Streaming complete (idle detected)")
|
|
317
|
+
break
|
|
318
|
+
|
|
319
|
+
await asyncio.sleep(self.poll_interval)
|
|
320
|
+
|
|
321
|
+
def _get_new_output(self, current: str) -> str:
|
|
322
|
+
"""Get only new output since last capture.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
current: Current output from tmux pane
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
New output that hasn't been seen before
|
|
329
|
+
"""
|
|
330
|
+
if current == self._last_output:
|
|
331
|
+
return ""
|
|
332
|
+
|
|
333
|
+
# Find where new content starts
|
|
334
|
+
if self._last_output and current.startswith(self._last_output):
|
|
335
|
+
new = current[len(self._last_output) :]
|
|
336
|
+
else:
|
|
337
|
+
new = current
|
|
338
|
+
|
|
339
|
+
self._last_output = current
|
|
340
|
+
return new
|
|
341
|
+
|
|
342
|
+
def _build_response(
|
|
343
|
+
self,
|
|
344
|
+
parsed: "ParsedResponse",
|
|
345
|
+
is_complete: bool,
|
|
346
|
+
) -> AdapterResponse:
|
|
347
|
+
"""Build AdapterResponse from ParsedResponse.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
parsed: ParsedResponse from RuntimeAdapter
|
|
351
|
+
is_complete: Whether response is complete
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
AdapterResponse with metadata
|
|
355
|
+
"""
|
|
356
|
+
# TODO: Extract tool uses and files modified from content
|
|
357
|
+
# This would require additional parsing patterns in RuntimeAdapter
|
|
358
|
+
# For now, return basic response
|
|
359
|
+
|
|
360
|
+
return AdapterResponse(
|
|
361
|
+
content=parsed.content,
|
|
362
|
+
state=self._state,
|
|
363
|
+
tool_uses=None, # Future: extract from content
|
|
364
|
+
files_modified=None, # Future: extract from content
|
|
365
|
+
is_complete=is_complete,
|
|
366
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""MPM Commander REST API.
|
|
2
|
+
|
|
3
|
+
This package provides a FastAPI-based REST API for managing projects,
|
|
4
|
+
sessions, and messages in the MPM Commander.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
Run the API server with uvicorn:
|
|
8
|
+
|
|
9
|
+
$ uvicorn claude_mpm.commander.api.app:app --host 127.0.0.1 --port 8000
|
|
10
|
+
|
|
11
|
+
Access the API documentation at http://127.0.0.1:8000/docs
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .app import app
|
|
15
|
+
|
|
16
|
+
__all__ = ["app"]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""FastAPI application for MPM Commander REST API.
|
|
2
|
+
|
|
3
|
+
This module defines the main FastAPI application instance with CORS,
|
|
4
|
+
lifecycle management, and route registration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import AsyncGenerator, Optional
|
|
10
|
+
|
|
11
|
+
from fastapi import FastAPI
|
|
12
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
13
|
+
from fastapi.responses import FileResponse
|
|
14
|
+
from fastapi.staticfiles import StaticFiles
|
|
15
|
+
|
|
16
|
+
from ..events.manager import EventManager
|
|
17
|
+
from ..inbox import Inbox
|
|
18
|
+
from ..registry import ProjectRegistry
|
|
19
|
+
from ..tmux_orchestrator import TmuxOrchestrator
|
|
20
|
+
from ..workflow import EventHandler
|
|
21
|
+
from .routes import events, inbox as inbox_routes, messages, projects, sessions, work
|
|
22
|
+
|
|
23
|
+
# Global instances (injected at startup via lifespan)
|
|
24
|
+
registry: Optional[ProjectRegistry] = None
|
|
25
|
+
tmux: Optional[TmuxOrchestrator] = None
|
|
26
|
+
event_manager: Optional[EventManager] = None
|
|
27
|
+
inbox: Optional[Inbox] = None
|
|
28
|
+
event_handler: Optional[EventHandler] = None
|
|
29
|
+
session_manager: dict = {} # project_id -> ProjectSession
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@asynccontextmanager
|
|
33
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
34
|
+
"""Manage application lifecycle.
|
|
35
|
+
|
|
36
|
+
Initializes shared resources on startup and cleans up on shutdown.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
app: FastAPI application instance
|
|
40
|
+
|
|
41
|
+
Yields:
|
|
42
|
+
None during application runtime
|
|
43
|
+
"""
|
|
44
|
+
# Startup
|
|
45
|
+
global registry, tmux, event_manager, inbox, event_handler, session_manager
|
|
46
|
+
registry = ProjectRegistry()
|
|
47
|
+
tmux = TmuxOrchestrator()
|
|
48
|
+
event_manager = EventManager()
|
|
49
|
+
inbox = Inbox(event_manager, registry)
|
|
50
|
+
session_manager = {} # Populated by daemon when sessions are created
|
|
51
|
+
event_handler = EventHandler(inbox, session_manager)
|
|
52
|
+
|
|
53
|
+
yield
|
|
54
|
+
|
|
55
|
+
# Shutdown
|
|
56
|
+
# No cleanup needed for Phase 1
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
app = FastAPI(
|
|
60
|
+
title="MPM Commander API",
|
|
61
|
+
description="REST API for MPM Commander - Autonomous AI Orchestration",
|
|
62
|
+
version="1.0.0",
|
|
63
|
+
lifespan=lifespan,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# CORS for local development
|
|
67
|
+
app.add_middleware(
|
|
68
|
+
CORSMiddleware,
|
|
69
|
+
allow_origins=["http://localhost:*"],
|
|
70
|
+
allow_credentials=True,
|
|
71
|
+
allow_methods=["*"],
|
|
72
|
+
allow_headers=["*"],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Include routers
|
|
76
|
+
app.include_router(projects.router, prefix="/api", tags=["projects"])
|
|
77
|
+
app.include_router(sessions.router, prefix="/api", tags=["sessions"])
|
|
78
|
+
app.include_router(messages.router, prefix="/api", tags=["messages"])
|
|
79
|
+
app.include_router(inbox_routes.router, prefix="/api", tags=["inbox"])
|
|
80
|
+
app.include_router(events.router, prefix="/api", tags=["events"])
|
|
81
|
+
app.include_router(work.router, prefix="/api", tags=["work"])
|
|
82
|
+
|
|
83
|
+
# Mount static files
|
|
84
|
+
static_path = Path(__file__).parent.parent / "web" / "static"
|
|
85
|
+
app.mount("/static", StaticFiles(directory=str(static_path)), name="static")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.get("/api/health")
|
|
89
|
+
async def health_check() -> dict:
|
|
90
|
+
"""Health check endpoint.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Status and version information
|
|
94
|
+
"""
|
|
95
|
+
return {"status": "ok", "version": "1.0.0"}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@app.get("/")
|
|
99
|
+
async def root() -> FileResponse:
|
|
100
|
+
"""Serve the web UI index page.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
HTML page for the web UI
|
|
104
|
+
"""
|
|
105
|
+
return FileResponse(static_path / "index.html")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Error handling for MPM Commander API.
|
|
2
|
+
|
|
3
|
+
This module defines custom exception classes that map to HTTP error responses
|
|
4
|
+
with structured error codes and messages.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastapi import HTTPException
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CommanderAPIError(HTTPException):
|
|
11
|
+
"""Base exception for all Commander API errors.
|
|
12
|
+
|
|
13
|
+
Provides consistent error response format with code and message.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
code: Machine-readable error code
|
|
17
|
+
message: Human-readable error message
|
|
18
|
+
status_code: HTTP status code
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, code: str, message: str, status_code: int = 400):
|
|
22
|
+
"""Initialize API error.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
code: Error code (e.g., "PROJECT_NOT_FOUND")
|
|
26
|
+
message: Descriptive error message
|
|
27
|
+
status_code: HTTP status code (default: 400)
|
|
28
|
+
"""
|
|
29
|
+
super().__init__(
|
|
30
|
+
status_code=status_code,
|
|
31
|
+
detail={"error": {"code": code, "message": message}},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ProjectNotFoundError(CommanderAPIError):
|
|
36
|
+
"""Project with given ID does not exist."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, project_id: str):
|
|
39
|
+
"""Initialize project not found error.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
project_id: The project ID that was not found
|
|
43
|
+
"""
|
|
44
|
+
super().__init__(
|
|
45
|
+
"PROJECT_NOT_FOUND",
|
|
46
|
+
f"Project not found: {project_id}",
|
|
47
|
+
404,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ProjectAlreadyExistsError(CommanderAPIError):
|
|
52
|
+
"""Project already registered at given path."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, path: str):
|
|
55
|
+
"""Initialize project already exists error.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
path: The path that is already registered
|
|
59
|
+
"""
|
|
60
|
+
super().__init__(
|
|
61
|
+
"PROJECT_ALREADY_EXISTS",
|
|
62
|
+
f"Project already registered: {path}",
|
|
63
|
+
409,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class InvalidPathError(CommanderAPIError):
|
|
68
|
+
"""Path does not exist or is not a directory."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, path: str):
|
|
71
|
+
"""Initialize invalid path error.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
path: The invalid path
|
|
75
|
+
"""
|
|
76
|
+
super().__init__(
|
|
77
|
+
"INVALID_PATH",
|
|
78
|
+
f"Path does not exist or is not a directory: {path}",
|
|
79
|
+
400,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class SessionNotFoundError(CommanderAPIError):
|
|
84
|
+
"""Session with given ID does not exist."""
|
|
85
|
+
|
|
86
|
+
def __init__(self, session_id: str):
|
|
87
|
+
"""Initialize session not found error.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
session_id: The session ID that was not found
|
|
91
|
+
"""
|
|
92
|
+
super().__init__(
|
|
93
|
+
"SESSION_NOT_FOUND",
|
|
94
|
+
f"Session not found: {session_id}",
|
|
95
|
+
404,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class InvalidRuntimeError(CommanderAPIError):
|
|
100
|
+
"""Invalid runtime adapter specified."""
|
|
101
|
+
|
|
102
|
+
def __init__(self, runtime: str):
|
|
103
|
+
"""Initialize invalid runtime error.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
runtime: The invalid runtime name
|
|
107
|
+
"""
|
|
108
|
+
super().__init__(
|
|
109
|
+
"INVALID_RUNTIME",
|
|
110
|
+
f"Invalid runtime: {runtime}",
|
|
111
|
+
400,
|
|
112
|
+
)
|