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,596 @@
|
|
|
1
|
+
"""Agent run orchestration: streaming retries, signal/key cancellation.
|
|
2
|
+
|
|
3
|
+
Replaces the monolithic ``BaseAgent.run`` coroutine. Everything here
|
|
4
|
+
is a free function; the agent is passed in explicitly. Integration points
|
|
5
|
+
preserved verbatim:
|
|
6
|
+
|
|
7
|
+
- Plugin-supplied async context managers wrap the run (see
|
|
8
|
+
``on_agent_run_context``); used e.g. by plugins to set a workflow
|
|
9
|
+
ID and swap external toolsets in/out.
|
|
10
|
+
- Signal-vs-key-listener branch driven by ``cancel_agent_uses_signal()``
|
|
11
|
+
- Windows terminal reset on graceful SIGINT
|
|
12
|
+
- ``is_awaiting_user_input()`` guards interrupt handling
|
|
13
|
+
- Subagent task cancellation via ``_active_subagent_tasks``
|
|
14
|
+
- ``_RUNNING_PROCESSES`` check before cancelling the agent
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import signal
|
|
19
|
+
import threading
|
|
20
|
+
import uuid
|
|
21
|
+
from collections.abc import Callable, Sequence
|
|
22
|
+
from contextlib import AsyncExitStack, suppress
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
import httpcore
|
|
27
|
+
import httpx
|
|
28
|
+
from pydantic_ai import (
|
|
29
|
+
BinaryContent,
|
|
30
|
+
DocumentUrl,
|
|
31
|
+
ImageUrl,
|
|
32
|
+
UnexpectedModelBehavior,
|
|
33
|
+
UsageLimitExceeded,
|
|
34
|
+
UsageLimits,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
try: # pragma: no cover - pydantic-ai version dependent
|
|
38
|
+
from pydantic_ai.exceptions import ModelHTTPError
|
|
39
|
+
except ImportError:
|
|
40
|
+
ModelHTTPError = None # type: ignore[misc,assignment]
|
|
41
|
+
|
|
42
|
+
try: # pragma: no cover - optional dependency
|
|
43
|
+
from openai import APIError as OpenAIAPIError
|
|
44
|
+
except ImportError:
|
|
45
|
+
OpenAIAPIError = None # type: ignore[assignment]
|
|
46
|
+
|
|
47
|
+
# Python 3.11+ builtin; graceful fallback for 3.10
|
|
48
|
+
try:
|
|
49
|
+
from builtins import BaseExceptionGroup # type: ignore[attr-defined]
|
|
50
|
+
except ImportError: # pragma: no cover - 3.10 only
|
|
51
|
+
BaseExceptionGroup = Exception # type: ignore[misc,assignment]
|
|
52
|
+
|
|
53
|
+
from code_muse.agents import _history, _key_listeners
|
|
54
|
+
from code_muse.agents._builder import build_pydantic_agent
|
|
55
|
+
from code_muse.agents._diagnostics import emit_exception_diagnostics
|
|
56
|
+
from code_muse.agents._non_streaming_render import (
|
|
57
|
+
StreamingTextDetector,
|
|
58
|
+
render_result_without_streaming,
|
|
59
|
+
should_render_fallback,
|
|
60
|
+
)
|
|
61
|
+
from code_muse.agents.event_stream_handler import event_stream_handler
|
|
62
|
+
from code_muse.callbacks import (
|
|
63
|
+
on_agent_exception,
|
|
64
|
+
on_agent_run_cancel,
|
|
65
|
+
on_agent_run_context,
|
|
66
|
+
on_agent_run_end,
|
|
67
|
+
on_agent_run_result,
|
|
68
|
+
on_agent_run_start,
|
|
69
|
+
on_should_skip_fallback_render,
|
|
70
|
+
)
|
|
71
|
+
from code_muse.config import (
|
|
72
|
+
get_enable_streaming,
|
|
73
|
+
get_max_hook_retries,
|
|
74
|
+
get_message_limit,
|
|
75
|
+
)
|
|
76
|
+
from code_muse.keymap import cancel_agent_uses_signal
|
|
77
|
+
from code_muse.messaging import emit_error, emit_info, emit_warning
|
|
78
|
+
from code_muse.model_factory import ModelFactory
|
|
79
|
+
from code_muse.tools.agent_tools import _active_subagent_tasks
|
|
80
|
+
from code_muse.tools.command_runner import is_awaiting_user_input
|
|
81
|
+
|
|
82
|
+
# ---- Streaming retry helpers ------------------------------------------------
|
|
83
|
+
|
|
84
|
+
# Every entry here is either an explicit provider "please retry" signal or an
|
|
85
|
+
# SSE framing / transport artifact that reliably succeeds on the next attempt.
|
|
86
|
+
# Keep this list substring-based and lower-case.
|
|
87
|
+
_RETRYABLE_SNIPPETS = (
|
|
88
|
+
"streamed response ended without content",
|
|
89
|
+
"malformed streamed sse event",
|
|
90
|
+
"extra json data in sse payload",
|
|
91
|
+
"too many requests",
|
|
92
|
+
"rate limit",
|
|
93
|
+
"rate limited",
|
|
94
|
+
"overloaded",
|
|
95
|
+
"service unavailable",
|
|
96
|
+
"server had an error processing your request",
|
|
97
|
+
"retry your request",
|
|
98
|
+
"internal server error",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
_RETRYABLE_EXCEPTIONS: tuple = (
|
|
102
|
+
httpx.RemoteProtocolError,
|
|
103
|
+
httpx.ReadTimeout,
|
|
104
|
+
httpcore.RemoteProtocolError,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _matches_retryable_snippet(msg: str) -> bool:
|
|
109
|
+
"""Return True if ``msg`` matches any known transient pattern.
|
|
110
|
+
|
|
111
|
+
Also accepts the generic ``stream ... ended`` wording variants so we don't
|
|
112
|
+
have to chase every phrasing tweak providers sneak in over time.
|
|
113
|
+
"""
|
|
114
|
+
msg = msg.lower()
|
|
115
|
+
if any(s in msg for s in _RETRYABLE_SNIPPETS):
|
|
116
|
+
return True
|
|
117
|
+
return "stream" in msg and "ended" in msg
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def should_retry_streaming(exc: Exception) -> bool:
|
|
121
|
+
"""Decide whether ``exc`` is a transient streaming hiccup worth retrying."""
|
|
122
|
+
if isinstance(exc, _RETRYABLE_EXCEPTIONS):
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
msg = str(exc)
|
|
126
|
+
if isinstance(exc, UnexpectedModelBehavior):
|
|
127
|
+
return _matches_retryable_snippet(msg)
|
|
128
|
+
|
|
129
|
+
if OpenAIAPIError is not None and isinstance(exc, OpenAIAPIError):
|
|
130
|
+
if _matches_retryable_snippet(msg):
|
|
131
|
+
return True
|
|
132
|
+
body = getattr(exc, "body", None)
|
|
133
|
+
if isinstance(body, dict):
|
|
134
|
+
body_msg = str(body.get("message", ""))
|
|
135
|
+
body_type = str(body.get("type", "")).lower()
|
|
136
|
+
if _matches_retryable_snippet(body_msg):
|
|
137
|
+
return True
|
|
138
|
+
if "rate" in body_type and "limit" in body_type:
|
|
139
|
+
return True
|
|
140
|
+
if body_type in {"server_error", "internal_server_error", "api_error"}:
|
|
141
|
+
return _matches_retryable_snippet(body_msg)
|
|
142
|
+
|
|
143
|
+
# Retry on pydantic-ai ModelHTTPError rate limits (e.g. 429 from providers)
|
|
144
|
+
if ModelHTTPError is not None and isinstance(exc, ModelHTTPError):
|
|
145
|
+
status_code = getattr(exc, "status_code", None)
|
|
146
|
+
if status_code == 429:
|
|
147
|
+
return True
|
|
148
|
+
# Retry on 5xx server errors as well
|
|
149
|
+
if isinstance(status_code, int) and status_code >= 500:
|
|
150
|
+
return True
|
|
151
|
+
if _matches_retryable_snippet(msg):
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def streaming_retry(
|
|
158
|
+
max_attempts: int = 3,
|
|
159
|
+
delays: Sequence[float] = (1, 2, 4),
|
|
160
|
+
) -> Callable[[Callable[[], Any]], Callable[[], Any]]:
|
|
161
|
+
"""Wrap a no-arg async callable with streaming-retry semantics."""
|
|
162
|
+
|
|
163
|
+
def decorator(factory: Callable[[], Any]) -> Callable[[], Any]:
|
|
164
|
+
async def runner() -> Any:
|
|
165
|
+
last_exc: Exception | None = None
|
|
166
|
+
for attempt in range(max_attempts):
|
|
167
|
+
try:
|
|
168
|
+
return await factory()
|
|
169
|
+
except Exception as exc:
|
|
170
|
+
if not should_retry_streaming(exc):
|
|
171
|
+
raise
|
|
172
|
+
last_exc = exc
|
|
173
|
+
if attempt < max_attempts - 1:
|
|
174
|
+
delay = delays[attempt] if attempt < len(delays) else delays[-1]
|
|
175
|
+
emit_warning(
|
|
176
|
+
f"⚡ Streaming interrupted, auto-retrying in {delay}s... "
|
|
177
|
+
f"(attempt {attempt + 1}/{max_attempts})"
|
|
178
|
+
)
|
|
179
|
+
await asyncio.sleep(delay)
|
|
180
|
+
else:
|
|
181
|
+
emit_error(f"❌ Streaming failed after {max_attempts} attempts")
|
|
182
|
+
assert last_exc is not None # loop always sets this before exiting
|
|
183
|
+
raise last_exc
|
|
184
|
+
|
|
185
|
+
return runner
|
|
186
|
+
|
|
187
|
+
return decorator
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ---- Small utilities --------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _model_allows_streaming(model_name: str | None) -> bool:
|
|
194
|
+
"""Check the model config for an explicit ``"streaming": false`` override.
|
|
195
|
+
|
|
196
|
+
Some providers (e.g. crof.ai for kimi models) have flaky SSE transports.
|
|
197
|
+
Setting ``"streaming": false`` in ``models.json`` disables streaming for
|
|
198
|
+
that model, falling back to a single-shot request like gac does.
|
|
199
|
+
"""
|
|
200
|
+
if not model_name:
|
|
201
|
+
return True
|
|
202
|
+
try:
|
|
203
|
+
cfg = ModelFactory.load_config().get(model_name, {})
|
|
204
|
+
return cfg.get("streaming", True) is not False
|
|
205
|
+
except Exception:
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _sanitize_prompt(prompt: str) -> str:
|
|
210
|
+
"""Strip lone UTF-16 surrogates (common on Windows copy-paste)."""
|
|
211
|
+
if not prompt:
|
|
212
|
+
return prompt
|
|
213
|
+
try:
|
|
214
|
+
return prompt.encode("utf-8", errors="surrogatepass").decode(
|
|
215
|
+
"utf-8", errors="replace"
|
|
216
|
+
)
|
|
217
|
+
except UnicodeEncodeError, UnicodeDecodeError:
|
|
218
|
+
return "".join(
|
|
219
|
+
ch if ord(ch) < 0xD800 or ord(ch) > 0xDFFF else "\ufffd" for ch in prompt
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _build_prompt_payload(
|
|
224
|
+
prompt: str,
|
|
225
|
+
attachments: Sequence[BinaryContent | None],
|
|
226
|
+
link_attachments: Sequence[ImageUrl | DocumentUrl | None],
|
|
227
|
+
) -> str | list[Any]:
|
|
228
|
+
"""Merge prompt + binary/link attachments into the pydantic-ai payload shape."""
|
|
229
|
+
parts: list[Any] = []
|
|
230
|
+
if attachments:
|
|
231
|
+
parts.extend(attachments)
|
|
232
|
+
if link_attachments:
|
|
233
|
+
parts.extend(link_attachments)
|
|
234
|
+
|
|
235
|
+
if not parts:
|
|
236
|
+
return prompt
|
|
237
|
+
|
|
238
|
+
payload: list[Any] = []
|
|
239
|
+
if prompt:
|
|
240
|
+
payload.append(prompt)
|
|
241
|
+
payload.extend(parts)
|
|
242
|
+
return payload
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _extract_response_text(result: Any) -> str:
|
|
246
|
+
"""Best-effort extraction of human-readable text from a pydantic-ai result."""
|
|
247
|
+
if result is None:
|
|
248
|
+
return ""
|
|
249
|
+
if hasattr(result, "data"):
|
|
250
|
+
return str(result.data) if result.data else ""
|
|
251
|
+
if hasattr(result, "output"):
|
|
252
|
+
return str(result.output) if result.output else ""
|
|
253
|
+
return str(result)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _should_prepend_system_prompt(agent: Any, prompt: str) -> str:
|
|
257
|
+
"""Prepend system prompt to user prompt on the first turn (claude-code etc)."""
|
|
258
|
+
from code_muse.agents._builder import load_muse_rules
|
|
259
|
+
from code_muse.model_utils import prepare_prompt_for_model
|
|
260
|
+
|
|
261
|
+
if agent._message_history:
|
|
262
|
+
return prompt
|
|
263
|
+
|
|
264
|
+
system_prompt = agent.get_full_system_prompt()
|
|
265
|
+
rules = load_muse_rules()
|
|
266
|
+
if rules:
|
|
267
|
+
system_prompt += f"\n{rules}"
|
|
268
|
+
|
|
269
|
+
prepared = prepare_prompt_for_model(
|
|
270
|
+
model_name=agent.get_model_name(),
|
|
271
|
+
system_prompt=system_prompt,
|
|
272
|
+
user_prompt=prompt,
|
|
273
|
+
prepend_system_to_user=True,
|
|
274
|
+
)
|
|
275
|
+
return prepared.user_prompt
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _collect_exceptions(
|
|
279
|
+
group: BaseException, predicate: Callable[[BaseException], bool]
|
|
280
|
+
) -> list[BaseException]:
|
|
281
|
+
"""Flatten an ExceptionGroup tree, returning leaves matching ``predicate``."""
|
|
282
|
+
out: list[BaseException] = []
|
|
283
|
+
stack: list[BaseException] = [group]
|
|
284
|
+
while stack:
|
|
285
|
+
exc = stack.pop()
|
|
286
|
+
if isinstance(exc, BaseExceptionGroup):
|
|
287
|
+
stack.extend(exc.exceptions)
|
|
288
|
+
elif predicate(exc):
|
|
289
|
+
out.append(exc)
|
|
290
|
+
return out
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@dataclass
|
|
294
|
+
class RunOutcome:
|
|
295
|
+
"""Structured result of a single agent run attempt."""
|
|
296
|
+
|
|
297
|
+
success: bool
|
|
298
|
+
result: Any = None
|
|
299
|
+
error: BaseException | None = None
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# ---- The main entry point ---------------------------------------------------
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
async def run(
|
|
306
|
+
agent: Any,
|
|
307
|
+
prompt: str,
|
|
308
|
+
*,
|
|
309
|
+
attachments: Sequence[BinaryContent | None] = None,
|
|
310
|
+
link_attachments: Sequence[ImageUrl | DocumentUrl | None] = None,
|
|
311
|
+
output_type: type[Any | None] = None,
|
|
312
|
+
**kwargs: Any,
|
|
313
|
+
) -> Any:
|
|
314
|
+
"""Run ``agent`` against ``prompt`` with full tool + cancellation support."""
|
|
315
|
+
|
|
316
|
+
prompt = _sanitize_prompt(prompt)
|
|
317
|
+
group_id = str(uuid.uuid4())
|
|
318
|
+
|
|
319
|
+
if agent._code_generation_agent is None:
|
|
320
|
+
build_pydantic_agent(agent)
|
|
321
|
+
pydantic_agent = agent._code_generation_agent
|
|
322
|
+
|
|
323
|
+
if output_type is not None:
|
|
324
|
+
pydantic_agent = build_pydantic_agent(agent, output_type=output_type)
|
|
325
|
+
|
|
326
|
+
prompt = _should_prepend_system_prompt(agent, prompt)
|
|
327
|
+
prompt_payload = _build_prompt_payload(prompt, attachments, link_attachments)
|
|
328
|
+
|
|
329
|
+
async def _do_run(prompt_to_use: Any) -> Any:
|
|
330
|
+
"""Run the agent once, then honour any plugin ``retry`` requests."""
|
|
331
|
+
usage_limits = UsageLimits(request_limit=get_message_limit())
|
|
332
|
+
|
|
333
|
+
# Streaming config gate (issue #295). When streaming is disabled we
|
|
334
|
+
# never install the stream handler at all and always render from the
|
|
335
|
+
# final result. When it's enabled we wrap the handler in a detector
|
|
336
|
+
# and fall back to a one-shot render only if no text actually streamed.
|
|
337
|
+
#
|
|
338
|
+
# Model-level override: models with ``"streaming": false`` in
|
|
339
|
+
# models.json always use non-streaming requests (e.g. kimi-k2.5
|
|
340
|
+
# via crof.ai whose SSE transport is flaky).
|
|
341
|
+
use_streaming = get_enable_streaming() and _model_allows_streaming(
|
|
342
|
+
agent.get_model_name()
|
|
343
|
+
)
|
|
344
|
+
detector: StreamingTextDetector | None = (
|
|
345
|
+
StreamingTextDetector(event_stream_handler) if use_streaming else None
|
|
346
|
+
)
|
|
347
|
+
stream_handler = detector if detector is not None else None
|
|
348
|
+
# When streaming is disabled we must also clear the handler stored on
|
|
349
|
+
# the pydantic agent itself. Some wrappers bake
|
|
350
|
+
# ``event_stream_handler`` into the agent at build
|
|
351
|
+
# time; passing ``None`` to ``.run()`` isn't enough because pydantic-ai
|
|
352
|
+
# falls back via ``event_stream_handler or self.event_stream_handler``.
|
|
353
|
+
# Nuking ``_event_stream_handler`` forces the property to return
|
|
354
|
+
# ``None``, which makes pydantic-ai use the non-streaming
|
|
355
|
+
# ``model.request()`` path instead of ``request_stream()``.
|
|
356
|
+
_saved_handler: Any = None
|
|
357
|
+
handler_was_modified = False
|
|
358
|
+
if not use_streaming:
|
|
359
|
+
_saved_handler = getattr(pydantic_agent, "_event_stream_handler", None)
|
|
360
|
+
pydantic_agent._event_stream_handler = None
|
|
361
|
+
handler_was_modified = True
|
|
362
|
+
# Plugins can render their own output and ask us to skip
|
|
363
|
+
# the non-streaming fallback render.
|
|
364
|
+
skip_fallback_render = on_should_skip_fallback_render(agent)
|
|
365
|
+
|
|
366
|
+
@streaming_retry()
|
|
367
|
+
async def _call() -> Any:
|
|
368
|
+
return await pydantic_agent.run(
|
|
369
|
+
prompt_to_use,
|
|
370
|
+
message_history=agent._message_history,
|
|
371
|
+
usage_limits=usage_limits,
|
|
372
|
+
event_stream_handler=stream_handler,
|
|
373
|
+
**kwargs,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
async def _call_with_exception_recovery() -> Any:
|
|
377
|
+
"""Run ``_call`` and let plugins request one exception retry."""
|
|
378
|
+
try:
|
|
379
|
+
return await _call()
|
|
380
|
+
except Exception as exc:
|
|
381
|
+
hook_results = await on_agent_exception(
|
|
382
|
+
exc,
|
|
383
|
+
agent=agent,
|
|
384
|
+
agent_name=agent.name,
|
|
385
|
+
model_name=agent.get_model_name(),
|
|
386
|
+
)
|
|
387
|
+
retry_req = next(
|
|
388
|
+
(r for r in hook_results if isinstance(r, dict) and r.get("retry")),
|
|
389
|
+
None,
|
|
390
|
+
)
|
|
391
|
+
if not retry_req:
|
|
392
|
+
raise
|
|
393
|
+
|
|
394
|
+
retry_delay = retry_req.get("delay", 0.0)
|
|
395
|
+
if retry_delay:
|
|
396
|
+
await asyncio.sleep(retry_delay)
|
|
397
|
+
return await _call()
|
|
398
|
+
|
|
399
|
+
try:
|
|
400
|
+
result = await _call_with_exception_recovery()
|
|
401
|
+
|
|
402
|
+
for _ in range(get_max_hook_retries()):
|
|
403
|
+
hook_results = await on_agent_run_result(
|
|
404
|
+
result,
|
|
405
|
+
agent_name=agent.name,
|
|
406
|
+
model_name=agent.get_model_name(),
|
|
407
|
+
)
|
|
408
|
+
retry_req = next(
|
|
409
|
+
(r for r in hook_results if isinstance(r, dict) and r.get("retry")),
|
|
410
|
+
None,
|
|
411
|
+
)
|
|
412
|
+
if not retry_req:
|
|
413
|
+
break
|
|
414
|
+
|
|
415
|
+
retry_prompt = retry_req.get("prompt", "Please continue.")
|
|
416
|
+
retry_delay = retry_req.get("delay", 1.0)
|
|
417
|
+
if hasattr(result, "all_messages"):
|
|
418
|
+
agent._message_history = list(result.all_messages())
|
|
419
|
+
await asyncio.sleep(retry_delay)
|
|
420
|
+
|
|
421
|
+
@streaming_retry()
|
|
422
|
+
async def _retry_call(prompt: str = retry_prompt) -> Any:
|
|
423
|
+
return await pydantic_agent.run(
|
|
424
|
+
prompt,
|
|
425
|
+
message_history=agent._message_history,
|
|
426
|
+
usage_limits=usage_limits,
|
|
427
|
+
event_stream_handler=stream_handler,
|
|
428
|
+
**kwargs,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
result = await _retry_call()
|
|
432
|
+
|
|
433
|
+
finally:
|
|
434
|
+
# Restore the handler we cleared (non-streaming models).
|
|
435
|
+
if handler_was_modified:
|
|
436
|
+
pydantic_agent._event_stream_handler = _saved_handler
|
|
437
|
+
|
|
438
|
+
# Fallback render when streaming didn't surface any text to the user.
|
|
439
|
+
if result is not None and should_render_fallback(
|
|
440
|
+
detector, skip=skip_fallback_render
|
|
441
|
+
):
|
|
442
|
+
# TODO: PEP 734 async bridge — render_result_without_streaming uses sync time.sleep
|
|
443
|
+
await asyncio.to_thread(render_result_without_streaming, result)
|
|
444
|
+
|
|
445
|
+
return result
|
|
446
|
+
|
|
447
|
+
async def run_agent_task() -> RunOutcome:
|
|
448
|
+
outcome: RunOutcome | None = None
|
|
449
|
+
try:
|
|
450
|
+
agent._message_history = _history.prune_interrupted_tool_calls(
|
|
451
|
+
agent._message_history
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
run_ctxs = on_agent_run_context(agent, pydantic_agent, group_id)
|
|
455
|
+
async with AsyncExitStack() as stack:
|
|
456
|
+
for cm in run_ctxs:
|
|
457
|
+
await stack.enter_async_context(cm)
|
|
458
|
+
result = await _do_run(prompt_payload)
|
|
459
|
+
outcome = RunOutcome(True, result=result)
|
|
460
|
+
except* UsageLimitExceeded as ule:
|
|
461
|
+
emit_info(f"Usage limit exceeded: {ule}", group_id=group_id)
|
|
462
|
+
emit_info(
|
|
463
|
+
"The agent has reached its usage limit. You can ask it to continue "
|
|
464
|
+
"by saying 'please continue' or similar.",
|
|
465
|
+
group_id=group_id,
|
|
466
|
+
)
|
|
467
|
+
outcome = RunOutcome(False, error=ule)
|
|
468
|
+
except* Exception as other:
|
|
469
|
+
unexpected = _collect_exceptions(
|
|
470
|
+
other,
|
|
471
|
+
lambda e: (
|
|
472
|
+
not isinstance(e, (asyncio.CancelledError, UsageLimitExceeded))
|
|
473
|
+
),
|
|
474
|
+
)
|
|
475
|
+
for exc in unexpected:
|
|
476
|
+
emit_exception_diagnostics(exc, group_id=group_id)
|
|
477
|
+
outcome = RunOutcome(False, error=other)
|
|
478
|
+
finally:
|
|
479
|
+
agent._message_history = _history.prune_interrupted_tool_calls(
|
|
480
|
+
agent._message_history
|
|
481
|
+
)
|
|
482
|
+
if outcome is None:
|
|
483
|
+
return RunOutcome(False)
|
|
484
|
+
return outcome
|
|
485
|
+
|
|
486
|
+
agent_task = asyncio.create_task(run_agent_task())
|
|
487
|
+
|
|
488
|
+
try:
|
|
489
|
+
await on_agent_run_start(
|
|
490
|
+
agent_name=agent.name,
|
|
491
|
+
model_name=agent.get_model_name(),
|
|
492
|
+
session_id=group_id,
|
|
493
|
+
)
|
|
494
|
+
except Exception:
|
|
495
|
+
# Hook failures never block the agent.
|
|
496
|
+
pass
|
|
497
|
+
|
|
498
|
+
loop = asyncio.get_running_loop()
|
|
499
|
+
|
|
500
|
+
def schedule_agent_cancel() -> None:
|
|
501
|
+
from code_muse.tools.command_runner import _RUNNING_PROCESSES
|
|
502
|
+
|
|
503
|
+
if _RUNNING_PROCESSES:
|
|
504
|
+
emit_warning(
|
|
505
|
+
"Refusing to cancel Agent while a shell command is running — "
|
|
506
|
+
"press Ctrl+X to cancel the shell command."
|
|
507
|
+
)
|
|
508
|
+
return
|
|
509
|
+
if agent_task.done():
|
|
510
|
+
return
|
|
511
|
+
if _active_subagent_tasks:
|
|
512
|
+
emit_warning(
|
|
513
|
+
f"Cancelling {len(_active_subagent_tasks)} active subagent task(s)..."
|
|
514
|
+
)
|
|
515
|
+
for task in list(_active_subagent_tasks):
|
|
516
|
+
if not task.done():
|
|
517
|
+
loop.call_soon_threadsafe(task.cancel)
|
|
518
|
+
loop.call_soon_threadsafe(agent_task.cancel)
|
|
519
|
+
|
|
520
|
+
def keyboard_interrupt_handler(_sig, _frame):
|
|
521
|
+
# Let input() handle its own KeyboardInterrupt if we're mid-prompt.
|
|
522
|
+
if is_awaiting_user_input():
|
|
523
|
+
return
|
|
524
|
+
schedule_agent_cancel()
|
|
525
|
+
|
|
526
|
+
def graceful_sigint_handler(_sig, _frame):
|
|
527
|
+
from code_muse.keymap import get_cancel_agent_display_name
|
|
528
|
+
from code_muse.terminal_utils import reset_windows_terminal_full
|
|
529
|
+
|
|
530
|
+
reset_windows_terminal_full()
|
|
531
|
+
emit_info(f"Use {get_cancel_agent_display_name()} to cancel the agent task.")
|
|
532
|
+
|
|
533
|
+
original_handler = None
|
|
534
|
+
key_listener_stop_event: threading.Event | None = None
|
|
535
|
+
key_listener_thread: threading.Thread | None = None
|
|
536
|
+
|
|
537
|
+
run_success = False
|
|
538
|
+
run_error: BaseException | None = None
|
|
539
|
+
run_response_text = ""
|
|
540
|
+
|
|
541
|
+
try:
|
|
542
|
+
if cancel_agent_uses_signal():
|
|
543
|
+
original_handler = signal.signal(signal.SIGINT, keyboard_interrupt_handler)
|
|
544
|
+
else:
|
|
545
|
+
original_handler = signal.signal(signal.SIGINT, graceful_sigint_handler)
|
|
546
|
+
key_listener_stop_event = threading.Event()
|
|
547
|
+
key_listener_thread = _key_listeners.spawn_key_listener(
|
|
548
|
+
key_listener_stop_event,
|
|
549
|
+
on_escape=lambda: None, # Ctrl+X handled by command_runner
|
|
550
|
+
on_cancel_agent=schedule_agent_cancel,
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
outcome = await agent_task
|
|
554
|
+
if outcome.success:
|
|
555
|
+
result = outcome.result
|
|
556
|
+
run_success = True
|
|
557
|
+
run_response_text = _extract_response_text(result)
|
|
558
|
+
return result
|
|
559
|
+
else:
|
|
560
|
+
run_error = outcome.error
|
|
561
|
+
if outcome.error is not None:
|
|
562
|
+
raise outcome.error
|
|
563
|
+
except asyncio.CancelledError as exc:
|
|
564
|
+
run_response_text = ""
|
|
565
|
+
run_error = exc
|
|
566
|
+
await on_agent_run_cancel(group_id)
|
|
567
|
+
agent_task.cancel()
|
|
568
|
+
raise
|
|
569
|
+
except KeyboardInterrupt:
|
|
570
|
+
run_response_text = ""
|
|
571
|
+
if not agent_task.done():
|
|
572
|
+
agent_task.cancel()
|
|
573
|
+
except BaseExceptionGroup as e:
|
|
574
|
+
run_error = e
|
|
575
|
+
raise
|
|
576
|
+
except Exception as e:
|
|
577
|
+
run_error = e
|
|
578
|
+
raise
|
|
579
|
+
finally:
|
|
580
|
+
with suppress(Exception):
|
|
581
|
+
await on_agent_run_end(
|
|
582
|
+
agent_name=agent.name,
|
|
583
|
+
model_name=agent.get_model_name(),
|
|
584
|
+
session_id=group_id,
|
|
585
|
+
success=run_success,
|
|
586
|
+
error=run_error,
|
|
587
|
+
response_text=run_response_text,
|
|
588
|
+
metadata={"model": agent.get_model_name()},
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
if key_listener_stop_event is not None:
|
|
592
|
+
key_listener_stop_event.set()
|
|
593
|
+
if key_listener_thread is not None:
|
|
594
|
+
key_listener_thread.join(timeout=1.0)
|
|
595
|
+
if original_handler is not None: # SIG_DFL is 0/falsy — explicit check!
|
|
596
|
+
signal.signal(signal.SIGINT, original_handler)
|