claude-mpm 5.4.65__py3-none-any.whl → 5.6.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (313) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
  3. claude_mpm/agents/CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md +413 -0
  4. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +107 -1928
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +119 -689
  6. claude_mpm/agents/WORKFLOW.md +2 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  8. claude_mpm/cli/__init__.py +5 -1
  9. claude_mpm/cli/commands/agents.py +2 -4
  10. claude_mpm/cli/commands/agents_reconcile.py +197 -0
  11. claude_mpm/cli/commands/autotodos.py +566 -0
  12. claude_mpm/cli/commands/commander.py +46 -0
  13. claude_mpm/cli/commands/configure.py +620 -21
  14. claude_mpm/cli/commands/hook_errors.py +60 -60
  15. claude_mpm/cli/commands/monitor.py +2 -2
  16. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  17. claude_mpm/cli/commands/run.py +35 -3
  18. claude_mpm/cli/commands/skill_source.py +51 -2
  19. claude_mpm/cli/commands/skills.py +171 -17
  20. claude_mpm/cli/executor.py +120 -16
  21. claude_mpm/cli/interactive/__init__.py +10 -0
  22. claude_mpm/cli/interactive/agent_wizard.py +30 -50
  23. claude_mpm/cli/interactive/questionary_styles.py +65 -0
  24. claude_mpm/cli/interactive/skill_selector.py +481 -0
  25. claude_mpm/cli/parsers/base_parser.py +76 -1
  26. claude_mpm/cli/parsers/commander_parser.py +83 -0
  27. claude_mpm/cli/parsers/run_parser.py +10 -0
  28. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  29. claude_mpm/cli/parsers/skills_parser.py +5 -0
  30. claude_mpm/cli/startup.py +203 -359
  31. claude_mpm/cli/startup_display.py +72 -5
  32. claude_mpm/cli/startup_logging.py +2 -2
  33. claude_mpm/cli/utils.py +7 -3
  34. claude_mpm/commander/__init__.py +72 -0
  35. claude_mpm/commander/adapters/__init__.py +31 -0
  36. claude_mpm/commander/adapters/base.py +191 -0
  37. claude_mpm/commander/adapters/claude_code.py +361 -0
  38. claude_mpm/commander/adapters/communication.py +366 -0
  39. claude_mpm/commander/api/__init__.py +16 -0
  40. claude_mpm/commander/api/app.py +105 -0
  41. claude_mpm/commander/api/errors.py +133 -0
  42. claude_mpm/commander/api/routes/__init__.py +8 -0
  43. claude_mpm/commander/api/routes/events.py +184 -0
  44. claude_mpm/commander/api/routes/inbox.py +171 -0
  45. claude_mpm/commander/api/routes/messages.py +148 -0
  46. claude_mpm/commander/api/routes/projects.py +271 -0
  47. claude_mpm/commander/api/routes/sessions.py +228 -0
  48. claude_mpm/commander/api/routes/work.py +260 -0
  49. claude_mpm/commander/api/schemas.py +182 -0
  50. claude_mpm/commander/chat/__init__.py +7 -0
  51. claude_mpm/commander/chat/cli.py +107 -0
  52. claude_mpm/commander/chat/commands.py +96 -0
  53. claude_mpm/commander/chat/repl.py +310 -0
  54. claude_mpm/commander/config.py +49 -0
  55. claude_mpm/commander/config_loader.py +115 -0
  56. claude_mpm/commander/daemon.py +398 -0
  57. claude_mpm/commander/events/__init__.py +26 -0
  58. claude_mpm/commander/events/manager.py +332 -0
  59. claude_mpm/commander/frameworks/__init__.py +12 -0
  60. claude_mpm/commander/frameworks/base.py +143 -0
  61. claude_mpm/commander/frameworks/claude_code.py +58 -0
  62. claude_mpm/commander/frameworks/mpm.py +62 -0
  63. claude_mpm/commander/inbox/__init__.py +16 -0
  64. claude_mpm/commander/inbox/dedup.py +128 -0
  65. claude_mpm/commander/inbox/inbox.py +224 -0
  66. claude_mpm/commander/inbox/models.py +70 -0
  67. claude_mpm/commander/instance_manager.py +337 -0
  68. claude_mpm/commander/llm/__init__.py +6 -0
  69. claude_mpm/commander/llm/openrouter_client.py +167 -0
  70. claude_mpm/commander/llm/summarizer.py +70 -0
  71. claude_mpm/commander/models/__init__.py +18 -0
  72. claude_mpm/commander/models/events.py +121 -0
  73. claude_mpm/commander/models/project.py +162 -0
  74. claude_mpm/commander/models/work.py +214 -0
  75. claude_mpm/commander/parsing/__init__.py +20 -0
  76. claude_mpm/commander/parsing/extractor.py +132 -0
  77. claude_mpm/commander/parsing/output_parser.py +270 -0
  78. claude_mpm/commander/parsing/patterns.py +100 -0
  79. claude_mpm/commander/persistence/__init__.py +11 -0
  80. claude_mpm/commander/persistence/event_store.py +274 -0
  81. claude_mpm/commander/persistence/state_store.py +309 -0
  82. claude_mpm/commander/persistence/work_store.py +164 -0
  83. claude_mpm/commander/polling/__init__.py +13 -0
  84. claude_mpm/commander/polling/event_detector.py +104 -0
  85. claude_mpm/commander/polling/output_buffer.py +49 -0
  86. claude_mpm/commander/polling/output_poller.py +153 -0
  87. claude_mpm/commander/project_session.py +268 -0
  88. claude_mpm/commander/proxy/__init__.py +12 -0
  89. claude_mpm/commander/proxy/formatter.py +89 -0
  90. claude_mpm/commander/proxy/output_handler.py +191 -0
  91. claude_mpm/commander/proxy/relay.py +155 -0
  92. claude_mpm/commander/registry.py +404 -0
  93. claude_mpm/commander/runtime/__init__.py +10 -0
  94. claude_mpm/commander/runtime/executor.py +191 -0
  95. claude_mpm/commander/runtime/monitor.py +316 -0
  96. claude_mpm/commander/session/__init__.py +6 -0
  97. claude_mpm/commander/session/context.py +81 -0
  98. claude_mpm/commander/session/manager.py +59 -0
  99. claude_mpm/commander/tmux_orchestrator.py +361 -0
  100. claude_mpm/commander/web/__init__.py +1 -0
  101. claude_mpm/commander/work/__init__.py +30 -0
  102. claude_mpm/commander/work/executor.py +189 -0
  103. claude_mpm/commander/work/queue.py +405 -0
  104. claude_mpm/commander/workflow/__init__.py +27 -0
  105. claude_mpm/commander/workflow/event_handler.py +219 -0
  106. claude_mpm/commander/workflow/notifier.py +146 -0
  107. claude_mpm/commands/mpm-config.md +8 -0
  108. claude_mpm/commands/mpm-doctor.md +8 -0
  109. claude_mpm/commands/mpm-help.md +8 -0
  110. claude_mpm/commands/mpm-init.md +8 -0
  111. claude_mpm/commands/mpm-monitor.md +8 -0
  112. claude_mpm/commands/mpm-organize.md +8 -0
  113. claude_mpm/commands/mpm-postmortem.md +8 -0
  114. claude_mpm/commands/mpm-session-resume.md +9 -1
  115. claude_mpm/commands/mpm-status.md +8 -0
  116. claude_mpm/commands/mpm-ticket-view.md +8 -0
  117. claude_mpm/commands/mpm-version.md +8 -0
  118. claude_mpm/commands/mpm.md +8 -0
  119. claude_mpm/config/agent_presets.py +8 -7
  120. claude_mpm/config/skill_sources.py +16 -0
  121. claude_mpm/constants.py +1 -0
  122. claude_mpm/core/claude_runner.py +2 -2
  123. claude_mpm/core/config.py +32 -19
  124. claude_mpm/core/hook_manager.py +51 -3
  125. claude_mpm/core/interactive_session.py +7 -7
  126. claude_mpm/core/logger.py +26 -9
  127. claude_mpm/core/logging_utils.py +35 -11
  128. claude_mpm/core/output_style_manager.py +31 -13
  129. claude_mpm/core/unified_config.py +54 -8
  130. claude_mpm/core/unified_paths.py +95 -90
  131. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  132. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  133. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  134. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  135. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  136. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  137. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  138. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  139. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  140. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  141. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  142. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  143. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  144. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  145. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  146. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  147. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  148. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  149. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  150. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  151. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  152. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  153. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  154. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  155. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  156. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  157. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  158. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  159. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  160. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  161. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  162. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  163. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  164. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  165. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  166. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  167. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  168. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  169. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  170. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  171. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  172. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  173. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  174. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  175. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  176. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  177. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  178. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  179. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  180. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  181. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  182. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  183. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  184. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  185. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  186. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  187. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  188. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  189. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  190. claude_mpm/experimental/cli_enhancements.py +2 -1
  191. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  192. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  193. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  194. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  195. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
  196. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
  197. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  198. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
  199. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
  200. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  201. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
  202. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
  203. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  204. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
  205. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  206. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
  207. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
  208. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  209. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
  210. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
  211. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
  212. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
  213. claude_mpm/hooks/claude_hooks/event_handlers.py +283 -87
  214. claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
  215. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  216. claude_mpm/hooks/claude_hooks/installer.py +116 -8
  217. claude_mpm/hooks/claude_hooks/memory_integration.py +51 -31
  218. claude_mpm/hooks/claude_hooks/response_tracking.py +42 -59
  219. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
  220. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  221. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
  222. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
  223. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
  224. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  225. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
  226. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
  227. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  228. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
  229. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
  230. claude_mpm/hooks/claude_hooks/services/connection_manager.py +39 -24
  231. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +36 -103
  232. claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
  233. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +73 -75
  234. claude_mpm/hooks/session_resume_hook.py +89 -1
  235. claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
  236. claude_mpm/init.py +1 -1
  237. claude_mpm/scripts/claude-hook-handler.sh +43 -16
  238. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  239. claude_mpm/services/agents/agent_selection_service.py +2 -2
  240. claude_mpm/services/agents/cache_git_manager.py +1 -1
  241. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  242. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  243. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  244. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  245. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  246. claude_mpm/services/agents/startup_sync.py +5 -2
  247. claude_mpm/services/cli/__init__.py +3 -0
  248. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  249. claude_mpm/services/cli/session_resume_helper.py +10 -2
  250. claude_mpm/services/delegation_detector.py +175 -0
  251. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  252. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  253. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  254. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  255. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  256. claude_mpm/services/diagnostics/models.py +14 -1
  257. claude_mpm/services/event_log.py +325 -0
  258. claude_mpm/services/infrastructure/__init__.py +4 -0
  259. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  260. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  261. claude_mpm/services/monitor/daemon_manager.py +15 -4
  262. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  263. claude_mpm/services/monitor/server.py +106 -16
  264. claude_mpm/services/pm_skills_deployer.py +259 -87
  265. claude_mpm/services/skills/git_skill_source_manager.py +135 -11
  266. claude_mpm/services/skills/selective_skill_deployer.py +142 -26
  267. claude_mpm/services/skills/skill_discovery_service.py +74 -4
  268. claude_mpm/services/skills_deployer.py +31 -5
  269. claude_mpm/services/socketio/handlers/hook.py +14 -7
  270. claude_mpm/services/socketio/server/main.py +12 -4
  271. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  272. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  273. claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
  274. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  275. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  276. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  277. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  278. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  279. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  280. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  281. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  282. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  283. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  284. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  285. claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
  286. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  287. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  288. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  289. claude_mpm/skills/skill_manager.py +4 -4
  290. claude_mpm/utils/agent_dependency_loader.py +4 -2
  291. claude_mpm/utils/robust_installer.py +10 -6
  292. claude_mpm-5.6.10.dist-info/METADATA +391 -0
  293. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/RECORD +303 -181
  294. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  295. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  296. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  297. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  298. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  299. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  300. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  301. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  302. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  303. claude_mpm-5.4.65.dist-info/METADATA +0 -999
  304. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  305. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  306. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  307. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  308. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  309. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/WHEEL +0 -0
  310. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/entry_points.txt +0 -0
  311. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/licenses/LICENSE +0 -0
  312. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  313. {claude_mpm-5.4.65.dist-info → claude_mpm-5.6.10.dist-info}/top_level.txt +0 -0
