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.
Files changed (254) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
  3. claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +101 -703
  5. claude_mpm/agents/WORKFLOW.md +2 -0
  6. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  7. claude_mpm/cli/commands/autotodos.py +566 -0
  8. claude_mpm/cli/commands/commander.py +46 -0
  9. claude_mpm/cli/commands/hook_errors.py +60 -60
  10. claude_mpm/cli/commands/monitor.py +2 -2
  11. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  12. claude_mpm/cli/commands/run.py +35 -3
  13. claude_mpm/cli/executor.py +119 -16
  14. claude_mpm/cli/parsers/base_parser.py +71 -1
  15. claude_mpm/cli/parsers/commander_parser.py +83 -0
  16. claude_mpm/cli/parsers/run_parser.py +10 -0
  17. claude_mpm/cli/startup.py +54 -16
  18. claude_mpm/cli/startup_display.py +72 -5
  19. claude_mpm/cli/startup_logging.py +2 -2
  20. claude_mpm/cli/utils.py +7 -3
  21. claude_mpm/commander/__init__.py +72 -0
  22. claude_mpm/commander/adapters/__init__.py +31 -0
  23. claude_mpm/commander/adapters/base.py +191 -0
  24. claude_mpm/commander/adapters/claude_code.py +361 -0
  25. claude_mpm/commander/adapters/communication.py +366 -0
  26. claude_mpm/commander/api/__init__.py +16 -0
  27. claude_mpm/commander/api/app.py +105 -0
  28. claude_mpm/commander/api/errors.py +112 -0
  29. claude_mpm/commander/api/routes/__init__.py +8 -0
  30. claude_mpm/commander/api/routes/events.py +184 -0
  31. claude_mpm/commander/api/routes/inbox.py +171 -0
  32. claude_mpm/commander/api/routes/messages.py +148 -0
  33. claude_mpm/commander/api/routes/projects.py +271 -0
  34. claude_mpm/commander/api/routes/sessions.py +215 -0
  35. claude_mpm/commander/api/routes/work.py +260 -0
  36. claude_mpm/commander/api/schemas.py +182 -0
  37. claude_mpm/commander/chat/__init__.py +7 -0
  38. claude_mpm/commander/chat/cli.py +107 -0
  39. claude_mpm/commander/chat/commands.py +96 -0
  40. claude_mpm/commander/chat/repl.py +310 -0
  41. claude_mpm/commander/config.py +49 -0
  42. claude_mpm/commander/config_loader.py +115 -0
  43. claude_mpm/commander/daemon.py +398 -0
  44. claude_mpm/commander/events/__init__.py +26 -0
  45. claude_mpm/commander/events/manager.py +332 -0
  46. claude_mpm/commander/frameworks/__init__.py +12 -0
  47. claude_mpm/commander/frameworks/base.py +143 -0
  48. claude_mpm/commander/frameworks/claude_code.py +58 -0
  49. claude_mpm/commander/frameworks/mpm.py +62 -0
  50. claude_mpm/commander/inbox/__init__.py +16 -0
  51. claude_mpm/commander/inbox/dedup.py +128 -0
  52. claude_mpm/commander/inbox/inbox.py +224 -0
  53. claude_mpm/commander/inbox/models.py +70 -0
  54. claude_mpm/commander/instance_manager.py +337 -0
  55. claude_mpm/commander/llm/__init__.py +6 -0
  56. claude_mpm/commander/llm/openrouter_client.py +167 -0
  57. claude_mpm/commander/llm/summarizer.py +70 -0
  58. claude_mpm/commander/models/__init__.py +18 -0
  59. claude_mpm/commander/models/events.py +121 -0
  60. claude_mpm/commander/models/project.py +162 -0
  61. claude_mpm/commander/models/work.py +214 -0
  62. claude_mpm/commander/parsing/__init__.py +20 -0
  63. claude_mpm/commander/parsing/extractor.py +132 -0
  64. claude_mpm/commander/parsing/output_parser.py +270 -0
  65. claude_mpm/commander/parsing/patterns.py +100 -0
  66. claude_mpm/commander/persistence/__init__.py +11 -0
  67. claude_mpm/commander/persistence/event_store.py +274 -0
  68. claude_mpm/commander/persistence/state_store.py +309 -0
  69. claude_mpm/commander/persistence/work_store.py +164 -0
  70. claude_mpm/commander/polling/__init__.py +13 -0
  71. claude_mpm/commander/polling/event_detector.py +104 -0
  72. claude_mpm/commander/polling/output_buffer.py +49 -0
  73. claude_mpm/commander/polling/output_poller.py +153 -0
  74. claude_mpm/commander/project_session.py +268 -0
  75. claude_mpm/commander/proxy/__init__.py +12 -0
  76. claude_mpm/commander/proxy/formatter.py +89 -0
  77. claude_mpm/commander/proxy/output_handler.py +191 -0
  78. claude_mpm/commander/proxy/relay.py +155 -0
  79. claude_mpm/commander/registry.py +404 -0
  80. claude_mpm/commander/runtime/__init__.py +10 -0
  81. claude_mpm/commander/runtime/executor.py +191 -0
  82. claude_mpm/commander/runtime/monitor.py +316 -0
  83. claude_mpm/commander/session/__init__.py +6 -0
  84. claude_mpm/commander/session/context.py +81 -0
  85. claude_mpm/commander/session/manager.py +59 -0
  86. claude_mpm/commander/tmux_orchestrator.py +361 -0
  87. claude_mpm/commander/web/__init__.py +1 -0
  88. claude_mpm/commander/work/__init__.py +30 -0
  89. claude_mpm/commander/work/executor.py +189 -0
  90. claude_mpm/commander/work/queue.py +405 -0
  91. claude_mpm/commander/workflow/__init__.py +27 -0
  92. claude_mpm/commander/workflow/event_handler.py +219 -0
  93. claude_mpm/commander/workflow/notifier.py +146 -0
  94. claude_mpm/commands/mpm-config.md +8 -0
  95. claude_mpm/commands/mpm-doctor.md +8 -0
  96. claude_mpm/commands/mpm-help.md +8 -0
  97. claude_mpm/commands/mpm-init.md +8 -0
  98. claude_mpm/commands/mpm-monitor.md +8 -0
  99. claude_mpm/commands/mpm-organize.md +8 -0
  100. claude_mpm/commands/mpm-postmortem.md +8 -0
  101. claude_mpm/commands/mpm-session-resume.md +9 -1
  102. claude_mpm/commands/mpm-status.md +8 -0
  103. claude_mpm/commands/mpm-ticket-view.md +8 -0
  104. claude_mpm/commands/mpm-version.md +8 -0
  105. claude_mpm/commands/mpm.md +8 -0
  106. claude_mpm/config/agent_presets.py +8 -7
  107. claude_mpm/core/config.py +5 -0
  108. claude_mpm/core/hook_manager.py +51 -3
  109. claude_mpm/core/logger.py +10 -7
  110. claude_mpm/core/logging_utils.py +4 -2
  111. claude_mpm/core/output_style_manager.py +15 -5
  112. claude_mpm/core/unified_config.py +10 -6
  113. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  114. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  115. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  116. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  117. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  118. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  119. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  120. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  121. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  122. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  123. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  124. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  125. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  126. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  127. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  128. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  129. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  130. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  131. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  132. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  133. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  134. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  135. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  136. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  137. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  138. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  139. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  140. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  141. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  142. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  143. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  144. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  145. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  146. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  147. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  148. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  149. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  150. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  151. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  152. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  153. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  154. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  155. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  156. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  157. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  158. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  159. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  160. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  161. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  162. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  163. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  164. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  165. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  166. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  167. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  168. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  169. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  170. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  171. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  172. claude_mpm/experimental/cli_enhancements.py +2 -1
  173. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  174. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  175. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
  176. claude_mpm/hooks/claude_hooks/event_handlers.py +250 -11
  177. claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
  178. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  179. claude_mpm/hooks/claude_hooks/installer.py +69 -5
  180. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
  181. claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
  182. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +14 -77
  183. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
  184. claude_mpm/hooks/session_resume_hook.py +85 -1
  185. claude_mpm/init.py +1 -1
  186. claude_mpm/scripts/claude-hook-handler.sh +36 -10
  187. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  188. claude_mpm/services/agents/cache_git_manager.py +1 -1
  189. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  190. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  191. claude_mpm/services/cli/__init__.py +3 -0
  192. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  193. claude_mpm/services/cli/session_resume_helper.py +10 -2
  194. claude_mpm/services/delegation_detector.py +175 -0
  195. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  196. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  197. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  198. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  199. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  200. claude_mpm/services/diagnostics/models.py +14 -1
  201. claude_mpm/services/event_log.py +325 -0
  202. claude_mpm/services/infrastructure/__init__.py +4 -0
  203. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  204. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  205. claude_mpm/services/monitor/daemon_manager.py +15 -4
  206. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  207. claude_mpm/services/monitor/server.py +106 -16
  208. claude_mpm/services/pm_skills_deployer.py +259 -87
  209. claude_mpm/services/skills/git_skill_source_manager.py +51 -2
  210. claude_mpm/services/skills/selective_skill_deployer.py +114 -16
  211. claude_mpm/services/skills/skill_discovery_service.py +57 -3
  212. claude_mpm/services/socketio/handlers/hook.py +14 -7
  213. claude_mpm/services/socketio/server/main.py +12 -4
  214. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  215. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  216. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  217. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  218. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  219. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  220. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  221. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  222. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  223. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  224. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  225. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  226. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  227. claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
  228. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  229. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  230. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  231. claude_mpm/skills/skill_manager.py +4 -4
  232. claude_mpm-5.6.1.dist-info/METADATA +391 -0
  233. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/RECORD +244 -145
  234. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  235. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  236. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  237. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  238. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  239. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  240. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  241. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  242. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  243. claude_mpm-5.4.85.dist-info/METADATA +0 -1023
  244. /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
  245. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  246. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  247. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  248. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  249. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  250. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/WHEEL +0 -0
  251. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/entry_points.txt +0 -0
  252. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE +0 -0
  253. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  254. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,184 @@
