auto-coder 1.0.0__py3-none-any.whl → 2.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.
Potentially problematic release.
This version of auto-coder might be problematic. Click here for more details.
- auto_coder-2.0.1.dist-info/LICENSE +158 -0
- auto_coder-2.0.1.dist-info/METADATA +558 -0
- auto_coder-2.0.1.dist-info/RECORD +795 -0
- {auto_coder-1.0.0.dist-info → auto_coder-2.0.1.dist-info}/WHEEL +1 -1
- {auto_coder-1.0.0.dist-info → auto_coder-2.0.1.dist-info}/entry_points.txt +3 -3
- autocoder/__init__.py +31 -0
- autocoder/agent/auto_filegroup.py +32 -13
- autocoder/agent/auto_learn_from_commit.py +9 -1
- autocoder/agent/base_agentic/__init__.py +3 -0
- autocoder/agent/base_agentic/agent_hub.py +1 -1
- autocoder/agent/base_agentic/base_agent.py +235 -136
- autocoder/agent/base_agentic/default_tools.py +119 -118
- autocoder/agent/base_agentic/test_base_agent.py +1 -1
- autocoder/agent/base_agentic/tool_registry.py +32 -20
- autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +24 -3
- autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +24 -11
- autocoder/agent/base_agentic/types.py +42 -0
- autocoder/agent/entry_command_agent/chat.py +77 -73
- autocoder/auto_coder.py +31 -40
- autocoder/auto_coder_rag.py +11 -1084
- autocoder/auto_coder_runner.py +962 -2345
- autocoder/auto_coder_terminal.py +26 -0
- autocoder/auto_coder_terminal_v3.py +190 -0
- autocoder/chat/conf_command.py +224 -124
- autocoder/chat/models_command.py +361 -299
- autocoder/chat/rules_command.py +79 -31
- autocoder/chat_auto_coder.py +988 -398
- autocoder/chat_auto_coder_lang.py +23 -732
- autocoder/commands/auto_command.py +25 -8
- autocoder/commands/auto_web.py +1 -1
- autocoder/commands/tools.py +44 -44
- autocoder/common/__init__.py +150 -128
- autocoder/common/ac_style_command_parser/__init__.py +39 -2
- autocoder/common/ac_style_command_parser/config.py +422 -0
- autocoder/common/ac_style_command_parser/parser.py +292 -78
- autocoder/common/ac_style_command_parser/test_parser.py +241 -16
- autocoder/common/ac_style_command_parser/test_typed_parser.py +342 -0
- autocoder/common/ac_style_command_parser/typed_parser.py +653 -0
- autocoder/common/action_yml_file_manager.py +25 -13
- autocoder/common/agent_events/__init__.py +52 -0
- autocoder/common/agent_events/agent_event_emitter.py +193 -0
- autocoder/common/agent_events/event_factory.py +177 -0
- autocoder/common/agent_events/examples.py +307 -0
- autocoder/common/agent_events/types.py +113 -0
- autocoder/common/agent_events/utils.py +68 -0
- autocoder/common/agent_hooks/__init__.py +44 -0
- autocoder/common/agent_hooks/examples.py +582 -0
- autocoder/common/agent_hooks/hook_executor.py +217 -0
- autocoder/common/agent_hooks/hook_manager.py +288 -0
- autocoder/common/agent_hooks/types.py +133 -0
- autocoder/common/agent_hooks/utils.py +99 -0
- autocoder/common/agent_query_queue/queue_executor.py +324 -0
- autocoder/common/agent_query_queue/queue_manager.py +325 -0
- autocoder/common/agents/__init__.py +11 -0
- autocoder/common/agents/agent_manager.py +323 -0
- autocoder/common/agents/agent_parser.py +189 -0
- autocoder/common/agents/example_usage.py +344 -0
- autocoder/common/agents/integration_example.py +330 -0
- autocoder/common/agents/test_agent_parser.py +545 -0
- autocoder/common/async_utils.py +101 -0
- autocoder/common/auto_coder_lang.py +23 -972
- autocoder/common/autocoderargs_parser/__init__.py +14 -0
- autocoder/common/autocoderargs_parser/parser.py +184 -0
- autocoder/common/autocoderargs_parser/tests/__init__.py +1 -0
- autocoder/common/autocoderargs_parser/tests/test_args_parser.py +235 -0
- autocoder/common/autocoderargs_parser/tests/test_token_parser.py +195 -0
- autocoder/common/autocoderargs_parser/token_parser.py +290 -0
- autocoder/common/buildin_tokenizer.py +2 -4
- autocoder/common/code_auto_generate.py +149 -74
- autocoder/common/code_auto_generate_diff.py +163 -70
- autocoder/common/code_auto_generate_editblock.py +179 -89
- autocoder/common/code_auto_generate_strict_diff.py +167 -72
- autocoder/common/code_auto_merge_editblock.py +13 -6
- autocoder/common/code_modification_ranker.py +1 -1
- autocoder/common/command_completer.py +3 -3
- autocoder/common/command_file_manager/manager.py +183 -47
- autocoder/common/command_file_manager/test_command_file_manager.py +507 -0
- autocoder/common/command_templates.py +1 -1
- autocoder/common/conf_utils.py +2 -4
- autocoder/common/conversations/config.py +11 -3
- autocoder/common/conversations/get_conversation_manager.py +100 -2
- autocoder/common/conversations/llm_stats_models.py +264 -0
- autocoder/common/conversations/manager.py +112 -28
- autocoder/common/conversations/models.py +16 -2
- autocoder/common/conversations/storage/index_manager.py +134 -10
- autocoder/common/core_config/__init__.py +63 -0
- autocoder/common/core_config/agentic_mode_manager.py +109 -0
- autocoder/common/core_config/base_manager.py +123 -0
- autocoder/common/core_config/compatibility.py +151 -0
- autocoder/common/core_config/config_manager.py +156 -0
- autocoder/common/core_config/conversation_manager.py +31 -0
- autocoder/common/core_config/exclude_manager.py +72 -0
- autocoder/common/core_config/file_manager.py +177 -0
- autocoder/common/core_config/human_as_model_manager.py +129 -0
- autocoder/common/core_config/lib_manager.py +54 -0
- autocoder/common/core_config/main_manager.py +81 -0
- autocoder/common/core_config/mode_manager.py +126 -0
- autocoder/common/core_config/models.py +70 -0
- autocoder/common/core_config/test_memory_manager.py +1056 -0
- autocoder/common/env_manager.py +282 -0
- autocoder/common/env_manager_usage_example.py +211 -0
- autocoder/common/file_checkpoint/conversation_checkpoint.py +19 -19
- autocoder/common/file_checkpoint/manager.py +264 -48
- autocoder/common/file_checkpoint/test_backup.py +1 -18
- autocoder/common/file_checkpoint/test_manager.py +270 -1
- autocoder/common/file_checkpoint/test_store.py +1 -17
- autocoder/common/file_handler/__init__.py +23 -0
- autocoder/common/file_handler/active_context_handler.py +159 -0
- autocoder/common/file_handler/add_files_handler.py +409 -0
- autocoder/common/file_handler/chat_handler.py +180 -0
- autocoder/common/file_handler/coding_handler.py +409 -0
- autocoder/common/file_handler/commit_handler.py +200 -0
- autocoder/common/file_handler/lib_handler.py +156 -0
- autocoder/common/file_handler/list_files_handler.py +111 -0
- autocoder/common/file_handler/mcp_handler.py +268 -0
- autocoder/common/file_handler/models_handler.py +493 -0
- autocoder/common/file_handler/remove_files_handler.py +172 -0
- autocoder/common/git_utils.py +44 -8
- autocoder/common/global_cancel.py +15 -6
- autocoder/common/ignorefiles/test_ignore_file_utils.py +1 -1
- autocoder/common/international/__init__.py +31 -0
- autocoder/common/international/demo_international.py +92 -0
- autocoder/common/international/message_manager.py +157 -0
- autocoder/common/international/messages/__init__.py +56 -0
- autocoder/common/international/messages/async_command_messages.py +507 -0
- autocoder/common/international/messages/auto_coder_messages.py +2208 -0
- autocoder/common/international/messages/chat_auto_coder_messages.py +1547 -0
- autocoder/common/international/messages/command_help_messages.py +986 -0
- autocoder/common/international/messages/conversation_command_messages.py +191 -0
- autocoder/common/international/messages/git_helper_plugin_messages.py +159 -0
- autocoder/common/international/messages/queue_command_messages.py +751 -0
- autocoder/common/international/messages/rules_command_messages.py +77 -0
- autocoder/common/international/messages/sdk_messages.py +1707 -0
- autocoder/common/international/messages/token_helper_plugin_messages.py +361 -0
- autocoder/common/international/messages/tool_display_messages.py +1212 -0
- autocoder/common/international/messages/workflow_exception_messages.py +473 -0
- autocoder/common/international/test_international.py +612 -0
- autocoder/common/linter_core/__init__.py +28 -0
- autocoder/common/linter_core/base_linter.py +61 -0
- autocoder/common/linter_core/config_loader.py +271 -0
- autocoder/common/linter_core/formatters/__init__.py +0 -0
- autocoder/common/linter_core/formatters/base_formatter.py +38 -0
- autocoder/common/linter_core/formatters/raw_formatter.py +17 -0
- autocoder/common/linter_core/linter.py +166 -0
- autocoder/common/linter_core/linter_factory.py +216 -0
- autocoder/common/linter_core/linter_manager.py +333 -0
- autocoder/common/linter_core/linters/__init__.py +9 -0
- autocoder/common/linter_core/linters/java_linter.py +342 -0
- autocoder/common/linter_core/linters/python_linter.py +115 -0
- autocoder/common/linter_core/linters/typescript_linter.py +119 -0
- autocoder/common/linter_core/models/__init__.py +7 -0
- autocoder/common/linter_core/models/lint_result.py +91 -0
- autocoder/common/linter_core/models.py +33 -0
- autocoder/common/linter_core/tests/__init__.py +3 -0
- autocoder/common/linter_core/tests/test_config_loader.py +323 -0
- autocoder/common/linter_core/tests/test_config_loading.py +308 -0
- autocoder/common/linter_core/tests/test_factory_manager.py +234 -0
- autocoder/common/linter_core/tests/test_formatters.py +147 -0
- autocoder/common/linter_core/tests/test_integration.py +317 -0
- autocoder/common/linter_core/tests/test_java_linter.py +496 -0
- autocoder/common/linter_core/tests/test_linters.py +265 -0
- autocoder/common/linter_core/tests/test_models.py +81 -0
- autocoder/common/linter_core/tests/verify_config_loading.py +296 -0
- autocoder/common/linter_core/tests/verify_fixes.py +183 -0
- autocoder/common/llm_friendly_package/__init__.py +31 -0
- autocoder/common/llm_friendly_package/base_manager.py +102 -0
- autocoder/common/llm_friendly_package/docs_manager.py +121 -0
- autocoder/common/llm_friendly_package/library_manager.py +171 -0
- autocoder/common/{llm_friendly_package.py → llm_friendly_package/main_manager.py} +204 -231
- autocoder/common/llm_friendly_package/models.py +40 -0
- autocoder/common/llm_friendly_package/test_llm_friendly_package.py +536 -0
- autocoder/common/llms/__init__.py +15 -0
- autocoder/common/llms/demo_error_handling.py +85 -0
- autocoder/common/llms/factory.py +142 -0
- autocoder/common/llms/manager.py +264 -0
- autocoder/common/llms/pricing.py +121 -0
- autocoder/common/llms/registry.py +316 -0
- autocoder/common/llms/schema.py +77 -0
- autocoder/common/llms/simple_demo.py +45 -0
- autocoder/common/llms/test_quick_model.py +116 -0
- autocoder/common/llms/test_remove_functionality.py +182 -0
- autocoder/common/llms/tests/__init__.py +1 -0
- autocoder/common/llms/tests/test_manager.py +330 -0
- autocoder/common/llms/tests/test_registry.py +364 -0
- autocoder/common/mcp_tools/__init__.py +62 -0
- autocoder/common/{mcp_tools.py → mcp_tools/executor.py} +49 -40
- autocoder/common/{mcp_hub.py → mcp_tools/hub.py} +42 -68
- autocoder/common/{mcp_server_install.py → mcp_tools/installer.py} +16 -28
- autocoder/common/{mcp_server.py → mcp_tools/server.py} +176 -48
- autocoder/common/mcp_tools/test_keyboard_interrupt.py +93 -0
- autocoder/common/mcp_tools/test_mcp_tools.py +391 -0
- autocoder/common/{mcp_server_types.py → mcp_tools/types.py} +121 -48
- autocoder/common/mcp_tools/verify_functionality.py +202 -0
- autocoder/common/model_speed_tester.py +32 -26
- autocoder/common/priority_directory_finder/__init__.py +142 -0
- autocoder/common/priority_directory_finder/examples.py +230 -0
- autocoder/common/priority_directory_finder/finder.py +283 -0
- autocoder/common/priority_directory_finder/models.py +236 -0
- autocoder/common/priority_directory_finder/test_priority_directory_finder.py +431 -0
- autocoder/common/project_scanner/__init__.py +18 -0
- autocoder/common/project_scanner/compat.py +77 -0
- autocoder/common/project_scanner/scanner.py +436 -0
- autocoder/common/project_tracker/__init__.py +27 -0
- autocoder/common/project_tracker/api.py +228 -0
- autocoder/common/project_tracker/demo.py +272 -0
- autocoder/common/project_tracker/tracker.py +487 -0
- autocoder/common/project_tracker/types.py +53 -0
- autocoder/common/pruner/__init__.py +67 -0
- autocoder/common/pruner/agentic_conversation_pruner.py +651 -102
- autocoder/common/pruner/conversation_message_ids_api.py +386 -0
- autocoder/common/pruner/conversation_message_ids_manager.py +347 -0
- autocoder/common/pruner/conversation_message_ids_pruner.py +473 -0
- autocoder/common/pruner/conversation_normalizer.py +347 -0
- autocoder/common/pruner/conversation_pruner.py +26 -6
- autocoder/common/pruner/test_agentic_conversation_pruner.py +554 -112
- autocoder/common/pruner/test_conversation_normalizer.py +502 -0
- autocoder/common/pruner/test_tool_content_detector.py +324 -0
- autocoder/common/pruner/tool_content_detector.py +227 -0
- autocoder/common/pruner/tools/__init__.py +18 -0
- autocoder/common/pruner/tools/query_message_ids.py +264 -0
- autocoder/common/pruner/tools/test_agentic_pruning_logic.py +432 -0
- autocoder/common/pruner/tools/test_message_ids_pruning_only.py +192 -0
- autocoder/common/pull_requests/__init__.py +9 -1
- autocoder/common/pull_requests/utils.py +122 -1
- autocoder/common/rag_manager/rag_manager.py +36 -40
- autocoder/common/rulefiles/__init__.py +53 -1
- autocoder/common/rulefiles/api.py +250 -0
- autocoder/common/rulefiles/core/__init__.py +14 -0
- autocoder/common/rulefiles/core/manager.py +241 -0
- autocoder/common/rulefiles/core/selector.py +805 -0
- autocoder/common/rulefiles/models/__init__.py +20 -0
- autocoder/common/rulefiles/models/index.py +16 -0
- autocoder/common/rulefiles/models/init_rule.py +18 -0
- autocoder/common/rulefiles/models/rule_file.py +18 -0
- autocoder/common/rulefiles/models/rule_relevance.py +14 -0
- autocoder/common/rulefiles/models/summary.py +16 -0
- autocoder/common/rulefiles/test_rulefiles.py +776 -0
- autocoder/common/rulefiles/utils/__init__.py +34 -0
- autocoder/common/rulefiles/utils/monitor.py +86 -0
- autocoder/common/rulefiles/utils/parser.py +230 -0
- autocoder/common/save_formatted_log.py +67 -10
- autocoder/common/search_replace.py +8 -1
- autocoder/common/search_replace_patch/__init__.py +24 -0
- autocoder/common/search_replace_patch/base.py +115 -0
- autocoder/common/search_replace_patch/manager.py +248 -0
- autocoder/common/search_replace_patch/patch_replacer.py +304 -0
- autocoder/common/search_replace_patch/similarity_replacer.py +306 -0
- autocoder/common/search_replace_patch/string_replacer.py +181 -0
- autocoder/common/search_replace_patch/tests/__init__.py +3 -0
- autocoder/common/search_replace_patch/tests/run_tests.py +126 -0
- autocoder/common/search_replace_patch/tests/test_base.py +188 -0
- autocoder/common/search_replace_patch/tests/test_empty_line_insert.py +233 -0
- autocoder/common/search_replace_patch/tests/test_integration.py +389 -0
- autocoder/common/search_replace_patch/tests/test_manager.py +351 -0
- autocoder/common/search_replace_patch/tests/test_patch_replacer.py +316 -0
- autocoder/common/search_replace_patch/tests/test_regex_replacer.py +306 -0
- autocoder/common/search_replace_patch/tests/test_similarity_replacer.py +384 -0
- autocoder/common/shell_commands/__init__.py +197 -0
- autocoder/common/shell_commands/background_process_notifier.py +346 -0
- autocoder/common/shell_commands/command_executor.py +1127 -0
- autocoder/common/shell_commands/error_recovery.py +541 -0
- autocoder/common/shell_commands/exceptions.py +120 -0
- autocoder/common/shell_commands/interactive_executor.py +476 -0
- autocoder/common/shell_commands/interactive_pexpect_process.py +623 -0
- autocoder/common/shell_commands/interactive_process.py +744 -0
- autocoder/common/shell_commands/interactive_session_manager.py +1014 -0
- autocoder/common/shell_commands/monitoring.py +529 -0
- autocoder/common/shell_commands/process_cleanup.py +386 -0
- autocoder/common/shell_commands/process_manager.py +606 -0
- autocoder/common/shell_commands/test_interactive_pexpect_process.py +281 -0
- autocoder/common/shell_commands/tests/__init__.py +6 -0
- autocoder/common/shell_commands/tests/conftest.py +118 -0
- autocoder/common/shell_commands/tests/test_background_process_notifier.py +703 -0
- autocoder/common/shell_commands/tests/test_command_executor.py +448 -0
- autocoder/common/shell_commands/tests/test_error_recovery.py +305 -0
- autocoder/common/shell_commands/tests/test_exceptions.py +299 -0
- autocoder/common/shell_commands/tests/test_execute_batch.py +588 -0
- autocoder/common/shell_commands/tests/test_indented_batch_commands.py +244 -0
- autocoder/common/shell_commands/tests/test_integration.py +664 -0
- autocoder/common/shell_commands/tests/test_monitoring.py +546 -0
- autocoder/common/shell_commands/tests/test_performance.py +632 -0
- autocoder/common/shell_commands/tests/test_process_cleanup.py +397 -0
- autocoder/common/shell_commands/tests/test_process_manager.py +606 -0
- autocoder/common/shell_commands/tests/test_timeout_config.py +343 -0
- autocoder/common/shell_commands/tests/test_timeout_manager.py +520 -0
- autocoder/common/shell_commands/timeout_config.py +315 -0
- autocoder/common/shell_commands/timeout_manager.py +352 -0
- autocoder/common/terminal_paste/__init__.py +14 -0
- autocoder/common/terminal_paste/demo.py +145 -0
- autocoder/common/terminal_paste/demo_paste_functionality.py +95 -0
- autocoder/common/terminal_paste/paste_handler.py +200 -0
- autocoder/common/terminal_paste/paste_manager.py +118 -0
- autocoder/common/terminal_paste/tests/__init__.py +1 -0
- autocoder/common/terminal_paste/tests/test_paste_handler.py +182 -0
- autocoder/common/terminal_paste/tests/test_paste_manager.py +126 -0
- autocoder/common/terminal_paste/utils.py +163 -0
- autocoder/common/test_autocoder_args.py +232 -0
- autocoder/common/test_env_manager.py +173 -0
- autocoder/common/test_env_manager_integration.py +159 -0
- autocoder/common/text_similarity/__init__.py +9 -0
- autocoder/common/text_similarity/demo.py +216 -0
- autocoder/common/text_similarity/examples.py +266 -0
- autocoder/common/text_similarity/test_text_similarity.py +306 -0
- autocoder/common/text_similarity/text_similarity.py +194 -0
- autocoder/common/text_similarity/utils.py +125 -0
- autocoder/common/todos/__init__.py +61 -0
- autocoder/common/todos/cache/__init__.py +16 -0
- autocoder/common/todos/cache/base_cache.py +89 -0
- autocoder/common/todos/cache/cache_manager.py +228 -0
- autocoder/common/todos/cache/memory_cache.py +225 -0
- autocoder/common/todos/config.py +155 -0
- autocoder/common/todos/exceptions.py +35 -0
- autocoder/common/todos/get_todo_manager.py +161 -0
- autocoder/common/todos/manager.py +537 -0
- autocoder/common/todos/models.py +239 -0
- autocoder/common/todos/storage/__init__.py +14 -0
- autocoder/common/todos/storage/base_storage.py +76 -0
- autocoder/common/todos/storage/file_storage.py +278 -0
- autocoder/common/tokens/counter.py +24 -2
- autocoder/common/tools_manager/__init__.py +17 -0
- autocoder/common/tools_manager/examples.py +162 -0
- autocoder/common/tools_manager/manager.py +385 -0
- autocoder/common/tools_manager/models.py +39 -0
- autocoder/common/tools_manager/test_tools_manager.py +303 -0
- autocoder/common/tools_manager/utils.py +191 -0
- autocoder/common/v2/agent/agentic_callbacks.py +270 -0
- autocoder/common/v2/agent/agentic_edit.py +2699 -1856
- autocoder/common/v2/agent/agentic_edit_change_manager.py +474 -0
- autocoder/common/v2/agent/agentic_edit_tools/__init__.py +35 -1
- autocoder/common/v2/agent/agentic_edit_tools/ac_mod_list_tool_resolver.py +279 -0
- autocoder/common/v2/agent/agentic_edit_tools/ac_mod_write_tool_resolver.py +10 -1
- autocoder/common/v2/agent/agentic_edit_tools/background_task_tool_resolver.py +1167 -0
- autocoder/common/v2/agent/agentic_edit_tools/base_tool_resolver.py +2 -2
- autocoder/common/v2/agent/agentic_edit_tools/conversation_message_ids_read_tool_resolver.py +214 -0
- autocoder/common/v2/agent/agentic_edit_tools/conversation_message_ids_write_tool_resolver.py +299 -0
- autocoder/common/v2/agent/agentic_edit_tools/count_tokens_tool_resolver.py +290 -0
- autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +564 -29
- autocoder/common/v2/agent/agentic_edit_tools/execute_workflow_tool_resolver.py +485 -0
- autocoder/common/v2/agent/agentic_edit_tools/extract_to_text_tool_resolver.py +225 -0
- autocoder/common/v2/agent/agentic_edit_tools/lint_report.py +79 -0
- autocoder/common/v2/agent/agentic_edit_tools/linter_config_models.py +343 -0
- autocoder/common/v2/agent/agentic_edit_tools/linter_enabled_tool_resolver.py +189 -0
- autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +169 -101
- autocoder/common/v2/agent/agentic_edit_tools/load_extra_document_tool_resolver.py +356 -0
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +243 -50
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +667 -147
- autocoder/common/v2/agent/agentic_edit_tools/run_named_subagents_tool_resolver.py +691 -0
- autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +410 -86
- autocoder/common/v2/agent/agentic_edit_tools/session_interactive_tool_resolver.py +115 -0
- autocoder/common/v2/agent/agentic_edit_tools/session_start_tool_resolver.py +190 -0
- autocoder/common/v2/agent/agentic_edit_tools/session_stop_tool_resolver.py +76 -0
- autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +207 -192
- autocoder/common/v2/agent/agentic_edit_tools/todo_read_tool_resolver.py +80 -63
- autocoder/common/v2/agent/agentic_edit_tools/todo_write_tool_resolver.py +237 -233
- autocoder/common/v2/agent/agentic_edit_tools/use_mcp_tool_resolver.py +2 -2
- autocoder/common/v2/agent/agentic_edit_tools/web_crawl_tool_resolver.py +557 -0
- autocoder/common/v2/agent/agentic_edit_tools/web_search_tool_resolver.py +600 -0
- autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +56 -121
- autocoder/common/v2/agent/agentic_edit_types.py +343 -9
- autocoder/common/v2/agent/runner/__init__.py +3 -3
- autocoder/common/v2/agent/runner/base_runner.py +12 -26
- autocoder/common/v2/agent/runner/{event_runner.py → file_based_event_runner.py} +3 -2
- autocoder/common/v2/agent/runner/sdk_runner.py +150 -8
- autocoder/common/v2/agent/runner/terminal_runner.py +170 -57
- autocoder/common/v2/agent/runner/tool_display.py +557 -159
- autocoder/common/v2/agent/test_agentic_callbacks.py +265 -0
- autocoder/common/v2/agent/test_agentic_edit.py +194 -0
- autocoder/common/v2/agent/tool_caller/__init__.py +24 -0
- autocoder/common/v2/agent/tool_caller/default_tool_resolver_map.py +135 -0
- autocoder/common/v2/agent/tool_caller/integration_test.py +172 -0
- autocoder/common/v2/agent/tool_caller/plugins/__init__.py +14 -0
- autocoder/common/v2/agent/tool_caller/plugins/base_plugin.py +126 -0
- autocoder/common/v2/agent/tool_caller/plugins/examples/__init__.py +13 -0
- autocoder/common/v2/agent/tool_caller/plugins/examples/logging_plugin.py +164 -0
- autocoder/common/v2/agent/tool_caller/plugins/examples/security_filter_plugin.py +198 -0
- autocoder/common/v2/agent/tool_caller/plugins/plugin_interface.py +141 -0
- autocoder/common/v2/agent/tool_caller/test_tool_caller.py +278 -0
- autocoder/common/v2/agent/tool_caller/tool_call_plugin_manager.py +331 -0
- autocoder/common/v2/agent/tool_caller/tool_caller.py +337 -0
- autocoder/common/v2/agent/tool_caller/usage_example.py +193 -0
- autocoder/common/v2/code_agentic_editblock_manager.py +4 -4
- autocoder/common/v2/code_auto_generate.py +136 -78
- autocoder/common/v2/code_auto_generate_diff.py +135 -79
- autocoder/common/v2/code_auto_generate_editblock.py +174 -99
- autocoder/common/v2/code_auto_generate_strict_diff.py +151 -71
- autocoder/common/v2/code_auto_merge.py +1 -1
- autocoder/common/v2/code_auto_merge_editblock.py +13 -1
- autocoder/common/v2/code_diff_manager.py +3 -3
- autocoder/common/v2/code_editblock_manager.py +4 -14
- autocoder/common/v2/code_manager.py +1 -1
- autocoder/common/v2/code_strict_diff_manager.py +2 -2
- autocoder/common/wrap_llm_hint/__init__.py +10 -0
- autocoder/common/wrap_llm_hint/test_wrap_llm_hint.py +1067 -0
- autocoder/common/wrap_llm_hint/utils.py +432 -0
- autocoder/common/wrap_llm_hint/wrap_llm_hint.py +323 -0
- autocoder/completer/__init__.py +8 -0
- autocoder/completer/command_completer_v2.py +1094 -0
- autocoder/default_project/__init__.py +501 -0
- autocoder/dispacher/__init__.py +4 -12
- autocoder/dispacher/actions/action.py +400 -129
- autocoder/dispacher/actions/plugins/action_regex_project.py +2 -2
- autocoder/index/entry.py +117 -125
- autocoder/{agent → index/filter}/agentic_filter.py +322 -333
- autocoder/index/filter/normal_filter.py +5 -11
- autocoder/index/filter/quick_filter.py +1 -1
- autocoder/index/index.py +36 -9
- autocoder/index/tests/__init__.py +1 -0
- autocoder/index/tests/run_tests.py +195 -0
- autocoder/index/tests/test_entry.py +303 -0
- autocoder/index/tests/test_index_manager.py +314 -0
- autocoder/index/tests/test_module_integration.py +300 -0
- autocoder/index/tests/test_symbols_utils.py +183 -0
- autocoder/inner/__init__.py +4 -0
- autocoder/inner/agentic.py +923 -0
- autocoder/inner/async_command_handler.py +992 -0
- autocoder/inner/conversation_command_handlers.py +623 -0
- autocoder/inner/merge_command_handler.py +213 -0
- autocoder/inner/queue_command_handler.py +684 -0
- autocoder/models.py +95 -266
- autocoder/plugins/git_helper_plugin.py +31 -29
- autocoder/plugins/token_helper_plugin.py +65 -46
- autocoder/pyproject/__init__.py +32 -29
- autocoder/rag/agentic_rag.py +215 -75
- autocoder/rag/cache/simple_cache.py +1 -2
- autocoder/rag/loaders/image_loader.py +1 -1
- autocoder/rag/long_context_rag.py +42 -26
- autocoder/rag/qa_conversation_strategy.py +1 -1
- autocoder/rag/terminal/__init__.py +17 -0
- autocoder/rag/terminal/args.py +581 -0
- autocoder/rag/terminal/bootstrap.py +61 -0
- autocoder/rag/terminal/command_handlers.py +653 -0
- autocoder/rag/terminal/formatters/__init__.py +20 -0
- autocoder/rag/terminal/formatters/base.py +70 -0
- autocoder/rag/terminal/formatters/json_format.py +66 -0
- autocoder/rag/terminal/formatters/stream_json.py +95 -0
- autocoder/rag/terminal/formatters/text.py +28 -0
- autocoder/rag/terminal/init.py +120 -0
- autocoder/rag/terminal/utils.py +106 -0
- autocoder/rag/test_agentic_rag.py +389 -0
- autocoder/rag/test_doc_filter.py +3 -3
- autocoder/rag/test_long_context_rag.py +1 -1
- autocoder/rag/test_token_limiter.py +517 -10
- autocoder/rag/token_counter.py +3 -0
- autocoder/rag/token_limiter.py +19 -15
- autocoder/rag/tools/__init__.py +26 -2
- autocoder/rag/tools/bochaai_example.py +343 -0
- autocoder/rag/tools/bochaai_sdk.py +541 -0
- autocoder/rag/tools/metaso_example.py +268 -0
- autocoder/rag/tools/metaso_sdk.py +417 -0
- autocoder/rag/tools/recall_tool.py +28 -7
- autocoder/rag/tools/run_integration_tests.py +204 -0
- autocoder/rag/tools/test_all_providers.py +318 -0
- autocoder/rag/tools/test_bochaai_integration.py +482 -0
- autocoder/rag/tools/test_final_integration.py +215 -0
- autocoder/rag/tools/test_metaso_integration.py +424 -0
- autocoder/rag/tools/test_metaso_real.py +171 -0
- autocoder/rag/tools/test_web_crawl_tool.py +639 -0
- autocoder/rag/tools/test_web_search_tool.py +509 -0
- autocoder/rag/tools/todo_read_tool.py +202 -0
- autocoder/rag/tools/todo_write_tool.py +412 -0
- autocoder/rag/tools/web_crawl_tool.py +634 -0
- autocoder/rag/tools/web_search_tool.py +558 -0
- autocoder/rag/tools/web_tools_example.py +119 -0
- autocoder/rag/types.py +16 -0
- autocoder/rag/variable_holder.py +4 -2
- autocoder/rags.py +86 -79
- autocoder/regexproject/__init__.py +23 -21
- autocoder/sdk/__init__.py +46 -190
- autocoder/sdk/api.py +370 -0
- autocoder/sdk/async_runner/__init__.py +26 -0
- autocoder/sdk/async_runner/async_executor.py +650 -0
- autocoder/sdk/async_runner/async_handler.py +356 -0
- autocoder/sdk/async_runner/markdown_processor.py +595 -0
- autocoder/sdk/async_runner/task_metadata.py +284 -0
- autocoder/sdk/async_runner/worktree_manager.py +438 -0
- autocoder/sdk/cli/__init__.py +2 -5
- autocoder/sdk/cli/formatters.py +28 -204
- autocoder/sdk/cli/handlers.py +77 -44
- autocoder/sdk/cli/main.py +154 -171
- autocoder/sdk/cli/options.py +95 -22
- autocoder/sdk/constants.py +139 -51
- autocoder/sdk/core/auto_coder_core.py +484 -109
- autocoder/sdk/core/bridge.py +297 -115
- autocoder/sdk/exceptions.py +18 -12
- autocoder/sdk/formatters/__init__.py +19 -0
- autocoder/sdk/formatters/input.py +64 -0
- autocoder/sdk/formatters/output.py +247 -0
- autocoder/sdk/formatters/stream.py +54 -0
- autocoder/sdk/models/__init__.py +6 -5
- autocoder/sdk/models/options.py +55 -18
- autocoder/sdk/utils/formatters.py +27 -195
- autocoder/suffixproject/__init__.py +28 -25
- autocoder/terminal/__init__.py +14 -0
- autocoder/terminal/app.py +454 -0
- autocoder/terminal/args.py +32 -0
- autocoder/terminal/bootstrap.py +178 -0
- autocoder/terminal/command_processor.py +521 -0
- autocoder/terminal/command_registry.py +57 -0
- autocoder/terminal/help.py +97 -0
- autocoder/terminal/tasks/__init__.py +5 -0
- autocoder/terminal/tasks/background.py +77 -0
- autocoder/terminal/tasks/task_event.py +70 -0
- autocoder/terminal/ui/__init__.py +13 -0
- autocoder/terminal/ui/completer.py +268 -0
- autocoder/terminal/ui/keybindings.py +75 -0
- autocoder/terminal/ui/session.py +41 -0
- autocoder/terminal/ui/toolbar.py +64 -0
- autocoder/terminal/utils/__init__.py +13 -0
- autocoder/terminal/utils/errors.py +18 -0
- autocoder/terminal/utils/paths.py +19 -0
- autocoder/terminal/utils/shell.py +43 -0
- autocoder/terminal_v3/__init__.py +10 -0
- autocoder/terminal_v3/app.py +201 -0
- autocoder/terminal_v3/handlers/__init__.py +5 -0
- autocoder/terminal_v3/handlers/command_handler.py +131 -0
- autocoder/terminal_v3/models/__init__.py +6 -0
- autocoder/terminal_v3/models/conversation_buffer.py +214 -0
- autocoder/terminal_v3/models/message.py +50 -0
- autocoder/terminal_v3/models/tool_display.py +247 -0
- autocoder/terminal_v3/ui/__init__.py +7 -0
- autocoder/terminal_v3/ui/keybindings.py +56 -0
- autocoder/terminal_v3/ui/layout.py +141 -0
- autocoder/terminal_v3/ui/styles.py +43 -0
- autocoder/tsproject/__init__.py +23 -23
- autocoder/utils/auto_coder_utils/chat_stream_out.py +1 -1
- autocoder/utils/llms.py +88 -80
- autocoder/utils/math_utils.py +101 -0
- autocoder/utils/model_provider_selector.py +16 -4
- autocoder/utils/operate_config_api.py +33 -5
- autocoder/utils/thread_utils.py +2 -2
- autocoder/version.py +4 -2
- autocoder/workflow_agents/__init__.py +84 -0
- autocoder/workflow_agents/agent.py +143 -0
- autocoder/workflow_agents/exceptions.py +573 -0
- autocoder/workflow_agents/executor.py +665 -0
- autocoder/workflow_agents/loader.py +749 -0
- autocoder/workflow_agents/runner.py +267 -0
- autocoder/workflow_agents/types.py +173 -0
- autocoder/workflow_agents/utils.py +434 -0
- autocoder/workflow_agents/workflow_manager.py +211 -0
- auto_coder-1.0.0.dist-info/METADATA +0 -396
- auto_coder-1.0.0.dist-info/RECORD +0 -442
- auto_coder-1.0.0.dist-info/licenses/LICENSE +0 -201
- autocoder/auto_coder_server.py +0 -672
- autocoder/benchmark.py +0 -138
- autocoder/common/ac_style_command_parser/example.py +0 -7
- autocoder/common/cleaner.py +0 -31
- autocoder/common/command_completer_v2.py +0 -615
- autocoder/common/context_pruner.py +0 -477
- autocoder/common/conversation_pruner.py +0 -132
- autocoder/common/directory_cache/__init__.py +0 -1
- autocoder/common/directory_cache/cache.py +0 -192
- autocoder/common/directory_cache/test_cache.py +0 -190
- autocoder/common/file_checkpoint/examples.py +0 -217
- autocoder/common/llm_friendly_package_example.py +0 -138
- autocoder/common/llm_friendly_package_test.py +0 -63
- autocoder/common/pull_requests/test_module.py +0 -1
- autocoder/common/rulefiles/autocoderrules_utils.py +0 -484
- autocoder/common/text.py +0 -30
- autocoder/common/v2/agent/agentic_edit_tools/list_package_info_tool_resolver.py +0 -42
- autocoder/common/v2/agent/agentic_edit_tools/test_execute_command_tool_resolver.py +0 -70
- autocoder/common/v2/agent/agentic_edit_tools/test_search_files_tool_resolver.py +0 -163
- autocoder/common/v2/agent/agentic_tool_display.py +0 -183
- autocoder/plugins/dynamic_completion_example.py +0 -148
- autocoder/plugins/sample_plugin.py +0 -160
- autocoder/sdk/cli/__main__.py +0 -26
- autocoder/sdk/cli/completion_wrapper.py +0 -38
- autocoder/sdk/cli/install_completion.py +0 -301
- autocoder/sdk/models/messages.py +0 -209
- autocoder/sdk/session/__init__.py +0 -32
- autocoder/sdk/session/session.py +0 -106
- autocoder/sdk/session/session_manager.py +0 -56
- {auto_coder-1.0.0.dist-info → auto_coder-2.0.1.dist-info}/top_level.txt +0 -0
- /autocoder/{sdk/example.py → common/agent_query_queue/__init__.py} +0 -0
|
@@ -1,127 +1,651 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import re
|
|
3
2
|
from typing import Dict, Any, Optional, List, Tuple
|
|
4
3
|
import typing
|
|
5
4
|
from autocoder.common import AutoCoderArgs
|
|
6
|
-
from autocoder.common.v2.agent.agentic_edit_tools.
|
|
5
|
+
from autocoder.common.v2.agent.agentic_edit_tools.linter_enabled_tool_resolver import LinterEnabledToolResolver
|
|
7
6
|
from autocoder.common.v2.agent.agentic_edit_types import ReplaceInFileTool, ToolResult
|
|
8
7
|
from autocoder.common.file_checkpoint.models import FileChange as CheckpointFileChange
|
|
9
|
-
from autocoder.common.file_checkpoint.manager import FileChangeManager as CheckpointFileChangeManager
|
|
10
|
-
from autocoder.linters.models import IssueSeverity, FileLintResult
|
|
11
8
|
from loguru import logger
|
|
12
9
|
from autocoder.common.auto_coder_lang import get_message_with_format
|
|
10
|
+
from autocoder.common.text_similarity import TextSimilarity
|
|
11
|
+
from autocoder.common.search_replace_patch import SearchReplaceManager
|
|
12
|
+
from autocoder.common.wrap_llm_hint.utils import add_hint_to_text
|
|
13
|
+
import pydantic
|
|
14
|
+
from autocoder.common import files as FileUtils
|
|
15
|
+
|
|
13
16
|
if typing.TYPE_CHECKING:
|
|
14
17
|
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
15
18
|
|
|
16
|
-
class
|
|
19
|
+
class PathAndCode(pydantic.BaseModel):
|
|
20
|
+
path: str
|
|
21
|
+
content: str
|
|
22
|
+
|
|
23
|
+
class ReplacementFailureBlockAnalysis(pydantic.BaseModel):
|
|
24
|
+
"""Structured analysis for a single SEARCH block when replacement fails."""
|
|
25
|
+
search_preview: str
|
|
26
|
+
similarity: float
|
|
27
|
+
start_line: int
|
|
28
|
+
end_line: int
|
|
29
|
+
best_window_preview: str
|
|
30
|
+
hints: List[str] = []
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ReplacementFailureReport(pydantic.BaseModel):
|
|
34
|
+
"""Structured, extensible failure feedback for replacement operations."""
|
|
35
|
+
reason: str = "replacement_failed"
|
|
36
|
+
file_path: Optional[str] = None
|
|
37
|
+
used_strategy: Optional[str] = None
|
|
38
|
+
tried_strategies: List[str] = []
|
|
39
|
+
suggestions: List[str] = []
|
|
40
|
+
blocks: List[ReplacementFailureBlockAnalysis] = []
|
|
41
|
+
|
|
42
|
+
class ReplaceInFileToolResolver(LinterEnabledToolResolver):
|
|
17
43
|
def __init__(self, agent: Optional['AgenticEdit'], tool: ReplaceInFileTool, args: AutoCoderArgs):
|
|
18
44
|
super().__init__(agent, tool, args)
|
|
19
45
|
self.tool: ReplaceInFileTool = tool # For type hinting
|
|
20
46
|
self.args = args
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
|
|
48
|
+
# 初始化智能替换管理器
|
|
49
|
+
self.search_replace_manager = SearchReplaceManager()
|
|
50
|
+
|
|
51
|
+
# Get fence parameters from tool
|
|
52
|
+
self.fence_0 = getattr(tool, 'fence_0', '```')
|
|
53
|
+
self.fence_1 = getattr(tool, 'fence_1', '```')
|
|
23
54
|
|
|
24
|
-
|
|
25
|
-
""
|
|
26
|
-
|
|
55
|
+
# Markers used in SEARCH/REPLACE blocks
|
|
56
|
+
self.SEARCH_MARKER: str = "<<<<<<< SEARCH" # exact literal
|
|
57
|
+
self.DIVIDER_MARKER: str = "======="
|
|
58
|
+
self.REPLACE_MARKER: str = ">>>>>>> REPLACE"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def parse_search_replace_blocks(self, diff_content: str) -> List[Tuple[str, str]]:
|
|
62
|
+
"""Parse diff content using configured markers into (search, replace) tuples.
|
|
63
|
+
|
|
64
|
+
Preserves original newlines within each block.
|
|
27
65
|
"""
|
|
28
|
-
blocks = []
|
|
66
|
+
blocks: List[Tuple[str, str]] = []
|
|
29
67
|
lines = diff_content.splitlines(keepends=True)
|
|
30
68
|
i = 0
|
|
31
69
|
n = len(lines)
|
|
32
70
|
|
|
33
71
|
while i < n:
|
|
34
72
|
line = lines[i]
|
|
35
|
-
if line.strip() ==
|
|
73
|
+
if line.strip() == self.SEARCH_MARKER:
|
|
36
74
|
i += 1
|
|
37
|
-
search_lines = []
|
|
38
|
-
|
|
39
|
-
while i < n and lines[i].strip() != "=======":
|
|
75
|
+
search_lines: List[str] = []
|
|
76
|
+
while i < n and lines[i].strip() != self.DIVIDER_MARKER:
|
|
40
77
|
search_lines.append(lines[i])
|
|
41
78
|
i += 1
|
|
42
79
|
if i >= n:
|
|
43
80
|
logger.warning("Unterminated SEARCH block found in diff content.")
|
|
44
81
|
break
|
|
45
|
-
i += 1 # skip
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
while i < n and lines[i].strip() !=
|
|
82
|
+
i += 1 # skip divider
|
|
83
|
+
|
|
84
|
+
replace_lines: List[str] = []
|
|
85
|
+
while i < n and lines[i].strip() != self.REPLACE_MARKER:
|
|
49
86
|
replace_lines.append(lines[i])
|
|
50
87
|
i += 1
|
|
51
88
|
if i >= n:
|
|
52
89
|
logger.warning("Unterminated REPLACE block found in diff content.")
|
|
53
90
|
break
|
|
54
|
-
i += 1 # skip
|
|
91
|
+
i += 1 # skip replace marker
|
|
55
92
|
|
|
56
|
-
|
|
57
|
-
replace_block = ''.join(replace_lines)
|
|
58
|
-
blocks.append((search_block, replace_block))
|
|
93
|
+
blocks.append((''.join(search_lines), ''.join(replace_lines)))
|
|
59
94
|
else:
|
|
60
95
|
i += 1
|
|
61
96
|
|
|
62
97
|
if not blocks and diff_content.strip():
|
|
63
|
-
logger.warning(
|
|
98
|
+
logger.warning(
|
|
99
|
+
f"Could not parse any SEARCH/REPLACE blocks from diff (using markers): {diff_content}"
|
|
100
|
+
)
|
|
64
101
|
return blocks
|
|
65
102
|
|
|
66
|
-
def
|
|
103
|
+
def _find_line_numbers(self, content: str, text_block: str) -> Tuple[int, int]:
|
|
67
104
|
"""
|
|
68
|
-
|
|
105
|
+
Find the line numbers for a given text block in the content.
|
|
69
106
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
107
|
+
Args:
|
|
108
|
+
content: The full file content
|
|
109
|
+
text_block: The text block to find
|
|
73
110
|
|
|
74
|
-
|
|
75
|
-
|
|
111
|
+
Returns:
|
|
112
|
+
Tuple of (start_line, end_line) numbers (1-indexed)
|
|
113
|
+
"""
|
|
114
|
+
block_lines = text_block.splitlines()
|
|
115
|
+
block_start_idx = content.find(text_block)
|
|
116
|
+
if block_start_idx == -1:
|
|
117
|
+
return (-1, -1)
|
|
118
|
+
lines_before = content[:block_start_idx].count('\n')
|
|
119
|
+
start_line = lines_before + 1
|
|
120
|
+
lines_in_block = len(block_lines)
|
|
121
|
+
end_line = start_line + lines_in_block - 1
|
|
122
|
+
return (start_line, end_line)
|
|
123
|
+
|
|
124
|
+
def _intelligent_replace(self, content: str, search_blocks: List[Tuple[str, str]]) -> Tuple[bool, str, List[str]]:
|
|
76
125
|
"""
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
filtered_issues = []
|
|
82
|
-
for issue in lint_result.issues:
|
|
83
|
-
if issue.severity in levels:
|
|
84
|
-
filtered_issues.append(issue)
|
|
85
|
-
|
|
86
|
-
# 更新 lint_result 的副本
|
|
87
|
-
filtered_result = lint_result
|
|
88
|
-
filtered_result.issues = filtered_issues
|
|
89
|
-
|
|
90
|
-
# 更新计数
|
|
91
|
-
filtered_result.error_count = sum(1 for issue in filtered_result.issues if issue.severity == IssueSeverity.ERROR)
|
|
92
|
-
filtered_result.warning_count = sum(1 for issue in filtered_result.issues if issue.severity == IssueSeverity.WARNING)
|
|
93
|
-
filtered_result.info_count = sum(1 for issue in filtered_result.issues if issue.severity == IssueSeverity.INFO)
|
|
94
|
-
|
|
95
|
-
return filtered_result
|
|
96
|
-
|
|
97
|
-
def _format_lint_issues(self, lint_result:FileLintResult):
|
|
126
|
+
使用智能替换策略进行文本替换(兼容旧签名)。
|
|
127
|
+
|
|
128
|
+
新的结构化失败报告通过 _apply_replacements_with_fallback 提供;
|
|
129
|
+
本方法保持返回 (success, new_content, errors) 以兼容旧调用点。
|
|
98
130
|
"""
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
131
|
+
success, new_content, errors, _ = self._apply_replacements_with_fallback(content, search_blocks)
|
|
132
|
+
return success, new_content, errors
|
|
133
|
+
|
|
134
|
+
def _apply_replacements_with_fallback(
|
|
135
|
+
self,
|
|
136
|
+
content: str,
|
|
137
|
+
search_blocks: List[Tuple[str, str]],
|
|
138
|
+
file_path: Optional[str] = None,
|
|
139
|
+
) -> Tuple[bool, str, List[str], Optional[ReplacementFailureReport]]:
|
|
140
|
+
"""Apply replacements using the advanced fallback strategy.
|
|
141
|
+
|
|
142
|
+
Returns (success, new_content, error_messages, failure_report).
|
|
106
143
|
"""
|
|
107
|
-
|
|
144
|
+
if not self.search_replace_manager:
|
|
145
|
+
return False, content, ["Advanced text replacement system is not available"], None
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
result = self.search_replace_manager.replace_with_fallback(content, search_blocks)
|
|
149
|
+
|
|
150
|
+
if result.success:
|
|
151
|
+
logger.info(
|
|
152
|
+
f"Intelligent replacement succeeded using {result.metadata.get('used_strategy', 'unknown')} strategy"
|
|
153
|
+
)
|
|
154
|
+
return True, (result.new_content or content), [], None
|
|
155
|
+
|
|
156
|
+
# Build structured, extensible feedback
|
|
157
|
+
failure_report = self._build_failure_report(content, search_blocks, result, file_path)
|
|
158
|
+
error_message = self._format_failure_message(failure_report)
|
|
159
|
+
logger.warning(f"Intelligent replacement failed: {error_message}")
|
|
160
|
+
return False, content, [error_message], failure_report
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"Error in intelligent replacement: {e}")
|
|
164
|
+
return False, content, [f"System error during text replacement: {str(e)}"], None
|
|
165
|
+
|
|
166
|
+
def _build_failure_report(
|
|
167
|
+
self,
|
|
168
|
+
content: str,
|
|
169
|
+
search_blocks: List[Tuple[str, str]],
|
|
170
|
+
result: Any,
|
|
171
|
+
file_path: Optional[str] = None,
|
|
172
|
+
) -> ReplacementFailureReport:
|
|
173
|
+
blocks_analysis: List[ReplacementFailureBlockAnalysis] = []
|
|
174
|
+
for search_text, _ in search_blocks:
|
|
175
|
+
analysis = self._analyze_search_block_similarity(content, search_text)
|
|
176
|
+
blocks_analysis.append(analysis)
|
|
177
|
+
|
|
178
|
+
return ReplacementFailureReport(
|
|
179
|
+
file_path=file_path,
|
|
180
|
+
used_strategy=result.metadata.get("used_strategy"),
|
|
181
|
+
tried_strategies=result.metadata.get("tried_strategies", []),
|
|
182
|
+
suggestions=self._generate_common_suggestions(content),
|
|
183
|
+
blocks=blocks_analysis,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def _format_failure_message(self, report: ReplacementFailureReport) -> str:
|
|
187
|
+
"""Format a human-friendly failure message while keeping details in the report.
|
|
108
188
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
line_info = f"第{issue.position.line}行"
|
|
112
|
-
if issue.position.column:
|
|
113
|
-
line_info += f", 第{issue.position.column}列"
|
|
189
|
+
Args:
|
|
190
|
+
report: ReplacementFailureReport containing structured failure information
|
|
114
191
|
|
|
115
|
-
|
|
116
|
-
|
|
192
|
+
Returns:
|
|
193
|
+
str: A formatted error message with detailed analysis and suggestions
|
|
194
|
+
|
|
195
|
+
Example output for single file with file_path:
|
|
196
|
+
```
|
|
197
|
+
Text replacement failed in file 'src/example.py' after trying multiple strategies.
|
|
198
|
+
---
|
|
199
|
+
[[Block 1 Analysis:
|
|
200
|
+
🔍 Search text: 'def old_function():'
|
|
201
|
+
📏 Best match similarity: 75.0%
|
|
202
|
+
📍 Best match lines: 42-45
|
|
203
|
+
📝 Actual content: 'def old_function_name():'
|
|
204
|
+
⚠️ Moderate similarity — check whitespace, indentation, or line endings
|
|
205
|
+
|
|
206
|
+
📋 Strategies attempted: string, similarity, patch
|
|
207
|
+
|
|
208
|
+
💡 Suggested actions:
|
|
209
|
+
• File uses LF (\\n) — ensure SEARCH blocks match exact line endings
|
|
210
|
+
• Use read_file tool to examine exact content around the target location
|
|
211
|
+
• Consider searching for a smaller, more unique fragment first]]
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Example output without file_path:
|
|
215
|
+
```
|
|
216
|
+
Text replacement failed after trying multiple strategies.
|
|
217
|
+
---
|
|
218
|
+
[[Block 1 Analysis:
|
|
219
|
+
🔍 Search text: 'import os'
|
|
220
|
+
📏 Best match similarity: 0.0%
|
|
221
|
+
📍 Best match lines: 1-0
|
|
222
|
+
📝 Actual content: ''
|
|
223
|
+
❌ No similar content found — verify the target location and file
|
|
224
|
+
|
|
225
|
+
📋 Strategies attempted: string, similarity, patch
|
|
226
|
+
|
|
227
|
+
💡 Suggested actions:
|
|
228
|
+
• Use read_file tool to examine exact content around the target location
|
|
229
|
+
• Consider searching for a smaller, more unique fragment first]]
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Example output with multiple blocks:
|
|
233
|
+
```
|
|
234
|
+
Text replacement failed in file 'utils.py' after trying multiple strategies.
|
|
235
|
+
---
|
|
236
|
+
[[Block 1 Analysis:
|
|
237
|
+
🔍 Search text: 'class Helper:'
|
|
238
|
+
📏 Best match similarity: 85.0%
|
|
239
|
+
📍 Best match lines: 10-12
|
|
240
|
+
📝 Actual content: 'class HelperClass:'
|
|
241
|
+
✅ High similarity — try using the exact content above as SEARCH block
|
|
242
|
+
|
|
243
|
+
Block 2 Analysis:
|
|
244
|
+
🔍 Search text: 'def process():'
|
|
245
|
+
📏 Best match similarity: 60.0%
|
|
246
|
+
📍 Best match lines: 25-27
|
|
247
|
+
📝 Actual content: 'def process_data():'
|
|
248
|
+
⚠️ Moderate similarity — check whitespace, indentation, or line endings
|
|
249
|
+
|
|
250
|
+
📋 Strategies attempted: string, similarity, patch
|
|
251
|
+
|
|
252
|
+
💡 Suggested actions:
|
|
253
|
+
• File uses LF (\\n) — ensure SEARCH blocks match exact line endings
|
|
254
|
+
• Use read_file tool to examine exact content around the target location
|
|
255
|
+
• Consider searching for a smaller, more unique fragment first]]
|
|
256
|
+
```
|
|
257
|
+
"""
|
|
258
|
+
if report.file_path:
|
|
259
|
+
base_error = f"Text replacement failed in file '{report.file_path}' after trying multiple strategies."
|
|
260
|
+
else:
|
|
261
|
+
base_error = "Text replacement failed after trying multiple strategies."
|
|
262
|
+
|
|
263
|
+
analysis_parts: List[str] = []
|
|
264
|
+
for idx, blk in enumerate(report.blocks, 1):
|
|
265
|
+
analysis = [
|
|
266
|
+
f"🔍 Search text: {repr(blk.search_preview)}",
|
|
267
|
+
f"📏 Best match similarity: {blk.similarity:.1%}",
|
|
268
|
+
]
|
|
269
|
+
if blk.start_line != -1:
|
|
270
|
+
analysis.append(f"📍 Best match lines: {blk.start_line}-{blk.end_line}")
|
|
271
|
+
analysis.append(f"📝 Actual content: {repr(blk.best_window_preview)}")
|
|
272
|
+
if blk.hints:
|
|
273
|
+
analysis.extend(blk.hints)
|
|
274
|
+
analysis_parts.append(f"Block {idx} Analysis:\n" + "\n".join(analysis))
|
|
275
|
+
|
|
276
|
+
if report.tried_strategies:
|
|
277
|
+
analysis_parts.append(
|
|
278
|
+
f"📋 Strategies attempted: {', '.join(report.tried_strategies)}"
|
|
117
279
|
)
|
|
118
|
-
|
|
119
|
-
|
|
280
|
+
if report.suggestions:
|
|
281
|
+
analysis_parts.append("💡 Suggested actions:\n" + "\n".join(report.suggestions))
|
|
282
|
+
|
|
283
|
+
detailed_analysis = "\n\n".join(analysis_parts)
|
|
284
|
+
return add_hint_to_text(base_error, detailed_analysis)
|
|
285
|
+
|
|
286
|
+
def _analyze_search_block_similarity(
|
|
287
|
+
self, content: str, search_text: str
|
|
288
|
+
) -> ReplacementFailureBlockAnalysis:
|
|
289
|
+
"""Analyze how well a SEARCH block matches the file content and produce hints."""
|
|
290
|
+
try:
|
|
291
|
+
similarity_finder = TextSimilarity(search_text, content)
|
|
292
|
+
similarity, best_window = similarity_finder.get_best_matching_window()
|
|
293
|
+
start_line, end_line = self._find_line_numbers(content, best_window)
|
|
294
|
+
|
|
295
|
+
hints: List[str] = []
|
|
296
|
+
if similarity > 0.8:
|
|
297
|
+
hints.append("✅ High similarity — try using the exact content above as SEARCH block")
|
|
298
|
+
elif similarity > 0.5:
|
|
299
|
+
hints.append(
|
|
300
|
+
"⚠️ Moderate similarity — check whitespace, indentation, or line endings"
|
|
301
|
+
)
|
|
302
|
+
else:
|
|
303
|
+
hints.append(
|
|
304
|
+
"❌ No similar content found — verify the target location and file"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return ReplacementFailureBlockAnalysis(
|
|
308
|
+
search_preview=(search_text[:50] + "..." if len(search_text) > 50 else search_text),
|
|
309
|
+
similarity=float(similarity),
|
|
310
|
+
start_line=start_line,
|
|
311
|
+
end_line=end_line,
|
|
312
|
+
best_window_preview=(
|
|
313
|
+
best_window[:100] + "..." if len(best_window) > 100 else best_window
|
|
314
|
+
),
|
|
315
|
+
hints=hints,
|
|
316
|
+
)
|
|
317
|
+
except Exception as e:
|
|
318
|
+
return ReplacementFailureBlockAnalysis(
|
|
319
|
+
search_preview=(search_text[:50] + "..." if len(search_text) > 50 else search_text),
|
|
320
|
+
similarity=0.0,
|
|
321
|
+
start_line=-1,
|
|
322
|
+
end_line=-1,
|
|
323
|
+
best_window_preview=f"<analysis error: {str(e)}>",
|
|
324
|
+
hints=["❌ Error analyzing block"],
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
def _generate_common_suggestions(self, content: str) -> List[str]:
|
|
328
|
+
"""Generate general suggestions to help the LLM fix mismatches."""
|
|
329
|
+
suggestions: List[str] = []
|
|
330
|
+
|
|
331
|
+
has_crlf = '\r\n' in content
|
|
332
|
+
has_lf = '\n' in content and '\r\n' not in content
|
|
333
|
+
if has_crlf or has_lf:
|
|
334
|
+
line_ending = "CRLF (\\r\\n)" if has_crlf else "LF (\\n)"
|
|
335
|
+
suggestions.append(
|
|
336
|
+
f"• File uses {line_ending} — ensure SEARCH blocks match exact line endings"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
content_lines = content.splitlines()
|
|
340
|
+
has_trailing_spaces = any(
|
|
341
|
+
line.endswith(' ') or line.endswith('\t') for line in content_lines
|
|
342
|
+
)
|
|
343
|
+
if has_trailing_spaces:
|
|
344
|
+
suggestions.append(
|
|
345
|
+
"• File contains trailing whitespace — include exact spacing in SEARCH blocks"
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
suggestions.append(
|
|
349
|
+
"• Use read_file tool to examine exact content around the target location"
|
|
350
|
+
)
|
|
351
|
+
suggestions.append(
|
|
352
|
+
"• Consider searching for a smaller, more unique fragment first"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
return suggestions
|
|
356
|
+
|
|
357
|
+
|
|
120
358
|
|
|
359
|
+
|
|
360
|
+
def parse_whole_text(self, text: str) -> List[PathAndCode]:
|
|
361
|
+
'''
|
|
362
|
+
从文本中抽取如下格式代码(two_line_mode):
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
##File: /project/path/src/autocoder/index/index.py
|
|
366
|
+
<<<<<<< SEARCH
|
|
367
|
+
=======
|
|
368
|
+
>>>>>>> REPLACE
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
或者 (one_line_mode)
|
|
372
|
+
|
|
373
|
+
```python:/project/path/src/autocoder/index/index.py
|
|
374
|
+
<<<<<<< SEARCH
|
|
375
|
+
=======
|
|
376
|
+
>>>>>>> REPLACE
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
'''
|
|
380
|
+
HEAD = self.SEARCH_MARKER
|
|
381
|
+
DIVIDER = self.DIVIDER_MARKER
|
|
382
|
+
UPDATED = self.REPLACE_MARKER
|
|
383
|
+
lines = text.split("\n")
|
|
384
|
+
lines_len = len(lines)
|
|
385
|
+
start_marker_count = 0
|
|
386
|
+
block = []
|
|
387
|
+
path_and_code_list = []
|
|
388
|
+
# two_line_mode or one_line_mode
|
|
389
|
+
current_editblock_mode = "two_line_mode"
|
|
390
|
+
current_editblock_path = None
|
|
391
|
+
|
|
392
|
+
def guard(index):
|
|
393
|
+
return index + 1 < lines_len
|
|
394
|
+
|
|
395
|
+
def start_marker(line, index):
|
|
396
|
+
nonlocal current_editblock_mode
|
|
397
|
+
nonlocal current_editblock_path
|
|
398
|
+
if (
|
|
399
|
+
line.startswith(self.fence_0)
|
|
400
|
+
and guard(index)
|
|
401
|
+
and ":" in line
|
|
402
|
+
and lines[index + 1].startswith(HEAD)
|
|
403
|
+
):
|
|
404
|
+
|
|
405
|
+
current_editblock_mode = "one_line_mode"
|
|
406
|
+
current_editblock_path = line.split(":", 1)[1].strip()
|
|
407
|
+
return True
|
|
408
|
+
|
|
409
|
+
if (
|
|
410
|
+
line.startswith(self.fence_0)
|
|
411
|
+
and guard(index)
|
|
412
|
+
and lines[index + 1].startswith("##File:")
|
|
413
|
+
):
|
|
414
|
+
current_editblock_mode = "two_line_mode"
|
|
415
|
+
current_editblock_path = None
|
|
416
|
+
return True
|
|
417
|
+
|
|
418
|
+
return False
|
|
419
|
+
|
|
420
|
+
def end_marker(line, index):
|
|
421
|
+
return line.startswith(self.fence_1) and UPDATED in lines[index - 1]
|
|
422
|
+
|
|
423
|
+
for index, line in enumerate(lines):
|
|
424
|
+
if start_marker(line, index) and start_marker_count == 0:
|
|
425
|
+
start_marker_count += 1
|
|
426
|
+
elif end_marker(line, index) and start_marker_count == 1:
|
|
427
|
+
start_marker_count -= 1
|
|
428
|
+
if block:
|
|
429
|
+
if current_editblock_mode == "two_line_mode":
|
|
430
|
+
path = block[0].split(":", 1)[1].strip()
|
|
431
|
+
content = "\n".join(block[1:])
|
|
432
|
+
else:
|
|
433
|
+
path = current_editblock_path
|
|
434
|
+
content = "\n".join(block)
|
|
435
|
+
block = []
|
|
436
|
+
path_and_code_list.append(
|
|
437
|
+
PathAndCode(path=path, content=content))
|
|
438
|
+
elif start_marker_count > 0:
|
|
439
|
+
block.append(line)
|
|
440
|
+
|
|
441
|
+
return path_and_code_list
|
|
442
|
+
|
|
443
|
+
def get_edits(self, content: str):
|
|
444
|
+
edits = self.parse_whole_text(content)
|
|
445
|
+
HEAD = self.SEARCH_MARKER
|
|
446
|
+
DIVIDER = self.DIVIDER_MARKER
|
|
447
|
+
UPDATED = self.REPLACE_MARKER
|
|
448
|
+
result = []
|
|
449
|
+
for edit in edits:
|
|
450
|
+
heads = []
|
|
451
|
+
updates = []
|
|
452
|
+
c = edit.content
|
|
453
|
+
in_head = False
|
|
454
|
+
in_updated = False
|
|
455
|
+
# 使用 splitlines(keepends=True) 来保留换行符信息
|
|
456
|
+
lines = c.splitlines(keepends=True)
|
|
457
|
+
for line in lines:
|
|
458
|
+
if line.strip() == HEAD:
|
|
459
|
+
in_head = True
|
|
460
|
+
continue
|
|
461
|
+
if line.strip() == DIVIDER:
|
|
462
|
+
in_head = False
|
|
463
|
+
in_updated = True
|
|
464
|
+
continue
|
|
465
|
+
if line.strip() == UPDATED:
|
|
466
|
+
in_head = False
|
|
467
|
+
in_updated = False
|
|
468
|
+
continue
|
|
469
|
+
if in_head:
|
|
470
|
+
heads.append(line)
|
|
471
|
+
if in_updated:
|
|
472
|
+
updates.append(line)
|
|
473
|
+
|
|
474
|
+
# 直接拼接,保留原始的换行符
|
|
475
|
+
head_content = "".join(heads)
|
|
476
|
+
update_content = "".join(updates)
|
|
477
|
+
|
|
478
|
+
# 去掉可能的末尾换行符以避免重复
|
|
479
|
+
if head_content.endswith('\n'):
|
|
480
|
+
head_content = head_content[:-1]
|
|
481
|
+
if update_content.endswith('\n'):
|
|
482
|
+
update_content = update_content[:-1]
|
|
483
|
+
|
|
484
|
+
result.append((edit.path, head_content, update_content))
|
|
485
|
+
return result
|
|
121
486
|
|
|
487
|
+
def replace_in_multiple_files(self, diff_content: str, source_dir: str, abs_project_dir: str) -> ToolResult:
|
|
488
|
+
"""Replace content in multiple files when path is '*'.
|
|
489
|
+
|
|
490
|
+
Enhanced with structured failure feedback in result.content when any block fails.
|
|
491
|
+
"""
|
|
492
|
+
try:
|
|
493
|
+
# 使用新的解析方法解析多文件格式
|
|
494
|
+
codes = self.get_edits(diff_content)
|
|
495
|
+
if not codes:
|
|
496
|
+
return ToolResult(success=False, message="No valid edit blocks found in diff content")
|
|
497
|
+
|
|
498
|
+
file_content_mapping: Dict[str, str] = {}
|
|
499
|
+
failed_blocks: List[Tuple[str, str, str]] = []
|
|
500
|
+
errors: List[str] = []
|
|
501
|
+
failed_details_by_file: Dict[str, Any] = {}
|
|
502
|
+
|
|
503
|
+
# 按文件分组处理块
|
|
504
|
+
file_blocks_map: Dict[str, List[Tuple[str, str]]] = {}
|
|
505
|
+
for block in codes:
|
|
506
|
+
file_path, head, update = block
|
|
507
|
+
abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
|
|
508
|
+
|
|
509
|
+
# Security check
|
|
510
|
+
if not abs_file_path.startswith(abs_project_dir):
|
|
511
|
+
errors.append(f"Access denied to file: {file_path}")
|
|
512
|
+
continue
|
|
513
|
+
|
|
514
|
+
if file_path not in file_blocks_map:
|
|
515
|
+
file_blocks_map[file_path] = []
|
|
516
|
+
file_blocks_map[file_path].append((head, update))
|
|
517
|
+
|
|
518
|
+
# 对每个文件使用智能替换策略
|
|
519
|
+
for file_path, blocks in file_blocks_map.items():
|
|
520
|
+
abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
|
|
521
|
+
|
|
522
|
+
if not os.path.exists(abs_file_path):
|
|
523
|
+
# New file - 对于新文件,直接使用所有更新块的内容
|
|
524
|
+
new_content = "\n".join([update for head, update in blocks if update])
|
|
525
|
+
file_content_mapping[file_path] = new_content
|
|
526
|
+
else:
|
|
527
|
+
# 读取现有文件内容
|
|
528
|
+
existing_content = FileUtils.read_file(abs_file_path)
|
|
529
|
+
|
|
530
|
+
# 使用智能替换策略(与 normal 方法保持一致)
|
|
531
|
+
logger.info(f"Using intelligent replacement for file: {file_path}")
|
|
532
|
+
success, new_content, intelligent_errors = self._intelligent_replace(existing_content, blocks)
|
|
533
|
+
|
|
534
|
+
if success:
|
|
535
|
+
file_content_mapping[file_path] = new_content
|
|
536
|
+
logger.info(f"Intelligent replacement succeeded for {len(blocks)} blocks in {file_path}")
|
|
537
|
+
else:
|
|
538
|
+
logger.warning(f"Intelligent replacement failed for {file_path}: {intelligent_errors}")
|
|
539
|
+
# Build structured details for each failed file
|
|
540
|
+
try:
|
|
541
|
+
# reuse new failure report via internal method
|
|
542
|
+
_, _, _, failure_report = self._apply_replacements_with_fallback(existing_content, blocks, file_path)
|
|
543
|
+
if failure_report is not None:
|
|
544
|
+
failed_details_by_file[file_path] = failure_report.dict()
|
|
545
|
+
except Exception:
|
|
546
|
+
pass
|
|
547
|
+
errors.extend([f"{file_path}: {err}" for err in intelligent_errors])
|
|
548
|
+
failed_blocks.extend([(file_path, head, update) for head, update in blocks])
|
|
549
|
+
|
|
550
|
+
if failed_blocks:
|
|
551
|
+
total_blocks = sum(len(blocks) for blocks in file_blocks_map.values())
|
|
552
|
+
failed_count = len(failed_blocks)
|
|
553
|
+
message = (
|
|
554
|
+
f"Failed to apply {failed_count}/{total_blocks} blocks across multiple files. "
|
|
555
|
+
f"See content.failed_files for details."
|
|
556
|
+
)
|
|
557
|
+
content_details: Dict[str, Any] = {
|
|
558
|
+
"failed_files": failed_details_by_file,
|
|
559
|
+
"total_blocks": total_blocks,
|
|
560
|
+
"failed_blocks": failed_count,
|
|
561
|
+
"errors": errors,
|
|
562
|
+
}
|
|
563
|
+
return ToolResult(success=False, message=message, content=content_details)
|
|
564
|
+
|
|
565
|
+
# Apply changes to files
|
|
566
|
+
changed_files = []
|
|
567
|
+
for file_path, new_content in file_content_mapping.items():
|
|
568
|
+
abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
|
|
569
|
+
os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
|
|
570
|
+
|
|
571
|
+
# Handle checkpoint manager if available
|
|
572
|
+
if self.agent and self.agent.checkpoint_manager:
|
|
573
|
+
changes = {
|
|
574
|
+
file_path: CheckpointFileChange(
|
|
575
|
+
file_path=file_path,
|
|
576
|
+
content=new_content,
|
|
577
|
+
is_deletion=False,
|
|
578
|
+
is_new=not os.path.exists(abs_file_path)
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
change_group_id = self.args.event_file
|
|
582
|
+
|
|
583
|
+
conversation_id = self.agent.conversation_config.conversation_id if self.agent else None
|
|
584
|
+
logger.debug(f"多文件对话检查点调试 - conversation_config存在: {self.agent.conversation_config is not None}, conversation_id: {conversation_id}")
|
|
585
|
+
|
|
586
|
+
if conversation_id:
|
|
587
|
+
first_message_id, last_message_id = self.agent.get_conversation_message_range()
|
|
588
|
+
logger.debug(f"多文件获取消息范围 - first_message_id: {first_message_id}, last_message_id: {last_message_id}")
|
|
589
|
+
|
|
590
|
+
self.agent.checkpoint_manager.apply_changes_with_conversation(
|
|
591
|
+
changes=changes,
|
|
592
|
+
conversation_id=conversation_id,
|
|
593
|
+
first_message_id=first_message_id,
|
|
594
|
+
last_message_id=last_message_id,
|
|
595
|
+
change_group_id=change_group_id,
|
|
596
|
+
metadata={"event_file": self.args.event_file}
|
|
597
|
+
)
|
|
598
|
+
logger.debug(f"多文件已调用 apply_changes_with_conversation")
|
|
599
|
+
else:
|
|
600
|
+
logger.warning(f"多文件conversation_id 为 None,跳过对话检查点保存")
|
|
601
|
+
else:
|
|
602
|
+
with open(abs_file_path, 'w', encoding='utf-8') as f:
|
|
603
|
+
f.write(new_content)
|
|
604
|
+
|
|
605
|
+
changed_files.append(file_path)
|
|
606
|
+
|
|
607
|
+
# Record file change for AgenticEdit
|
|
608
|
+
if self.agent:
|
|
609
|
+
rel_path = os.path.relpath(abs_file_path, abs_project_dir)
|
|
610
|
+
self.agent.record_file_change(rel_path, "modified", content=new_content)
|
|
611
|
+
|
|
612
|
+
# 计算统计信息(与 normal 方法保持一致)
|
|
613
|
+
total_blocks = sum(len(blocks) for blocks in file_blocks_map.values())
|
|
614
|
+
applied_blocks = total_blocks - len(failed_blocks)
|
|
615
|
+
|
|
616
|
+
# 构建成功消息(与 normal 方法保持一致)
|
|
617
|
+
if errors:
|
|
618
|
+
success_message = f"Successfully applied {applied_blocks}/{total_blocks} blocks across {len(changed_files)} files: {', '.join(changed_files)}. Warnings: {'; '.join(errors)}"
|
|
619
|
+
else:
|
|
620
|
+
success_message = f"Successfully applied {applied_blocks}/{total_blocks} blocks across {len(changed_files)} files: {', '.join(changed_files)}"
|
|
621
|
+
|
|
622
|
+
# Run linter check if enabled for multiple files
|
|
623
|
+
result = ToolResult(success=True, message=success_message)
|
|
624
|
+
if self.linter_config and self.linter_config.enabled and self.linter_config.check_after_modification:
|
|
625
|
+
# Collect all modified files for linting
|
|
626
|
+
files_to_lint = []
|
|
627
|
+
for file_path in changed_files:
|
|
628
|
+
if self.should_lint(file_path):
|
|
629
|
+
abs_path = os.path.abspath(os.path.join(source_dir, file_path))
|
|
630
|
+
files_to_lint.append(abs_path)
|
|
631
|
+
|
|
632
|
+
if files_to_lint:
|
|
633
|
+
logger.info(f"Running linter check on {len(files_to_lint)} modified files")
|
|
634
|
+
lint_report = self.lint_files(files_to_lint)
|
|
635
|
+
if lint_report:
|
|
636
|
+
result = self.handle_lint_results(result, lint_report)
|
|
637
|
+
|
|
638
|
+
return result
|
|
639
|
+
|
|
640
|
+
except Exception as e:
|
|
641
|
+
logger.error(f"Error in multiple file replacement: {str(e)}")
|
|
642
|
+
return ToolResult(success=False, message=f"Error processing multiple file replacement: {str(e)}")
|
|
122
643
|
|
|
123
644
|
def replace_in_file_normal(self, file_path: str, diff_content: str, source_dir: str, abs_project_dir: str, abs_file_path: str) -> ToolResult:
|
|
124
|
-
"""Replace content in file directly without using shadow manager
|
|
645
|
+
"""Replace content in file directly without using shadow manager.
|
|
646
|
+
|
|
647
|
+
Adds structured failure details to result.content when no blocks applied.
|
|
648
|
+
"""
|
|
125
649
|
try:
|
|
126
650
|
# Read original content
|
|
127
651
|
if not os.path.exists(abs_file_path):
|
|
@@ -132,7 +656,7 @@ class ReplaceInFileToolResolver(BaseToolResolver):
|
|
|
132
656
|
with open(abs_file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
133
657
|
original_content = f.read()
|
|
134
658
|
|
|
135
|
-
parsed_blocks = self.
|
|
659
|
+
parsed_blocks = self.parse_search_replace_blocks(diff_content)
|
|
136
660
|
if not parsed_blocks:
|
|
137
661
|
return ToolResult(success=False, message=get_message_with_format("replace_in_file.no_valid_blocks"))
|
|
138
662
|
|
|
@@ -140,24 +664,27 @@ class ReplaceInFileToolResolver(BaseToolResolver):
|
|
|
140
664
|
applied_count = 0
|
|
141
665
|
errors = []
|
|
142
666
|
|
|
143
|
-
#
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
context_start = max(0, original_content.find(search_block[:20]) - 100)
|
|
155
|
-
context_end = min(len(original_content), context_start + 200 + len(search_block[:20]))
|
|
156
|
-
logger.warning(f"Approximate context in file:\n---\n{original_content[context_start:context_end]}\n---")
|
|
157
|
-
errors.append(error_message)
|
|
667
|
+
# 使用智能替换
|
|
668
|
+
logger.info("Using intelligent replacement with multiple strategies")
|
|
669
|
+
success, new_content, intelligent_errors = self._intelligent_replace(current_content, parsed_blocks)
|
|
670
|
+
|
|
671
|
+
if success:
|
|
672
|
+
current_content = new_content
|
|
673
|
+
applied_count = len(parsed_blocks)
|
|
674
|
+
logger.info(f"Intelligent replacement succeeded for all {applied_count} blocks")
|
|
675
|
+
else:
|
|
676
|
+
logger.warning(f"Intelligent replacement failed: {intelligent_errors}")
|
|
677
|
+
errors.extend(intelligent_errors)
|
|
158
678
|
|
|
159
679
|
if applied_count == 0 and errors:
|
|
160
|
-
|
|
680
|
+
# Provide structured report
|
|
681
|
+
_, _, _, failure_report = self._apply_replacements_with_fallback(original_content, parsed_blocks, file_path)
|
|
682
|
+
content_details = failure_report.dict() if failure_report else None
|
|
683
|
+
return ToolResult(
|
|
684
|
+
success=False,
|
|
685
|
+
message=get_message_with_format("replace_in_file.apply_failed", errors="\n\n".join(errors)),
|
|
686
|
+
content=content_details,
|
|
687
|
+
)
|
|
161
688
|
|
|
162
689
|
# Write the modified content back to file
|
|
163
690
|
if self.agent and self.agent.checkpoint_manager:
|
|
@@ -166,93 +693,86 @@ class ReplaceInFileToolResolver(BaseToolResolver):
|
|
|
166
693
|
file_path=file_path,
|
|
167
694
|
content=current_content,
|
|
168
695
|
is_deletion=False,
|
|
169
|
-
|
|
696
|
+
is_new=False
|
|
170
697
|
)
|
|
171
698
|
}
|
|
172
699
|
change_group_id = self.args.event_file
|
|
173
700
|
|
|
174
|
-
self.agent.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
701
|
+
conversation_id = self.agent.conversation_config.conversation_id if self.agent else None
|
|
702
|
+
logger.debug(f"对话检查点调试 - conversation_config存在: {self.agent.conversation_config is not None}, conversation_id: {conversation_id}")
|
|
703
|
+
|
|
704
|
+
if conversation_id:
|
|
705
|
+
first_message_id, last_message_id = self.agent.get_conversation_message_range()
|
|
706
|
+
logger.debug(f"获取消息范围 - first_message_id: {first_message_id}, last_message_id: {last_message_id}")
|
|
707
|
+
|
|
708
|
+
self.agent.checkpoint_manager.apply_changes_with_conversation(
|
|
709
|
+
changes=changes,
|
|
710
|
+
conversation_id=conversation_id,
|
|
711
|
+
first_message_id=first_message_id,
|
|
712
|
+
last_message_id=last_message_id,
|
|
713
|
+
change_group_id=change_group_id,
|
|
714
|
+
metadata={"event_file": self.args.event_file}
|
|
715
|
+
)
|
|
716
|
+
logger.debug(f"已调用 apply_changes_with_conversation")
|
|
717
|
+
else:
|
|
718
|
+
logger.warning(f"conversation_id 为 None,跳过对话检查点保存")
|
|
180
719
|
else:
|
|
181
720
|
with open(abs_file_path, 'w', encoding='utf-8') as f:
|
|
182
721
|
f.write(current_content)
|
|
183
722
|
|
|
184
723
|
logger.info(f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}")
|
|
185
724
|
|
|
186
|
-
#
|
|
187
|
-
lint_results = None
|
|
188
|
-
lint_message = ""
|
|
189
|
-
formatted_issues = ""
|
|
190
|
-
has_lint_issues = False
|
|
191
|
-
|
|
192
|
-
# 检查是否启用了Lint功能
|
|
193
|
-
enable_lint = self.args.enable_auto_fix_lint
|
|
194
|
-
logger.info(f"检查Lint功能状态: enable_lint={enable_lint}")
|
|
195
|
-
|
|
196
|
-
if enable_lint:
|
|
197
|
-
try:
|
|
198
|
-
if self.agent.linter:
|
|
199
|
-
lint_results = self.agent.linter.lint_file(file_path)
|
|
200
|
-
if lint_results and lint_results.issues:
|
|
201
|
-
# 过滤 lint 结果,只保留 ERROR 和 WARNING 级别的问题
|
|
202
|
-
filtered_results = self._filter_lint_issues(lint_results)
|
|
203
|
-
if filtered_results.issues:
|
|
204
|
-
has_lint_issues = True
|
|
205
|
-
# 格式化 lint 问题
|
|
206
|
-
formatted_issues = self._format_lint_issues(filtered_results)
|
|
207
|
-
lint_message = f"\n\n代码质量检查发现 {len(filtered_results.issues)} 个问题"
|
|
208
|
-
except Exception as e:
|
|
209
|
-
logger.error(f"Lint 检查失败: {str(e)}")
|
|
210
|
-
lint_message = "\n\n尝试进行代码质量检查时出错。"
|
|
211
|
-
else:
|
|
212
|
-
logger.info("代码质量检查已禁用")
|
|
213
|
-
|
|
214
|
-
# 构建包含 lint 结果的返回消息
|
|
725
|
+
# 构建成功消息
|
|
215
726
|
if errors:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
727
|
+
final_message = get_message_with_format("replace_in_file.apply_success_with_warnings",
|
|
728
|
+
applied=applied_count,
|
|
729
|
+
total=len(parsed_blocks),
|
|
730
|
+
file_path=file_path,
|
|
731
|
+
errors="\n".join(errors))
|
|
221
732
|
else:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
733
|
+
final_message = get_message_with_format("replace_in_file.apply_success",
|
|
734
|
+
applied=applied_count,
|
|
735
|
+
total=len(parsed_blocks),
|
|
736
|
+
file_path=file_path)
|
|
226
737
|
|
|
227
738
|
# 变更跟踪,回调AgenticEdit
|
|
228
739
|
if self.agent:
|
|
229
740
|
rel_path = os.path.relpath(abs_file_path, abs_project_dir)
|
|
230
741
|
self.agent.record_file_change(rel_path, "modified", diff=diff_content, content=current_content)
|
|
231
|
-
|
|
232
|
-
# 附加 lint 结果到返回内容
|
|
233
|
-
result_content = {
|
|
234
|
-
"content": current_content,
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
# 只有在启用Lint时才添加Lint结果
|
|
238
|
-
if enable_lint:
|
|
239
|
-
message = message + "\n" + lint_message
|
|
240
|
-
result_content["lint_results"] = {
|
|
241
|
-
"has_issues": has_lint_issues,
|
|
242
|
-
"issues": formatted_issues if has_lint_issues else None
|
|
243
|
-
}
|
|
244
742
|
|
|
245
|
-
|
|
743
|
+
# Run linter check if enabled
|
|
744
|
+
result = ToolResult(success=True, message=final_message)
|
|
745
|
+
if self.should_lint(file_path) and self.linter_config and self.linter_config.check_after_modification:
|
|
746
|
+
logger.info(f"Running linter check on modified file: {file_path}")
|
|
747
|
+
lint_report = self.lint_files([abs_file_path])
|
|
748
|
+
if lint_report:
|
|
749
|
+
result = self.handle_lint_results(result, lint_report)
|
|
750
|
+
|
|
751
|
+
return result
|
|
246
752
|
except Exception as e:
|
|
247
753
|
logger.error(f"Error writing replaced content to file '{file_path}': {str(e)}")
|
|
248
754
|
return ToolResult(success=False, message=get_message_with_format("replace_in_file.write_error", error=str(e)))
|
|
249
755
|
|
|
250
756
|
def resolve(self) -> ToolResult:
|
|
251
757
|
"""Resolve the replace in file tool by calling the appropriate implementation"""
|
|
758
|
+
# Check if we are in plan mode
|
|
759
|
+
if self.args.agentic_mode == "plan":
|
|
760
|
+
return ToolResult(
|
|
761
|
+
success=False,
|
|
762
|
+
message="Currently in plan mode, modification tools are disabled. "
|
|
763
|
+
)
|
|
764
|
+
|
|
252
765
|
file_path = self.tool.path
|
|
253
|
-
diff_content = self.tool.diff
|
|
766
|
+
diff_content = self.tool.diff.strip()
|
|
254
767
|
source_dir = self.args.source_dir or "."
|
|
255
768
|
abs_project_dir = os.path.abspath(source_dir)
|
|
769
|
+
|
|
770
|
+
# Check if this is multiple file mode (path="*")
|
|
771
|
+
if file_path == "*":
|
|
772
|
+
logger.info("Multiple file replacement mode detected")
|
|
773
|
+
return self.replace_in_multiple_files(diff_content, source_dir, abs_project_dir)
|
|
774
|
+
|
|
775
|
+
# Single file mode
|
|
256
776
|
abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
|
|
257
777
|
|
|
258
778
|
# Security check
|