@@ -7,14 +7,27 @@ This service manages:
7
7
  DESIGN DECISION: Use stateless HTTP POST instead of persistent SocketIO
8
8
  connections because hook handlers are ephemeral processes (< 1 second lifetime).
9
9
  This eliminates disconnection issues and matches the process lifecycle.
10
+
11
+ DESIGN DECISION: Synchronous HTTP POST only (no async)
12
+ Hook handlers are too short-lived (~25ms lifecycle) to benefit from async.
13
+ Using asyncio.run() creates event loops that close before HTTP operations complete,
14
+ causing "Event loop is closed" errors. Synchronous HTTP POST in a thread pool
15
+ is simpler and more reliable for ephemeral processes.
10
16
  """
11
17
 
12
- import asyncio
13
18
  import os
14
- import sys
15
19
  from concurrent.futures import ThreadPoolExecutor
16
20
  from datetime import datetime, timezone
17
21
 
22
+ # Try to import _log from hook_handler, fall back to no-op
23
+ try:
24
+ from claude_mpm.hooks.claude_hooks.hook_handler import _log
25
+ except ImportError:
26
+
27
+ def _log(msg: str) -> None:
28
+ pass # Silent fallback
29
+
30
+
18
31
  # Debug mode is enabled by default for better visibility into hook processing
19
32
  DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
20
33
 
@@ -27,9 +40,6 @@ except ImportError:
27
40
  REQUESTS_AVAILABLE = False
28
41
  requests = None
29
42
 
30
- # Import high-performance event emitter - lazy loaded in _async_emit()
31
- # to reduce hook handler initialization time by ~85% (792ms -> minimal)
32
-
33
43
  # Import EventNormalizer for consistent event formatting
34
44
  try:
35
45
  from claude_mpm.services.socketio.event_normalizer import EventNormalizer
@@ -55,10 +65,6 @@ except ImportError:
55
65
  )
56
66
 
57
67
 
58
- # EventBus removed - using direct HTTP POST only
59
- # This eliminates duplicate events and simplifies the architecture
60
-
61
-
62
68
  class ConnectionManagerService:
63
69
  """Manages connections for the Claude hook handler using HTTP POST."""
64
70
 
@@ -72,35 +78,26 @@ class ConnectionManagerService:
72
78
  self.server_port = int(os.environ.get("CLAUDE_MPM_SERVER_PORT", "8765"))
73
79
  self.http_endpoint = f"http://{self.server_host}:{self.server_port}/api/events"
74
80
 
75
- # EventBus removed - using direct HTTP POST only
76
-
77
- # For backward compatibility with tests
78
- self.connection_pool = None # No longer used
79
-
80
- # Track async emit tasks to prevent garbage collection
81
- self._emit_tasks: set = set()
82
-
83
81
  # Thread pool for non-blocking HTTP requests
84
82
  # WHY: Prevents HTTP POST from blocking hook processing (2s timeout → 0ms blocking)
85
- # max_workers=2: Sufficient for low-frequency HTTP fallback events
83
+ # max_workers=2: Sufficient for low-frequency hook events
86
84
  self._http_executor = ThreadPoolExecutor(
87
85
  max_workers=2, thread_name_prefix="http-emit"
88
86
  )
89
87
 
90
88
  if DEBUG:
91
- print(
92
- f"✅ HTTP connection manager initialized - endpoint: {self.http_endpoint}",
93
- file=sys.stderr,
89
+ _log(
90
+ f"✅ HTTP connection manager initialized - endpoint: {self.http_endpoint}"
94
91
  )
95
92
 
96
93
  def emit_event(self, namespace: str, event: str, data: dict):
97
- """Emit event using high-performance async emitter with HTTP fallback.
94
+ """Emit event using HTTP POST.
98
95
 
99
- WHY Hybrid approach:
100
- - Direct async calls for ultra-low latency in-process events
101
- - HTTP POST fallback for cross-process communication
102
- - Connection pooling for memory protection
103
- - Automatic routing based on availability
96
+ WHY HTTP POST only:
97
+ - Hook handlers are ephemeral (~25ms lifecycle)
98
+ - Async emission causes "Event loop is closed" errors
99
+ - HTTP POST in thread pool is simpler and more reliable
100
+ - Completes in 20-50ms, which is acceptable for hook handlers
104
101
  """
