claude-mpm 5.4.85__py3-none-any.whl → 5.6.76__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
- claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
- claude_mpm/agents/PM_INSTRUCTIONS.md +109 -706
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +216 -0
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +2 -2
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/executor.py +128 -16
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/parsers/base_parser.py +84 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +345 -40
- claude_mpm/cli/startup_display.py +76 -7
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/startup_migrations.py +236 -0
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +78 -0
- claude_mpm/commander/adapters/__init__.py +60 -0
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +288 -0
- claude_mpm/commander/adapters/claude_code.py +392 -0
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +121 -0
- claude_mpm/commander/api/errors.py +133 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +226 -0
- claude_mpm/commander/api/routes/work.py +296 -0
- claude_mpm/commander/api/schemas.py +186 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +149 -0
- claude_mpm/commander/chat/commands.py +124 -0
- claude_mpm/commander/chat/repl.py +1957 -0
- claude_mpm/commander/config.py +51 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +603 -0
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +392 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +233 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +57 -0
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +868 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +127 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +403 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +410 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +346 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +362 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +207 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +241 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +8 -0
- claude_mpm/commands/mpm-doctor.md +8 -0
- claude_mpm/commands/mpm-help.md +8 -0
- claude_mpm/commands/mpm-init.md +8 -0
- claude_mpm/commands/mpm-monitor.md +8 -0
- claude_mpm/commands/mpm-organize.md +8 -0
- claude_mpm/commands/mpm-postmortem.md +8 -0
- claude_mpm/commands/mpm-session-resume.md +9 -1
- claude_mpm/commands/mpm-status.md +8 -0
- claude_mpm/commands/mpm-ticket-view.md +8 -0
- claude_mpm/commands/mpm-version.md +8 -0
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/constants.py +5 -0
- claude_mpm/core/claude_runner.py +152 -0
- claude_mpm/core/config.py +35 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +53 -4
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +39 -13
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +52 -12
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_config.py +10 -6
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +9 -9
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +466 -136
- claude_mpm/hooks/claude_hooks/hook_handler.py +204 -104
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +291 -59
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +41 -26
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
- claude_mpm/hooks/claude_hooks/services/container.py +326 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
- claude_mpm/hooks/session_resume_hook.py +89 -1
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- claude_mpm/init.py +22 -15
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +46 -19
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/event_log.py +325 -0
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- claude_mpm/services/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +111 -16
- claude_mpm/services/pm_skills_deployer.py +261 -87
- claude_mpm/services/skills/git_skill_source_manager.py +130 -10
- claude_mpm/services/skills/selective_skill_deployer.py +142 -16
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/registry.py +295 -90
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm-5.6.76.dist-info/METADATA +416 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +312 -175
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
- claude_mpm-5.4.85.dist-info/METADATA +0 -1023
- /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
- /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""OpenRouter LLM client for Commander chat interface."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import AsyncIterator
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class OpenRouterConfig:
|
|
12
|
+
"""Configuration for OpenRouter API client."""
|
|
13
|
+
|
|
14
|
+
api_key: str | None = None # Falls back to OPENROUTER_API_KEY env
|
|
15
|
+
model: str = "anthropic/claude-3.5-sonnet"
|
|
16
|
+
base_url: str = "https://openrouter.ai/api/v1"
|
|
17
|
+
max_tokens: int = 4096
|
|
18
|
+
temperature: float = 0.7
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OpenRouterClient:
|
|
22
|
+
"""Async client for OpenRouter API."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config: OpenRouterConfig | None = None):
|
|
25
|
+
"""Initialize client with config.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
config: OpenRouter configuration. Defaults to OpenRouterConfig().
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
ValueError: If OPENROUTER_API_KEY is not set.
|
|
32
|
+
"""
|
|
33
|
+
self.config = config or OpenRouterConfig()
|
|
34
|
+
self._api_key = self.config.api_key or os.getenv("OPENROUTER_API_KEY")
|
|
35
|
+
if not self._api_key:
|
|
36
|
+
raise ValueError("OPENROUTER_API_KEY not set")
|
|
37
|
+
|
|
38
|
+
async def chat(self, messages: list[dict], system: str | None = None) -> str:
|
|
39
|
+
"""Send chat completion request, return response content.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
messages: List of message dicts with 'role' and 'content' keys.
|
|
43
|
+
system: Optional system prompt.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Response content from the model.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
httpx.HTTPStatusError: If API request fails.
|
|
50
|
+
"""
|
|
51
|
+
async with httpx.AsyncClient() as client:
|
|
52
|
+
# Build request payload
|
|
53
|
+
payload = {
|
|
54
|
+
"model": self.config.model,
|
|
55
|
+
"messages": messages,
|
|
56
|
+
"max_tokens": self.config.max_tokens,
|
|
57
|
+
"temperature": self.config.temperature,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if system:
|
|
61
|
+
payload["system"] = system
|
|
62
|
+
|
|
63
|
+
# Send request
|
|
64
|
+
response = await client.post(
|
|
65
|
+
f"{self.config.base_url}/chat/completions",
|
|
66
|
+
headers={
|
|
67
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
},
|
|
70
|
+
json=payload,
|
|
71
|
+
timeout=30.0,
|
|
72
|
+
)
|
|
73
|
+
response.raise_for_status()
|
|
74
|
+
|
|
75
|
+
# Extract content from response
|
|
76
|
+
data = response.json()
|
|
77
|
+
return data["choices"][0]["message"]["content"]
|
|
78
|
+
|
|
79
|
+
async def chat_stream(
|
|
80
|
+
self, messages: list[dict], system: str | None = None
|
|
81
|
+
) -> AsyncIterator[str]:
|
|
82
|
+
"""Stream chat completion, yield chunks.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
messages: List of message dicts with 'role' and 'content' keys.
|
|
86
|
+
system: Optional system prompt.
|
|
87
|
+
|
|
88
|
+
Yields:
|
|
89
|
+
Content chunks from the streaming response.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
httpx.HTTPStatusError: If API request fails.
|
|
93
|
+
"""
|
|
94
|
+
async with httpx.AsyncClient() as client:
|
|
95
|
+
# Build request payload
|
|
96
|
+
payload = {
|
|
97
|
+
"model": self.config.model,
|
|
98
|
+
"messages": messages,
|
|
99
|
+
"max_tokens": self.config.max_tokens,
|
|
100
|
+
"temperature": self.config.temperature,
|
|
101
|
+
"stream": True,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if system:
|
|
105
|
+
payload["system"] = system
|
|
106
|
+
|
|
107
|
+
# Send streaming request
|
|
108
|
+
async with client.stream(
|
|
109
|
+
"POST",
|
|
110
|
+
f"{self.config.base_url}/chat/completions",
|
|
111
|
+
headers={
|
|
112
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
113
|
+
"Content-Type": "application/json",
|
|
114
|
+
},
|
|
115
|
+
json=payload,
|
|
116
|
+
timeout=30.0,
|
|
117
|
+
) as response:
|
|
118
|
+
response.raise_for_status()
|
|
119
|
+
|
|
120
|
+
# Parse SSE stream
|
|
121
|
+
async for line in response.aiter_lines():
|
|
122
|
+
if not line.strip():
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
# SSE format: "data: {json}"
|
|
126
|
+
if line.startswith("data: "):
|
|
127
|
+
data_str = line[6:] # Remove "data: " prefix
|
|
128
|
+
|
|
129
|
+
# Check for stream end
|
|
130
|
+
if data_str == "[DONE]":
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
# Parse JSON chunk
|
|
134
|
+
import json
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
data = json.loads(data_str)
|
|
138
|
+
delta = data["choices"][0].get("delta", {})
|
|
139
|
+
if "content" in delta:
|
|
140
|
+
yield delta["content"]
|
|
141
|
+
except (json.JSONDecodeError, KeyError, IndexError):
|
|
142
|
+
# Skip malformed chunks
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
async def summarize(self, text: str, max_length: int = 500) -> str:
|
|
146
|
+
"""Summarize text to max_length characters.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
text: Text to summarize.
|
|
150
|
+
max_length: Maximum length of summary in characters.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Summarized text.
|
|
154
|
+
"""
|
|
155
|
+
messages = [
|
|
156
|
+
{
|
|
157
|
+
"role": "user",
|
|
158
|
+
"content": f"Summarize the following text in approximately {max_length} characters or less:\n\n{text}",
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
system = (
|
|
163
|
+
"You are a concise summarization assistant. "
|
|
164
|
+
"Provide clear, accurate summaries that capture the key points."
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return await self.chat(messages, system=system)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Summarizes Claude Code output for user display."""
|
|
2
|
+
|
|
3
|
+
from .openrouter_client import OpenRouterClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class OutputSummarizer:
|
|
7
|
+
"""Summarizes long output from Claude Code instances."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, client: OpenRouterClient):
|
|
10
|
+
"""Initialize with OpenRouter client.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
client: OpenRouterClient instance for LLM requests.
|
|
14
|
+
"""
|
|
15
|
+
self.client = client
|
|
16
|
+
|
|
17
|
+
async def summarize(self, output: str, context: str | None = None) -> str:
|
|
18
|
+
"""Summarize Claude Code output.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
output: Raw output from Claude Code.
|
|
22
|
+
context: Optional context about what was requested.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Concise summary of the output.
|
|
26
|
+
"""
|
|
27
|
+
# Build summarization prompt
|
|
28
|
+
if context:
|
|
29
|
+
prompt = f"""Summarize the following Claude Code output.
|
|
30
|
+
|
|
31
|
+
Context: {context}
|
|
32
|
+
|
|
33
|
+
Output:
|
|
34
|
+
{output}
|
|
35
|
+
|
|
36
|
+
Provide a concise summary (2-3 sentences) that captures:
|
|
37
|
+
1. What action was taken
|
|
38
|
+
2. The key result or outcome
|
|
39
|
+
3. Any important warnings or next steps
|
|
40
|
+
|
|
41
|
+
Summary:"""
|
|
42
|
+
else:
|
|
43
|
+
prompt = f"""Summarize the following Claude Code output in 2-3 sentences.
|
|
44
|
+
|
|
45
|
+
Output:
|
|
46
|
+
{output}
|
|
47
|
+
|
|
48
|
+
Summary:"""
|
|
49
|
+
|
|
50
|
+
messages = [{"role": "user", "content": prompt}]
|
|
51
|
+
|
|
52
|
+
system = (
|
|
53
|
+
"You are a technical summarization assistant. "
|
|
54
|
+
"Provide clear, concise summaries of command output and code execution results. "
|
|
55
|
+
"Focus on actionable information and key outcomes."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return await self.client.chat(messages, system=system)
|
|
59
|
+
|
|
60
|
+
def needs_summarization(self, output: str, threshold: int = 500) -> bool:
|
|
61
|
+
"""Check if output is long enough to warrant summarization.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
output: Output text to check.
|
|
65
|
+
threshold: Character count threshold for summarization.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
True if output exceeds threshold, False otherwise.
|
|
69
|
+
"""
|
|
70
|
+
return len(output) > threshold
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Conversation memory system for Commander.
|
|
2
|
+
|
|
3
|
+
This module provides semantic search, storage, and context compression
|
|
4
|
+
for all Claude Code instance conversations.
|
|
5
|
+
|
|
6
|
+
Key Components:
|
|
7
|
+
- ConversationStore: CRUD operations for conversations
|
|
8
|
+
- EmbeddingService: Generate vector embeddings
|
|
9
|
+
- SemanticSearch: Query conversations semantically
|
|
10
|
+
- ContextCompressor: Summarize conversations for context
|
|
11
|
+
- EntityExtractor: Extract files, functions, errors
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> from claude_mpm.commander.memory import (
|
|
15
|
+
... ConversationStore,
|
|
16
|
+
... EmbeddingService,
|
|
17
|
+
... SemanticSearch,
|
|
18
|
+
... ContextCompressor,
|
|
19
|
+
... )
|
|
20
|
+
>>> store = ConversationStore()
|
|
21
|
+
>>> embeddings = EmbeddingService()
|
|
22
|
+
>>> search = SemanticSearch(store, embeddings)
|
|
23
|
+
>>> results = await search.search("how did we fix the login bug?")
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from .compression import ContextCompressor
|
|
27
|
+
from .embeddings import EmbeddingService
|
|
28
|
+
from .entities import Entity, EntityExtractor, EntityType
|
|
29
|
+
from .integration import MemoryIntegration
|
|
30
|
+
from .search import SearchResult, SemanticSearch
|
|
31
|
+
from .store import Conversation, ConversationMessage, ConversationStore
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"ContextCompressor",
|
|
35
|
+
"Conversation",
|
|
36
|
+
"ConversationMessage",
|
|
37
|
+
"ConversationStore",
|
|
38
|
+
"EmbeddingService",
|
|
39
|
+
"Entity",
|
|
40
|
+
"EntityExtractor",
|
|
41
|
+
"EntityType",
|
|
42
|
+
"MemoryIntegration",
|
|
43
|
+
"SearchResult",
|
|
44
|
+
"SemanticSearch",
|
|
45
|
+
]
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""Context compression and conversation summarization.
|
|
2
|
+
|
|
3
|
+
Compresses long conversations into concise summaries for efficient context
|
|
4
|
+
loading when resuming sessions or searching past work.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
from ..llm.openrouter_client import OpenRouterClient
|
|
11
|
+
from .store import Conversation, ConversationMessage
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ContextCompressor:
|
|
17
|
+
"""Compress conversations into summaries for context loading.
|
|
18
|
+
|
|
19
|
+
Uses cheap LLM (mistral-small) to generate summaries of conversations
|
|
20
|
+
and compress multiple conversations into context strings.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
client: OpenRouterClient for LLM requests
|
|
24
|
+
summary_threshold: Minimum messages to trigger summarization
|
|
25
|
+
max_context_tokens: Maximum tokens for compressed context
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
>>> compressor = ContextCompressor(client)
|
|
29
|
+
>>> summary = await compressor.summarize(messages)
|
|
30
|
+
>>> context = await compressor.compress_for_context(
|
|
31
|
+
... conversations,
|
|
32
|
+
... max_tokens=4000
|
|
33
|
+
... )
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
client: OpenRouterClient,
|
|
39
|
+
summary_threshold: int = 10,
|
|
40
|
+
max_context_tokens: int = 4000,
|
|
41
|
+
):
|
|
42
|
+
"""Initialize context compressor.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
client: OpenRouterClient for LLM requests
|
|
46
|
+
summary_threshold: Minimum messages to summarize
|
|
47
|
+
max_context_tokens: Maximum tokens for context string
|
|
48
|
+
"""
|
|
49
|
+
self.client = client
|
|
50
|
+
self.summary_threshold = summary_threshold
|
|
51
|
+
self.max_context_tokens = max_context_tokens
|
|
52
|
+
|
|
53
|
+
logger.info(
|
|
54
|
+
"ContextCompressor initialized (threshold: %d msgs, max_tokens: %d)",
|
|
55
|
+
summary_threshold,
|
|
56
|
+
max_context_tokens,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
async def summarize(
|
|
60
|
+
self,
|
|
61
|
+
messages: List[ConversationMessage],
|
|
62
|
+
focus: Optional[str] = None,
|
|
63
|
+
) -> str:
|
|
64
|
+
"""Generate summary of conversation messages.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
messages: List of messages to summarize
|
|
68
|
+
focus: Optional focus area (e.g., "bug fixes", "API changes")
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Concise summary (2-4 sentences)
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
>>> summary = await compressor.summarize(messages)
|
|
75
|
+
>>> print(summary)
|
|
76
|
+
"Fixed login authentication bug in src/auth.py by updating token validation..."
|
|
77
|
+
"""
|
|
78
|
+
if len(messages) < 2:
|
|
79
|
+
# Too short to summarize
|
|
80
|
+
return messages[0].content if messages else ""
|
|
81
|
+
|
|
82
|
+
# Build conversation text
|
|
83
|
+
conversation_text = self._format_messages(messages)
|
|
84
|
+
|
|
85
|
+
# Build summarization prompt
|
|
86
|
+
if focus:
|
|
87
|
+
prompt = f"""Summarize the following conversation, focusing on: {focus}
|
|
88
|
+
|
|
89
|
+
Conversation:
|
|
90
|
+
{conversation_text}
|
|
91
|
+
|
|
92
|
+
Provide a concise summary (2-4 sentences) that captures:
|
|
93
|
+
1. What was the main task or problem
|
|
94
|
+
2. What actions were taken
|
|
95
|
+
3. What was the outcome or current status
|
|
96
|
+
4. Any important files, functions, or errors mentioned
|
|
97
|
+
|
|
98
|
+
Summary:"""
|
|
99
|
+
else:
|
|
100
|
+
prompt = f"""Summarize the following conversation in 2-4 sentences.
|
|
101
|
+
|
|
102
|
+
Conversation:
|
|
103
|
+
{conversation_text}
|
|
104
|
+
|
|
105
|
+
Focus on:
|
|
106
|
+
1. What was the main task or problem
|
|
107
|
+
2. What actions were taken
|
|
108
|
+
3. What was the outcome or current status
|
|
109
|
+
|
|
110
|
+
Summary:"""
|
|
111
|
+
|
|
112
|
+
messages_for_llm = [{"role": "user", "content": prompt}]
|
|
113
|
+
|
|
114
|
+
system = (
|
|
115
|
+
"You are a technical summarization assistant. "
|
|
116
|
+
"Provide clear, concise summaries of development conversations. "
|
|
117
|
+
"Focus on actionable information and key outcomes."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
summary = await self.client.chat(messages_for_llm, system=system)
|
|
121
|
+
logger.debug(
|
|
122
|
+
"Generated summary (%d chars) from %d messages", len(summary), len(messages)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return summary.strip()
|
|
126
|
+
|
|
127
|
+
async def compress_for_context(
|
|
128
|
+
self,
|
|
129
|
+
conversations: List[Conversation],
|
|
130
|
+
max_tokens: Optional[int] = None,
|
|
131
|
+
prioritize_recent: bool = True,
|
|
132
|
+
) -> str:
|
|
133
|
+
"""Compress multiple conversations into context string.
|
|
134
|
+
|
|
135
|
+
Prioritizes recent conversations and uses summaries for older ones
|
|
136
|
+
to fit within token budget.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
conversations: List of conversations to compress
|
|
140
|
+
max_tokens: Maximum tokens (default: self.max_context_tokens)
|
|
141
|
+
prioritize_recent: Whether to prioritize recent conversations
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Compressed context string ready for LLM input
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
>>> context = await compressor.compress_for_context(
|
|
148
|
+
... conversations,
|
|
149
|
+
... max_tokens=4000
|
|
150
|
+
... )
|
|
151
|
+
>>> print(f"Context: {len(context)} chars")
|
|
152
|
+
"""
|
|
153
|
+
if max_tokens is None:
|
|
154
|
+
max_tokens = self.max_context_tokens
|
|
155
|
+
|
|
156
|
+
# Sort by recency if prioritizing
|
|
157
|
+
if prioritize_recent:
|
|
158
|
+
conversations = sorted(
|
|
159
|
+
conversations, key=lambda c: c.updated_at, reverse=True
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Build context incrementally
|
|
163
|
+
context_parts = []
|
|
164
|
+
current_tokens = 0
|
|
165
|
+
|
|
166
|
+
for conv in conversations:
|
|
167
|
+
# Use summary if available, else generate one
|
|
168
|
+
if conv.summary:
|
|
169
|
+
summary_text = conv.summary
|
|
170
|
+
elif len(conv.messages) >= self.summary_threshold:
|
|
171
|
+
# Generate summary on-the-fly
|
|
172
|
+
summary_text = await self.summarize(conv.messages)
|
|
173
|
+
else:
|
|
174
|
+
# Use full conversation for short ones
|
|
175
|
+
summary_text = conv.get_full_text()
|
|
176
|
+
|
|
177
|
+
# Format conversation section
|
|
178
|
+
section = self._format_conversation_section(conv, summary_text)
|
|
179
|
+
section_tokens = len(section) // 4 # Rough approximation
|
|
180
|
+
|
|
181
|
+
# Check if adding this would exceed budget
|
|
182
|
+
if current_tokens + section_tokens > max_tokens:
|
|
183
|
+
# Try to fit summary only
|
|
184
|
+
short_summary = summary_text.split(". ")[0] + "."
|
|
185
|
+
short_section = self._format_conversation_section(conv, short_summary)
|
|
186
|
+
short_tokens = len(short_section) // 4
|
|
187
|
+
|
|
188
|
+
if current_tokens + short_tokens <= max_tokens:
|
|
189
|
+
context_parts.append(short_section)
|
|
190
|
+
current_tokens += short_tokens
|
|
191
|
+
else:
|
|
192
|
+
# Can't fit any more, stop
|
|
193
|
+
break
|
|
194
|
+
else:
|
|
195
|
+
context_parts.append(section)
|
|
196
|
+
current_tokens += section_tokens
|
|
197
|
+
|
|
198
|
+
context = "\n\n---\n\n".join(context_parts)
|
|
199
|
+
|
|
200
|
+
logger.info(
|
|
201
|
+
"Compressed %d conversations into context (%d chars, ~%d tokens)",
|
|
202
|
+
len(context_parts),
|
|
203
|
+
len(context),
|
|
204
|
+
current_tokens,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return context
|
|
208
|
+
|
|
209
|
+
def needs_summarization(self, messages: List[ConversationMessage]) -> bool:
|
|
210
|
+
"""Check if conversation needs summarization.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
messages: List of messages to check
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
True if message count exceeds threshold
|
|
217
|
+
|
|
218
|
+
Example:
|
|
219
|
+
>>> if compressor.needs_summarization(messages):
|
|
220
|
+
... summary = await compressor.summarize(messages)
|
|
221
|
+
"""
|
|
222
|
+
return len(messages) >= self.summary_threshold
|
|
223
|
+
|
|
224
|
+
def _format_messages(
|
|
225
|
+
self,
|
|
226
|
+
messages: List[ConversationMessage],
|
|
227
|
+
max_messages: Optional[int] = None,
|
|
228
|
+
) -> str:
|
|
229
|
+
"""Format messages as text for summarization.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
messages: Messages to format
|
|
233
|
+
max_messages: Maximum messages to include
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Formatted conversation text
|
|
237
|
+
"""
|
|
238
|
+
if max_messages:
|
|
239
|
+
messages = messages[:max_messages]
|
|
240
|
+
|
|
241
|
+
lines = []
|
|
242
|
+
for msg in messages:
|
|
243
|
+
# Format: ROLE: content
|
|
244
|
+
lines.append(f"{msg.role.upper()}: {msg.content}")
|
|
245
|
+
|
|
246
|
+
return "\n\n".join(lines)
|
|
247
|
+
|
|
248
|
+
def _format_conversation_section(
|
|
249
|
+
self, conversation: Conversation, summary: str
|
|
250
|
+
) -> str:
|
|
251
|
+
"""Format conversation section for context string.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
conversation: Conversation to format
|
|
255
|
+
summary: Summary or full text
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Formatted section with metadata
|
|
259
|
+
"""
|
|
260
|
+
# Format timestamp
|
|
261
|
+
timestamp = conversation.updated_at.strftime("%Y-%m-%d %H:%M")
|
|
262
|
+
|
|
263
|
+
# Build section
|
|
264
|
+
return f"""## Conversation: {conversation.id}
|
|
265
|
+
**Project:** {conversation.project_id}
|
|
266
|
+
**Instance:** {conversation.instance_name}
|
|
267
|
+
**Updated:** {timestamp}
|
|
268
|
+
**Messages:** {conversation.message_count}
|
|
269
|
+
|
|
270
|
+
{summary}"""
|
|
271
|
+
|
|
272
|
+
async def auto_summarize_conversation(
|
|
273
|
+
self, conversation: Conversation
|
|
274
|
+
) -> Optional[str]:
|
|
275
|
+
"""Automatically summarize conversation if needed.
|
|
276
|
+
|
|
277
|
+
Checks if conversation needs summarization and generates one if so.
|
|
278
|
+
Updates the conversation's summary field but does NOT save to store.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
conversation: Conversation to summarize
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Summary if generated, None if not needed
|
|
285
|
+
|
|
286
|
+
Example:
|
|
287
|
+
>>> summary = await compressor.auto_summarize_conversation(conv)
|
|
288
|
+
>>> if summary:
|
|
289
|
+
... conv.summary = summary
|
|
290
|
+
... await store.save(conv)
|
|
291
|
+
"""
|
|
292
|
+
if not self.needs_summarization(conversation.messages):
|
|
293
|
+
logger.debug(
|
|
294
|
+
"Conversation %s too short to summarize (%d messages)",
|
|
295
|
+
conversation.id,
|
|
296
|
+
len(conversation.messages),
|
|
297
|
+
)
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
if conversation.summary:
|
|
301
|
+
logger.debug("Conversation %s already has summary", conversation.id)
|
|
302
|
+
return conversation.summary
|
|
303
|
+
|
|
304
|
+
# Generate summary
|
|
305
|
+
summary = await self.summarize(conversation.messages)
|
|
306
|
+
logger.info("Auto-generated summary for conversation %s", conversation.id)
|
|
307
|
+
|
|
308
|
+
return summary
|
|
309
|
+
|
|
310
|
+
async def update_summary_if_stale(
|
|
311
|
+
self,
|
|
312
|
+
conversation: Conversation,
|
|
313
|
+
message_threshold: int = 5,
|
|
314
|
+
) -> Optional[str]:
|
|
315
|
+
"""Update summary if conversation has grown significantly.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
conversation: Conversation to check
|
|
319
|
+
message_threshold: New messages required to trigger update
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Updated summary if regenerated, None otherwise
|
|
323
|
+
|
|
324
|
+
Example:
|
|
325
|
+
>>> updated = await compressor.update_summary_if_stale(conv)
|
|
326
|
+
>>> if updated:
|
|
327
|
+
... conv.summary = updated
|
|
328
|
+
... await store.save(conv)
|
|
329
|
+
"""
|
|
330
|
+
if not conversation.summary:
|
|
331
|
+
# No existing summary, generate one
|
|
332
|
+
return await self.auto_summarize_conversation(conversation)
|
|
333
|
+
|
|
334
|
+
# Check if conversation has grown significantly
|
|
335
|
+
# (Simple heuristic: if more than threshold messages since last summarization)
|
|
336
|
+
# In practice, you'd track when summary was generated
|
|
337
|
+
if len(conversation.messages) < self.summary_threshold + message_threshold:
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
# Regenerate summary
|
|
341
|
+
logger.info(
|
|
342
|
+
"Regenerating stale summary for conversation %s (%d messages)",
|
|
343
|
+
conversation.id,
|
|
344
|
+
len(conversation.messages),
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
return await self.summarize(conversation.messages)
|