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,75 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from fnmatch import fnmatch
|
|
3
|
+
|
|
4
|
+
from code_muse.plugins.policy_engine.policy_toml_schema import Decision, ToolRule
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _tool_name_matches(tool_name: str, rule_tool_name: str) -> bool:
|
|
10
|
+
if rule_tool_name == "*":
|
|
11
|
+
return True
|
|
12
|
+
# Support fnmatch wildcards (e.g., "agent_run_*")
|
|
13
|
+
return fnmatch(tool_name, rule_tool_name)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def evaluate_policy(
|
|
17
|
+
tool_name: str,
|
|
18
|
+
command: str | None,
|
|
19
|
+
rules: list[ToolRule],
|
|
20
|
+
) -> tuple[Decision, ToolRule | None]:
|
|
21
|
+
matched: list[ToolRule] = []
|
|
22
|
+
for rule in rules:
|
|
23
|
+
if not _tool_name_matches(tool_name, rule.tool_name):
|
|
24
|
+
continue
|
|
25
|
+
if rule.command_prefix is not None:
|
|
26
|
+
if command is None:
|
|
27
|
+
# command_prefix specified but no command provided — skip
|
|
28
|
+
continue
|
|
29
|
+
if not command.startswith(rule.command_prefix):
|
|
30
|
+
continue
|
|
31
|
+
matched.append(rule)
|
|
32
|
+
|
|
33
|
+
if not matched:
|
|
34
|
+
logger.debug(
|
|
35
|
+
"Policy: no rules matched for tool=%s command=%s — default ALLOW",
|
|
36
|
+
tool_name,
|
|
37
|
+
command,
|
|
38
|
+
)
|
|
39
|
+
return (Decision.ALLOW, None)
|
|
40
|
+
|
|
41
|
+
# Sort by priority descending
|
|
42
|
+
matched.sort(key=lambda r: r.priority, reverse=True)
|
|
43
|
+
highest_priority = matched[0].priority
|
|
44
|
+
top_rules = [r for r in matched if r.priority == highest_priority]
|
|
45
|
+
|
|
46
|
+
if len(top_rules) > 1:
|
|
47
|
+
decisions = {r.decision for r in top_rules}
|
|
48
|
+
if len(decisions) > 1:
|
|
49
|
+
logger.warning(
|
|
50
|
+
"Policy conflict: %d rules at priority %d with different decisions "
|
|
51
|
+
"for tool=%s command=%s. Using first registered: %s",
|
|
52
|
+
len(top_rules),
|
|
53
|
+
highest_priority,
|
|
54
|
+
tool_name,
|
|
55
|
+
command,
|
|
56
|
+
top_rules[0],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
chosen = top_rules[0]
|
|
60
|
+
logger.debug(
|
|
61
|
+
"Policy: matched rule for tool=%s command=%s → %s (priority=%d, source=%s)",
|
|
62
|
+
tool_name,
|
|
63
|
+
command,
|
|
64
|
+
chosen.decision.value,
|
|
65
|
+
chosen.priority,
|
|
66
|
+
chosen.source,
|
|
67
|
+
)
|
|
68
|
+
return (chosen.decision, chosen)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def evaluate_tool_policy(
|
|
72
|
+
tool_name: str,
|
|
73
|
+
rules: list[ToolRule],
|
|
74
|
+
) -> tuple[Decision, ToolRule | None]:
|
|
75
|
+
return evaluate_policy(tool_name, None, rules)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from code_muse.plugins.policy_engine.policy_toml_schema import (
|
|
6
|
+
ToolRule,
|
|
7
|
+
parse_policy_toml,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
# Simple cache: list of rules + dict of file mtimes
|
|
13
|
+
_rule_cache: list[ToolRule] | None = None
|
|
14
|
+
_file_mtimes: dict[str, float] = {}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_user_policies_dir() -> Path:
|
|
18
|
+
return Path.home() / ".muse" / "policies"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_project_policies_dir() -> Path:
|
|
22
|
+
return Path(os.getcwd()) / ".muse" / "policies"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def discover_policy_files() -> list[Path]:
|
|
26
|
+
files: list[Path] = []
|
|
27
|
+
for directory in (_get_user_policies_dir(), _get_project_policies_dir()):
|
|
28
|
+
if not directory.exists():
|
|
29
|
+
continue
|
|
30
|
+
try:
|
|
31
|
+
toml_files = sorted(directory.glob("*.toml"))
|
|
32
|
+
except OSError as exc:
|
|
33
|
+
logger.warning("Cannot scan policy directory %s: %s", directory, exc)
|
|
34
|
+
continue
|
|
35
|
+
for f in toml_files:
|
|
36
|
+
if not f.is_file():
|
|
37
|
+
continue
|
|
38
|
+
try:
|
|
39
|
+
# Check readability
|
|
40
|
+
_ = f.stat()
|
|
41
|
+
except OSError as exc:
|
|
42
|
+
logger.warning("Unreadable policy file %s: %s", f, exc)
|
|
43
|
+
continue
|
|
44
|
+
files.append(f)
|
|
45
|
+
return files
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _files_changed(files: list[Path]) -> bool:
|
|
49
|
+
global _file_mtimes
|
|
50
|
+
current: dict[str, float] = {}
|
|
51
|
+
for f in files:
|
|
52
|
+
try:
|
|
53
|
+
current[str(f)] = f.stat().st_mtime
|
|
54
|
+
except OSError:
|
|
55
|
+
return True
|
|
56
|
+
return current != _file_mtimes
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_all_policies(force_reload: bool = False) -> list[ToolRule]:
|
|
60
|
+
global _rule_cache, _file_mtimes
|
|
61
|
+
|
|
62
|
+
files = discover_policy_files()
|
|
63
|
+
|
|
64
|
+
if not force_reload and _rule_cache is not None and not _files_changed(files):
|
|
65
|
+
return _rule_cache
|
|
66
|
+
|
|
67
|
+
all_rules: list[ToolRule] = []
|
|
68
|
+
new_mtimes: dict[str, float] = {}
|
|
69
|
+
|
|
70
|
+
for f in files:
|
|
71
|
+
try:
|
|
72
|
+
rules = parse_policy_toml(f)
|
|
73
|
+
all_rules.extend(rules)
|
|
74
|
+
new_mtimes[str(f)] = f.stat().st_mtime
|
|
75
|
+
except ValueError as exc:
|
|
76
|
+
logger.warning("Skipping invalid policy file %s: %s", f, exc)
|
|
77
|
+
except OSError as exc:
|
|
78
|
+
logger.warning("Cannot read policy file %s: %s", f, exc)
|
|
79
|
+
|
|
80
|
+
_rule_cache = all_rules
|
|
81
|
+
_file_mtimes = new_mtimes
|
|
82
|
+
logger.info("Loaded %d policy rules from %d file(s)", len(all_rules), len(files))
|
|
83
|
+
return all_rules
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def clear_policy_cache() -> None:
|
|
87
|
+
global _rule_cache, _file_mtimes
|
|
88
|
+
_rule_cache = None
|
|
89
|
+
_file_mtimes = {}
|
|
90
|
+
logger.info("Policy cache cleared")
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Decision(Enum):
|
|
11
|
+
ALLOW = "allow"
|
|
12
|
+
DENY = "deny"
|
|
13
|
+
ASK_USER = "ask_user"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ToolRule:
|
|
18
|
+
tool_name: str
|
|
19
|
+
decision: Decision
|
|
20
|
+
command_prefix: str | None = None
|
|
21
|
+
priority: int = 0
|
|
22
|
+
description: str = ""
|
|
23
|
+
source: str = field(default="", repr=False)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _warn_unknown_fields(rule_table: dict[str, Any], path: str | Path) -> None:
|
|
27
|
+
known = {"toolName", "commandPrefix", "decision", "priority", "description"}
|
|
28
|
+
unknown = set(rule_table.keys()) - known
|
|
29
|
+
if unknown:
|
|
30
|
+
logger.warning("Unknown fields in rule from %s: %s", path, ", ".join(unknown))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_policy_toml(path: str | Path) -> list[ToolRule]:
|
|
34
|
+
path = Path(path)
|
|
35
|
+
import tomllib
|
|
36
|
+
|
|
37
|
+
raw = path.read_text(encoding="utf-8")
|
|
38
|
+
data = tomllib.loads(raw)
|
|
39
|
+
|
|
40
|
+
schema_version = data.get("schema_version")
|
|
41
|
+
if schema_version is not None and str(schema_version) != "1":
|
|
42
|
+
logger.warning(
|
|
43
|
+
"Policy file %s has schema_version %s; expected 1. "
|
|
44
|
+
"Forward compatibility assumed.",
|
|
45
|
+
path,
|
|
46
|
+
schema_version,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
rules: list[ToolRule] = []
|
|
50
|
+
rule_tables = data.get("rule", [])
|
|
51
|
+
if isinstance(rule_tables, dict):
|
|
52
|
+
# Single [[rule]] can be parsed as dict by some TOML libs
|
|
53
|
+
rule_tables = [rule_tables]
|
|
54
|
+
|
|
55
|
+
for idx, rule_table in enumerate(rule_tables):
|
|
56
|
+
if not isinstance(rule_table, dict):
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Invalid rule entry at index {idx} in {path}: expected table, got {type(rule_table).__name__}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
_warn_unknown_fields(rule_table, path)
|
|
62
|
+
|
|
63
|
+
tool_name = rule_table.get("toolName")
|
|
64
|
+
if tool_name is None or not isinstance(tool_name, str) or not tool_name.strip():
|
|
65
|
+
raise ValueError(f"Rule {idx + 1} in {path}: missing or invalid 'toolName'")
|
|
66
|
+
|
|
67
|
+
decision_str = rule_table.get("decision")
|
|
68
|
+
if decision_str is None or not isinstance(decision_str, str):
|
|
69
|
+
raise ValueError(f"Rule {idx + 1} in {path}: missing or invalid 'decision'")
|
|
70
|
+
try:
|
|
71
|
+
decision = Decision(decision_str)
|
|
72
|
+
except ValueError as exc:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"Rule {idx + 1} in {path}: invalid decision '{decision_str}'. "
|
|
75
|
+
f"Must be one of: {', '.join(d.value for d in Decision)}"
|
|
76
|
+
) from exc
|
|
77
|
+
|
|
78
|
+
command_prefix = rule_table.get("commandPrefix")
|
|
79
|
+
if command_prefix is not None and not isinstance(command_prefix, str):
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"Rule {idx + 1} in {path}: 'commandPrefix' must be a string"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
priority = rule_table.get("priority", 0)
|
|
85
|
+
if not isinstance(priority, int):
|
|
86
|
+
raise ValueError(f"Rule {idx + 1} in {path}: 'priority' must be an integer")
|
|
87
|
+
|
|
88
|
+
description = rule_table.get("description", "")
|
|
89
|
+
if description is not None and not isinstance(description, str):
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Rule {idx + 1} in {path}: 'description' must be a string"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
rules.append(
|
|
95
|
+
ToolRule(
|
|
96
|
+
tool_name=tool_name,
|
|
97
|
+
decision=decision,
|
|
98
|
+
command_prefix=command_prefix if command_prefix else None,
|
|
99
|
+
priority=priority,
|
|
100
|
+
description=description,
|
|
101
|
+
source=str(path),
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return rules
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def validate_rules(rules: list[ToolRule]) -> None:
|
|
109
|
+
for rule in rules:
|
|
110
|
+
if not rule.tool_name or not rule.tool_name.strip():
|
|
111
|
+
raise ValueError(f"Invalid rule: tool_name is empty (from {rule.source})")
|
|
112
|
+
if rule.decision == Decision.DENY and not rule.tool_name.strip():
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"Invalid rule: deny with empty tool_name (from {rule.source})"
|
|
115
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from code_muse.callbacks import register_callback
|
|
5
|
+
from code_muse.messaging import emit_info
|
|
6
|
+
from code_muse.plugins.policy_engine.approval_flow_integration import (
|
|
7
|
+
integrate_policy_check,
|
|
8
|
+
)
|
|
9
|
+
from code_muse.plugins.policy_engine.policy_file_discovery import (
|
|
10
|
+
clear_policy_cache,
|
|
11
|
+
load_all_policies,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# Load policies at import time so they're available immediately.
|
|
17
|
+
# This is safe because load_all_policies caches results and logs warnings
|
|
18
|
+
# for invalid files rather than raising.
|
|
19
|
+
_INITIALIZED_RULES = load_all_policies()
|
|
20
|
+
logger.info(
|
|
21
|
+
"Policy engine initialized with %d rule(s)",
|
|
22
|
+
len(_INITIALIZED_RULES),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def _on_run_shell_command(
|
|
27
|
+
context: Any,
|
|
28
|
+
command: str,
|
|
29
|
+
cwd: str | None = None,
|
|
30
|
+
timeout: int = 60,
|
|
31
|
+
) -> dict[str, Any | None]:
|
|
32
|
+
"""Policy check for shell commands.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
- {"auto_approve": True} if policy says ALLOW
|
|
36
|
+
- {"blocked": True, "error_message": "..."} if policy says DENY
|
|
37
|
+
- None for ASK_USER or no match (normal confirmation flow)
|
|
38
|
+
"""
|
|
39
|
+
return integrate_policy_check("agent_run_shell_command", command)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def _on_pre_tool_call(
|
|
43
|
+
tool_name: str,
|
|
44
|
+
tool_args: dict,
|
|
45
|
+
context: Any = None,
|
|
46
|
+
) -> dict[str, Any | None]:
|
|
47
|
+
"""Policy check for all tool calls.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
- {"blocked": True, "error_message": "..."} if policy says DENY
|
|
51
|
+
- None otherwise (ALLOW, ASK_USER, or no match)
|
|
52
|
+
"""
|
|
53
|
+
result = integrate_policy_check(tool_name, None)
|
|
54
|
+
if result and result.get("blocked"):
|
|
55
|
+
return result
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _on_custom_command(command: str, name: str) -> Any:
|
|
60
|
+
"""Handle /policies and /policies reload commands."""
|
|
61
|
+
if name == "policies":
|
|
62
|
+
parts = command.split(maxsplit=1)
|
|
63
|
+
subcommand = parts[1].strip().lower() if len(parts) > 1 else ""
|
|
64
|
+
|
|
65
|
+
if subcommand == "reload":
|
|
66
|
+
clear_policy_cache()
|
|
67
|
+
rules = load_all_policies(force_reload=True)
|
|
68
|
+
emit_info(f"[Run] Policy rules reloaded: {len(rules)} active rule(s)")
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
# Default /policies — list loaded rules
|
|
72
|
+
rules = load_all_policies()
|
|
73
|
+
if not rules:
|
|
74
|
+
emit_info("[Warn] No policy rules loaded.")
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
lines: list[str] = ["[Run] Active rules:"]
|
|
78
|
+
seen_sources: set[str] = set()
|
|
79
|
+
for rule in rules:
|
|
80
|
+
prefix = f" [{rule.priority}] {rule.tool_name}"
|
|
81
|
+
if rule.command_prefix:
|
|
82
|
+
prefix += f" (prefix='{rule.command_prefix}')"
|
|
83
|
+
prefix += f" → {rule.decision.value}"
|
|
84
|
+
if rule.description:
|
|
85
|
+
prefix += f" — {rule.description}"
|
|
86
|
+
lines.append(prefix)
|
|
87
|
+
if rule.source:
|
|
88
|
+
seen_sources.add(rule.source)
|
|
89
|
+
|
|
90
|
+
if seen_sources:
|
|
91
|
+
lines.append("")
|
|
92
|
+
lines.append("Sources:")
|
|
93
|
+
for src in sorted(seen_sources):
|
|
94
|
+
lines.append(f" • {src}")
|
|
95
|
+
|
|
96
|
+
emit_info("\n".join(lines))
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _on_custom_command_help() -> list[tuple[str, str]]:
|
|
103
|
+
return [
|
|
104
|
+
("policies", "List loaded policy rules"),
|
|
105
|
+
("policies reload", "Reload policy rules from disk"),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
register_callback("run_shell_command", _on_run_shell_command, priority=50)
|
|
110
|
+
register_callback("pre_tool_call", _on_pre_tool_call, priority=50)
|
|
111
|
+
register_callback("custom_command", _on_custom_command)
|
|
112
|
+
register_callback("custom_command_help", _on_custom_command_help)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""/pop command plugin."""
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Plugin that adds /pop for trimming recent conversation history."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from code_muse.callbacks import register_callback
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def emit_error(message: Any) -> None:
|
|
9
|
+
from code_muse.messaging import emit_error as _emit_error
|
|
10
|
+
|
|
11
|
+
_emit_error(message)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def emit_info(message: Any) -> None:
|
|
15
|
+
from code_muse.messaging import emit_info as _emit_info
|
|
16
|
+
|
|
17
|
+
_emit_info(message)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def emit_success(message: Any) -> None:
|
|
21
|
+
from code_muse.messaging import emit_success as _emit_success
|
|
22
|
+
|
|
23
|
+
_emit_success(message)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def emit_warning(message: Any) -> None:
|
|
27
|
+
from code_muse.messaging import emit_warning as _emit_warning
|
|
28
|
+
|
|
29
|
+
_emit_warning(message)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _custom_help() -> list[tuple[str, str]]:
|
|
33
|
+
return [
|
|
34
|
+
(
|
|
35
|
+
"pop",
|
|
36
|
+
"Delete the N most-recent messages and prune broken tool-call fragments",
|
|
37
|
+
)
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _has_only_tool_returns(message: Any) -> bool:
|
|
42
|
+
"""Return True when a request message contains only tool-return parts."""
|
|
43
|
+
try:
|
|
44
|
+
from pydantic_ai.messages import ModelRequest, ToolReturnPart
|
|
45
|
+
|
|
46
|
+
if not isinstance(message, ModelRequest):
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
parts = getattr(message, "parts", []) or []
|
|
50
|
+
return bool(parts) and all(isinstance(part, ToolReturnPart) for part in parts)
|
|
51
|
+
except Exception:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _has_unresolved_tool_calls(message: Any) -> bool:
|
|
56
|
+
"""Return True when a response message still contains unresolved tool calls."""
|
|
57
|
+
try:
|
|
58
|
+
from pydantic_ai.messages import ModelResponse, ToolCallPart
|
|
59
|
+
|
|
60
|
+
if not isinstance(message, ModelResponse):
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
parts = getattr(message, "parts", []) or []
|
|
64
|
+
return any(isinstance(part, ToolCallPart) for part in parts)
|
|
65
|
+
except Exception:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _prune_dangling_tool_fragments(history: list[Any]) -> tuple[list[Any], int]:
|
|
70
|
+
"""Strip incomplete tool-call sequences from the tail of history."""
|
|
71
|
+
pruned = 0
|
|
72
|
+
|
|
73
|
+
while history:
|
|
74
|
+
tail = history[-1]
|
|
75
|
+
if _has_only_tool_returns(tail):
|
|
76
|
+
history.pop()
|
|
77
|
+
pruned += 1
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
if _has_unresolved_tool_calls(tail):
|
|
81
|
+
history.pop()
|
|
82
|
+
pruned += 1
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
return history, pruned
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _parse_pop_count(command: str) -> int | None:
|
|
91
|
+
tokens = command.split()
|
|
92
|
+
if len(tokens) < 2:
|
|
93
|
+
return 1
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
count = int(tokens[1])
|
|
97
|
+
except ValueError:
|
|
98
|
+
emit_error(f"/pop: '{tokens[1]}' is not a valid integer – usage: /pop [N]")
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
if count < 1:
|
|
102
|
+
emit_error("/pop: N must be a positive integer")
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
return count
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _handle_pop_command(command: str) -> bool:
|
|
109
|
+
from code_muse.agents.agent_manager import get_current_agent
|
|
110
|
+
|
|
111
|
+
count = _parse_pop_count(command)
|
|
112
|
+
if count is None:
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
agent = get_current_agent()
|
|
117
|
+
except Exception as exc:
|
|
118
|
+
emit_error(f"/pop: could not get current agent – {exc}")
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
history: list[Any] = list(agent.get_message_history())
|
|
122
|
+
if not history:
|
|
123
|
+
emit_warning("/pop: conversation history is empty – nothing to remove")
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
poppable = len(history) - 1
|
|
127
|
+
if poppable <= 0:
|
|
128
|
+
emit_warning("/pop: only the system prompt is in history – nothing to remove")
|
|
129
|
+
return True
|
|
130
|
+
|
|
131
|
+
if count > poppable:
|
|
132
|
+
emit_warning(
|
|
133
|
+
f"/pop: requested {count} but only {poppable} message(s) can be removed "
|
|
134
|
+
f"(the system prompt is always preserved). Removing {poppable}."
|
|
135
|
+
)
|
|
136
|
+
count = poppable
|
|
137
|
+
|
|
138
|
+
before_count = len(history)
|
|
139
|
+
history = history[: before_count - count]
|
|
140
|
+
history, extra_pruned = _prune_dangling_tool_fragments(history)
|
|
141
|
+
after_count = len(history)
|
|
142
|
+
total_removed = before_count - after_count
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
agent.set_message_history(history)
|
|
146
|
+
except Exception as exc:
|
|
147
|
+
emit_error(f"/pop: failed to update message history – {exc}")
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
summary_parts = [f":scissors: Popped {count} message(s)"]
|
|
151
|
+
if extra_pruned:
|
|
152
|
+
summary_parts.append(
|
|
153
|
+
f"and pruned {extra_pruned} extra incomplete tool-call fragment(s)"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
remaining = max(after_count - 1, 0)
|
|
157
|
+
emit_success(
|
|
158
|
+
" ".join(summary_parts)
|
|
159
|
+
+ ".\n"
|
|
160
|
+
+ f":scroll: History: {before_count - 1} → {remaining} message(s) "
|
|
161
|
+
+ f"(excluding system prompt, removed {total_removed} total)"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if after_count <= 1:
|
|
165
|
+
emit_info(":bulb: History is now empty (system prompt only). Starting fresh!")
|
|
166
|
+
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _handle_custom_command(command: str, name: str) -> bool | None:
|
|
171
|
+
if name != "pop":
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
return _handle_pop_command(command)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
register_callback("custom_command_help", _custom_help)
|
|
178
|
+
register_callback("custom_command", _handle_custom_command)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
__all__ = [
|
|
182
|
+
"_custom_help",
|
|
183
|
+
"_handle_custom_command",
|
|
184
|
+
"_handle_pop_command",
|
|
185
|
+
"_parse_pop_count",
|
|
186
|
+
"_prune_dangling_tool_fragments",
|
|
187
|
+
"_has_only_tool_returns",
|
|
188
|
+
"_has_unresolved_tool_calls",
|
|
189
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Plugin: drop user input onto a fresh line below the prompt chrome.
|
|
2
|
+
|
|
3
|
+
When enabled, transforms
|
|
4
|
+
|
|
5
|
+
[Run] prompt [agent] [model] (~/very/long/cwd) >>> typed text
|
|
6
|
+
|
|
7
|
+
into
|
|
8
|
+
|
|
9
|
+
[Run] prompt [agent] [model] (~/very/long/cwd) >>>
|
|
10
|
+
typed text
|
|
11
|
+
|
|
12
|
+
Toggle at runtime with ``/prompt_newline [on|off]``. Persisted in muse.cfg.
|
|
13
|
+
"""
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Plugin-local config for the prompt_newline plugin."""
|
|
2
|
+
|
|
3
|
+
from code_muse.config import get_value, set_config_value
|
|
4
|
+
|
|
5
|
+
_CONFIG_KEY = "prompt_newline"
|
|
6
|
+
_TRUTHY = ("true", "1", "yes", "on")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def is_enabled() -> bool:
|
|
10
|
+
"""Return True if the prompt_newline hack is enabled. Default: False."""
|
|
11
|
+
cfg_val = get_value(_CONFIG_KEY)
|
|
12
|
+
if cfg_val is None:
|
|
13
|
+
return False
|
|
14
|
+
return str(cfg_val).strip().lower() in _TRUTHY
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def set_enabled(enabled: bool) -> None:
|
|
18
|
+
"""Persist the on/off switch to muse.cfg."""
|
|
19
|
+
set_config_value(_CONFIG_KEY, "true" if enabled else "false")
|