105
102
  # Create event data for normalization
106
103
  raw_event = {
@@ -120,74 +117,17 @@ class ConnectionManagerService:
120
117
  if DEBUG and event in ["subagent_stop", "pre_tool"]:
121
118
  if event == "subagent_stop":
122
119
  agent_type = data.get("agent_type", "unknown")
123
- print(
124
- f"Hook handler: Publishing SubagentStop for agent '{agent_type}'",
125
- file=sys.stderr,
126
- )
120
+ _log(f"Hook handler: Publishing SubagentStop for agent '{agent_type}'")
127
121
  elif event == "pre_tool" and data.get("tool_name") == "Task":
128
122
  delegation = data.get("delegation_details", {})
129
123
  agent_type = delegation.get("agent_type", "unknown")
130
- print(
131
- f"Hook handler: Publishing Task delegation to agent '{agent_type}'",
132
- file=sys.stderr,
124
+ _log(
125
+ f"Hook handler: Publishing Task delegation to agent '{agent_type}'"
133
126
  )
134
127
 
135
- # Try high-performance async emitter first (direct calls)
136
- success = self._try_async_emit(namespace, event, claude_event_data)
137
- if success:
138
- return
139
-
140
- # Fallback to HTTP POST for cross-process communication
128
+ # Emit via HTTP POST (non-blocking, runs in thread pool)
141
129
  self._try_http_emit(namespace, event, claude_event_data)
142
130
 
143
- def _try_async_emit(self, namespace: str, event: str, data: dict) -> bool:
144
- """Try to emit event using high-performance async emitter."""
145
- try:
146
- # Run async emission in the current event loop or create one
147
- loop = None
148
- try:
149
- loop = asyncio.get_running_loop()
150
- except RuntimeError:
151
- # No running loop, create a new one
152
- pass
153
-
154
- if loop:
155
- # We're in an async context, create a task with tracking
156
- task = loop.create_task(self._async_emit(namespace, event, data))
157
- self._emit_tasks.add(task)
158
- task.add_done_callback(self._emit_tasks.discard)
159
- # Don't wait for completion to maintain low latency
160
- if DEBUG:
161
- print(f"✅ Async emit scheduled: {event}", file=sys.stderr)
162
- return True
163
- # No event loop, run synchronously
164
- success = asyncio.run(self._async_emit(namespace, event, data))
165
- if DEBUG and success:
166
- print(f"✅ Async emit successful: {event}", file=sys.stderr)
167
- return success
168
-
169
- except Exception as e:
170
- if DEBUG:
171
- print(f"⚠️ Async emit failed: {e}", file=sys.stderr)
172
- return False
173
-
174
- async def _async_emit(self, namespace: str, event: str, data: dict) -> bool:
175
- """Async helper for event emission."""
176
- try:
177
- # Lazy load event emitter to reduce initialization overhead
178
- from claude_mpm.services.monitor.event_emitter import get_event_emitter
179
-
180
- emitter = await get_event_emitter()
181
- return await emitter.emit_event(namespace, "claude_event", data)
182
- except ImportError:
183
- if DEBUG:
184
- print("⚠️ Event emitter not available", file=sys.stderr)
185
- return False
186
- except Exception as e:
187
- if DEBUG:
188
- print(f"⚠️ Async emitter error: {e}", file=sys.stderr)
189
- return False
190
-
191
131
  def _try_http_emit(self, namespace: str, event: str, data: dict):
192
132
  """Try to emit event using HTTP POST fallback (non-blocking).
193
133
 
@@ -196,10 +136,7 @@ class ConnectionManagerService:
196
136
  """
197
137
  if not REQUESTS_AVAILABLE:
198
138
  if DEBUG:
199
- print(
200
- "⚠️ requests module not available - cannot emit via HTTP",
201
- file=sys.stderr,
202
- )
139
+ _log("⚠️ requests module not available - cannot emit via HTTP")
203
140
  return
204
141
 
205
142
  # Submit to thread pool - don't wait for result (fire-and-forget)
@@ -225,25 +162,21 @@ class ConnectionManagerService:
225
162
 
226
163
  if response.status_code in [200, 204]:
227
164
  if DEBUG:
228
- print(f"✅ HTTP POST successful: {event}", file=sys.stderr)
165
+ _log(f"✅ HTTP POST successful: {event}")
229
166
  elif DEBUG:
230
- print(
231
- f"⚠️ HTTP POST failed with status {response.status_code}: {event}",
232
- file=sys.stderr,
233
- )
167
+ _log(f"⚠️ HTTP POST failed with status {response.status_code}: {event}")
234
168
 
235
169
  except requests.exceptions.Timeout:
236
170
  if DEBUG:
237
- print(f"⚠️ HTTP POST timeout for: {event}", file=sys.stderr)
171
+ _log(f"⚠️ HTTP POST timeout for: {event}")
238
172
  except requests.exceptions.ConnectionError:
239
173
  if DEBUG:
240
- print(
241
- f"⚠️ HTTP POST connection failed for: {event} (server not running?)",
242
- file=sys.stderr,
174
+ _log(
175
+ f"⚠️ HTTP POST connection failed for: {event} (server not running?)"
243
176
  )
244
177
  except Exception as e:
245
178
  if DEBUG:
246
- print(f"⚠️ HTTP POST error for {event}: {e}", file=sys.stderr)
179
+ _log(f"⚠️ HTTP POST error for {event}: {e}")
247
180
 
248
181
  def cleanup(self):
249
182
  """Cleanup connections on service destruction."""
@@ -251,4 +184,4 @@ class ConnectionManagerService:
251
184
  if hasattr(self, "_http_executor"):
252
185
  self._http_executor.shutdown(wait=False)
253
186
  if DEBUG:
254
- print("✅ HTTP executor shutdown", file=sys.stderr)
187
+ _log("✅ HTTP executor shutdown")
@@ -8,13 +8,22 @@ This service manages:
8
8
  """
9
9
 
10
10
  import os
11
- import subprocess
11
+ import subprocess # nosec B404
12
12
  import time
13
13
  from collections import deque
14
14
  from datetime import datetime, timezone
15
15
  from pathlib import Path
16
16
  from typing import Optional
17
17
 
18
+ # Try to import _log from hook_handler, fall back to no-op
19
+ try:
20
+ from claude_mpm.hooks.claude_hooks.hook_handler import _log
21
+ except ImportError:
22
+
23
+ def _log(msg: str) -> None:
24
+ pass # Silent fallback
25
+
26
+
18
27
  # Import constants for configuration
19
28
  try:
20
29
  from claude_mpm.core.constants import TimeoutConfig
@@ -63,17 +72,11 @@ class StateManagerService:
63
72
  ):
64
73
  """Track a new agent delegation with optional request data for response correlation."""
65
74
  if DEBUG:
66
- import sys
67
-
68
- print(
69
- f" - session_id: {session_id[:16] if session_id else 'None'}...",
70
- file=sys.stderr,
71
- )
72
- print(f" - agent_type: {agent_type}", file=sys.stderr)
73
- print(f" - request_data provided: {bool(request_data)}", file=sys.stderr)
74
- print(
75
- f" - delegation_requests size before: {len(self.delegation_requests)}",
76
- file=sys.stderr,
75
+ _log(f" - session_id: {session_id[:16] if session_id else 'None'}...")
76
+ _log(f" - agent_type: {agent_type}")
77
+ _log(f" - request_data provided: {bool(request_data)}")
78
+ _log(
79
+ f" - delegation_requests size before: {len(self.delegation_requests)}"
77
80
  )
78
81
 
79
82
  if session_id and agent_type and agent_type != "unknown":
@@ -89,15 +92,9 @@ class StateManagerService:
89
92
  "timestamp": datetime.now(timezone.utc).isoformat(),
90
93
  }
91
94
  if DEBUG:
92
- import sys
93
-
94
- print(
95
- f" - ✅ Stored in delegation_requests[{session_id[:16]}...]",
96
- file=sys.stderr,
97
- )
98
- print(
99
- f" - delegation_requests size after: {len(self.delegation_requests)}",
100
- file=sys.stderr,
95
+ _log(f" - ✅ Stored in delegation_requests[{session_id[:16]}...]")
96
+ _log(
97
+ f" - delegation_requests size after: {len(self.delegation_requests)}"
101
98
  )
102
99
 
103
100
  # Clean up old delegations (older than 5 minutes)
@@ -197,7 +194,7 @@ class StateManagerService:
197
194
  os.chdir(working_dir)
198
195
 
199
196
  # Run git command to get current branch
200
- result = subprocess.run(
197
+ result = subprocess.run( # nosec B603 B607
201
198
  ["git", "branch", "--show-current"],
202
199
  capture_output=True,
203
200
  text=True,
@@ -233,17 +230,12 @@ class StateManagerService:
233
230
  def find_matching_request(self, session_id: str) -> Optional[dict]:
234
231
  """Find matching request data for a session, with fuzzy matching fallback."""
235
232
  # First try exact match
236
- request_info = self.delegation_requests.get(session_id)
233
+ request_info = self.delegation_requests.get(session_id) # nosec B113
237
234
 
238
235
  # If exact match fails, try partial matching
239
236
  if not request_info and session_id:
240
237
  if DEBUG:
241
- import sys
242
-
243
- print(
244
- f" - Trying fuzzy match for session {session_id[:16]}...",
245
- file=sys.stderr,
246
- )
238
+ _log(f" - Trying fuzzy match for session {session_id[:16]}...")
247
239
  # Try to find a session that matches the first 8-16 characters
248
240
  for stored_sid in list(self.delegation_requests.keys()):
249
241
  if (
@@ -256,13 +248,8 @@ class StateManagerService:
256
248
  )
257
249
  ):
258
250
  if DEBUG:
259
- import sys
260
-
261
- print(
262
- f" - ✅ Fuzzy match found: {stored_sid[:16]}...",
263
- file=sys.stderr,
264
- )
265
- request_info = self.delegation_requests.get(stored_sid)
251
+ _log(f" - ✅ Fuzzy match found: {stored_sid[:16]}...")
252
+ request_info = self.delegation_requests.get(stored_sid) # nosec B113
266
253
  # Update the key to use the current session_id for consistency
267
254
  if request_info:
268
255
  self.delegation_requests[session_id] = request_info
@@ -10,10 +10,18 @@ This service handles:
10
10
  import json
11
11
  import os
12
12
  import re
13
- import sys
14
13
  from datetime import datetime, timezone
15
14
  from typing import Optional, Tuple
16
15
 
16
+ # Try to import _log from hook_handler, fall back to no-op
17
+ try:
18
+ from claude_mpm.hooks.claude_hooks.hook_handler import _log
19
+ except ImportError:
20
+
21
+ def _log(msg: str) -> None:
22
+ pass # Silent fallback
23
+
24
+
17
25
  # Debug mode is enabled by default for better visibility into hook processing
18
26
  DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
19
27
 
@@ -45,35 +53,31 @@ class SubagentResponseProcessor:
45
53
  # Enhanced debug logging for session correlation
46
54
  session_id = event.get("session_id", "")
47
55
  if DEBUG:
48
- print(
49
- f" - session_id: {session_id[:16] if session_id else 'None'}...",
50
- file=sys.stderr,
51
- )
52
- print(f" - event keys: {list(event.keys())}", file=sys.stderr)
53
- print(
54
- f" - delegation_requests size: {len(self.state_manager.delegation_requests)}",
55
- file=sys.stderr,
56
+ _log(f" - session_id: {session_id[:16] if session_id else 'None'}...")
57
+ _log(f" - event keys: {list(event.keys())}")
58
+ _log(
59
+ f" - delegation_requests size: {len(self.state_manager.delegation_requests)}"
56
60
  )
57
61
  # Show all stored session IDs for comparison
58
62
  all_sessions = list(self.state_manager.delegation_requests.keys())
59
63
  if all_sessions:
60
- print(" - Stored sessions (first 16 chars):", file=sys.stderr)
64
+ _log(" - Stored sessions (first 16 chars):")
61
65
  for sid in all_sessions[:10]: # Show up to 10
62
- print(
63
- f" - {sid[:16]}... (agent: {self.state_manager.delegation_requests[sid].get('agent_type', 'unknown')})",
64
- file=sys.stderr,
66
+ _log(
67
+ f" - {sid[:16]}... (agent: {self.state_manager.delegation_requests[sid].get('agent_type', 'unknown')})"
65
68
  )
66
69
  else:
67
- print(" - No stored sessions in delegation_requests!", file=sys.stderr)
70
+ _log(" - No stored sessions in delegation_requests!")
68
71
 
69
72
  # Get agent type and other basic info
70
- agent_type, agent_id, reason = self._extract_basic_info(event, session_id)
73
+ agent_type, agent_id, reason, agent_type_inferred = self._extract_basic_info(
74
+ event, session_id
75
+ )
71
76
 
72
77
  # Always log SubagentStop events for debugging
73
78
  if DEBUG or agent_type != "unknown":
74
- print(
75
- f"Hook handler: Processing SubagentStop - agent: '{agent_type}', session: '{session_id}', reason: '{reason}'",
76
- file=sys.stderr,
79
+ _log(
80
+ f"Hook handler: Processing SubagentStop - agent: '{agent_type}', session: '{session_id}', reason: '{reason}'"
77
81
  )
78
82
 
79
83
  # Get working directory and git branch
@@ -108,20 +112,29 @@ class SubagentResponseProcessor:
108
112
  working_dir,
109
113
  git_branch,
110
114
  structured_response,
115
+ agent_type_inferred,
111
116
  )
112
117
 
113
118
  # Debug log the processed data
114
119
  if DEBUG:
115
- print(
116
- f"SubagentStop processed data: agent_type='{agent_type}', session_id='{session_id}'",
117
- file=sys.stderr,
120
+ _log(
121
+ f"SubagentStop processed data: agent_type='{agent_type}', session_id='{session_id}'"
118
122
  )
119
123
 
120
- # Emit to /hook namespace with high priority
121
- self.connection_manager.emit_event("/hook", "subagent_stop", subagent_stop_data)
124
+ # Emit to default namespace (consistent with subagent_start)
125
+ self.connection_manager.emit_event("", "subagent_stop", subagent_stop_data)
126
+
127
+ def _extract_basic_info(
128
+ self, event: dict, session_id: str
129
+ ) -> Tuple[str, str, str, bool]:
130
+ """Extract basic info from the event.
131
+
132
+ Returns:
133
+ Tuple of (agent_type, agent_id, reason, agent_type_inferred)
134
+ - agent_type_inferred is True when defaulted to "pm"
135
+ """
136
+ agent_type_inferred = False
122
137
 
123
- def _extract_basic_info(self, event: dict, session_id: str) -> Tuple[str, str, str]:
124
- """Extract basic info from the event."""
125
138
  # First try to get agent type from our tracking
126
139
  agent_type = (
127
140
  self.state_manager.get_delegation_agent_type(session_id)
@@ -146,7 +159,14 @@ class SubagentResponseProcessor:
146
159
  elif "pm" in task_desc or "project" in task_desc:
147
160
  agent_type = "pm"
148
161
 
149
- return agent_type, agent_id, reason
162
+ # Default to "pm" if still unknown (main conversation doesn't use Task tool)
163
+ if agent_type == "unknown":
164
+ agent_type = "pm"
165
+ agent_type_inferred = True
166
+ if DEBUG:
167
+ _log(" - Inferred agent_type='pm' (no explicit type found)")
168
+
169
+ return agent_type, agent_id, reason, agent_type_inferred
150
170
 
151
171
  def _extract_structured_response(
152
172
  self, output: str, agent_type: str
@@ -160,17 +180,15 @@ class SubagentResponseProcessor:
160
180
  if json_match:
161
181
  structured_response = json.loads(json_match.group(1))
162
182
  if DEBUG:
163
- print(
164
- f"Extracted structured response from {agent_type} agent in SubagentStop",
165
- file=sys.stderr,
183
+ _log(
184
+ f"Extracted structured response from {agent_type} agent in SubagentStop"
166
185
  )
167
186
 
168
187
  # Log if MEMORIES field is present
169
188
  if structured_response.get("MEMORIES") and DEBUG:
170
189
  memories_count = len(structured_response["MEMORIES"])
171
- print(
172
- f"Agent {agent_type} returned MEMORIES field with {memories_count} items",
173
- file=sys.stderr,
190
+ _log(
191
+ f"Agent {agent_type} returned MEMORIES field with {memories_count} items"
174
192
  )
175
193
 
176
194
  return structured_response
@@ -192,20 +210,15 @@ class SubagentResponseProcessor:
192
210
  ):
193
211
  """Track the agent response if response tracking is enabled."""
194
212
  if DEBUG:
195
- print(
196
- f" - response_tracking_enabled: {self.response_tracking_manager.response_tracking_enabled}",
197
- file=sys.stderr,
213
+ _log(
214
+ f" - response_tracking_enabled: {self.response_tracking_manager.response_tracking_enabled}"
198
215
  )
199
- print(
200
- f" - response_tracker exists: {self.response_tracking_manager.response_tracker is not None}",
201
- file=sys.stderr,
216
+ _log(
217
+ f" - response_tracker exists: {self.response_tracking_manager.response_tracker is not None}"
202
218
  )
203
- print(
204
- f" - session_id: {session_id[:16] if session_id else 'None'}...",
205
- file=sys.stderr,
206
- )
207
- print(f" - agent_type: {agent_type}", file=sys.stderr)
208
- print(f" - reason: {reason}", file=sys.stderr)
219
+ _log(f" - session_id: {session_id[:16] if session_id else 'None'}...")
220
+ _log(f" - agent_type: {agent_type}")
221
+ _log(f" - reason: {reason}")
209
222
 
210
223
  if (
211
224
  self.response_tracking_manager.response_tracking_enabled
@@ -216,27 +229,16 @@ class SubagentResponseProcessor:
216
229
  request_info = self.state_manager.find_matching_request(session_id)
217
230
 
218
231
  if DEBUG:
219
- print(
220
- f" - request_info present: {bool(request_info)}",
221
- file=sys.stderr,
222
- )
232
+ _log(f" - request_info present: {bool(request_info)}")
223
233
  if request_info:
224
- print(
225
- " - Found request data for response tracking",
226
- file=sys.stderr,
227
- )
228
- print(
229
- f" - stored agent_type: {request_info.get('agent_type')}",
230
- file=sys.stderr,
231
- )
232
- print(
233
- f" - request keys: {list(request_info.get('request', {}).keys())}",
234
- file=sys.stderr,
234
+ _log(" - ✅ Found request data for response tracking")
235
+ _log(f" - stored agent_type: {request_info.get('agent_type')}")
236
+ _log(
237
+ f" - request keys: {list(request_info.get('request', {}).keys())}"
235
238
  )
236
239
  else:
237
- print(
238
- f" - ❌ No request data found for session {session_id[:16]}...",
239
- file=sys.stderr,
240
+ _log(
241
+ f" - ❌ No request data found for session {session_id[:16]}..."
240
242
  )
241
243
 
242
244
  if request_info:
@@ -288,9 +290,8 @@ class SubagentResponseProcessor:
288
290
  # Check for MEMORIES field and process if present
289
291
  if structured_response.get("MEMORIES") and DEBUG:
290
292
  memories = structured_response["MEMORIES"]
291
- print(
292
- f"Found MEMORIES field in {agent_type} response with {len(memories)} items",
293
- file=sys.stderr,
293
+ _log(
294
+ f"Found MEMORIES field in {agent_type} response with {len(memories)} items"
294
295
  )
295
296
  # The memory will be processed by extract_and_update_memory
296
297
  # which is called by the memory hook service
@@ -307,26 +308,21 @@ class SubagentResponseProcessor:
307
308
  )
308
309
 
309
310
  if file_path and DEBUG:
310
- print(
311
- f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}",
312
- file=sys.stderr,
311
+ _log(
312
+ f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}"
313
313
  )
314
314
 
315
315
  # Clean up the request data
316
316
  self.state_manager.remove_request(session_id)
317
317
 
318
318
  elif DEBUG:
319
- print(
320
- f"No request data for SubagentStop session {session_id[:8]}..., agent: {agent_type}",
321
- file=sys.stderr,
319
+ _log(
320
+ f"No request data for SubagentStop session {session_id[:8]}..., agent: {agent_type}"
322
321
  )
323
322
 
324
323
  except Exception as e:
325
324
  if DEBUG:
326
- print(
327
- f"❌ Failed to track response on SubagentStop: {e}",
328
- file=sys.stderr,
329
- )
325
+ _log(f"❌ Failed to track response on SubagentStop: {e}")
330
326
 
331
327
  def _build_subagent_stop_data(
332
328
  self,
@@ -338,10 +334,12 @@ class SubagentResponseProcessor:
338
334
  working_dir: str,
339
335
  git_branch: str,
340
336
  structured_response: Optional[dict],
337
+ agent_type_inferred: bool,
341
338
  ) -> dict:
342
339
  """Build the subagent stop data for event emission."""
343
340
  subagent_stop_data = {
344
341
  "agent_type": agent_type,
342
+ "agent_type_inferred": agent_type_inferred,
345
343
  "agent_id": agent_id,
346
344
  "reason": reason,
347
345
  "session_id": session_id,