code-muse 0.0.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.
- code_muse/__init__.py +26 -0
- code_muse/__main__.py +10 -0
- code_muse/agents/__init__.py +31 -0
- code_muse/agents/_builder.py +214 -0
- code_muse/agents/_compaction.py +506 -0
- code_muse/agents/_diagnostics.py +171 -0
- code_muse/agents/_history.py +382 -0
- code_muse/agents/_key_listeners.py +148 -0
- code_muse/agents/_non_streaming_render.py +148 -0
- code_muse/agents/_runtime.py +596 -0
- code_muse/agents/agent_creator_agent.py +603 -0
- code_muse/agents/agent_helios.py +47 -0
- code_muse/agents/agent_manager.py +740 -0
- code_muse/agents/agent_muse.py +78 -0
- code_muse/agents/agent_planning.py +44 -0
- code_muse/agents/agent_qa_melpomene.py +207 -0
- code_muse/agents/base_agent.py +194 -0
- code_muse/agents/event_stream_handler.py +361 -0
- code_muse/agents/json_agent.py +201 -0
- code_muse/agents/prompt_v3.py +521 -0
- code_muse/agents/subagent_stream_handler.py +273 -0
- code_muse/callbacks.py +941 -0
- code_muse/chatgpt_codex_client.py +333 -0
- code_muse/claude_cache_client.py +853 -0
- code_muse/cli_runner/__init__.py +319 -0
- code_muse/cli_runner/args.py +63 -0
- code_muse/cli_runner/loop.py +510 -0
- code_muse/cli_runner/resume.py +72 -0
- code_muse/cli_runner/runner.py +161 -0
- code_muse/command_line/__init__.py +1 -0
- code_muse/command_line/add_model_menu.py +1331 -0
- code_muse/command_line/agent_menu.py +674 -0
- code_muse/command_line/attachments.py +397 -0
- code_muse/command_line/autosave_menu.py +709 -0
- code_muse/command_line/clipboard.py +528 -0
- code_muse/command_line/colors_menu.py +530 -0
- code_muse/command_line/command_handler.py +262 -0
- code_muse/command_line/command_registry.py +150 -0
- code_muse/command_line/config_commands.py +711 -0
- code_muse/command_line/core_commands.py +740 -0
- code_muse/command_line/diff_menu.py +865 -0
- code_muse/command_line/file_path_completion.py +73 -0
- code_muse/command_line/load_context_completion.py +57 -0
- code_muse/command_line/model_picker_completion.py +512 -0
- code_muse/command_line/model_settings_menu.py +983 -0
- code_muse/command_line/onboarding_slides.py +162 -0
- code_muse/command_line/onboarding_wizard.py +337 -0
- code_muse/command_line/pagination.py +41 -0
- code_muse/command_line/pin_command_completion.py +329 -0
- code_muse/command_line/prompt_toolkit_completion.py +886 -0
- code_muse/command_line/session_commands.py +304 -0
- code_muse/command_line/shell_passthrough.py +145 -0
- code_muse/command_line/skills_completion.py +158 -0
- code_muse/command_line/types.py +18 -0
- code_muse/command_line/uc_menu.py +908 -0
- code_muse/command_line/utils.py +105 -0
- code_muse/command_line/wiggum_state.py +77 -0
- code_muse/config.py +1138 -0
- code_muse/config_agent.py +168 -0
- code_muse/config_appearance.py +241 -0
- code_muse/config_model.py +357 -0
- code_muse/config_security.py +73 -0
- code_muse/error_logging.py +132 -0
- code_muse/evals/__init__.py +35 -0
- code_muse/evals/eval_helpers.py +81 -0
- code_muse/evals/eval_runner.py +299 -0
- code_muse/evals/sample_evals/__init__.py +1 -0
- code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
- code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
- code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
- code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
- code_muse/fs_scan_cache/__init__.py +31 -0
- code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
- code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
- code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
- code_muse/fs_scan_cache/tool_integration.py +309 -0
- code_muse/fs_scan_cache/ttl_policy.py +44 -0
- code_muse/gemini_code_assist.py +383 -0
- code_muse/gemini_model.py +838 -0
- code_muse/hook_engine/README.md +105 -0
- code_muse/hook_engine/__init__.py +21 -0
- code_muse/hook_engine/aliases.py +153 -0
- code_muse/hook_engine/engine.py +221 -0
- code_muse/hook_engine/executor.py +347 -0
- code_muse/hook_engine/matcher.py +154 -0
- code_muse/hook_engine/models.py +245 -0
- code_muse/hook_engine/registry.py +114 -0
- code_muse/hook_engine/trust.py +268 -0
- code_muse/hook_engine/validator.py +144 -0
- code_muse/http_utils.py +360 -0
- code_muse/keymap.py +128 -0
- code_muse/list_filtering.py +26 -0
- code_muse/main.py +10 -0
- code_muse/messaging/__init__.py +259 -0
- code_muse/messaging/bus.py +621 -0
- code_muse/messaging/commands.py +166 -0
- code_muse/messaging/markdown_patches.py +57 -0
- code_muse/messaging/message_queue.py +397 -0
- code_muse/messaging/messages.py +591 -0
- code_muse/messaging/queue_console.py +269 -0
- code_muse/messaging/renderers.py +308 -0
- code_muse/messaging/rich_renderer.py +1158 -0
- code_muse/messaging/shimmer.py +154 -0
- code_muse/messaging/spinner/__init__.py +87 -0
- code_muse/messaging/spinner/console_spinner.py +250 -0
- code_muse/messaging/spinner/spinner_base.py +82 -0
- code_muse/messaging/subagent_console.py +458 -0
- code_muse/model_factory.py +1203 -0
- code_muse/model_switching.py +59 -0
- code_muse/model_utils.py +156 -0
- code_muse/models.json +66 -0
- code_muse/models_cache/__init__.py +26 -0
- code_muse/models_cache/blocking_lru_cache.py +98 -0
- code_muse/models_cache/cache_writer.py +86 -0
- code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
- code_muse/models_cache/sha256_hash.pyx +34 -0
- code_muse/models_cache/startup_integration.py +75 -0
- code_muse/models_dev_api.json +1 -0
- code_muse/models_dev_parser.py +590 -0
- code_muse/motion.py +126 -0
- code_muse/plugins/__init__.py +471 -0
- code_muse/plugins/agent_skills/__init__.py +32 -0
- code_muse/plugins/agent_skills/config.py +176 -0
- code_muse/plugins/agent_skills/discovery.py +309 -0
- code_muse/plugins/agent_skills/downloader.py +389 -0
- code_muse/plugins/agent_skills/installer.py +19 -0
- code_muse/plugins/agent_skills/metadata.py +293 -0
- code_muse/plugins/agent_skills/prompt_builder.py +66 -0
- code_muse/plugins/agent_skills/register_callbacks.py +298 -0
- code_muse/plugins/agent_skills/remote_catalog.py +320 -0
- code_muse/plugins/agent_skills/skill_catalog.py +254 -0
- code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
- code_muse/plugins/agent_skills/skills_menu.py +791 -0
- code_muse/plugins/autonomous_memory/__init__.py +39 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
- code_muse/plugins/autonomous_memory/consolidation.py +82 -0
- code_muse/plugins/autonomous_memory/extraction.py +382 -0
- code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
- code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
- code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
- code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
- code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
- code_muse/plugins/aws_bedrock/__init__.py +14 -0
- code_muse/plugins/aws_bedrock/config.py +99 -0
- code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
- code_muse/plugins/aws_bedrock/utils.py +153 -0
- code_muse/plugins/azure_foundry/README.md +238 -0
- code_muse/plugins/azure_foundry/__init__.py +15 -0
- code_muse/plugins/azure_foundry/config.py +125 -0
- code_muse/plugins/azure_foundry/discovery.py +187 -0
- code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
- code_muse/plugins/azure_foundry/token.py +180 -0
- code_muse/plugins/azure_foundry/utils.py +345 -0
- code_muse/plugins/build_filter/__init__.py +1 -0
- code_muse/plugins/build_filter/register_callbacks.py +201 -0
- code_muse/plugins/build_filter/strategies/__init__.py +1 -0
- code_muse/plugins/build_filter/strategies/build.py +397 -0
- code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
- code_muse/plugins/chatgpt_oauth/config.py +52 -0
- code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
- code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
- code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
- code_muse/plugins/chatgpt_oauth/utils.py +538 -0
- code_muse/plugins/checkpointing/__init__.py +29 -0
- code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
- code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
- code_muse/plugins/checkpointing/register_callbacks.py +51 -0
- code_muse/plugins/checkpointing/restore_command.py +263 -0
- code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
- code_muse/plugins/checkpointing/shadow_git.py +90 -0
- code_muse/plugins/claude_code_hooks/__init__.py +1 -0
- code_muse/plugins/claude_code_hooks/config.py +188 -0
- code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
- code_muse/plugins/claude_code_oauth/README.md +167 -0
- code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
- code_muse/plugins/claude_code_oauth/__init__.py +25 -0
- code_muse/plugins/claude_code_oauth/config.py +52 -0
- code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
- code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
- code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
- code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
- code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
- code_muse/plugins/claude_code_oauth/utils.py +664 -0
- code_muse/plugins/copilot_auth/__init__.py +11 -0
- code_muse/plugins/copilot_auth/config.py +91 -0
- code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
- code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
- code_muse/plugins/copilot_auth/utils.py +584 -0
- code_muse/plugins/custom_commands/__init__.py +14 -0
- code_muse/plugins/custom_commands/args_injection.py +82 -0
- code_muse/plugins/custom_commands/command_discovery.py +89 -0
- code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
- code_muse/plugins/custom_commands/register_callbacks.py +176 -0
- code_muse/plugins/customizable_commands/__init__.py +0 -0
- code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
- code_muse/plugins/destructive_command_guard/__init__.py +14 -0
- code_muse/plugins/destructive_command_guard/detector.py +375 -0
- code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
- code_muse/plugins/example_custom_command/README.md +280 -0
- code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
- code_muse/plugins/file_permission_handler/__init__.py +4 -0
- code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
- code_muse/plugins/filter_engine/__init__.py +30 -0
- code_muse/plugins/filter_engine/classifier.py +153 -0
- code_muse/plugins/filter_engine/content_detector.py +184 -0
- code_muse/plugins/filter_engine/dispatcher.py +244 -0
- code_muse/plugins/filter_engine/register_callbacks.py +188 -0
- code_muse/plugins/filter_engine/registry.py +279 -0
- code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
- code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
- code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
- code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
- code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
- code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
- code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
- code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
- code_muse/plugins/filter_engine/verbosity.py +63 -0
- code_muse/plugins/force_push_guard/__init__.py +5 -0
- code_muse/plugins/force_push_guard/detector.py +96 -0
- code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
- code_muse/plugins/force_push_guard/test_detector.py +143 -0
- code_muse/plugins/frontend_emitter/__init__.py +25 -0
- code_muse/plugins/frontend_emitter/emitter.py +121 -0
- code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
- code_muse/plugins/gac/__init__.py +4 -0
- code_muse/plugins/gac/git_ops.py +136 -0
- code_muse/plugins/gac/prompt.py +191 -0
- code_muse/plugins/gac/register_callbacks.py +82 -0
- code_muse/plugins/hook_creator/__init__.py +1 -0
- code_muse/plugins/hook_creator/register_callbacks.py +34 -0
- code_muse/plugins/hook_manager/__init__.py +1 -0
- code_muse/plugins/hook_manager/config.py +289 -0
- code_muse/plugins/hook_manager/hooks_menu.py +563 -0
- code_muse/plugins/hook_manager/register_callbacks.py +227 -0
- code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
- code_muse/plugins/mindpack/__init__.py +0 -0
- code_muse/plugins/mindpack/factory.py +930 -0
- code_muse/plugins/mindpack/judge.py +573 -0
- code_muse/plugins/mindpack/memory.py +100 -0
- code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
- code_muse/plugins/mindpack/orchestration.py +605 -0
- code_muse/plugins/mindpack/register_callbacks.py +175 -0
- code_muse/plugins/mindpack/schemas.py +358 -0
- code_muse/plugins/mindpack/tools.py +387 -0
- code_muse/plugins/oauth_muse_html.py +226 -0
- code_muse/plugins/ollama_setup/__init__.py +5 -0
- code_muse/plugins/ollama_setup/completer.py +36 -0
- code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
- code_muse/plugins/plan_command/__init__.py +0 -0
- code_muse/plugins/plan_command/register_callbacks.py +206 -0
- code_muse/plugins/plan_mode/__init__.py +37 -0
- code_muse/plugins/plan_mode/mode_cycling.py +40 -0
- code_muse/plugins/plan_mode/plan_generation.py +68 -0
- code_muse/plugins/plan_mode/plan_hooks.py +74 -0
- code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
- code_muse/plugins/plan_mode/register_callbacks.py +121 -0
- code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
- code_muse/plugins/policy_engine/__init__.py +46 -0
- code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
- code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
- code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
- code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
- code_muse/plugins/policy_engine/register_callbacks.py +112 -0
- code_muse/plugins/pop_command/__init__.py +1 -0
- code_muse/plugins/pop_command/register_callbacks.py +189 -0
- code_muse/plugins/prompt_newline/__init__.py +13 -0
- code_muse/plugins/prompt_newline/config.py +19 -0
- code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
- code_muse/plugins/safety_status/__init__.py +0 -0
- code_muse/plugins/safety_status/register_callbacks.py +113 -0
- code_muse/plugins/semantic_compression/__init__.py +6 -0
- code_muse/plugins/semantic_compression/compressor.py +295 -0
- code_muse/plugins/semantic_compression/config.py +123 -0
- code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
- code_muse/plugins/shell_minimizer/__init__.py +50 -0
- code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
- code_muse/plugins/shell_minimizer/pipeline.py +556 -0
- code_muse/plugins/shell_minimizer/primitives.py +482 -0
- code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
- code_muse/plugins/shell_safety/__init__.py +6 -0
- code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_muse/plugins/shell_safety/command_cache.py +149 -0
- code_muse/plugins/shell_safety/register_callbacks.py +202 -0
- code_muse/plugins/synthetic_status/__init__.py +1 -0
- code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
- code_muse/plugins/synthetic_status/status_api.py +145 -0
- code_muse/plugins/token_caching/__init__.py +21 -0
- code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
- code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
- code_muse/plugins/token_caching/register_callbacks.py +54 -0
- code_muse/plugins/token_caching/stats_display.py +35 -0
- code_muse/plugins/token_tracking/__init__.py +26 -0
- code_muse/plugins/token_tracking/database.py +381 -0
- code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
- code_muse/plugins/token_tracking/record.py +55 -0
- code_muse/plugins/token_tracking/register_callbacks.py +277 -0
- code_muse/plugins/token_tracking/reports.py +329 -0
- code_muse/plugins/universal_constructor/__init__.py +13 -0
- code_muse/plugins/universal_constructor/models.py +136 -0
- code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
- code_muse/plugins/universal_constructor/registry.py +390 -0
- code_muse/plugins/universal_constructor/runner.py +474 -0
- code_muse/plugins/universal_constructor/safety.py +440 -0
- code_muse/plugins/universal_constructor/sandbox.py +584 -0
- code_muse/provider_identity.py +105 -0
- code_muse/pydantic_patches.py +410 -0
- code_muse/reopenable_async_client.py +233 -0
- code_muse/round_robin_model.py +151 -0
- code_muse/secret_storage.py +74 -0
- code_muse/security/__init__.py +1 -0
- code_muse/security/redaction.cpython-314-darwin.so +0 -0
- code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/security/redaction.pyx +135 -0
- code_muse/session_storage.py +565 -0
- code_muse/status_display.py +261 -0
- code_muse/stream_parser/__init__.py +76 -0
- code_muse/stream_parser/assistant_text_parser.py +90 -0
- code_muse/stream_parser/citation_parser.py +76 -0
- code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
- code_muse/stream_parser/proposed_plan_parser.py +158 -0
- code_muse/stream_parser/stream_text_chunk.py +23 -0
- code_muse/stream_parser/stream_text_parser.py +27 -0
- code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
- code_muse/stream_parser/tagged_line_parser.pyx +251 -0
- code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
- code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
- code_muse/summarization_agent.py +308 -0
- code_muse/terminal_utils.cpython-314-darwin.so +0 -0
- code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/terminal_utils.pyx +483 -0
- code_muse/tools/__init__.py +459 -0
- code_muse/tools/agent_tools.py +613 -0
- code_muse/tools/ask_user_question/__init__.py +26 -0
- code_muse/tools/ask_user_question/constants.py +73 -0
- code_muse/tools/ask_user_question/demo_tui.py +55 -0
- code_muse/tools/ask_user_question/handler.py +232 -0
- code_muse/tools/ask_user_question/models.py +302 -0
- code_muse/tools/ask_user_question/registration.py +37 -0
- code_muse/tools/ask_user_question/renderers.py +336 -0
- code_muse/tools/ask_user_question/terminal_ui.py +327 -0
- code_muse/tools/ask_user_question/theme.py +156 -0
- code_muse/tools/ask_user_question/tui_loop.py +422 -0
- code_muse/tools/background_jobs.py +99 -0
- code_muse/tools/browser/__init__.py +37 -0
- code_muse/tools/browser/browser_control.py +289 -0
- code_muse/tools/browser/browser_interactions.py +545 -0
- code_muse/tools/browser/browser_locators.py +640 -0
- code_muse/tools/browser/browser_manager.py +376 -0
- code_muse/tools/browser/browser_navigation.py +251 -0
- code_muse/tools/browser/browser_screenshot.py +180 -0
- code_muse/tools/browser/browser_scripts.py +462 -0
- code_muse/tools/browser/browser_workflows.py +222 -0
- code_muse/tools/chrome_cdp/__init__.py +1070 -0
- code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
- code_muse/tools/command_runner.py +1401 -0
- code_muse/tools/common.py +1407 -0
- code_muse/tools/display.py +87 -0
- code_muse/tools/file_modifications.py +1099 -0
- code_muse/tools/file_operations.py +860 -0
- code_muse/tools/image_tools.py +185 -0
- code_muse/tools/meetin_proxy/__init__.py +243 -0
- code_muse/tools/meetin_proxy/capture_addon.py +82 -0
- code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
- code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
- code_muse/tools/path_policy.py +219 -0
- code_muse/tools/skills_tools.py +586 -0
- code_muse/tools/subagent_context.py +158 -0
- code_muse/tools/tools_content.py +50 -0
- code_muse/tools/universal_constructor.py +965 -0
- code_muse/uvx_detection.py +241 -0
- code_muse/version_checker.py +86 -0
- code_muse-0.0.1.data/data/code_muse/models.json +66 -0
- code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
- code_muse-0.0.1.dist-info/METADATA +845 -0
- code_muse-0.0.1.dist-info/RECORD +394 -0
- code_muse-0.0.1.dist-info/WHEEL +4 -0
- code_muse-0.0.1.dist-info/entry_points.txt +2 -0
- code_muse-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
"""Expert agent factory — creates read-only sub-agents from ExpertDescriptors.
|
|
2
|
+
|
|
3
|
+
Follows the same sub-agent construction pattern as
|
|
4
|
+
``code_muse.tools.agent_tools.register_invoke_agent`` but with two key
|
|
5
|
+
constraints:
|
|
6
|
+
|
|
7
|
+
1. **Read-only tool set** — experts can inspect code but never mutate it.
|
|
8
|
+
2. **Structured output** — experts return ``ExpertReport`` models, not free
|
|
9
|
+
text, so the judge merger receives machine-parseable data.
|
|
10
|
+
|
|
11
|
+
The factory is deliberately stateless: it builds an agent per call and
|
|
12
|
+
tears it down afterwards. No session history is persisted to disk because
|
|
13
|
+
expert consultations are ephemeral — they exist only for the duration of a
|
|
14
|
+
single ``MindPackOrchestrator.consult()`` call.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import logging
|
|
19
|
+
import uuid
|
|
20
|
+
from contextlib import AsyncExitStack
|
|
21
|
+
from functools import partial
|
|
22
|
+
from typing import Any, get_args
|
|
23
|
+
|
|
24
|
+
from pydantic_ai import Agent as PydanticAgent
|
|
25
|
+
from pydantic_ai import UsageLimits
|
|
26
|
+
|
|
27
|
+
from code_muse.plugins.mindpack.schemas import (
|
|
28
|
+
AskMindPackInput,
|
|
29
|
+
ExpertDescriptor,
|
|
30
|
+
ExpertSpawnMode,
|
|
31
|
+
MindPackExpertPoolConfig,
|
|
32
|
+
)
|
|
33
|
+
from code_muse.plugins.mindpack.schemas import MindPackExpertReport as ExpertReport
|
|
34
|
+
from code_muse.tools.subagent_context import subagent_context
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# INI config helpers
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_config_str(key: str, default: str) -> str:
|
|
44
|
+
from code_muse.config import get_value
|
|
45
|
+
|
|
46
|
+
return get_value(key) or default
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _get_config_int(key: str, default: int) -> int:
|
|
50
|
+
from code_muse.config import get_value
|
|
51
|
+
|
|
52
|
+
val = get_value(key)
|
|
53
|
+
if val is None:
|
|
54
|
+
return default
|
|
55
|
+
try:
|
|
56
|
+
return int(val)
|
|
57
|
+
except ValueError, TypeError:
|
|
58
|
+
logger.warning(
|
|
59
|
+
"Invalid integer config '%s=%s'; using default %s", key, val, default
|
|
60
|
+
)
|
|
61
|
+
return default
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _get_config_bool(key: str, default: bool) -> bool:
|
|
65
|
+
from code_muse.config import get_value
|
|
66
|
+
|
|
67
|
+
val = get_value(key)
|
|
68
|
+
if val is None:
|
|
69
|
+
return default
|
|
70
|
+
return str(val).lower() in ("1", "true", "yes", "on")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def load_pool_config_from_ini(
|
|
74
|
+
overrides: MindPackExpertPoolConfig | None = None,
|
|
75
|
+
) -> MindPackExpertPoolConfig:
|
|
76
|
+
"""Build a ``MindPackExpertPoolConfig`` from INI settings.
|
|
77
|
+
|
|
78
|
+
Reads ``packmind_expert_spawn_mode``, ``packmind_expert_count``,
|
|
79
|
+
``packmind_min_experts``, ``packmind_max_experts``, and
|
|
80
|
+
``packmind_model_strategy`` from the Muse INI config.
|
|
81
|
+
|
|
82
|
+
Any caller-supplied ``overrides`` take precedence over INI values.
|
|
83
|
+
"""
|
|
84
|
+
spawn_mode_raw = _get_config_str("packmind_expert_spawn_mode", "fixed")
|
|
85
|
+
valid_modes = set(get_args(ExpertSpawnMode))
|
|
86
|
+
if spawn_mode_raw not in valid_modes:
|
|
87
|
+
logger.warning(
|
|
88
|
+
"Invalid packmind_expert_spawn_mode '%s'; falling back to 'fixed'",
|
|
89
|
+
spawn_mode_raw,
|
|
90
|
+
)
|
|
91
|
+
spawn_mode_raw = "fixed"
|
|
92
|
+
|
|
93
|
+
config = MindPackExpertPoolConfig(
|
|
94
|
+
spawn_mode=spawn_mode_raw, # type: ignore[arg-type]
|
|
95
|
+
default_expert_count=_get_config_int("packmind_expert_count", 5),
|
|
96
|
+
min_experts=_get_config_int("packmind_min_experts", 3),
|
|
97
|
+
max_experts=_get_config_int("packmind_max_experts", 7),
|
|
98
|
+
model_strategy=_get_config_str("packmind_model_strategy", "same_model"), # type: ignore[arg-type]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if overrides is not None:
|
|
102
|
+
# Apply caller overrides field-by-field
|
|
103
|
+
for field_name in MindPackExpertPoolConfig.model_fields:
|
|
104
|
+
override_val = getattr(overrides, field_name, None)
|
|
105
|
+
if override_val is not None:
|
|
106
|
+
setattr(config, field_name, override_val)
|
|
107
|
+
|
|
108
|
+
return config
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Read-only tool allow-list
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
READ_ONLY_TOOLS: list[str] = [
|
|
116
|
+
"list_files",
|
|
117
|
+
"read_file",
|
|
118
|
+
"grep",
|
|
119
|
+
"load_image_for_analysis",
|
|
120
|
+
"list_or_search_skills",
|
|
121
|
+
]
|
|
122
|
+
"""Tools that an expert agent may use. All write-capable tools (create_file,
|
|
123
|
+
replace_in_file, delete_snippet, delete_file, agent_run_shell_command,
|
|
124
|
+
ask_user_question, invoke_agent, list_agents, browser_*, activate_skill,
|
|
125
|
+
universal_constructor) are deliberately excluded."""
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# Expert system prompt template
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
_EXPERT_SYSTEM_PROMPT_TEMPLATE = """\
|
|
132
|
+
You are {expert_name}, a specialist in {speciality}.
|
|
133
|
+
|
|
134
|
+
{system_prompt_fragment}
|
|
135
|
+
|
|
136
|
+
## CRITICAL CONSTRAINTS
|
|
137
|
+
|
|
138
|
+
You are operating in **read-only advisory mode**. You must NEVER:
|
|
139
|
+
|
|
140
|
+
- Create, modify, or delete any files
|
|
141
|
+
- Run shell commands
|
|
142
|
+
- Invoke other agents or sub-agents
|
|
143
|
+
- Ask the user questions
|
|
144
|
+
- Navigate websites or use browser tools
|
|
145
|
+
|
|
146
|
+
You MAY:
|
|
147
|
+
- List and read files
|
|
148
|
+
- Search code with grep
|
|
149
|
+
- Load images for analysis
|
|
150
|
+
|
|
151
|
+
Your task is to analyse the problem, explore the relevant code, and produce
|
|
152
|
+
a structured report with your findings, recommendations, identified risks,
|
|
153
|
+
and confidence level.
|
|
154
|
+
|
|
155
|
+
## OUTPUT FORMAT
|
|
156
|
+
|
|
157
|
+
You MUST produce your response as a structured report containing:
|
|
158
|
+
- **summary**: Your detailed analysis of the problem
|
|
159
|
+
- **findings**: Key findings and observations
|
|
160
|
+
- **proposed_plan**: A list of specific, actionable recommendations
|
|
161
|
+
- **risks**: A list of risks, edge cases, or failure modes you identified
|
|
162
|
+
- **files_to_inspect**: Files you recommend the executor inspect or change
|
|
163
|
+
- **tests_to_run**: Tests you recommend running
|
|
164
|
+
- **confidence**: Your confidence in your analysis (0.0 to 1.0)
|
|
165
|
+
- **assumptions**: Any assumptions you made
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
# Prompt builder
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def build_expert_prompt(
|
|
174
|
+
expert: ExpertDescriptor,
|
|
175
|
+
request: AskMindPackInput,
|
|
176
|
+
) -> str:
|
|
177
|
+
"""Compose the user-facing prompt for an expert consultation.
|
|
178
|
+
|
|
179
|
+
The prompt carries the full problem context so the expert can work
|
|
180
|
+
autonomously with only read-only tools.
|
|
181
|
+
"""
|
|
182
|
+
parts: list[str] = []
|
|
183
|
+
|
|
184
|
+
parts.append(f"## Problem Statement\n{request.problem_statement}")
|
|
185
|
+
|
|
186
|
+
parts.append(f"## Current Goal\n{request.current_goal}")
|
|
187
|
+
|
|
188
|
+
if request.current_plan:
|
|
189
|
+
parts.append(f"## Current Plan\n{request.current_plan}")
|
|
190
|
+
|
|
191
|
+
if request.what_has_been_tried:
|
|
192
|
+
parts.append(
|
|
193
|
+
"## What Has Been Tried\n"
|
|
194
|
+
+ "\n".join(f"- {item}" for item in request.what_has_been_tried)
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if request.relevant_files:
|
|
198
|
+
parts.append(
|
|
199
|
+
"## Relevant Files\n" + "\n".join(f"- {f}" for f in request.relevant_files)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if request.observed_errors:
|
|
203
|
+
parts.append(
|
|
204
|
+
"## Observed Errors\n"
|
|
205
|
+
+ "\n".join(f"- {e}" for e in request.observed_errors)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if request.uncertainty:
|
|
209
|
+
parts.append(f"## Uncertainty\n{request.uncertainty}")
|
|
210
|
+
|
|
211
|
+
parts.append(f"## Desired Output Type\n{request.desired_output}")
|
|
212
|
+
|
|
213
|
+
return "\n\n".join(parts)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
# ExpertAgentFactory
|
|
218
|
+
# ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class ExpertAgentFactory:
|
|
222
|
+
"""Creates and runs read-only expert sub-agents.
|
|
223
|
+
|
|
224
|
+
Usage::
|
|
225
|
+
|
|
226
|
+
factory = ExpertAgentFactory()
|
|
227
|
+
report = await factory.invoke_expert(descriptor, request, session_id)
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
def __init__(self) -> None:
|
|
231
|
+
self._concurrency_limit = _get_config_int("packmind_concurrency_limit", 8)
|
|
232
|
+
self._semaphore = asyncio.Semaphore(self._concurrency_limit)
|
|
233
|
+
logger.debug(
|
|
234
|
+
"ExpertAgentFactory: concurrency limit set to %d",
|
|
235
|
+
self._concurrency_limit,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def create_expert_agent(
|
|
239
|
+
self,
|
|
240
|
+
expert: ExpertDescriptor,
|
|
241
|
+
*,
|
|
242
|
+
session_id: str,
|
|
243
|
+
message_group: str | None = None,
|
|
244
|
+
model_override: str | None = None,
|
|
245
|
+
) -> PydanticAgent:
|
|
246
|
+
"""Build a pydantic-ai agent for the given expert descriptor.
|
|
247
|
+
|
|
248
|
+
The agent is configured with:
|
|
249
|
+
- The expert's system prompt (identity + fragment + constraints)
|
|
250
|
+
- Read-only tools only
|
|
251
|
+
- ``ExpertReport`` as the structured output type
|
|
252
|
+
|
|
253
|
+
This is a **temporary agent** — it is not registered in the agent
|
|
254
|
+
manager and does not persist across calls.
|
|
255
|
+
"""
|
|
256
|
+
from code_muse.agents._builder import load_muse_rules
|
|
257
|
+
from code_muse.model_factory import ModelFactory, make_model_settings
|
|
258
|
+
from code_muse.model_utils import prepare_prompt_for_model
|
|
259
|
+
|
|
260
|
+
# Resolve model
|
|
261
|
+
model_name = model_override or self._resolve_model_name(expert)
|
|
262
|
+
models_config = ModelFactory.load_config()
|
|
263
|
+
|
|
264
|
+
if model_name not in models_config:
|
|
265
|
+
raise ValueError(
|
|
266
|
+
f"Model '{model_name}' not found in configuration — "
|
|
267
|
+
"cannot create expert agent"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
model = ModelFactory.get_model(model_name, models_config)
|
|
271
|
+
|
|
272
|
+
# Build instructions
|
|
273
|
+
instructions = _EXPERT_SYSTEM_PROMPT_TEMPLATE.format(
|
|
274
|
+
expert_name=expert.name,
|
|
275
|
+
speciality=expert.speciality,
|
|
276
|
+
system_prompt_fragment=expert.system_prompt_fragment,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Append AGENTS.md rules if available
|
|
280
|
+
agent_rules = load_muse_rules()
|
|
281
|
+
if agent_rules:
|
|
282
|
+
instructions += f"\n\n{agent_rules}"
|
|
283
|
+
|
|
284
|
+
# Append plugin prompt additions
|
|
285
|
+
from code_muse import callbacks
|
|
286
|
+
|
|
287
|
+
prompt_additions = callbacks.on_load_prompt()
|
|
288
|
+
if prompt_additions:
|
|
289
|
+
instructions += "\n" + "\n".join(prompt_additions)
|
|
290
|
+
|
|
291
|
+
# Prepare for model (handles claude-code prepending etc.)
|
|
292
|
+
prepared = prepare_prompt_for_model(
|
|
293
|
+
model_name,
|
|
294
|
+
instructions,
|
|
295
|
+
"", # user prompt is empty at build time
|
|
296
|
+
prepend_system_to_user=False,
|
|
297
|
+
)
|
|
298
|
+
instructions = prepared.instructions
|
|
299
|
+
|
|
300
|
+
model_settings = make_model_settings(model_name)
|
|
301
|
+
|
|
302
|
+
# Build the pydantic-ai agent with ExpertReport output type
|
|
303
|
+
temp_agent = PydanticAgent(
|
|
304
|
+
model=model,
|
|
305
|
+
instructions=instructions,
|
|
306
|
+
output_type=ExpertReport,
|
|
307
|
+
retries=2,
|
|
308
|
+
# Explicitly restrict tools via registration later,
|
|
309
|
+
# but ensure this agent has NO access to agent/browser control.
|
|
310
|
+
toolsets=[],
|
|
311
|
+
history_processors=[],
|
|
312
|
+
model_settings=model_settings,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Register read-only tools
|
|
316
|
+
from code_muse.tools import register_tools_for_agent
|
|
317
|
+
|
|
318
|
+
tools = list(READ_ONLY_TOOLS)
|
|
319
|
+
register_tools_for_agent(temp_agent, tools, model_name=model_name)
|
|
320
|
+
|
|
321
|
+
logger.debug(
|
|
322
|
+
"ExpertAgentFactory: created agent for '%s' with tools=%s session=%s",
|
|
323
|
+
expert.name,
|
|
324
|
+
tools,
|
|
325
|
+
session_id,
|
|
326
|
+
)
|
|
327
|
+
return temp_agent
|
|
328
|
+
|
|
329
|
+
async def build_expert_pool(
|
|
330
|
+
self,
|
|
331
|
+
experts: list[ExpertDescriptor],
|
|
332
|
+
request: AskMindPackInput,
|
|
333
|
+
pool_config: MindPackExpertPoolConfig,
|
|
334
|
+
session_id: str,
|
|
335
|
+
) -> list[tuple[ExpertDescriptor, PydanticAgent]]:
|
|
336
|
+
"""Spawns an asynchronous expert pool based on the configured mode."""
|
|
337
|
+
spawn_mode = pool_config.spawn_mode
|
|
338
|
+
|
|
339
|
+
if spawn_mode == "fixed":
|
|
340
|
+
return await self._build_pool_fixed(
|
|
341
|
+
experts,
|
|
342
|
+
request,
|
|
343
|
+
pool_config,
|
|
344
|
+
session_id,
|
|
345
|
+
count=pool_config.default_expert_count,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
if spawn_mode == "adaptive":
|
|
349
|
+
return await self._build_pool_fixed(
|
|
350
|
+
experts,
|
|
351
|
+
request,
|
|
352
|
+
pool_config,
|
|
353
|
+
session_id,
|
|
354
|
+
count=pool_config.min_experts,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if spawn_mode == "same_agent_replicas":
|
|
358
|
+
return await self._build_pool_same_agent_replicas(
|
|
359
|
+
experts,
|
|
360
|
+
request,
|
|
361
|
+
pool_config,
|
|
362
|
+
session_id,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
if spawn_mode == "multi_model_replicas":
|
|
366
|
+
return await self._build_pool_multi_model_replicas(
|
|
367
|
+
experts,
|
|
368
|
+
request,
|
|
369
|
+
pool_config,
|
|
370
|
+
session_id,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if spawn_mode == "hybrid":
|
|
374
|
+
return await self._build_pool_hybrid(
|
|
375
|
+
experts,
|
|
376
|
+
request,
|
|
377
|
+
pool_config,
|
|
378
|
+
session_id,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if spawn_mode == "multi_agent":
|
|
382
|
+
return await self._build_pool_multi_agent(
|
|
383
|
+
experts,
|
|
384
|
+
request,
|
|
385
|
+
pool_config,
|
|
386
|
+
session_id,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
logger.warning(
|
|
390
|
+
"Unknown spawn mode '%s'; falling back to fixed pool",
|
|
391
|
+
spawn_mode,
|
|
392
|
+
)
|
|
393
|
+
return await self._build_pool_fixed(
|
|
394
|
+
experts,
|
|
395
|
+
request,
|
|
396
|
+
pool_config,
|
|
397
|
+
session_id,
|
|
398
|
+
count=pool_config.default_expert_count,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
async def _build_pool_fixed(
|
|
402
|
+
self,
|
|
403
|
+
experts: list[ExpertDescriptor],
|
|
404
|
+
request: AskMindPackInput,
|
|
405
|
+
pool_config: MindPackExpertPoolConfig,
|
|
406
|
+
session_id: str,
|
|
407
|
+
count: int | None = None,
|
|
408
|
+
) -> list[tuple[ExpertDescriptor, PydanticAgent]]:
|
|
409
|
+
"""Fixed-mode pool builder: spawn up to *count* agents from *experts*."""
|
|
410
|
+
target_count = count if count is not None else pool_config.default_expert_count
|
|
411
|
+
selected = experts[:target_count]
|
|
412
|
+
if len(selected) < target_count:
|
|
413
|
+
logger.warning(
|
|
414
|
+
"Requested %d experts but registry only has %d; using all available",
|
|
415
|
+
target_count,
|
|
416
|
+
len(selected),
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
pool: list[tuple[ExpertDescriptor, PydanticAgent]] = []
|
|
420
|
+
global_model = self._resolve_model_name(selected[0]) if selected else None
|
|
421
|
+
|
|
422
|
+
for expert in selected:
|
|
423
|
+
model_to_use = global_model
|
|
424
|
+
if pool_config.model_strategy == "per_expert" and expert.model:
|
|
425
|
+
model_to_use = expert.model
|
|
426
|
+
elif pool_config.model_strategy == "model_pool":
|
|
427
|
+
# Round-robin model rotation across experts
|
|
428
|
+
if not hasattr(self, "_model_pool_index"):
|
|
429
|
+
self._model_pool_index = 0
|
|
430
|
+
from code_muse.model_factory import ModelFactory
|
|
431
|
+
|
|
432
|
+
models_config = ModelFactory.load_config()
|
|
433
|
+
model_names = list(models_config.keys())
|
|
434
|
+
if model_names:
|
|
435
|
+
model_to_use = model_names[
|
|
436
|
+
self._model_pool_index % len(model_names)
|
|
437
|
+
]
|
|
438
|
+
self._model_pool_index += 1
|
|
439
|
+
else:
|
|
440
|
+
model_to_use = global_model
|
|
441
|
+
|
|
442
|
+
agent = self.create_expert_agent(
|
|
443
|
+
expert,
|
|
444
|
+
session_id=session_id,
|
|
445
|
+
model_override=model_to_use,
|
|
446
|
+
)
|
|
447
|
+
pool.append((expert, agent))
|
|
448
|
+
|
|
449
|
+
return pool
|
|
450
|
+
|
|
451
|
+
async def _build_pool_same_agent_replicas(
|
|
452
|
+
self,
|
|
453
|
+
experts: list[ExpertDescriptor],
|
|
454
|
+
request: AskMindPackInput,
|
|
455
|
+
pool_config: MindPackExpertPoolConfig,
|
|
456
|
+
session_id: str,
|
|
457
|
+
) -> list[tuple[ExpertDescriptor, PydanticAgent]]:
|
|
458
|
+
"""Spawn N copies of the first expert with different role lenses.
|
|
459
|
+
|
|
460
|
+
Each replica gets a distinct lens (scout, architect, watchdog,
|
|
461
|
+
test_planner, challenger) injected into its system prompt, providing
|
|
462
|
+
diverse perspectives from a single base expert configuration.
|
|
463
|
+
"""
|
|
464
|
+
if not experts:
|
|
465
|
+
return []
|
|
466
|
+
|
|
467
|
+
base = experts[0]
|
|
468
|
+
count = min(pool_config.default_expert_count, pool_config.max_experts)
|
|
469
|
+
lenses = ["scout", "architect", "watchdog", "test_planner", "challenger"]
|
|
470
|
+
|
|
471
|
+
pool: list[tuple[ExpertDescriptor, PydanticAgent]] = []
|
|
472
|
+
for i in range(count):
|
|
473
|
+
lens = lenses[i % len(lenses)]
|
|
474
|
+
variant = ExpertDescriptor(
|
|
475
|
+
name=f"{base.name}-{lens}",
|
|
476
|
+
speciality=f"{base.speciality} [{lens} lens]",
|
|
477
|
+
system_prompt_fragment=(
|
|
478
|
+
f"LENS: {lens} perspective\n\n{base.system_prompt_fragment}"
|
|
479
|
+
),
|
|
480
|
+
model=base.model,
|
|
481
|
+
max_experts_override=base.max_experts_override,
|
|
482
|
+
)
|
|
483
|
+
model_name = self._resolve_model_name(base)
|
|
484
|
+
agent = self.create_expert_agent(
|
|
485
|
+
variant,
|
|
486
|
+
session_id=session_id,
|
|
487
|
+
model_override=model_name,
|
|
488
|
+
)
|
|
489
|
+
pool.append((variant, agent))
|
|
490
|
+
|
|
491
|
+
logger.info(
|
|
492
|
+
"same_agent_replicas: spawned %d replicas of '%s'",
|
|
493
|
+
count,
|
|
494
|
+
base.name,
|
|
495
|
+
)
|
|
496
|
+
return pool
|
|
497
|
+
|
|
498
|
+
async def _build_pool_multi_model_replicas(
|
|
499
|
+
self,
|
|
500
|
+
experts: list[ExpertDescriptor],
|
|
501
|
+
request: AskMindPackInput,
|
|
502
|
+
pool_config: MindPackExpertPoolConfig,
|
|
503
|
+
session_id: str,
|
|
504
|
+
) -> list[tuple[ExpertDescriptor, PydanticAgent]]:
|
|
505
|
+
"""Spawn experts across multiple models from the available model pool.
|
|
506
|
+
|
|
507
|
+
Rotates through available models, assigning each expert a different
|
|
508
|
+
model to get diverse LLM perspectives on the same problem.
|
|
509
|
+
Falls back to fixed pool if no models are available.
|
|
510
|
+
"""
|
|
511
|
+
if not experts:
|
|
512
|
+
return []
|
|
513
|
+
|
|
514
|
+
from code_muse.model_factory import ModelFactory
|
|
515
|
+
|
|
516
|
+
models_config = ModelFactory.load_config()
|
|
517
|
+
model_names = list(models_config.keys())
|
|
518
|
+
|
|
519
|
+
if not model_names:
|
|
520
|
+
logger.warning(
|
|
521
|
+
"multi_model_replicas: no models available; falling back to fixed"
|
|
522
|
+
)
|
|
523
|
+
return await self._build_pool_fixed(
|
|
524
|
+
experts,
|
|
525
|
+
request,
|
|
526
|
+
pool_config,
|
|
527
|
+
session_id,
|
|
528
|
+
count=pool_config.default_expert_count,
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
count = min(
|
|
532
|
+
pool_config.default_expert_count,
|
|
533
|
+
len(model_names),
|
|
534
|
+
pool_config.max_experts,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
pool: list[tuple[ExpertDescriptor, PydanticAgent]] = []
|
|
538
|
+
for i in range(count):
|
|
539
|
+
expert = experts[i % len(experts)]
|
|
540
|
+
model_name = model_names[i % len(model_names)]
|
|
541
|
+
|
|
542
|
+
variant = ExpertDescriptor(
|
|
543
|
+
name=f"{expert.name}",
|
|
544
|
+
speciality=expert.speciality,
|
|
545
|
+
system_prompt_fragment=expert.system_prompt_fragment,
|
|
546
|
+
model=model_name,
|
|
547
|
+
max_experts_override=expert.max_experts_override,
|
|
548
|
+
)
|
|
549
|
+
agent = self.create_expert_agent(
|
|
550
|
+
variant,
|
|
551
|
+
session_id=session_id,
|
|
552
|
+
model_override=model_name,
|
|
553
|
+
)
|
|
554
|
+
pool.append((variant, agent))
|
|
555
|
+
|
|
556
|
+
logger.info(
|
|
557
|
+
"multi_model_replicas: spawned %d experts across models: %s",
|
|
558
|
+
count,
|
|
559
|
+
model_names[:count],
|
|
560
|
+
)
|
|
561
|
+
return pool
|
|
562
|
+
|
|
563
|
+
async def _build_pool_hybrid(
|
|
564
|
+
self,
|
|
565
|
+
experts: list[ExpertDescriptor],
|
|
566
|
+
request: AskMindPackInput,
|
|
567
|
+
pool_config: MindPackExpertPoolConfig,
|
|
568
|
+
session_id: str,
|
|
569
|
+
) -> list[tuple[ExpertDescriptor, PydanticAgent]]:
|
|
570
|
+
"""Hybrid mode: fixed base + adaptive extras.
|
|
571
|
+
|
|
572
|
+
Starts with a fixed base of min_experts, then adds adaptive bonus
|
|
573
|
+
experts scaled by problem complexity (statement length heuristic).
|
|
574
|
+
Caps at max_experts.
|
|
575
|
+
"""
|
|
576
|
+
if not experts:
|
|
577
|
+
return []
|
|
578
|
+
|
|
579
|
+
fixed_base = min(pool_config.min_experts, len(experts))
|
|
580
|
+
# Heuristic: +1 extra expert per 500 chars of problem statement, max +2
|
|
581
|
+
problem_length = len(request.problem_statement)
|
|
582
|
+
adaptive_bonus = min(2, problem_length // 500)
|
|
583
|
+
total = min(fixed_base + adaptive_bonus, pool_config.max_experts, len(experts))
|
|
584
|
+
|
|
585
|
+
logger.info(
|
|
586
|
+
"hybrid: fixed_base=%d + adaptive_bonus=%d → total=%d experts",
|
|
587
|
+
fixed_base,
|
|
588
|
+
adaptive_bonus,
|
|
589
|
+
total,
|
|
590
|
+
)
|
|
591
|
+
return await self._build_pool_fixed(
|
|
592
|
+
experts,
|
|
593
|
+
request,
|
|
594
|
+
pool_config,
|
|
595
|
+
session_id,
|
|
596
|
+
count=total,
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
async def _build_pool_multi_agent(
|
|
600
|
+
self,
|
|
601
|
+
experts: list[ExpertDescriptor],
|
|
602
|
+
request: AskMindPackInput,
|
|
603
|
+
pool_config: MindPackExpertPoolConfig,
|
|
604
|
+
session_id: str,
|
|
605
|
+
) -> list[tuple[ExpertDescriptor, PydanticAgent]]:
|
|
606
|
+
"""Multi-agent mode: attempts to load named agent configs per expert.
|
|
607
|
+
|
|
608
|
+
For each expert, tries to load a matching agent from the agent
|
|
609
|
+
manager. If the expert's name matches a registered agent, that
|
|
610
|
+
agent's config/model are used. Otherwise falls back to the
|
|
611
|
+
standard expert agent builder.
|
|
612
|
+
|
|
613
|
+
This enables mixing MindPack experts with full Muse agents.
|
|
614
|
+
"""
|
|
615
|
+
if not experts:
|
|
616
|
+
return []
|
|
617
|
+
|
|
618
|
+
from code_muse.agents.agent_manager import load_agent
|
|
619
|
+
|
|
620
|
+
count = min(
|
|
621
|
+
pool_config.default_expert_count, len(experts), pool_config.max_experts
|
|
622
|
+
)
|
|
623
|
+
selected = experts[:count]
|
|
624
|
+
|
|
625
|
+
pool: list[tuple[ExpertDescriptor, PydanticAgent]] = []
|
|
626
|
+
for expert in selected:
|
|
627
|
+
# Try to load a matching named agent
|
|
628
|
+
agent_config = None
|
|
629
|
+
try:
|
|
630
|
+
agent_config = load_agent(expert.name)
|
|
631
|
+
logger.debug(
|
|
632
|
+
"multi_agent: loaded agent '%s' for expert '%s'",
|
|
633
|
+
expert.name,
|
|
634
|
+
expert.name,
|
|
635
|
+
)
|
|
636
|
+
except ValueError:
|
|
637
|
+
logger.debug(
|
|
638
|
+
"multi_agent: no agent named '%s'; using standard expert agent",
|
|
639
|
+
expert.name,
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# Use the agent's model if available, otherwise fall back
|
|
643
|
+
model_name = expert.model or self._resolve_model_name(expert)
|
|
644
|
+
if agent_config is not None:
|
|
645
|
+
model_name = getattr(agent_config, "model", None) or model_name
|
|
646
|
+
|
|
647
|
+
agent = self.create_expert_agent(
|
|
648
|
+
expert,
|
|
649
|
+
session_id=session_id,
|
|
650
|
+
model_override=model_name,
|
|
651
|
+
)
|
|
652
|
+
pool.append((expert, agent))
|
|
653
|
+
|
|
654
|
+
logger.info(
|
|
655
|
+
"multi_agent: spawned %d experts (agent-mapped where available)",
|
|
656
|
+
len(pool),
|
|
657
|
+
)
|
|
658
|
+
return pool
|
|
659
|
+
|
|
660
|
+
async def invoke_expert(
|
|
661
|
+
self,
|
|
662
|
+
expert: ExpertDescriptor,
|
|
663
|
+
request: AskMindPackInput,
|
|
664
|
+
session_id: str,
|
|
665
|
+
) -> ExpertReport | None:
|
|
666
|
+
"""Run an expert agent and return its structured report.
|
|
667
|
+
|
|
668
|
+
Returns ``None`` if the agent fails to produce a valid report.
|
|
669
|
+
"""
|
|
670
|
+
group_id = f"mindpack-{expert.name}-{uuid.uuid4().hex[:6]}"
|
|
671
|
+
|
|
672
|
+
try:
|
|
673
|
+
temp_agent = self.create_expert_agent(
|
|
674
|
+
expert, session_id=session_id, message_group=group_id
|
|
675
|
+
)
|
|
676
|
+
except ValueError as exc:
|
|
677
|
+
logger.error(
|
|
678
|
+
"ExpertAgentFactory: failed to create agent for '%s': %s",
|
|
679
|
+
expert.name,
|
|
680
|
+
exc,
|
|
681
|
+
)
|
|
682
|
+
return None
|
|
683
|
+
|
|
684
|
+
user_prompt = build_expert_prompt(expert, request)
|
|
685
|
+
|
|
686
|
+
return await self._run_expert(
|
|
687
|
+
temp_agent=temp_agent,
|
|
688
|
+
expert=expert,
|
|
689
|
+
user_prompt=user_prompt,
|
|
690
|
+
session_id=session_id,
|
|
691
|
+
group_id=group_id,
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
# -- internal -----------------------------------------------------------
|
|
695
|
+
|
|
696
|
+
@staticmethod
|
|
697
|
+
def _resolve_model_name(expert: ExpertDescriptor) -> str:
|
|
698
|
+
"""Resolve the model name to use for expert agents.
|
|
699
|
+
|
|
700
|
+
If the expert descriptor specifies a per-expert model override,
|
|
701
|
+
use that. Otherwise fall back to the global model name.
|
|
702
|
+
"""
|
|
703
|
+
if expert.model:
|
|
704
|
+
return expert.model
|
|
705
|
+
|
|
706
|
+
from code_muse.config import get_global_model_name
|
|
707
|
+
|
|
708
|
+
name = get_global_model_name()
|
|
709
|
+
if not name:
|
|
710
|
+
raise ValueError("No global model configured — cannot create expert agent")
|
|
711
|
+
return name
|
|
712
|
+
|
|
713
|
+
async def _run_expert(
|
|
714
|
+
self,
|
|
715
|
+
temp_agent: PydanticAgent,
|
|
716
|
+
expert: ExpertDescriptor,
|
|
717
|
+
user_prompt: str,
|
|
718
|
+
session_id: str,
|
|
719
|
+
group_id: str,
|
|
720
|
+
) -> ExpertReport | None:
|
|
721
|
+
"""Execute the expert agent in a subagent context.
|
|
722
|
+
|
|
723
|
+
Handles streaming, cancellation, and error recovery. Falls back
|
|
724
|
+
to a text-parsed report if structured output fails.
|
|
725
|
+
"""
|
|
726
|
+
from code_muse.agents.subagent_stream_handler import (
|
|
727
|
+
subagent_stream_handler,
|
|
728
|
+
)
|
|
729
|
+
from code_muse.callbacks import on_agent_run_cancel, on_agent_run_context
|
|
730
|
+
from code_muse.config import get_message_limit
|
|
731
|
+
from code_muse.messaging import (
|
|
732
|
+
SubAgentInvocationMessage,
|
|
733
|
+
SubAgentResponseMessage,
|
|
734
|
+
emit_success,
|
|
735
|
+
get_message_bus,
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
bus = get_message_bus()
|
|
739
|
+
|
|
740
|
+
# Emit invocation message for the console
|
|
741
|
+
bus.emit(
|
|
742
|
+
SubAgentInvocationMessage(
|
|
743
|
+
agent_name=f"mindpack-{expert.name}",
|
|
744
|
+
session_id=session_id,
|
|
745
|
+
prompt=user_prompt[:200],
|
|
746
|
+
is_new_session=True,
|
|
747
|
+
message_count=0,
|
|
748
|
+
)
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
stream_handler = partial(subagent_stream_handler, session_id=session_id)
|
|
752
|
+
|
|
753
|
+
async with self._semaphore:
|
|
754
|
+
with subagent_context(f"mindpack-{expert.name}"):
|
|
755
|
+
run_ctxs = on_agent_run_context(
|
|
756
|
+
# Provide a minimal agent-like object for the hook
|
|
757
|
+
_MinimalAgentProxy(expert.name),
|
|
758
|
+
temp_agent,
|
|
759
|
+
group_id,
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
task = None
|
|
763
|
+
try:
|
|
764
|
+
async with AsyncExitStack() as stack:
|
|
765
|
+
for cm in run_ctxs:
|
|
766
|
+
await stack.enter_async_context(cm)
|
|
767
|
+
|
|
768
|
+
task = asyncio.create_task(
|
|
769
|
+
temp_agent.run(
|
|
770
|
+
user_prompt,
|
|
771
|
+
message_history=[],
|
|
772
|
+
usage_limits=UsageLimits(
|
|
773
|
+
request_limit=get_message_limit()
|
|
774
|
+
),
|
|
775
|
+
event_stream_handler=stream_handler,
|
|
776
|
+
)
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
result = await task
|
|
780
|
+
|
|
781
|
+
except asyncio.CancelledError:
|
|
782
|
+
if task and not task.done():
|
|
783
|
+
task.cancel()
|
|
784
|
+
await on_agent_run_cancel(group_id)
|
|
785
|
+
logger.warning(
|
|
786
|
+
"ExpertAgentFactory: expert '%s' cancelled", expert.name
|
|
787
|
+
)
|
|
788
|
+
return None
|
|
789
|
+
|
|
790
|
+
except Exception as exc:
|
|
791
|
+
logger.error(
|
|
792
|
+
"ExpertAgentFactory: expert '%s' failed: %s",
|
|
793
|
+
expert.name,
|
|
794
|
+
exc,
|
|
795
|
+
exc_info=True,
|
|
796
|
+
)
|
|
797
|
+
return self._fallback_report(expert, session_id, str(exc))
|
|
798
|
+
|
|
799
|
+
# Extract structured output
|
|
800
|
+
report = self._extract_report(result, expert, session_id)
|
|
801
|
+
|
|
802
|
+
# Emit completion message
|
|
803
|
+
bus.emit(
|
|
804
|
+
SubAgentResponseMessage(
|
|
805
|
+
agent_name=f"mindpack-{expert.name}",
|
|
806
|
+
session_id=session_id,
|
|
807
|
+
response=report.summary[:200] if report else "",
|
|
808
|
+
message_count=0,
|
|
809
|
+
)
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
emit_success(
|
|
813
|
+
f"✓ mindpack-{expert.name} completed",
|
|
814
|
+
message_group=group_id,
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
return report
|
|
818
|
+
|
|
819
|
+
@staticmethod
|
|
820
|
+
def _extract_report(
|
|
821
|
+
result: Any,
|
|
822
|
+
expert: ExpertDescriptor,
|
|
823
|
+
session_id: str,
|
|
824
|
+
) -> ExpertReport | None:
|
|
825
|
+
"""Extract an ExpertReport from the pydantic-ai result.
|
|
826
|
+
|
|
827
|
+
Tries structured output first (``result.output``), then falls
|
|
828
|
+
back to best-effort text parsing.
|
|
829
|
+
"""
|
|
830
|
+
# Structured output path
|
|
831
|
+
if result is not None and hasattr(result, "output"):
|
|
832
|
+
output = result.output
|
|
833
|
+
if isinstance(output, ExpertReport):
|
|
834
|
+
# Ensure run_id is set correctly
|
|
835
|
+
if output.run_id != session_id:
|
|
836
|
+
output = output.model_copy(update={"run_id": session_id})
|
|
837
|
+
if output.expert_id != expert.name:
|
|
838
|
+
output = output.model_copy(update={"expert_id": expert.name})
|
|
839
|
+
return output
|
|
840
|
+
|
|
841
|
+
# If output is a dict, try to build ExpertReport from it
|
|
842
|
+
if isinstance(output, dict):
|
|
843
|
+
try:
|
|
844
|
+
report = ExpertReport(
|
|
845
|
+
expert_id=expert.name,
|
|
846
|
+
run_id=session_id,
|
|
847
|
+
**{
|
|
848
|
+
k: v
|
|
849
|
+
for k, v in output.items()
|
|
850
|
+
if k in ExpertReport.model_fields
|
|
851
|
+
},
|
|
852
|
+
)
|
|
853
|
+
return report
|
|
854
|
+
except Exception:
|
|
855
|
+
pass
|
|
856
|
+
|
|
857
|
+
# Text fallback — try to parse the raw output as text
|
|
858
|
+
raw_text = ""
|
|
859
|
+
if result is not None:
|
|
860
|
+
if hasattr(result, "output"):
|
|
861
|
+
raw_text = str(result.output)
|
|
862
|
+
elif hasattr(result, "data"):
|
|
863
|
+
raw_text = str(result.data)
|
|
864
|
+
else:
|
|
865
|
+
raw_text = str(result)
|
|
866
|
+
|
|
867
|
+
if raw_text:
|
|
868
|
+
return ExpertReport(
|
|
869
|
+
expert_id=expert.name,
|
|
870
|
+
run_id=session_id,
|
|
871
|
+
lens="unknown",
|
|
872
|
+
prompt_variant="fallback",
|
|
873
|
+
summary=raw_text,
|
|
874
|
+
findings=[],
|
|
875
|
+
proposed_plan=[],
|
|
876
|
+
risks=["Fallback: structured output not produced"],
|
|
877
|
+
files_to_inspect=[],
|
|
878
|
+
confidence=0.3,
|
|
879
|
+
status="partial",
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
return None
|
|
883
|
+
|
|
884
|
+
@staticmethod
|
|
885
|
+
def _fallback_report(
|
|
886
|
+
expert: ExpertDescriptor,
|
|
887
|
+
session_id: str,
|
|
888
|
+
error_msg: str,
|
|
889
|
+
) -> ExpertReport:
|
|
890
|
+
"""Build a minimal error report when the expert fails entirely."""
|
|
891
|
+
return ExpertReport(
|
|
892
|
+
expert_id=expert.name,
|
|
893
|
+
run_id=session_id,
|
|
894
|
+
lens="error",
|
|
895
|
+
prompt_variant="fallback",
|
|
896
|
+
summary=f"[Error] Expert '{expert.name}' failed: {error_msg}",
|
|
897
|
+
findings=[],
|
|
898
|
+
proposed_plan=[],
|
|
899
|
+
risks=[f"Expert invocation failed: {error_msg}"],
|
|
900
|
+
files_to_inspect=[],
|
|
901
|
+
confidence=0.0,
|
|
902
|
+
status="failed",
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
# ---------------------------------------------------------------------------
|
|
907
|
+
# Minimal agent-like proxy for callback hooks
|
|
908
|
+
# ---------------------------------------------------------------------------
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
class _MinimalAgentProxy:
|
|
912
|
+
"""Lightweight object that satisfies the attribute requirements of
|
|
913
|
+
``on_agent_run_context`` and related hooks without needing a full
|
|
914
|
+
``BaseAgent`` instance.
|
|
915
|
+
|
|
916
|
+
Expert agents are ephemeral — they don't have a real BaseAgent backing
|
|
917
|
+
them, but some callback hooks expect an object with ``name`` and
|
|
918
|
+
``get_model_name()``.
|
|
919
|
+
"""
|
|
920
|
+
|
|
921
|
+
def __init__(self, expert_name: str) -> None:
|
|
922
|
+
self.name = f"mindpack-{expert_name}"
|
|
923
|
+
self._model_name: str | None = None
|
|
924
|
+
|
|
925
|
+
def get_model_name(self) -> str:
|
|
926
|
+
if self._model_name is None:
|
|
927
|
+
from code_muse.config import get_global_model_name
|
|
928
|
+
|
|
929
|
+
self._model_name = get_global_model_name() or "unknown"
|
|
930
|
+
return self._model_name
|