1
+ """REST API routes for event resolution.
2
+
3
+ Provides HTTP endpoints for resolving events and managing event responses.
4
+ """
5
+
6
+ from typing import List, Optional
7
+
8
+ from fastapi import APIRouter, Depends, HTTPException, Query
9
+ from pydantic import BaseModel
10
+
11
+ from ...events.manager import EventManager
12
+
13
+ router = APIRouter()
14
+
15
+
16
+ def get_event_manager() -> EventManager:
17
+ """Dependency to get the global event manager instance.
18
+
19
+ Returns:
20
+ The global event manager instance
21
+
22
+ Raises:
23
+ RuntimeError: If event manager is not initialized
24
+ """
25
+ from ..app import event_manager
26
+
27
+ if event_manager is None:
28
+ raise RuntimeError("Event manager not initialized")
29
+ return event_manager
30
+
31
+
32
+ def get_event_handler():
33
+ """Dependency to get the global event handler instance.
34
+
35
+ Returns:
36
+ The global event handler instance
37
+
38
+ Raises:
39
+ RuntimeError: If event handler is not initialized
40
+ """
41
+ from ..app import event_handler
42
+
43
+ if event_handler is None:
44
+ raise RuntimeError("Event handler not initialized")
45
+ return event_handler
46
+
47
+
48
+ class EventResponse(BaseModel):
49
+ """Request model for event resolution.
50
+
51
+ Attributes:
52
+ response: User's response to the event
53
+ """
54
+
55
+ response: str
56
+
57
+
58
+ class EventResolutionResponse(BaseModel):
59
+ """Response model for event resolution.
60
+
61
+ Attributes:
62
+ event_id: ID of resolved event
63
+ status: Resolution status
64
+ session_resumed: Whether project session was resumed
65
+ """
66
+
67
+ event_id: str
68
+ status: str
69
+ session_resumed: bool
70
+
71
+
72
+ class PendingEventResponse(BaseModel):
73
+ """Response model for pending event.
74
+
75
+ Attributes:
76
+ event_id: Unique event identifier
77
+ project_id: Project that raised this event
78
+ event_type: Type of event
79
+ priority: Urgency level
80
+ status: Current lifecycle status
81
+ title: Short event summary
82
+ content: Detailed event message
83
+ options: For DECISION_NEEDED events, list of choices
84
+ is_blocking: Whether event blocks progress
85
+ """
86
+
87
+ event_id: str
88
+ project_id: str
89
+ event_type: str
90
+ priority: str
91
+ status: str
92
+ title: str
93
+ content: str
94
+ options: Optional[List[str]]
95
+ is_blocking: bool
96
+
97
+
98
+ @router.post("/events/{event_id}/resolve", response_model=EventResolutionResponse)
99
+ async def resolve_event(
100
+ event_id: str,
101
+ response: EventResponse,
102
+ event_handler=Depends(get_event_handler),
103
+ ) -> EventResolutionResponse:
104
+ """Resolve an event with user response.
105
+
106
+ Marks the event as resolved and resumes the project session if it was
107
+ paused for this event.
108
+
109
+ Args:
110
+ event_id: ID of event to resolve
111
+ response: User's response to the event
112
+
113
+ Returns:
114
+ Resolution status and whether session was resumed
115
+
116
+ Raises:
117
+ HTTPException: If event not found (404) or resolution fails (500)
118
+
119
+ Example:
120
+ POST /api/events/evt_123/resolve
121
+ {
122
+ "response": "Use authlib for OAuth2"
123
+ }
124
+
125
+ Response:
126
+ {
127
+ "event_id": "evt_123",
128
+ "status": "resolved",
129
+ "session_resumed": true
130
+ }
131
+ """
132
+ try:
133
+ session_resumed = await event_handler.resolve_event(event_id, response.response)
134
+ return EventResolutionResponse(
135
+ event_id=event_id,
136
+ status="resolved",
137
+ session_resumed=session_resumed,
138
+ )
139
+ except KeyError as e:
140
+ raise HTTPException(
141
+ status_code=404, detail=f"Event not found: {event_id}"
142
+ ) from e
143
+ except Exception as e:
144
+ raise HTTPException(
145
+ status_code=500, detail=f"Failed to resolve event: {e!s}"
146
+ ) from e
147
+
148
+
149
+ @router.get("/events/pending", response_model=List[PendingEventResponse])
150
+ async def get_pending_events(
151
+ project_id: Optional[str] = Query(None, description="Filter by project ID"),
152
+ event_handler=Depends(get_event_handler),
153
+ ) -> List[PendingEventResponse]:
154
+ """Get pending events requiring resolution.
155
+
156
+ Returns all unresolved events, optionally filtered by project.
157
+ Events are sorted by priority (high to low) then created time (old to new).
158
+
159
+ Args:
160
+ project_id: If provided, only return events for this project
161
+
162
+ Returns:
163
+ List of pending events
164
+
165
+ Example:
166
+ GET /api/events/pending
167
+ GET /api/events/pending?project_id=proj_123
168
+ """
169
+ events = await event_handler.get_pending_events(project_id)
170
+
171
+ return [
172
+ PendingEventResponse(
173
+ event_id=event.id,
174
+ project_id=event.project_id,
175
+ event_type=event.type.value,
176
+ priority=event.priority.value,
177
+ status=event.status.value,
178
+ title=event.title,
179
+ content=event.content,
180
+ options=event.options,
181
+ is_blocking=event_handler.is_blocking(event),
182
+ )
183
+ for event in events
184
+ ]
@@ -0,0 +1,171 @@
1
+ """REST API routes for inbox system.
2
+
3
+ Provides HTTP endpoints for querying and managing the event inbox.
4
+ """
5
+
6
+ from datetime import datetime
7
+ from typing import List, Optional
8
+
9
+ from fastapi import APIRouter, Depends, Query
10
+ from pydantic import BaseModel
11
+
12
+ from ...inbox import Inbox
13
+ from ...models.events import EventPriority, EventType
14
+
15
+ router = APIRouter()
16
+
17
+
18
+ def get_inbox() -> Inbox:
19
+ """Dependency to get the global inbox instance.
20
+
21
+ Returns:
22
+ The global inbox instance
23
+
24
+ Raises:
25
+ RuntimeError: If inbox is not initialized
26
+ """
27
+ from ..app import inbox
28
+
29
+ if inbox is None:
30
+ raise RuntimeError("Inbox not initialized")
31
+ return inbox
32
+
33
+
34
+ class InboxItemResponse(BaseModel):
35
+ """Response model for inbox item.
36
+
37
+ Attributes:
38
+ event_id: Unique event identifier
39
+ project_id: Project that raised this event
40
+ project_name: Human-readable project name
41
+ project_path: Filesystem path to project
42
+ event_type: Type of event (decision, error, status, etc.)
43
+ priority: Urgency level (critical, high, normal, low, info)
44
+ status: Current lifecycle status
45
+ title: Short event summary
46
+ content: Detailed event message
47
+ options: For DECISION_NEEDED events, list of choices
48
+ age_display: Human-readable age (e.g., "5m ago")
49
+ created_at: When event was created
50
+ session_runtime: Optional session runtime identifier
51
+ """
52
+
53
+ event_id: str
54
+ project_id: str
55
+ project_name: str
56
+ project_path: str
57
+ event_type: str
58
+ priority: str
59
+ status: str
60
+ title: str
61
+ content: str
62
+ options: Optional[List[str]]
63
+ age_display: str
64
+ created_at: datetime
65
+ session_runtime: Optional[str]
66
+
67
+
68
+ class InboxCountsResponse(BaseModel):
69
+ """Response model for inbox counts.
70
+
71
+ Attributes:
72
+ critical: Count of CRITICAL priority events
73
+ high: Count of HIGH priority events
74
+ normal: Count of NORMAL priority events
75
+ low: Count of LOW priority events
76
+ info: Count of INFO priority events
77
+ total: Total count of all pending events
78
+ """
79
+
80
+ critical: int
81
+ high: int
82
+ normal: int
83
+ low: int
84
+ info: int
85
+ total: int
86
+
87
+
88
+ @router.get("/inbox", response_model=List[InboxItemResponse])
89
+ async def get_inbox_items(
90
+ limit: int = Query(50, ge=1, le=100, description="Maximum items to return"),
91
+ offset: int = Query(0, ge=0, description="Number of items to skip"),
92
+ priority: Optional[str] = Query(None, description="Filter by priority level"),
93
+ project_id: Optional[str] = Query(None, description="Filter by project ID"),
94
+ event_type: Optional[str] = Query(None, description="Filter by event type"),
95
+ inbox: Inbox = Depends(get_inbox),
96
+ ) -> List[InboxItemResponse]:
97
+ """Get inbox items with optional filtering and pagination.
98
+
99
+ Returns a list of inbox items sorted by priority (high to low) then
100
+ created time (old to new). Supports filtering by priority, project,
101
+ and event type.
102
+
103
+ Args:
104
+ limit: Maximum number of items to return (1-100, default: 50)
105
+ offset: Number of items to skip for pagination (default: 0)
106
+ priority: Filter by priority (critical, high, normal, low, info)
107
+ project_id: Filter by project ID
108
+ event_type: Filter by event type (decision_needed, error, etc.)
109
+
110
+ Returns:
111
+ List of inbox items matching the filters
112
+
113
+ Example:
114
+ GET /api/inbox?limit=20&priority=critical
115
+ GET /api/inbox?project_id=proj_123&event_type=error
116
+ GET /api/inbox?offset=50&limit=50 # Pagination
117
+ """
118
+ # Parse enum values if provided
119
+ pri = EventPriority(priority) if priority else None
120
+ evt = EventType(event_type) if event_type else None
121
+
122
+ items = inbox.get_items(
123
+ limit=limit,
124
+ offset=offset,
125
+ priority=pri,
126
+ project_id=project_id,
127
+ event_type=evt,
128
+ )
129
+
130
+ return [
131
+ InboxItemResponse(
132
+ event_id=item.event.id,
133
+ project_id=item.event.project_id,
134
+ project_name=item.project_name,
135
+ project_path=item.project_path,
136
+ event_type=item.event.type.value,
137
+ priority=item.event.priority.value,
138
+ status=item.event.status.value,
139
+ title=item.event.title,
140
+ content=item.event.content,
141
+ options=item.event.options,
142
+ age_display=item.age_display,
143
+ created_at=item.event.created_at,
144
+ session_runtime=item.session_runtime,
145
+ )
146
+ for item in items
147
+ ]
148
+
149
+
150
+ @router.get("/inbox/counts", response_model=InboxCountsResponse)
151
+ async def get_inbox_counts(
152
+ project_id: Optional[str] = Query(None, description="Filter by project ID"),
153
+ inbox: Inbox = Depends(get_inbox),
154
+ ) -> InboxCountsResponse:
155
+ """Get count of pending events by priority.
156
+
157
+ Returns summary statistics showing how many events exist at each
158
+ priority level. Optionally filtered to a specific project.
159
+
160
+ Args:
161
+ project_id: If provided, only count events for this project
162
+
163
+ Returns:
164
+ Breakdown of event counts by priority
165
+
166
+ Example:
167
+ GET /api/inbox/counts
168
+ GET /api/inbox/counts?project_id=proj_123
169
+ """
170
+ counts = inbox.get_counts(project_id)
171
+ return InboxCountsResponse(**counts.__dict__)
@@ -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
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():
20
+ """Get registry instance from app global."""
21
+ from ..app import registry
22
+
23
+ if registry is None:
24
+ raise RuntimeError("Registry not initialized")
25
+ return registry
26
+
27
+
28
+ def _message_to_response(message: ThreadMessage) -> MessageResponse:
29
+ """Convert ThreadMessage model to MessageResponse schema.
30
+
31
+ Args:
32
+ message: ThreadMessage instance
33
+
34
+ Returns:
35
+ MessageResponse with message data
36
+ """
37
+ return MessageResponse(
38
+ id=message.id,
39
+ role=message.role,
40
+ content=message.content,
41
+ session_id=message.session_id,
42
+ timestamp=message.timestamp,
43
+ )
44
+
45
+
46
+ @router.get("/projects/{project_id}/thread", response_model=List[MessageResponse])
47
+ async def get_thread(project_id: str) -> List[MessageResponse]:
48
+ """Get conversation thread for a project.
49
+
50
+ Returns all messages in chronological order.
51
+
52
+ Args:
53
+ project_id: Unique project identifier
54
+
55
+ Returns:
56
+ List of messages in thread (may be empty)
57
+
58
+ Raises:
59
+ ProjectNotFoundError: If project_id doesn't exist
60
+
61
+ Example:
62
+ GET /api/projects/abc-123/thread
63
+ Response: [
64
+ {
65
+ "id": "msg-1",
66
+ "role": "user",
67
+ "content": "Fix the login bug",
68
+ "session_id": null,
69
+ "timestamp": "2025-01-12T10:00:00Z"
70
+ },
71
+ {
72
+ "id": "msg-2",
73
+ "role": "assistant",
74
+ "content": "I'll investigate the login issue",
75
+ "session_id": "sess-456",
76
+ "timestamp": "2025-01-12T10:00:30Z"
77
+ }
78
+ ]
79
+ """
80
+ registry = _get_registry()
81
+ project = registry.get(project_id)
82
+
83
+ if project is None:
84
+ raise ProjectNotFoundError(project_id)
85
+
86
+ # Convert thread messages to responses
87
+ return [_message_to_response(m) for m in project.thread]
88
+
89
+
90
+ @router.post(
91
+ "/projects/{project_id}/messages", response_model=MessageResponse, status_code=201
92
+ )
93
+ async def send_message(project_id: str, req: SendMessageRequest) -> 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()
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)