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,144 @@
|
|
|
1
|
+
"""Callback registration for the force push guard plugin.
|
|
2
|
+
|
|
3
|
+
Hooks into the run_shell_command phase to intercept git force push
|
|
4
|
+
commands and prompt the user for approval before allowing them through.
|
|
5
|
+
Returns {"blocked": True} to deny, None to allow.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
|
|
13
|
+
from code_muse.callbacks import register_callback
|
|
14
|
+
from code_muse.messaging import emit_info, emit_warning
|
|
15
|
+
from code_muse.plugins.force_push_guard.detector import detect_force_push
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _is_interactive() -> bool:
|
|
19
|
+
"""Check if we're in an interactive terminal that can show prompts."""
|
|
20
|
+
try:
|
|
21
|
+
return sys.stdin.isatty()
|
|
22
|
+
except AttributeError, OSError:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def force_push_guard_callback(
|
|
27
|
+
context: Any, command: str, cwd: str | None = None, timeout: int = 60
|
|
28
|
+
) -> dict[str, Any | None]:
|
|
29
|
+
"""Intercept shell commands containing git force push operations.
|
|
30
|
+
|
|
31
|
+
When a force push is detected:
|
|
32
|
+
- Interactive TTY: prompt the user with approve/reject options.
|
|
33
|
+
- Non-interactive (CI, sub-agent, piped): hard-block with an error.
|
|
34
|
+
|
|
35
|
+
This runs on *every* shell command, but the heavy lifting (regex
|
|
36
|
+
matching) is gated behind a cheap "push" substring check inside
|
|
37
|
+
detect_force_push().
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
context: Execution context (unused).
|
|
41
|
+
command: The shell command about to run.
|
|
42
|
+
cwd: Working directory (unused).
|
|
43
|
+
timeout: Command timeout (unused).
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
None if the command is safe to proceed or user approved it.
|
|
47
|
+
Dict with blocked=True if a force push was detected and rejected.
|
|
48
|
+
"""
|
|
49
|
+
match = detect_force_push(command)
|
|
50
|
+
if match is None:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
# --- Interactive TTY: ask the user ---
|
|
54
|
+
if _is_interactive():
|
|
55
|
+
return await _prompt_user_approval(command, match)
|
|
56
|
+
|
|
57
|
+
# --- Non-interactive: hard-block ---
|
|
58
|
+
return _block_command(command, match)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def _prompt_user_approval(command: str, match: Any) -> dict[str, Any | None]:
|
|
62
|
+
"""Show an interactive approval prompt for the detected force push.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
command: The original shell command.
|
|
66
|
+
match: The ForcePushMatch from the detector.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
None if user approves, Dict with blocked=True if rejected.
|
|
70
|
+
"""
|
|
71
|
+
from code_muse.tools.common import get_user_approval_async
|
|
72
|
+
|
|
73
|
+
panel_content = Text()
|
|
74
|
+
panel_content.append("⚠️ Force push detected: ", style="bold yellow")
|
|
75
|
+
panel_content.append(match.pattern_name, style="bold red")
|
|
76
|
+
panel_content.append("\n", style="")
|
|
77
|
+
panel_content.append(f" {match.description}", style="dim")
|
|
78
|
+
panel_content.append("\n\n", style="")
|
|
79
|
+
panel_content.append("$ ", style="bold green")
|
|
80
|
+
panel_content.append(command, style="bold white")
|
|
81
|
+
panel_content.append(
|
|
82
|
+
"\n\nForce pushing rewrites remote history and can destroy others' work.",
|
|
83
|
+
style="yellow",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
confirmed, user_feedback = await get_user_approval_async(
|
|
87
|
+
title="Force Push Guard 🛡️",
|
|
88
|
+
content=panel_content,
|
|
89
|
+
border_style="red",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if confirmed:
|
|
93
|
+
emit_info("⚠️ Force push approved — proceeding with caution.")
|
|
94
|
+
return None # Allow the command through
|
|
95
|
+
|
|
96
|
+
# Rejected
|
|
97
|
+
reason = user_feedback or "User rejected force push"
|
|
98
|
+
return {
|
|
99
|
+
"blocked": True,
|
|
100
|
+
"reasoning": f"Force push rejected: {match.pattern_name} — {reason}",
|
|
101
|
+
"error_message": (
|
|
102
|
+
f"🛑 Force push rejected. Detected {match.pattern_name} "
|
|
103
|
+
f"in command:\n {command}\n"
|
|
104
|
+
f" {match.description}\n"
|
|
105
|
+
f"Feedback: {reason}"
|
|
106
|
+
),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _block_command(command: str, match: Any) -> dict[str, Any]:
|
|
111
|
+
"""Hard-block a force push in non-interactive contexts.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
command: The original shell command.
|
|
115
|
+
match: The ForcePushMatch from the detector.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Dict with blocked=True and a descriptive error.
|
|
119
|
+
"""
|
|
120
|
+
error_message = (
|
|
121
|
+
f"🛑 Force push blocked! Detected {match.pattern_name} "
|
|
122
|
+
f"in command:\n {command}\n"
|
|
123
|
+
f" {match.description}\n\n"
|
|
124
|
+
f"Force pushing rewrites remote history and can destroy others' work.\n"
|
|
125
|
+
f"If you *really* need to force push, use the exact command directly\n"
|
|
126
|
+
f"in your terminal (outside Muse) after double-checking the target branch."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
emit_warning(error_message)
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
"blocked": True,
|
|
133
|
+
"reasoning": f"Force push detected: {match.pattern_name} — {match.description}",
|
|
134
|
+
"error_message": error_message,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def register() -> None:
|
|
139
|
+
"""Register the force push guard callback."""
|
|
140
|
+
register_callback("run_shell_command", force_push_guard_callback)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# Auto-register when this module is imported
|
|
144
|
+
register()
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Tests for the force push guard detector."""
|
|
2
|
+
|
|
3
|
+
from code_muse.plugins.force_push_guard.detector import detect_force_push
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestDetectForcePush:
|
|
7
|
+
"""Test suite for force push pattern detection."""
|
|
8
|
+
|
|
9
|
+
# --- Should BLOCK these commands ---
|
|
10
|
+
|
|
11
|
+
def test_long_force_flag(self):
|
|
12
|
+
result = detect_force_push("git push --force origin main")
|
|
13
|
+
assert result is not None
|
|
14
|
+
assert result.pattern_name == "--force"
|
|
15
|
+
|
|
16
|
+
def test_short_f_flag(self):
|
|
17
|
+
result = detect_force_push("git push -f origin main")
|
|
18
|
+
assert result is not None
|
|
19
|
+
assert result.pattern_name == "-f"
|
|
20
|
+
|
|
21
|
+
def test_capital_f_flag(self):
|
|
22
|
+
result = detect_force_push("git push -F origin main")
|
|
23
|
+
assert result is not None
|
|
24
|
+
assert result.pattern_name == "-F"
|
|
25
|
+
|
|
26
|
+
def test_force_with_lease(self):
|
|
27
|
+
result = detect_force_push("git push --force-with-lease origin feature")
|
|
28
|
+
assert result is not None
|
|
29
|
+
assert result.pattern_name == "--force-with-lease"
|
|
30
|
+
|
|
31
|
+
def test_force_if_includes(self):
|
|
32
|
+
result = detect_force_push("git push --force-if-includes origin feature")
|
|
33
|
+
assert result is not None
|
|
34
|
+
assert result.pattern_name == "--force-if-includes"
|
|
35
|
+
|
|
36
|
+
def test_plus_refspec(self):
|
|
37
|
+
result = detect_force_push("git push origin +main")
|
|
38
|
+
assert result is not None
|
|
39
|
+
assert result.pattern_name == "+refspec"
|
|
40
|
+
|
|
41
|
+
def test_plus_refspec_head(self):
|
|
42
|
+
result = detect_force_push("git push origin +HEAD:refs/heads/main")
|
|
43
|
+
assert result is not None
|
|
44
|
+
assert result.pattern_name == "+refspec"
|
|
45
|
+
|
|
46
|
+
def test_force_before_remote(self):
|
|
47
|
+
result = detect_force_push(
|
|
48
|
+
"git push --force-with-lease --set-upstream origin foo"
|
|
49
|
+
)
|
|
50
|
+
assert result is not None
|
|
51
|
+
assert result.pattern_name == "--force-with-lease"
|
|
52
|
+
|
|
53
|
+
def test_force_after_remote(self):
|
|
54
|
+
result = detect_force_push("git push origin feature --force")
|
|
55
|
+
assert result is not None
|
|
56
|
+
assert result.pattern_name == "--force"
|
|
57
|
+
|
|
58
|
+
def test_force_flag_with_equals(self):
|
|
59
|
+
result = detect_force_push("git push --force=yes origin main")
|
|
60
|
+
assert result is not None
|
|
61
|
+
assert result.pattern_name == "--force"
|
|
62
|
+
|
|
63
|
+
def test_force_with_other_flags(self):
|
|
64
|
+
result = detect_force_push("git push -v -f origin main")
|
|
65
|
+
assert result is not None
|
|
66
|
+
assert result.pattern_name == "-f"
|
|
67
|
+
|
|
68
|
+
# --- Should ALLOW these commands ---
|
|
69
|
+
|
|
70
|
+
def test_normal_push(self):
|
|
71
|
+
assert detect_force_push("git push origin main") is None
|
|
72
|
+
|
|
73
|
+
def test_push_with_set_upstream(self):
|
|
74
|
+
assert detect_force_push("git push --set-upstream origin feature") is None
|
|
75
|
+
|
|
76
|
+
def test_push_with_tags(self):
|
|
77
|
+
assert detect_force_push("git push origin --tags") is None
|
|
78
|
+
|
|
79
|
+
def test_push_u(self):
|
|
80
|
+
assert detect_force_push("git push -u origin main") is None
|
|
81
|
+
|
|
82
|
+
def test_git_pull(self):
|
|
83
|
+
assert detect_force_push("git pull origin main") is None
|
|
84
|
+
|
|
85
|
+
def test_git_status(self):
|
|
86
|
+
assert detect_force_push("git status") is None
|
|
87
|
+
|
|
88
|
+
def test_unrelated_command(self):
|
|
89
|
+
assert detect_force_push("npm install --force") is None
|
|
90
|
+
|
|
91
|
+
def test_empty_string(self):
|
|
92
|
+
assert detect_force_push("") is None
|
|
93
|
+
|
|
94
|
+
def test_echo_push(self):
|
|
95
|
+
assert detect_force_push("echo 'git push --force'") is None
|
|
96
|
+
|
|
97
|
+
def test_push_dry_run(self):
|
|
98
|
+
assert detect_force_push("git push --dry-run origin main") is None
|
|
99
|
+
|
|
100
|
+
def test_git_push_all(self):
|
|
101
|
+
assert detect_force_push("git push --all origin") is None
|
|
102
|
+
|
|
103
|
+
def test_git_push_mirror(self):
|
|
104
|
+
"""--mirror IS destructive, but it's not a 'force push' per se.
|
|
105
|
+
We don't block it — different safety concern."""
|
|
106
|
+
assert detect_force_push("git push --mirror") is None
|
|
107
|
+
|
|
108
|
+
def test_push_no_force_file(self):
|
|
109
|
+
"""A file named '--force' in a weirdly structured command shouldn't match."""
|
|
110
|
+
# This is a contrived edge case — the regex should not match
|
|
111
|
+
assert detect_force_push("git push origin main") is None
|
|
112
|
+
|
|
113
|
+
def test_grep_push(self):
|
|
114
|
+
"""grep containing 'push' should not trigger."""
|
|
115
|
+
assert detect_force_push("grep -r push src/") is None
|
|
116
|
+
|
|
117
|
+
# --- Compound commands (shell operators) ---
|
|
118
|
+
|
|
119
|
+
def test_compound_and_force(self):
|
|
120
|
+
result = detect_force_push("cd foo && git push --force origin main")
|
|
121
|
+
assert result is not None
|
|
122
|
+
assert result.pattern_name == "--force"
|
|
123
|
+
|
|
124
|
+
def test_compound_semicolon_force(self):
|
|
125
|
+
result = detect_force_push("echo hi; git push -f origin main")
|
|
126
|
+
assert result is not None
|
|
127
|
+
assert result.pattern_name == "-f"
|
|
128
|
+
|
|
129
|
+
def test_compound_or_force(self):
|
|
130
|
+
result = detect_force_push("git pull || git push --force origin main")
|
|
131
|
+
assert result is not None
|
|
132
|
+
assert result.pattern_name == "--force"
|
|
133
|
+
|
|
134
|
+
def test_compound_pipe_not_force(self):
|
|
135
|
+
"""Piped git push (uncommon) should still be caught if forced."""
|
|
136
|
+
result = detect_force_push("cat file | git push --force")
|
|
137
|
+
# Note: piping to git push makes no sense, but regex should still match
|
|
138
|
+
assert result is not None
|
|
139
|
+
assert result.pattern_name == "--force"
|
|
140
|
+
|
|
141
|
+
def test_compound_and_normal_push(self):
|
|
142
|
+
"""Compound with a normal push should be allowed."""
|
|
143
|
+
assert detect_force_push("cd foo && git push origin main") is None
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Frontend emitter plugin for Muse.
|
|
2
|
+
|
|
3
|
+
This plugin provides event emission capabilities for frontend integration,
|
|
4
|
+
allowing WebSocket handlers to subscribe to real-time events from the
|
|
5
|
+
agent system including tool calls, streaming events, and agent invocations.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from code_muse.plugins.frontend_emitter.emitter import (
|
|
9
|
+
emit_event,
|
|
10
|
+
subscribe,
|
|
11
|
+
unsubscribe,
|
|
12
|
+
get_recent_events,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Subscribe to events
|
|
16
|
+
queue = subscribe()
|
|
17
|
+
|
|
18
|
+
# Process events in your WebSocket handler
|
|
19
|
+
while True:
|
|
20
|
+
event = await queue.get()
|
|
21
|
+
await websocket.send_json(event)
|
|
22
|
+
|
|
23
|
+
# Clean up
|
|
24
|
+
unsubscribe(queue)
|
|
25
|
+
"""
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Event emitter for frontend integration.
|
|
2
|
+
|
|
3
|
+
Provides a global event queue that WebSocket handlers can subscribe to.
|
|
4
|
+
Events are JSON-serializable dicts with type, timestamp, and data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import UTC, datetime
|
|
10
|
+
from typing import Any
|
|
11
|
+
from uuid import uuid4
|
|
12
|
+
|
|
13
|
+
from code_muse.config import (
|
|
14
|
+
get_frontend_emitter_enabled,
|
|
15
|
+
get_frontend_emitter_max_recent_events,
|
|
16
|
+
get_frontend_emitter_queue_size,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# Global state for event distribution
|
|
22
|
+
_subscribers: set[asyncio.Queue[dict[str, Any]]] = set()
|
|
23
|
+
_recent_events: list[dict[str, Any]] = [] # Keep last N events for new subscribers
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def emit_event(event_type: str, data: Any = None) -> None:
|
|
27
|
+
"""Emit an event to all subscribers.
|
|
28
|
+
|
|
29
|
+
Creates a structured event dict with unique ID, type, timestamp, and data,
|
|
30
|
+
then broadcasts it to all active subscriber queues.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
event_type: Type of event (e.g., "tool_call_start", "stream_token")
|
|
34
|
+
data: Event data payload - should be JSON-serializable
|
|
35
|
+
"""
|
|
36
|
+
# Early return if emitter is disabled
|
|
37
|
+
if not get_frontend_emitter_enabled():
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
event: dict[str, Any] = {
|
|
41
|
+
"id": str(uuid4()),
|
|
42
|
+
"type": event_type,
|
|
43
|
+
"timestamp": datetime.now(UTC).isoformat(),
|
|
44
|
+
"data": data or {},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Store in recent events for replay to new subscribers
|
|
48
|
+
max_recent = get_frontend_emitter_max_recent_events()
|
|
49
|
+
_recent_events.append(event)
|
|
50
|
+
if len(_recent_events) > max_recent:
|
|
51
|
+
_recent_events.pop(0)
|
|
52
|
+
|
|
53
|
+
# Broadcast to all active subscribers
|
|
54
|
+
for subscriber_queue in _subscribers.copy():
|
|
55
|
+
try:
|
|
56
|
+
subscriber_queue.put_nowait(event)
|
|
57
|
+
except asyncio.QueueFull:
|
|
58
|
+
logger.warning(f"Subscriber queue full, dropping event: {event_type}")
|
|
59
|
+
except Exception as e:
|
|
60
|
+
logger.error(f"Failed to emit event to subscriber: {e}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def subscribe() -> asyncio.Queue[dict[str, Any]]:
|
|
64
|
+
"""Subscribe to events.
|
|
65
|
+
|
|
66
|
+
Creates and returns a new async queue that will receive all future events.
|
|
67
|
+
The queue has a configurable max size (via frontend_emitter_queue_size)
|
|
68
|
+
to prevent unbounded memory growth if the subscriber is slow to process events.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
An asyncio.Queue that will receive event dictionaries.
|
|
72
|
+
"""
|
|
73
|
+
queue_size = get_frontend_emitter_queue_size()
|
|
74
|
+
queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue(maxsize=queue_size)
|
|
75
|
+
_subscribers.add(queue)
|
|
76
|
+
logger.debug(f"New subscriber added, total subscribers: {len(_subscribers)}")
|
|
77
|
+
return queue
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def unsubscribe(queue: asyncio.Queue[dict[str, Any]]) -> None:
|
|
81
|
+
"""Unsubscribe from events.
|
|
82
|
+
|
|
83
|
+
Removes the queue from the subscriber set. Safe to call even if the queue
|
|
84
|
+
was never subscribed or already unsubscribed.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
queue: The queue returned from subscribe()
|
|
88
|
+
"""
|
|
89
|
+
_subscribers.discard(queue)
|
|
90
|
+
logger.debug(f"Subscriber removed, remaining subscribers: {len(_subscribers)}")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_recent_events() -> list[dict[str, Any]]:
|
|
94
|
+
"""Get recent events for new subscribers.
|
|
95
|
+
|
|
96
|
+
Returns a copy of the most recent events (up to frontend_emitter_max_recent_events).
|
|
97
|
+
Useful for allowing new WebSocket connections to "catch up" on
|
|
98
|
+
recent activity.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
A list of recent event dictionaries.
|
|
102
|
+
"""
|
|
103
|
+
return _recent_events.copy()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_subscriber_count() -> int:
|
|
107
|
+
"""Get the current number of active subscribers.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Number of active subscriber queues.
|
|
111
|
+
"""
|
|
112
|
+
return len(_subscribers)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def clear_recent_events() -> None:
|
|
116
|
+
"""Clear the recent events buffer.
|
|
117
|
+
|
|
118
|
+
Useful for testing or resetting state.
|
|
119
|
+
"""
|
|
120
|
+
_recent_events.clear()
|
|
121
|
+
logger.debug("Recent events cleared")
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Callback registration for frontend event emission.
|
|
2
|
+
|
|
3
|
+
This module registers callbacks for various agent events and emits them
|
|
4
|
+
to subscribed WebSocket handlers via the emitter module.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from code_muse.callbacks import register_callback
|
|
12
|
+
from code_muse.plugins.frontend_emitter.emitter import emit_event
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def on_pre_tool_call(
|
|
18
|
+
tool_name: str, tool_args: dict[str, Any], context: Any = None
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Emit an event when a tool call starts.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
tool_name: Name of the tool being called
|
|
24
|
+
tool_args: Arguments being passed to the tool
|
|
25
|
+
context: Optional context data for the tool call
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
emit_event(
|
|
29
|
+
"tool_call_start",
|
|
30
|
+
{
|
|
31
|
+
"tool_name": tool_name,
|
|
32
|
+
"tool_args": _sanitize_args(tool_args),
|
|
33
|
+
"start_time": time.time(),
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
logger.debug(f"Emitted tool_call_start for {tool_name}")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.error(f"Failed to emit pre_tool_call event: {e}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def on_post_tool_call(
|
|
42
|
+
tool_name: str,
|
|
43
|
+
tool_args: dict[str, Any],
|
|
44
|
+
result: Any,
|
|
45
|
+
duration_ms: float,
|
|
46
|
+
context: Any = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Emit an event when a tool call completes.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
tool_name: Name of the tool that was called
|
|
52
|
+
tool_args: Arguments that were passed to the tool
|
|
53
|
+
result: The result returned by the tool
|
|
54
|
+
duration_ms: Execution time in milliseconds
|
|
55
|
+
context: Optional context data for the tool call
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
emit_event(
|
|
59
|
+
"tool_call_complete",
|
|
60
|
+
{
|
|
61
|
+
"tool_name": tool_name,
|
|
62
|
+
"tool_args": _sanitize_args(tool_args),
|
|
63
|
+
"duration_ms": duration_ms,
|
|
64
|
+
"success": _is_successful_result(result),
|
|
65
|
+
"result_summary": _summarize_result(result),
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
logger.debug(
|
|
69
|
+
f"Emitted tool_call_complete for {tool_name} ({duration_ms:.2f}ms)"
|
|
70
|
+
)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Failed to emit post_tool_call event: {e}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def on_stream_event(
|
|
76
|
+
event_type: str, event_data: Any, agent_session_id: str | None = None
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Emit streaming events from the agent.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
event_type: Type of the streaming event
|
|
82
|
+
event_data: Data associated with the event
|
|
83
|
+
agent_session_id: Optional session ID of the agent emitting the event
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
emit_event(
|
|
87
|
+
"stream_event",
|
|
88
|
+
{
|
|
89
|
+
"event_type": event_type,
|
|
90
|
+
"event_data": _sanitize_event_data(event_data),
|
|
91
|
+
"agent_session_id": agent_session_id,
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
logger.debug(f"Emitted stream_event: {event_type}")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"Failed to emit stream_event: {e}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def on_invoke_agent(*args: Any, **kwargs: Any) -> None:
|
|
100
|
+
"""Emit an event when an agent is invoked.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
*args: Positional arguments from the invoke_agent callback
|
|
104
|
+
**kwargs: Keyword arguments from the invoke_agent callback
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
# Extract relevant info from args/kwargs
|
|
108
|
+
agent_info = {
|
|
109
|
+
"agent_name": kwargs.get("agent_name") or (args[0] if args else None),
|
|
110
|
+
"session_id": kwargs.get("session_id"),
|
|
111
|
+
"prompt_preview": _truncate_string(
|
|
112
|
+
kwargs.get("prompt") or (args[1] if len(args) > 1 else None),
|
|
113
|
+
max_length=200,
|
|
114
|
+
),
|
|
115
|
+
}
|
|
116
|
+
emit_event("agent_invoked", agent_info)
|
|
117
|
+
logger.debug(f"Emitted agent_invoked: {agent_info.get('agent_name')}")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.error(f"Failed to emit invoke_agent event: {e}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _sanitize_args(args: dict[str, Any]) -> dict[str, Any]:
|
|
123
|
+
"""Sanitize tool arguments for safe emission.
|
|
124
|
+
|
|
125
|
+
Truncates large values and removes potentially sensitive data.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
args: The raw tool arguments
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Sanitized arguments safe for emission
|
|
132
|
+
"""
|
|
133
|
+
if not isinstance(args, dict):
|
|
134
|
+
return {}
|
|
135
|
+
|
|
136
|
+
sanitized: dict[str, Any] = {}
|
|
137
|
+
for key, value in args.items():
|
|
138
|
+
if isinstance(value, str):
|
|
139
|
+
sanitized[key] = _truncate_string(value, max_length=500)
|
|
140
|
+
elif isinstance(value, (int, float, bool, type(None))):
|
|
141
|
+
sanitized[key] = value
|
|
142
|
+
elif isinstance(value, (list, dict)):
|
|
143
|
+
# Just indicate the type and length for complex types
|
|
144
|
+
sanitized[key] = f"<{type(value).__name__}[{len(value)}]>"
|
|
145
|
+
else:
|
|
146
|
+
sanitized[key] = f"<{type(value).__name__}>"
|
|
147
|
+
|
|
148
|
+
return sanitized
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _sanitize_event_data(data: Any) -> Any:
|
|
152
|
+
"""Sanitize event data for safe emission.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
data: The raw event data
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Sanitized data safe for emission
|
|
159
|
+
"""
|
|
160
|
+
if data is None:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
if isinstance(data, str):
|
|
164
|
+
return _truncate_string(data, max_length=1000)
|
|
165
|
+
|
|
166
|
+
if isinstance(data, (int, float, bool)):
|
|
167
|
+
return data
|
|
168
|
+
|
|
169
|
+
if isinstance(data, dict):
|
|
170
|
+
return {k: _sanitize_event_data(v) for k, v in list(data.items())[:20]}
|
|
171
|
+
|
|
172
|
+
if isinstance(data, (list, tuple)):
|
|
173
|
+
return [_sanitize_event_data(item) for item in data[:20]]
|
|
174
|
+
|
|
175
|
+
return f"<{type(data).__name__}>"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _is_successful_result(result: Any) -> bool:
|
|
179
|
+
"""Determine if a tool result indicates success.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
result: The tool result
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
True if the result appears successful
|
|
186
|
+
"""
|
|
187
|
+
if result is None:
|
|
188
|
+
return True # No result often means success
|
|
189
|
+
|
|
190
|
+
if isinstance(result, dict):
|
|
191
|
+
# Check for error indicators
|
|
192
|
+
if result.get("error"):
|
|
193
|
+
return False
|
|
194
|
+
return result.get("success") is not False
|
|
195
|
+
|
|
196
|
+
if isinstance(result, bool):
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
return True # Default to success
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _summarize_result(result: Any) -> str:
|
|
203
|
+
"""Create a brief summary of a tool result.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
result: The tool result
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
A string summary of the result
|
|
210
|
+
"""
|
|
211
|
+
if result is None:
|
|
212
|
+
return "<no result>"
|
|
213
|
+
|
|
214
|
+
if isinstance(result, str):
|
|
215
|
+
return _truncate_string(result, max_length=200)
|
|
216
|
+
|
|
217
|
+
if isinstance(result, dict):
|
|
218
|
+
if "error" in result:
|
|
219
|
+
return f"Error: {_truncate_string(str(result['error']), max_length=100)}"
|
|
220
|
+
if "message" in result:
|
|
221
|
+
return _truncate_string(str(result["message"]), max_length=100)
|
|
222
|
+
return f"<dict with {len(result)} keys>"
|
|
223
|
+
|
|
224
|
+
if isinstance(result, (list, tuple)):
|
|
225
|
+
return f"<{type(result).__name__}[{len(result)}]>"
|
|
226
|
+
|
|
227
|
+
return _truncate_string(str(result), max_length=200)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _truncate_string(value: Any, max_length: int = 100) -> str | None:
|
|
231
|
+
"""Truncate a string value if it exceeds max_length.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
value: The value to truncate (will be converted to str)
|
|
235
|
+
max_length: Maximum length before truncation
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Truncated string or None if value is None
|
|
239
|
+
"""
|
|
240
|
+
if value is None:
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
s = str(value)
|
|
244
|
+
if len(s) > max_length:
|
|
245
|
+
return s[: max_length - 3] + "..."
|
|
246
|
+
return s
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def register() -> None:
|
|
250
|
+
"""Register all frontend emitter callbacks."""
|
|
251
|
+
register_callback("pre_tool_call", on_pre_tool_call)
|
|
252
|
+
register_callback("post_tool_call", on_post_tool_call)
|
|
253
|
+
register_callback("stream_event", on_stream_event)
|
|
254
|
+
register_callback("invoke_agent", on_invoke_agent)
|
|
255
|
+
logger.debug("Frontend emitter callbacks registered")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# Auto-register callbacks when this module is imported
|
|
259
|
+
register()
|