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,613 @@
|
|
|
1
|
+
# agent_tools.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import threading
|
|
7
|
+
import traceback
|
|
8
|
+
from contextlib import AsyncExitStack, suppress
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from functools import partial
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel
|
|
15
|
+
|
|
16
|
+
# Import Agent from pydantic_ai to create temporary agents for invocation
|
|
17
|
+
from pydantic_ai import Agent, RunContext, UsageLimits
|
|
18
|
+
from pydantic_ai.messages import ModelMessage, ModelMessagesTypeAdapter
|
|
19
|
+
|
|
20
|
+
from code_muse.callbacks import (
|
|
21
|
+
on_agent_run_cancel,
|
|
22
|
+
on_agent_run_context,
|
|
23
|
+
on_invoke_agent,
|
|
24
|
+
)
|
|
25
|
+
from code_muse.config import (
|
|
26
|
+
DATA_DIR,
|
|
27
|
+
get_message_limit,
|
|
28
|
+
)
|
|
29
|
+
from code_muse.messaging import (
|
|
30
|
+
SubAgentInvocationMessage,
|
|
31
|
+
SubAgentResponseMessage,
|
|
32
|
+
emit_error,
|
|
33
|
+
emit_info,
|
|
34
|
+
emit_success,
|
|
35
|
+
get_message_bus,
|
|
36
|
+
get_session_context,
|
|
37
|
+
set_session_context,
|
|
38
|
+
)
|
|
39
|
+
from code_muse.secret_storage import atomic_write_private_json
|
|
40
|
+
from code_muse.tools.common import generate_group_id
|
|
41
|
+
from code_muse.tools.subagent_context import subagent_context
|
|
42
|
+
|
|
43
|
+
# Set to track active subagent invocation tasks
|
|
44
|
+
_active_subagent_tasks: set[asyncio.Task] = set()
|
|
45
|
+
|
|
46
|
+
# PERF-08: Cache model instances by name to avoid rebuilding AsyncAnthropic
|
|
47
|
+
# clients, ClaudeCacheAsyncClient, etc. on every invoke_agent call.
|
|
48
|
+
_model_instance_cache: dict[str, Any] = {}
|
|
49
|
+
# FREE-THREADED: _model_instance_cache_lock guards a cache accessed from async
|
|
50
|
+
# invoke_agent and potentially sync paths. Keep threading.Lock for safety.
|
|
51
|
+
_model_instance_cache_lock = threading.Lock()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _generate_session_hash_suffix() -> str:
|
|
55
|
+
"""Generate a short SHA1 hash suffix based on current timestamp for uniqueness.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A 6-character hex string, e.g., "a3f2b1"
|
|
59
|
+
"""
|
|
60
|
+
timestamp = str(datetime.now().timestamp())
|
|
61
|
+
return hashlib.sha1(timestamp.encode()).hexdigest()[:6]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Regex pattern for kebab-case session IDs
|
|
65
|
+
SESSION_ID_PATTERN = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
|
|
66
|
+
SESSION_ID_MAX_LENGTH = 128
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _validate_session_id(session_id: str) -> None:
|
|
70
|
+
"""Validate that a session ID follows kebab-case naming conventions.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
session_id: The session identifier to validate
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValueError: If the session_id is invalid
|
|
77
|
+
|
|
78
|
+
Valid format:
|
|
79
|
+
- Lowercase letters (a-z)
|
|
80
|
+
- Numbers (0-9)
|
|
81
|
+
- Hyphens (-) to separate words
|
|
82
|
+
- No uppercase, no underscores, no special characters
|
|
83
|
+
- Length between 1 and 128 characters
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
Valid: "my-session", "agent-session-1", "discussion-about-code"
|
|
87
|
+
Invalid: "MySession", "my_session", "my session", "my--session"
|
|
88
|
+
"""
|
|
89
|
+
if not session_id:
|
|
90
|
+
raise ValueError("session_id cannot be empty")
|
|
91
|
+
|
|
92
|
+
if len(session_id) > SESSION_ID_MAX_LENGTH:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Invalid session_id '{session_id}': must be {SESSION_ID_MAX_LENGTH} characters or less"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if not SESSION_ID_PATTERN.match(session_id):
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"Invalid session_id '{session_id}': must be kebab-case "
|
|
100
|
+
"(lowercase letters, numbers, and hyphens only). "
|
|
101
|
+
"Examples: 'my-session', 'agent-session-1', 'discussion-about-code'"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _get_subagent_sessions_dir() -> Path:
|
|
106
|
+
"""Get the directory for storing subagent session data.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Path to XDG data directory/subagent_sessions/
|
|
110
|
+
"""
|
|
111
|
+
sessions_dir = Path(DATA_DIR) / "subagent_sessions"
|
|
112
|
+
sessions_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
113
|
+
return sessions_dir
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _save_session_history(
|
|
117
|
+
session_id: str,
|
|
118
|
+
message_history: list[ModelMessage],
|
|
119
|
+
agent_name: str,
|
|
120
|
+
initial_prompt: str | None = None,
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Save session history to filesystem as JSON with atomic private writes.
|
|
123
|
+
|
|
124
|
+
Writes a canonical ``.json`` file and a backward-compat ``.pkl`` file
|
|
125
|
+
(containing the same JSON payload) so old callers checking for
|
|
126
|
+
``.pkl`` still find what they expect.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
session_id: The session identifier (must be kebab-case)
|
|
130
|
+
message_history: List of messages to save
|
|
131
|
+
agent_name: Name of the agent being invoked
|
|
132
|
+
initial_prompt: The first prompt that started this session (for .txt metadata)
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
ValueError: If session_id is not valid kebab-case format
|
|
136
|
+
"""
|
|
137
|
+
# Validate session_id format before saving
|
|
138
|
+
_validate_session_id(session_id)
|
|
139
|
+
|
|
140
|
+
sessions_dir = _get_subagent_sessions_dir()
|
|
141
|
+
|
|
142
|
+
# Build the JSON payload
|
|
143
|
+
session_data = {
|
|
144
|
+
"schema": "muse.subagent.session.v1",
|
|
145
|
+
"format": "pydantic-ai-model-messages-json",
|
|
146
|
+
"messages": ModelMessagesTypeAdapter.dump_python(message_history, mode="json"),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Save canonical .json file
|
|
150
|
+
json_path = sessions_dir / f"{session_id}.json"
|
|
151
|
+
atomic_write_private_json(json_path, session_data)
|
|
152
|
+
|
|
153
|
+
# Save compat .pkl file (same JSON content, different extension)
|
|
154
|
+
pkl_path = sessions_dir / f"{session_id}.pkl"
|
|
155
|
+
try:
|
|
156
|
+
tmp_pkl = pkl_path.with_suffix(".tmp")
|
|
157
|
+
with open(tmp_pkl, "w", encoding="utf-8") as f:
|
|
158
|
+
json.dump(session_data, f, indent=2)
|
|
159
|
+
tmp_pkl.replace(pkl_path)
|
|
160
|
+
except OSError:
|
|
161
|
+
pass # best-effort compat file
|
|
162
|
+
|
|
163
|
+
# Save or update txt file with metadata
|
|
164
|
+
txt_path = sessions_dir / f"{session_id}.txt"
|
|
165
|
+
if not txt_path.exists() and initial_prompt:
|
|
166
|
+
# Only write initial metadata on first save
|
|
167
|
+
metadata = {
|
|
168
|
+
"session_id": session_id,
|
|
169
|
+
"agent_name": agent_name,
|
|
170
|
+
"initial_prompt": initial_prompt,
|
|
171
|
+
"created_at": datetime.now().isoformat(),
|
|
172
|
+
"message_count": len(message_history),
|
|
173
|
+
}
|
|
174
|
+
with open(txt_path, "w") as f:
|
|
175
|
+
json.dump(metadata, f, indent=2)
|
|
176
|
+
elif txt_path.exists():
|
|
177
|
+
# Update message count on subsequent saves
|
|
178
|
+
try:
|
|
179
|
+
with open(txt_path) as f:
|
|
180
|
+
metadata = json.load(f)
|
|
181
|
+
metadata["message_count"] = len(message_history)
|
|
182
|
+
metadata["last_updated"] = datetime.now().isoformat()
|
|
183
|
+
with open(txt_path, "w") as f:
|
|
184
|
+
json.dump(metadata, f, indent=2)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass # If we can't update metadata, no big deal
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _load_session_history(session_id: str) -> list[ModelMessage]:
|
|
190
|
+
"""Load session history from filesystem.
|
|
191
|
+
|
|
192
|
+
Prefers the canonical ``.json`` file; falls back to ``.pkl``
|
|
193
|
+
(which contains the same JSON payload for backward compat).
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
session_id: The session identifier (must be kebab-case)
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of ModelMessage objects, or empty list if session doesn't exist
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
ValueError: If session_id is not valid kebab-case format
|
|
203
|
+
"""
|
|
204
|
+
# Validate session_id format before loading
|
|
205
|
+
_validate_session_id(session_id)
|
|
206
|
+
|
|
207
|
+
sessions_dir = _get_subagent_sessions_dir()
|
|
208
|
+
|
|
209
|
+
# Try canonical .json first
|
|
210
|
+
json_path = sessions_dir / f"{session_id}.json"
|
|
211
|
+
if json_path.exists():
|
|
212
|
+
try:
|
|
213
|
+
with open(json_path, encoding="utf-8") as f:
|
|
214
|
+
session_data = json.load(f)
|
|
215
|
+
raw_messages = session_data.get("messages", [])
|
|
216
|
+
if isinstance(raw_messages, list):
|
|
217
|
+
try:
|
|
218
|
+
return ModelMessagesTypeAdapter.validate_python(raw_messages)
|
|
219
|
+
except Exception:
|
|
220
|
+
# Validation may fail due to pydantic-ai version
|
|
221
|
+
# differences; return raw messages as best effort
|
|
222
|
+
return raw_messages
|
|
223
|
+
except Exception:
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
# Try compat .pkl (contains JSON written by current save)
|
|
227
|
+
pkl_path = sessions_dir / f"{session_id}.pkl"
|
|
228
|
+
if pkl_path.exists():
|
|
229
|
+
try:
|
|
230
|
+
with open(pkl_path, encoding="utf-8") as f:
|
|
231
|
+
session_data = json.load(f)
|
|
232
|
+
raw_messages = session_data.get("messages", [])
|
|
233
|
+
if isinstance(raw_messages, list):
|
|
234
|
+
try:
|
|
235
|
+
return ModelMessagesTypeAdapter.validate_python(raw_messages)
|
|
236
|
+
except Exception:
|
|
237
|
+
return raw_messages
|
|
238
|
+
except UnicodeDecodeError, ValueError:
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
return []
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class AgentInfo(BaseModel):
|
|
245
|
+
"""Information about an available agent."""
|
|
246
|
+
|
|
247
|
+
name: str
|
|
248
|
+
display_name: str
|
|
249
|
+
description: str
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class ListAgentsOutput(BaseModel):
|
|
253
|
+
"""Output for the list_agents tool."""
|
|
254
|
+
|
|
255
|
+
agents: list[AgentInfo]
|
|
256
|
+
error: str | None = None
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class AgentInvokeOutput(BaseModel):
|
|
260
|
+
"""Output for the invoke_agent tool."""
|
|
261
|
+
|
|
262
|
+
response: str | None
|
|
263
|
+
agent_name: str
|
|
264
|
+
session_id: str | None = None
|
|
265
|
+
error: str | None = None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def register_list_agents(agent):
|
|
269
|
+
"""Register the list_agents tool with the provided agent.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
agent: The agent to register the tool with
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
@agent.tool
|
|
276
|
+
def list_agents(context: RunContext) -> ListAgentsOutput:
|
|
277
|
+
"""List all available sub-agents that can be invoked."""
|
|
278
|
+
# Generate a group ID for this tool execution
|
|
279
|
+
group_id = generate_group_id("list_agents")
|
|
280
|
+
|
|
281
|
+
from rich.text import Text
|
|
282
|
+
|
|
283
|
+
from code_muse.config import get_banner_color
|
|
284
|
+
|
|
285
|
+
list_agents_color = get_banner_color("list_agents")
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
from code_muse.agents import get_agent_descriptions, get_available_agents
|
|
289
|
+
|
|
290
|
+
# Get available agents and their descriptions from the agent manager
|
|
291
|
+
agents_dict = get_available_agents()
|
|
292
|
+
descriptions_dict = get_agent_descriptions()
|
|
293
|
+
|
|
294
|
+
# Convert to list of AgentInfo objects
|
|
295
|
+
agents = [
|
|
296
|
+
AgentInfo(
|
|
297
|
+
name=name,
|
|
298
|
+
display_name=display_name,
|
|
299
|
+
description=descriptions_dict.get(name, "No description available"),
|
|
300
|
+
)
|
|
301
|
+
for name, display_name in agents_dict.items()
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
# Quiet output - banner and count on same line
|
|
305
|
+
agent_count = len(agents)
|
|
306
|
+
emit_info(
|
|
307
|
+
Text.from_markup(
|
|
308
|
+
f"[bold white on {list_agents_color}] LIST AGENTS [/bold white on {list_agents_color}] "
|
|
309
|
+
f"[dim]Found {agent_count} agent(s).[/dim]"
|
|
310
|
+
),
|
|
311
|
+
message_group=group_id,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return ListAgentsOutput(agents=agents)
|
|
315
|
+
|
|
316
|
+
except Exception as e:
|
|
317
|
+
error_msg = f"Error listing agents: {str(e)}"
|
|
318
|
+
emit_error(error_msg, message_group=group_id)
|
|
319
|
+
return ListAgentsOutput(agents=[], error=error_msg)
|
|
320
|
+
|
|
321
|
+
return list_agents
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def register_invoke_agent(agent):
|
|
325
|
+
"""Register the invoke_agent tool with the provided agent.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
agent: The agent to register the tool with
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
@agent.tool
|
|
332
|
+
async def invoke_agent(
|
|
333
|
+
context: RunContext, agent_name: str, prompt: str, session_id: str | None = None
|
|
334
|
+
) -> AgentInvokeOutput:
|
|
335
|
+
"""Invoke a specific sub-agent with a given prompt.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
AgentInvokeOutput: Contains response, agent_name, session_id, and error fields.
|
|
339
|
+
"""
|
|
340
|
+
from code_muse.agents.agent_manager import load_agent
|
|
341
|
+
|
|
342
|
+
# Validate user-provided session_id if given
|
|
343
|
+
if session_id is not None:
|
|
344
|
+
try:
|
|
345
|
+
_validate_session_id(session_id)
|
|
346
|
+
except ValueError as e:
|
|
347
|
+
# Return error immediately if session_id is invalid
|
|
348
|
+
group_id = generate_group_id("invoke_agent", agent_name)
|
|
349
|
+
emit_error(str(e), message_group=group_id)
|
|
350
|
+
return AgentInvokeOutput(
|
|
351
|
+
response=None, agent_name=agent_name, error=str(e)
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Generate a group ID for this tool execution
|
|
355
|
+
group_id = generate_group_id("invoke_agent", agent_name)
|
|
356
|
+
|
|
357
|
+
# Check if this is an existing session or a new one
|
|
358
|
+
# For user-provided session_id, check if it exists
|
|
359
|
+
# For None, we'll generate a new one below
|
|
360
|
+
if session_id is not None:
|
|
361
|
+
message_history = _load_session_history(session_id)
|
|
362
|
+
is_new_session = len(message_history) == 0
|
|
363
|
+
else:
|
|
364
|
+
message_history = []
|
|
365
|
+
is_new_session = True
|
|
366
|
+
|
|
367
|
+
# Generate or finalize session_id
|
|
368
|
+
if session_id is None:
|
|
369
|
+
# Auto-generate a session ID with hash suffix for uniqueness
|
|
370
|
+
# Example: "qa-expert-session-a3f2b1"
|
|
371
|
+
hash_suffix = _generate_session_hash_suffix()
|
|
372
|
+
session_id = f"{agent_name}-session-{hash_suffix}"
|
|
373
|
+
elif is_new_session:
|
|
374
|
+
# User provided a base name for a NEW session - append hash suffix
|
|
375
|
+
# Example: "review-auth" -> "review-auth-a3f2b1"
|
|
376
|
+
hash_suffix = _generate_session_hash_suffix()
|
|
377
|
+
session_id = f"{session_id}-{hash_suffix}"
|
|
378
|
+
# else: continuing existing session, use session_id as-is
|
|
379
|
+
|
|
380
|
+
# Lazy imports to avoid circular dependency
|
|
381
|
+
from code_muse.agents.subagent_stream_handler import subagent_stream_handler
|
|
382
|
+
|
|
383
|
+
# Fire plugin hook so plugins can observe/log agent invocations
|
|
384
|
+
with suppress(Exception):
|
|
385
|
+
await on_invoke_agent(
|
|
386
|
+
agent_name, prompt, session_id=session_id, context=context
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Emit structured invocation message via MessageBus
|
|
390
|
+
bus = get_message_bus()
|
|
391
|
+
bus.emit(
|
|
392
|
+
SubAgentInvocationMessage(
|
|
393
|
+
agent_name=agent_name,
|
|
394
|
+
session_id=session_id,
|
|
395
|
+
prompt=prompt,
|
|
396
|
+
is_new_session=is_new_session,
|
|
397
|
+
message_count=len(message_history),
|
|
398
|
+
)
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# Save current session context and set the new one for this sub-agent
|
|
402
|
+
previous_session_id = get_session_context()
|
|
403
|
+
set_session_context(session_id)
|
|
404
|
+
|
|
405
|
+
# Set browser session for browser tools (qa-melpomene, etc.)
|
|
406
|
+
# This allows parallel agent invocations to each have their own browser
|
|
407
|
+
from code_muse.tools.browser.browser_manager import (
|
|
408
|
+
set_browser_session,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
browser_session_token = set_browser_session(f"browser-{session_id}")
|
|
412
|
+
|
|
413
|
+
# Bound up-front so the ``except`` block can always reach for it even
|
|
414
|
+
# if load_agent() itself fails before assignment.
|
|
415
|
+
agent_config = None
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
# Lazy import to break circular dependency with messaging module
|
|
419
|
+
from code_muse.model_factory import ModelFactory, make_model_settings
|
|
420
|
+
|
|
421
|
+
# Load the specified agent config
|
|
422
|
+
agent_config = load_agent(agent_name)
|
|
423
|
+
|
|
424
|
+
# Seed the wrapper's message history with the loaded session so that
|
|
425
|
+
# ``make_history_processor(agent_config)`` — wired into the temp
|
|
426
|
+
# agent's ``history_processors`` — mutates ``agent_config._message_history``
|
|
427
|
+
# in place as the run progresses. That means on a mid-run crash we
|
|
428
|
+
# can read partial progress straight off the wrapper below.
|
|
429
|
+
agent_config.set_message_history(list(message_history))
|
|
430
|
+
|
|
431
|
+
# Get the current model for creating a temporary agent
|
|
432
|
+
model_name = agent_config.get_model_name()
|
|
433
|
+
models_config = ModelFactory.load_config()
|
|
434
|
+
|
|
435
|
+
# Only proceed if we have a valid model configuration
|
|
436
|
+
if model_name not in models_config:
|
|
437
|
+
raise ValueError(f"Model '{model_name}' not found in configuration")
|
|
438
|
+
|
|
439
|
+
# PERF-08: Reuse cached model instance for the same model name
|
|
440
|
+
with _model_instance_cache_lock:
|
|
441
|
+
model = _model_instance_cache.get(model_name)
|
|
442
|
+
if model is None:
|
|
443
|
+
model = ModelFactory.get_model(model_name, models_config)
|
|
444
|
+
with _model_instance_cache_lock:
|
|
445
|
+
_model_instance_cache[model_name] = model
|
|
446
|
+
|
|
447
|
+
# Create a temporary agent instance to avoid interfering with current agent state
|
|
448
|
+
instructions = agent_config.get_full_system_prompt()
|
|
449
|
+
|
|
450
|
+
# Add AGENTS.md content to subagents.
|
|
451
|
+
# ``load_muse_rules`` lives on the builder module since the
|
|
452
|
+
# base_agent split in 79dfc3c8; it's not a method on the agent.
|
|
453
|
+
from code_muse.agents._builder import load_muse_rules
|
|
454
|
+
|
|
455
|
+
agent_rules = load_muse_rules()
|
|
456
|
+
if agent_rules:
|
|
457
|
+
instructions += f"\n\n{agent_rules}"
|
|
458
|
+
|
|
459
|
+
# Apply prompt additions (like file permission handling) to temporary agents
|
|
460
|
+
from code_muse import callbacks
|
|
461
|
+
from code_muse.model_utils import prepare_prompt_for_model
|
|
462
|
+
|
|
463
|
+
prompt_additions = callbacks.on_load_prompt()
|
|
464
|
+
if len(prompt_additions):
|
|
465
|
+
instructions += "\n" + "\n".join(prompt_additions)
|
|
466
|
+
|
|
467
|
+
# Handle claude-code models: swap instructions, and prepend system prompt only on first message
|
|
468
|
+
prepared = prepare_prompt_for_model(
|
|
469
|
+
model_name,
|
|
470
|
+
instructions,
|
|
471
|
+
prompt,
|
|
472
|
+
prepend_system_to_user=is_new_session, # Only prepend on first message
|
|
473
|
+
)
|
|
474
|
+
instructions = prepared.instructions
|
|
475
|
+
prompt = prepared.user_prompt
|
|
476
|
+
|
|
477
|
+
model_settings = make_model_settings(model_name)
|
|
478
|
+
|
|
479
|
+
external_servers = []
|
|
480
|
+
|
|
481
|
+
from code_muse.agents._compaction import make_history_processor
|
|
482
|
+
|
|
483
|
+
# Build the pydantic-ai agent. external servers are always included in
|
|
484
|
+
# the constructor; plugins may swap them out at run
|
|
485
|
+
# time via the ``agent_run_context`` hook if their wrapper can't
|
|
486
|
+
# handle them directly.
|
|
487
|
+
temp_agent = Agent(
|
|
488
|
+
model=model,
|
|
489
|
+
instructions=instructions,
|
|
490
|
+
output_type=str,
|
|
491
|
+
retries=3,
|
|
492
|
+
toolsets=external_servers,
|
|
493
|
+
history_processors=[make_history_processor(agent_config)],
|
|
494
|
+
model_settings=model_settings,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# Register the tools that the agent needs
|
|
498
|
+
from code_muse.tools import register_tools_for_agent
|
|
499
|
+
|
|
500
|
+
agent_tools = agent_config.get_available_tools()
|
|
501
|
+
register_tools_for_agent(temp_agent, agent_tools, model_name=model_name)
|
|
502
|
+
|
|
503
|
+
# Always use subagent_stream_handler to silence output and update console manager
|
|
504
|
+
# This ensures all sub-agent output goes through the aggregated dashboard
|
|
505
|
+
stream_handler = partial(subagent_stream_handler, session_id=session_id)
|
|
506
|
+
|
|
507
|
+
# Wrap the agent run in subagent context for tracking
|
|
508
|
+
with subagent_context(agent_name):
|
|
509
|
+
run_ctxs = on_agent_run_context(agent_config, temp_agent, group_id)
|
|
510
|
+
async with AsyncExitStack() as stack:
|
|
511
|
+
for cm in run_ctxs:
|
|
512
|
+
await stack.enter_async_context(cm)
|
|
513
|
+
task = asyncio.create_task(
|
|
514
|
+
temp_agent.run(
|
|
515
|
+
prompt,
|
|
516
|
+
message_history=message_history,
|
|
517
|
+
usage_limits=UsageLimits(request_limit=get_message_limit()),
|
|
518
|
+
event_stream_handler=stream_handler,
|
|
519
|
+
)
|
|
520
|
+
)
|
|
521
|
+
_active_subagent_tasks.add(task)
|
|
522
|
+
|
|
523
|
+
try:
|
|
524
|
+
result = await task
|
|
525
|
+
finally:
|
|
526
|
+
_active_subagent_tasks.discard(task)
|
|
527
|
+
if task.cancelled():
|
|
528
|
+
await on_agent_run_cancel(group_id)
|
|
529
|
+
|
|
530
|
+
# Extract the response from the result
|
|
531
|
+
response = result.output
|
|
532
|
+
|
|
533
|
+
# Update the session history with the new messages from this interaction
|
|
534
|
+
# The result contains all_messages which includes the full conversation
|
|
535
|
+
updated_history = result.all_messages()
|
|
536
|
+
|
|
537
|
+
# Save to filesystem (include initial prompt only for new sessions)
|
|
538
|
+
_save_session_history(
|
|
539
|
+
session_id=session_id,
|
|
540
|
+
message_history=updated_history,
|
|
541
|
+
agent_name=agent_name,
|
|
542
|
+
initial_prompt=prompt if is_new_session else None,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
# Emit structured response message via MessageBus
|
|
546
|
+
bus.emit(
|
|
547
|
+
SubAgentResponseMessage(
|
|
548
|
+
agent_name=agent_name,
|
|
549
|
+
session_id=session_id,
|
|
550
|
+
response=response,
|
|
551
|
+
message_count=len(updated_history),
|
|
552
|
+
)
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
# Emit clean completion summary
|
|
556
|
+
emit_success(
|
|
557
|
+
f"✓ {agent_name} completed successfully", message_group=group_id
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
return AgentInvokeOutput(
|
|
561
|
+
response=response, agent_name=agent_name, session_id=session_id
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
except Exception as e:
|
|
565
|
+
# Emit clean failure summary
|
|
566
|
+
emit_error(f"✗ {agent_name} failed: {str(e)}", message_group=group_id)
|
|
567
|
+
|
|
568
|
+
# Full traceback for debugging
|
|
569
|
+
error_msg = f"Error invoking agent '{agent_name}': {traceback.format_exc()}"
|
|
570
|
+
emit_error(error_msg, message_group=group_id)
|
|
571
|
+
|
|
572
|
+
# Save whatever progress the agent made before crashing. The history
|
|
573
|
+
# processor keeps ``agent_config._message_history`` in sync with each
|
|
574
|
+
# completed turn, so this captures every committed turn up to the
|
|
575
|
+
# failure point. Best-effort: a save failure must not mask the
|
|
576
|
+
# original error, so we swallow anything the save itself raises.
|
|
577
|
+
try:
|
|
578
|
+
partial_history = (
|
|
579
|
+
agent_config.get_message_history() if agent_config else []
|
|
580
|
+
)
|
|
581
|
+
if partial_history and len(partial_history) > len(message_history):
|
|
582
|
+
_save_session_history(
|
|
583
|
+
session_id=session_id,
|
|
584
|
+
message_history=partial_history,
|
|
585
|
+
agent_name=agent_name,
|
|
586
|
+
initial_prompt=prompt if is_new_session else None,
|
|
587
|
+
)
|
|
588
|
+
emit_info(
|
|
589
|
+
f"💾 Saved partial session '{session_id}' "
|
|
590
|
+
f"({len(partial_history)} message(s)) before error",
|
|
591
|
+
message_group=group_id,
|
|
592
|
+
)
|
|
593
|
+
except Exception:
|
|
594
|
+
pass
|
|
595
|
+
|
|
596
|
+
return AgentInvokeOutput(
|
|
597
|
+
response=None,
|
|
598
|
+
agent_name=agent_name,
|
|
599
|
+
session_id=session_id,
|
|
600
|
+
error=error_msg,
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
finally:
|
|
604
|
+
# Restore the previous session context
|
|
605
|
+
set_session_context(previous_session_id)
|
|
606
|
+
# Reset browser session context
|
|
607
|
+
from code_muse.tools.browser.browser_manager import (
|
|
608
|
+
_browser_session_var,
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
_browser_session_var.reset(browser_session_token)
|
|
612
|
+
|
|
613
|
+
return invoke_agent
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Ask User Question tool for Muse.
|
|
2
|
+
|
|
3
|
+
This tool allows agents to ask users interactive multiple-choice questions
|
|
4
|
+
through a terminal TUI interface. Uses prompt_toolkit for the split-panel
|
|
5
|
+
UI similar to the /colors command.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .handler import ask_user_question
|
|
9
|
+
from .models import (
|
|
10
|
+
AskUserQuestionInput,
|
|
11
|
+
AskUserQuestionOutput,
|
|
12
|
+
Question,
|
|
13
|
+
QuestionAnswer,
|
|
14
|
+
QuestionOption,
|
|
15
|
+
)
|
|
16
|
+
from .registration import register_ask_user_question
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"ask_user_question",
|
|
20
|
+
"register_ask_user_question",
|
|
21
|
+
"AskUserQuestionInput",
|
|
22
|
+
"AskUserQuestionOutput",
|
|
23
|
+
"Question",
|
|
24
|
+
"QuestionAnswer",
|
|
25
|
+
"QuestionOption",
|
|
26
|
+
]
|