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,605 @@
|
|
|
1
|
+
"""MindPack orchestration — MindPackOrchestrator lifecycle manager.
|
|
2
|
+
|
|
3
|
+
Orchestrates the full consultation lifecycle:
|
|
4
|
+
1. Receive a consultation request (AskMindPackInput).
|
|
5
|
+
2. Select and spawn expert agents via ExpertAgentFactory.
|
|
6
|
+
3. Collect their reports into the ReportStore.
|
|
7
|
+
4. Invoke the judge merger to produce a unified AskMindPackOutput.
|
|
8
|
+
5. Clean up session state.
|
|
9
|
+
|
|
10
|
+
Data models live in ``schemas.py`` to avoid circular imports.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import uuid
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from code_muse.plugins.mindpack.memory import ReportStore
|
|
22
|
+
from code_muse.plugins.mindpack.schemas import (
|
|
23
|
+
AskMindPackInput,
|
|
24
|
+
AskMindPackOutput,
|
|
25
|
+
ExpertDescriptor,
|
|
26
|
+
MindPackConfig,
|
|
27
|
+
MindPackNestedConfig,
|
|
28
|
+
MindPackRankedOption,
|
|
29
|
+
ProfileDescriptor,
|
|
30
|
+
)
|
|
31
|
+
from code_muse.plugins.mindpack.schemas import MindPackExpertReport as ExpertReport
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from code_muse.plugins.mindpack.factory import ExpertAgentFactory
|
|
35
|
+
from code_muse.plugins.mindpack.tools import MindPackInvocationContext
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Expert selector (pluggable strategy)
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ExpertSelector(ABC):
|
|
45
|
+
"""Strategy for choosing which experts to consult given an input."""
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def select(
|
|
49
|
+
self, request: AskMindPackInput, registry: list[ExpertDescriptor]
|
|
50
|
+
) -> list[ExpertDescriptor]:
|
|
51
|
+
"""Return the subset of experts to consult for this request."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DefaultExpertSelector(ExpertSelector):
|
|
55
|
+
"""Simple selector: returns up to `max_experts` from the registry.
|
|
56
|
+
|
|
57
|
+
For now this is a naive top-N slice. Smarter selection (semantic
|
|
58
|
+
matching, relevance scoring) will come in a later epic.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def select(
|
|
62
|
+
self, request: AskMindPackInput, registry: list[ExpertDescriptor]
|
|
63
|
+
) -> list[ExpertDescriptor]:
|
|
64
|
+
cap = request.max_experts or len(registry)
|
|
65
|
+
return registry[:cap]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Judge merger (pluggable strategy)
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class JudgeMerger(ABC):
|
|
74
|
+
"""Strategy for merging expert reports into a final advisory output.
|
|
75
|
+
|
|
76
|
+
All implementations must provide the async ``merge`` method. The
|
|
77
|
+
``session_id`` parameter allows mergers to log and emit events in the
|
|
78
|
+
correct consultation context.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
async def merge(
|
|
83
|
+
self,
|
|
84
|
+
request: AskMindPackInput,
|
|
85
|
+
reports: list[ExpertReport],
|
|
86
|
+
session_id: str,
|
|
87
|
+
) -> AskMindPackOutput:
|
|
88
|
+
"""Produce a unified advisory from all expert reports."""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class PlaceholderJudgeMerger(JudgeMerger):
|
|
92
|
+
"""Minimal placeholder that echoes back a summary without real merging.
|
|
93
|
+
|
|
94
|
+
Kept as an explicit opt-out for cases where the LLM judge should
|
|
95
|
+
not be used (e.g. testing, offline mode). The ``LLMJudgeMerger``
|
|
96
|
+
is the default production implementation.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
async def merge(
|
|
100
|
+
self,
|
|
101
|
+
request: AskMindPackInput,
|
|
102
|
+
reports: list[ExpertReport],
|
|
103
|
+
session_id: str,
|
|
104
|
+
) -> AskMindPackOutput:
|
|
105
|
+
expert_names = [r.expert_id for r in reports]
|
|
106
|
+
all_risks = [r for report in reports for r in report.risks]
|
|
107
|
+
all_files = [f for report in reports for f in report.files_to_inspect]
|
|
108
|
+
all_recs = [r for report in reports for r in report.proposed_plan]
|
|
109
|
+
|
|
110
|
+
return AskMindPackOutput(
|
|
111
|
+
summary=(
|
|
112
|
+
f"Placeholder merger: consulted {len(reports)} expert(s) "
|
|
113
|
+
f"for '{request.desired_output}' on: "
|
|
114
|
+
f"{request.problem_statement[:200]}"
|
|
115
|
+
),
|
|
116
|
+
recommended_plan="\n".join(all_recs) or "No recommendations produced.",
|
|
117
|
+
ranked_options=[
|
|
118
|
+
MindPackRankedOption(
|
|
119
|
+
rank=1,
|
|
120
|
+
title="Placeholder option",
|
|
121
|
+
source_experts=expert_names,
|
|
122
|
+
summary="Placeholder merged option — LLM judge not active.",
|
|
123
|
+
)
|
|
124
|
+
],
|
|
125
|
+
risks=all_risks or ["Placeholder: no risks identified."],
|
|
126
|
+
tests_to_run=[],
|
|
127
|
+
files_to_inspect_or_change=list(dict.fromkeys(all_files)),
|
|
128
|
+
expert_consensus=(f"Placeholder: {len(reports)} expert(s) consulted."),
|
|
129
|
+
disagreements=[],
|
|
130
|
+
confidence=sum(r.confidence for r in reports) / max(len(reports), 1),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# MindPackOrchestrator
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class MindPackOrchestrator:
|
|
140
|
+
"""Manages the lifecycle of a MindPack consultation.
|
|
141
|
+
|
|
142
|
+
Typical usage::
|
|
143
|
+
|
|
144
|
+
orchestrator = MindPackOrchestrator()
|
|
145
|
+
result = await orchestrator.consult(request)
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def __init__(
|
|
149
|
+
self,
|
|
150
|
+
report_store: ReportStore | None = None,
|
|
151
|
+
expert_selector: ExpertSelector | None = None,
|
|
152
|
+
judge_merger: JudgeMerger | None = None,
|
|
153
|
+
expert_factory: ExpertAgentFactory | None = None,
|
|
154
|
+
config: MindPackConfig | None = None,
|
|
155
|
+
) -> None:
|
|
156
|
+
self._store = report_store or ReportStore()
|
|
157
|
+
self._selector = expert_selector or DefaultExpertSelector()
|
|
158
|
+
if judge_merger is not None:
|
|
159
|
+
self._merger = judge_merger
|
|
160
|
+
else:
|
|
161
|
+
from code_muse.plugins.mindpack.judge import LLMJudgeMerger
|
|
162
|
+
|
|
163
|
+
self._merger = LLMJudgeMerger()
|
|
164
|
+
if expert_factory is not None:
|
|
165
|
+
self._factory = expert_factory
|
|
166
|
+
else:
|
|
167
|
+
from code_muse.plugins.mindpack.factory import ExpertAgentFactory
|
|
168
|
+
|
|
169
|
+
self._factory = ExpertAgentFactory()
|
|
170
|
+
self._expert_registry: list[ExpertDescriptor] = []
|
|
171
|
+
self._profile_registry: list[ProfileDescriptor] = []
|
|
172
|
+
self.config: MindPackConfig = config if config is not None else MindPackConfig()
|
|
173
|
+
|
|
174
|
+
# -- registry -----------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def _get_experts_config_path() -> Path:
|
|
178
|
+
"""Return the path to the experts config file."""
|
|
179
|
+
config_dir = Path.home() / ".muse" / "mindpack"
|
|
180
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
181
|
+
return config_dir / "experts.json"
|
|
182
|
+
|
|
183
|
+
def save_experts(self) -> None:
|
|
184
|
+
"""Persist current expert registry to disk."""
|
|
185
|
+
config_path = self._get_experts_config_path()
|
|
186
|
+
data = [e.model_dump() for e in self._expert_registry]
|
|
187
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
188
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
189
|
+
logger.info("Orchestrator: saved %d experts to %s", len(data), config_path)
|
|
190
|
+
|
|
191
|
+
def load_experts(self) -> None:
|
|
192
|
+
"""Load custom experts from disk, merging with defaults.
|
|
193
|
+
|
|
194
|
+
Preserves experts that were registered before this call
|
|
195
|
+
(i.e., the default experts from tools.py). Custom experts
|
|
196
|
+
from the config file are added, and any experts with the
|
|
197
|
+
same name are replaced.
|
|
198
|
+
"""
|
|
199
|
+
config_path = self._get_experts_config_path()
|
|
200
|
+
if not config_path.exists():
|
|
201
|
+
logger.debug("Orchestrator: no custom experts config found")
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
with open(config_path, encoding="utf-8") as f:
|
|
206
|
+
data = json.load(f)
|
|
207
|
+
|
|
208
|
+
# Remove any existing experts that will be replaced
|
|
209
|
+
custom_experts = [ExpertDescriptor(**d) for d in data]
|
|
210
|
+
names_to_replace = {e.name for e in custom_experts}
|
|
211
|
+
self._expert_registry = [
|
|
212
|
+
e for e in self._expert_registry if e.name not in names_to_replace
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
# Add custom experts
|
|
216
|
+
self._expert_registry.extend(custom_experts)
|
|
217
|
+
logger.info(
|
|
218
|
+
"Orchestrator: loaded %d custom expert(s) from %s",
|
|
219
|
+
len(custom_experts),
|
|
220
|
+
config_path,
|
|
221
|
+
)
|
|
222
|
+
except Exception as exc:
|
|
223
|
+
logger.error("Orchestrator: failed to load experts config: %s", exc)
|
|
224
|
+
|
|
225
|
+
def register_expert(self, expert: ExpertDescriptor) -> None:
|
|
226
|
+
"""Add an expert to the available pool."""
|
|
227
|
+
self._expert_registry.append(expert)
|
|
228
|
+
logger.debug("Orchestrator: registered expert '%s'", expert.name)
|
|
229
|
+
|
|
230
|
+
def register_experts(self, experts: list[ExpertDescriptor]) -> None:
|
|
231
|
+
"""Bulk-register a list of experts."""
|
|
232
|
+
for e in experts:
|
|
233
|
+
self.register_expert(e)
|
|
234
|
+
|
|
235
|
+
def remove_expert(self, name: str) -> bool:
|
|
236
|
+
"""Remove an expert from the registry by name.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
True if an expert was removed, False if not found.
|
|
240
|
+
"""
|
|
241
|
+
original_len = len(self._expert_registry)
|
|
242
|
+
self._expert_registry = [e for e in self._expert_registry if e.name != name]
|
|
243
|
+
removed = len(self._expert_registry) < original_len
|
|
244
|
+
if removed:
|
|
245
|
+
logger.debug("Orchestrator: removed expert '%s'", name)
|
|
246
|
+
return removed
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def expert_registry(self) -> list[ExpertDescriptor]:
|
|
250
|
+
"""Read-only view of the current expert pool.
|
|
251
|
+
|
|
252
|
+
When an active profile is set, returns only the experts
|
|
253
|
+
belonging to that profile. Otherwise returns all experts.
|
|
254
|
+
"""
|
|
255
|
+
active = self.get_active_profile_name()
|
|
256
|
+
if active:
|
|
257
|
+
return self.get_experts_for_profile(active)
|
|
258
|
+
return list(self._expert_registry)
|
|
259
|
+
|
|
260
|
+
# -- active profile ----------------------------------------------------
|
|
261
|
+
|
|
262
|
+
@staticmethod
|
|
263
|
+
def _get_active_profile_path() -> Path:
|
|
264
|
+
"""Return the path to the active profile marker file."""
|
|
265
|
+
config_dir = Path.home() / ".muse" / "mindpack"
|
|
266
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
267
|
+
return config_dir / "active_profile.txt"
|
|
268
|
+
|
|
269
|
+
def get_active_profile_name(self) -> str | None:
|
|
270
|
+
"""Return the currently active profile name, or None."""
|
|
271
|
+
path = self._get_active_profile_path()
|
|
272
|
+
if not path.exists():
|
|
273
|
+
return None
|
|
274
|
+
try:
|
|
275
|
+
return path.read_text().strip() or None
|
|
276
|
+
except Exception:
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
def set_active_profile(self, profile_name: str) -> None:
|
|
280
|
+
"""Set the active profile name, persisting to disk."""
|
|
281
|
+
path = self._get_active_profile_path()
|
|
282
|
+
path.write_text(profile_name)
|
|
283
|
+
logger.info("Orchestrator: active profile set to '%s'", profile_name)
|
|
284
|
+
|
|
285
|
+
def clear_active_profile(self) -> None:
|
|
286
|
+
"""Clear the active profile."""
|
|
287
|
+
path = self._get_active_profile_path()
|
|
288
|
+
if path.exists():
|
|
289
|
+
path.unlink()
|
|
290
|
+
logger.info("Orchestrator: active profile cleared")
|
|
291
|
+
|
|
292
|
+
# -- profile registry ---------------------------------------------------
|
|
293
|
+
|
|
294
|
+
@staticmethod
|
|
295
|
+
def _get_profiles_config_path() -> Path:
|
|
296
|
+
"""Return the path to the profiles config file."""
|
|
297
|
+
config_dir = Path.home() / ".muse" / "mindpack"
|
|
298
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
299
|
+
return config_dir / "profiles.json"
|
|
300
|
+
|
|
301
|
+
def save_profiles(self) -> None:
|
|
302
|
+
"""Persist current profile registry to disk."""
|
|
303
|
+
config_path = self._get_profiles_config_path()
|
|
304
|
+
data = [p.model_dump() for p in self._profile_registry]
|
|
305
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
306
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
307
|
+
logger.info("Orchestrator: saved %d profiles to %s", len(data), config_path)
|
|
308
|
+
|
|
309
|
+
def load_profiles(self) -> None:
|
|
310
|
+
"""Load profiles from disk, seeding defaults on first run.
|
|
311
|
+
|
|
312
|
+
On first run (no profiles.json), seeds default profiles.
|
|
313
|
+
Auto-migrates: any experts not referenced by any profile
|
|
314
|
+
get added to the 'Default' profile.
|
|
315
|
+
"""
|
|
316
|
+
config_path = self._get_profiles_config_path()
|
|
317
|
+
if not config_path.exists():
|
|
318
|
+
logger.info("Orchestrator: no profiles config found — seeding defaults")
|
|
319
|
+
from code_muse.plugins.mindpack.schemas import _DEFAULT_PROFILES
|
|
320
|
+
|
|
321
|
+
self._profile_registry = list(_DEFAULT_PROFILES)
|
|
322
|
+
self._migrate_orphans_to_default()
|
|
323
|
+
self.save_profiles()
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
with open(config_path, encoding="utf-8") as f:
|
|
328
|
+
data = json.load(f)
|
|
329
|
+
self._profile_registry = [ProfileDescriptor(**d) for d in data]
|
|
330
|
+
self._migrate_orphans_to_default()
|
|
331
|
+
logger.info(
|
|
332
|
+
"Orchestrator: loaded %d profile(s) from %s",
|
|
333
|
+
len(self._profile_registry),
|
|
334
|
+
config_path,
|
|
335
|
+
)
|
|
336
|
+
except Exception as exc:
|
|
337
|
+
logger.error("Orchestrator: failed to load profiles config: %s", exc)
|
|
338
|
+
from code_muse.plugins.mindpack.schemas import _DEFAULT_PROFILES
|
|
339
|
+
|
|
340
|
+
self._profile_registry = list(_DEFAULT_PROFILES)
|
|
341
|
+
|
|
342
|
+
def _migrate_orphans_to_default(self) -> None:
|
|
343
|
+
"""Add experts not referenced by any profile to the 'Default' profile."""
|
|
344
|
+
referenced = set()
|
|
345
|
+
for profile in self._profile_registry:
|
|
346
|
+
referenced.update(profile.expert_names)
|
|
347
|
+
|
|
348
|
+
orphan_names = [
|
|
349
|
+
e.name for e in self._expert_registry if e.name not in referenced
|
|
350
|
+
]
|
|
351
|
+
|
|
352
|
+
if not orphan_names:
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
# Find or create the Default profile
|
|
356
|
+
default = next((p for p in self._profile_registry if p.name == "Default"), None)
|
|
357
|
+
if default is None:
|
|
358
|
+
default = ProfileDescriptor(
|
|
359
|
+
name="Default",
|
|
360
|
+
description="Catch-all for experts not in any other profile",
|
|
361
|
+
expert_names=[],
|
|
362
|
+
)
|
|
363
|
+
self._profile_registry.append(default)
|
|
364
|
+
|
|
365
|
+
for name in orphan_names:
|
|
366
|
+
if name not in default.expert_names:
|
|
367
|
+
default.expert_names.append(name)
|
|
368
|
+
logger.debug(
|
|
369
|
+
"Orchestrator: migrated orphan expert '%s' into Default profile",
|
|
370
|
+
name,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def register_profile(self, profile: ProfileDescriptor) -> None:
|
|
374
|
+
"""Add a profile to the registry (replaces existing with same name)."""
|
|
375
|
+
self._profile_registry = [
|
|
376
|
+
p for p in self._profile_registry if p.name != profile.name
|
|
377
|
+
]
|
|
378
|
+
self._profile_registry.append(profile)
|
|
379
|
+
logger.debug("Orchestrator: registered profile '%s'", profile.name)
|
|
380
|
+
|
|
381
|
+
def remove_profile(self, name: str) -> bool:
|
|
382
|
+
"""Remove a profile by name.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
True if a profile was removed, False if not found.
|
|
386
|
+
"""
|
|
387
|
+
original_len = len(self._profile_registry)
|
|
388
|
+
self._profile_registry = [p for p in self._profile_registry if p.name != name]
|
|
389
|
+
removed = len(self._profile_registry) < original_len
|
|
390
|
+
if removed:
|
|
391
|
+
logger.debug("Orchestrator: removed profile '%s'", name)
|
|
392
|
+
return removed
|
|
393
|
+
|
|
394
|
+
def get_experts_for_profile(self, profile_name: str) -> list[ExpertDescriptor]:
|
|
395
|
+
"""Resolve a profile name to its ExpertDescriptors.
|
|
396
|
+
|
|
397
|
+
Returns an empty list if the profile is not found.
|
|
398
|
+
"""
|
|
399
|
+
profile = next(
|
|
400
|
+
(p for p in self._profile_registry if p.name == profile_name), None
|
|
401
|
+
)
|
|
402
|
+
if profile is None:
|
|
403
|
+
return []
|
|
404
|
+
|
|
405
|
+
expert_map = {e.name: e for e in self._expert_registry}
|
|
406
|
+
return [expert_map[name] for name in profile.expert_names if name in expert_map]
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
def profile_registry(self) -> list[ProfileDescriptor]:
|
|
410
|
+
"""Read-only view of the current profile pool."""
|
|
411
|
+
return list(self._profile_registry)
|
|
412
|
+
|
|
413
|
+
# -- consultation lifecycle ---------------------------------------------
|
|
414
|
+
|
|
415
|
+
async def consult(
|
|
416
|
+
self,
|
|
417
|
+
request: AskMindPackInput,
|
|
418
|
+
invocation_context: MindPackInvocationContext | None = None,
|
|
419
|
+
nested_config: MindPackNestedConfig | None = None,
|
|
420
|
+
) -> AskMindPackOutput:
|
|
421
|
+
"""Run a full consultation cycle.
|
|
422
|
+
|
|
423
|
+
1. Generate a session ID.
|
|
424
|
+
2. Select experts from the registry.
|
|
425
|
+
3. Spawn experts and collect reports.
|
|
426
|
+
4. Run the judge merger.
|
|
427
|
+
5. Clean up session data.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
request: The consultation input.
|
|
431
|
+
invocation_context: Nested-workflow tracking (depth, call counts).
|
|
432
|
+
nested_config: INI-driven nested limits (timeout, max_depth).
|
|
433
|
+
|
|
434
|
+
Returns the merged advisory output.
|
|
435
|
+
"""
|
|
436
|
+
session_id = uuid.uuid4().hex[:12]
|
|
437
|
+
if invocation_context is not None:
|
|
438
|
+
logger.info(
|
|
439
|
+
"Orchestrator: nested consultation depth=%d/%d session='%s'",
|
|
440
|
+
invocation_context.nested_depth,
|
|
441
|
+
invocation_context.max_depth,
|
|
442
|
+
session_id,
|
|
443
|
+
)
|
|
444
|
+
logger.info(
|
|
445
|
+
"Orchestrator: starting consultation session='%s' "
|
|
446
|
+
"desired_output='%s' problem='%s'",
|
|
447
|
+
session_id,
|
|
448
|
+
request.desired_output,
|
|
449
|
+
request.problem_statement[:120],
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Select experts
|
|
453
|
+
selected = self._selector.select(request, self._expert_registry)
|
|
454
|
+
logger.info(
|
|
455
|
+
"Orchestrator: selected %d expert(s) for session '%s': %s",
|
|
456
|
+
len(selected),
|
|
457
|
+
session_id,
|
|
458
|
+
[e.name for e in selected],
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
# Spawn experts and collect reports (with optional timeout cap)
|
|
462
|
+
timeout = None
|
|
463
|
+
if nested_config is not None:
|
|
464
|
+
timeout = nested_config.timeout_sec
|
|
465
|
+
|
|
466
|
+
try:
|
|
467
|
+
if timeout:
|
|
468
|
+
reports = await asyncio.wait_for(
|
|
469
|
+
self._spawn_and_collect(session_id, request, selected),
|
|
470
|
+
timeout=timeout,
|
|
471
|
+
)
|
|
472
|
+
else:
|
|
473
|
+
reports = await self._spawn_and_collect(session_id, request, selected)
|
|
474
|
+
except TimeoutError:
|
|
475
|
+
logger.error(
|
|
476
|
+
"Orchestrator: consultation session='%s' exceeded timeout (%ss)",
|
|
477
|
+
session_id,
|
|
478
|
+
timeout,
|
|
479
|
+
)
|
|
480
|
+
# Return a graceful timeout advisory instead of crashing
|
|
481
|
+
return AskMindPackOutput(
|
|
482
|
+
summary=f"MindPack consultation timed out after {timeout}s.",
|
|
483
|
+
recommended_plan=(
|
|
484
|
+
"The expert panel did not finish in time. "
|
|
485
|
+
"Consider simplifying the problem statement or increasing "
|
|
486
|
+
"packmind_nested_timeout_sec in your config."
|
|
487
|
+
),
|
|
488
|
+
ranked_options=[],
|
|
489
|
+
risks=[f"Timeout: expert pool exceeded {timeout}s limit"],
|
|
490
|
+
tests_to_run=[],
|
|
491
|
+
files_to_inspect_or_change=[],
|
|
492
|
+
expert_consensus="No consensus — consultation aborted by timeout.",
|
|
493
|
+
disagreements=[],
|
|
494
|
+
confidence=0.0,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# Merge via judge (async — LLM-backed or placeholder)
|
|
498
|
+
output = await self._merger.merge(request, reports, session_id)
|
|
499
|
+
|
|
500
|
+
# Clean up session memory
|
|
501
|
+
self._store.clear_run(session_id)
|
|
502
|
+
|
|
503
|
+
logger.info(
|
|
504
|
+
"Orchestrator: consultation complete session='%s' "
|
|
505
|
+
"experts=%d confidence=%.2f",
|
|
506
|
+
session_id,
|
|
507
|
+
len(reports),
|
|
508
|
+
output.confidence,
|
|
509
|
+
)
|
|
510
|
+
return output
|
|
511
|
+
|
|
512
|
+
# -- expert spawning (skeleton) -----------------------------------------
|
|
513
|
+
|
|
514
|
+
async def _spawn_and_collect(
|
|
515
|
+
self,
|
|
516
|
+
session_id: str,
|
|
517
|
+
request: AskMindPackInput,
|
|
518
|
+
experts: list[ExpertDescriptor],
|
|
519
|
+
) -> list[ExpertReport]:
|
|
520
|
+
"""Spawn each expert agent and collect their reports in parallel.
|
|
521
|
+
|
|
522
|
+
Uses the Factory to prepare and run experts concurrently via asyncio.gather.
|
|
523
|
+
"""
|
|
524
|
+
if hasattr(self._factory, "invoke_expert"):
|
|
525
|
+
# If the factory doesn't support batching directly, use TaskGroup
|
|
526
|
+
async with asyncio.TaskGroup() as tg:
|
|
527
|
+
tg_tasks = [
|
|
528
|
+
tg.create_task(self._invoke_expert(session_id, request, expert))
|
|
529
|
+
for expert in experts
|
|
530
|
+
]
|
|
531
|
+
results = [t.result() for t in tg_tasks]
|
|
532
|
+
reports = [r for r in results if r is not None]
|
|
533
|
+
for report in reports:
|
|
534
|
+
self._store.add_report(report)
|
|
535
|
+
return reports
|
|
536
|
+
|
|
537
|
+
# Fallback to serial for safe implementation if batch API changes
|
|
538
|
+
return await self._spawn_and_collect_serial(session_id, request, experts)
|
|
539
|
+
|
|
540
|
+
async def _spawn_and_collect_serial(
|
|
541
|
+
self,
|
|
542
|
+
session_id: str,
|
|
543
|
+
request: AskMindPackInput,
|
|
544
|
+
experts: list[ExpertDescriptor],
|
|
545
|
+
) -> list[ExpertReport]:
|
|
546
|
+
"""Serial fallback for expert invocation."""
|
|
547
|
+
reports: list[ExpertReport] = []
|
|
548
|
+
for expert in experts:
|
|
549
|
+
report = await self._invoke_expert(session_id, request, expert)
|
|
550
|
+
if report is not None:
|
|
551
|
+
self._store.add_report(report)
|
|
552
|
+
reports.append(report)
|
|
553
|
+
return reports
|
|
554
|
+
|
|
555
|
+
async def _invoke_expert(
|
|
556
|
+
self,
|
|
557
|
+
session_id: str,
|
|
558
|
+
request: AskMindPackInput,
|
|
559
|
+
expert: ExpertDescriptor,
|
|
560
|
+
) -> ExpertReport | None:
|
|
561
|
+
"""Invoke a single expert via the ExpertAgentFactory.
|
|
562
|
+
|
|
563
|
+
The factory creates a read-only sub-agent with the expert's
|
|
564
|
+
system prompt fragment, runs it against the consultation prompt,
|
|
565
|
+
and returns a structured ``ExpertReport``.
|
|
566
|
+
|
|
567
|
+
Falls back to a minimal error report if the factory fails entirely.
|
|
568
|
+
"""
|
|
569
|
+
logger.info(
|
|
570
|
+
"Orchestrator: invoking expert '%s' for session '%s'",
|
|
571
|
+
expert.name,
|
|
572
|
+
session_id,
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
try:
|
|
576
|
+
report = await self._factory.invoke_expert(expert, request, session_id)
|
|
577
|
+
except Exception as exc:
|
|
578
|
+
logger.error(
|
|
579
|
+
"Orchestrator: expert '%s' raised unexpected error: %s",
|
|
580
|
+
expert.name,
|
|
581
|
+
exc,
|
|
582
|
+
exc_info=True,
|
|
583
|
+
)
|
|
584
|
+
report = ExpertReport(
|
|
585
|
+
expert_id=expert.name,
|
|
586
|
+
run_id=session_id,
|
|
587
|
+
lens="error",
|
|
588
|
+
prompt_variant="fallback",
|
|
589
|
+
summary=f"[Error] Unexpected failure: {exc}",
|
|
590
|
+
findings=[],
|
|
591
|
+
proposed_plan=[],
|
|
592
|
+
risks=[f"Expert invocation raised: {exc}"],
|
|
593
|
+
files_to_inspect=[],
|
|
594
|
+
confidence=0.0,
|
|
595
|
+
status="failed",
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
return report
|
|
599
|
+
|
|
600
|
+
# -- cleanup ------------------------------------------------------------
|
|
601
|
+
|
|
602
|
+
async def shutdown(self) -> None:
|
|
603
|
+
"""Graceful shutdown — clear all session data."""
|
|
604
|
+
self._store.clear_all()
|
|
605
|
+
logger.info("Orchestrator: shutdown complete")
|