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
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interactive process wrapper for shell command execution.
|
|
3
|
+
|
|
4
|
+
This module provides interactive process functionality including:
|
|
5
|
+
- Real-time input/output streaming
|
|
6
|
+
- Cross-platform PTY/pipe support
|
|
7
|
+
- Signal handling (Ctrl-C, etc.)
|
|
8
|
+
- Thread-safe operations
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import platform
|
|
13
|
+
import subprocess
|
|
14
|
+
import threading
|
|
15
|
+
import time
|
|
16
|
+
import queue
|
|
17
|
+
import select
|
|
18
|
+
import signal
|
|
19
|
+
from typing import Optional, Dict, Any, Generator, Union, List
|
|
20
|
+
from loguru import logger
|
|
21
|
+
|
|
22
|
+
from .exceptions import CommandExecutionError, ProcessCleanupError
|
|
23
|
+
from .process_cleanup import cleanup_process_tree
|
|
24
|
+
|
|
25
|
+
# Platform-specific imports
|
|
26
|
+
PLATFORM = platform.system()
|
|
27
|
+
PTY_AVAILABLE = False
|
|
28
|
+
WINPTY_AVAILABLE = False
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
if PLATFORM != "Windows":
|
|
32
|
+
import pty
|
|
33
|
+
import termios
|
|
34
|
+
import fcntl
|
|
35
|
+
PTY_AVAILABLE = True
|
|
36
|
+
except ImportError:
|
|
37
|
+
logger.debug("PTY not available on this platform")
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
if PLATFORM == "Windows":
|
|
41
|
+
import winpty # type: ignore
|
|
42
|
+
WINPTY_AVAILABLE = True
|
|
43
|
+
except ImportError:
|
|
44
|
+
logger.debug("winpty not available, falling back to pipes")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class InteractiveProcess:
|
|
48
|
+
"""
|
|
49
|
+
Interactive process wrapper for real-time command execution.
|
|
50
|
+
|
|
51
|
+
This class provides a convenient interface for executing commands that
|
|
52
|
+
require interactive input/output, supporting both PTY and pipe modes
|
|
53
|
+
across different platforms.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
command: Union[str, List[str]],
|
|
59
|
+
cwd: Optional[str] = None,
|
|
60
|
+
env: Optional[Dict[str, str]] = None,
|
|
61
|
+
use_pty: Optional[bool] = None,
|
|
62
|
+
shell: bool = True,
|
|
63
|
+
encoding: str = 'utf-8',
|
|
64
|
+
**kwargs
|
|
65
|
+
):
|
|
66
|
+
"""
|
|
67
|
+
Initialize interactive process.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
command: Command to execute
|
|
71
|
+
cwd: Working directory
|
|
72
|
+
env: Environment variables
|
|
73
|
+
use_pty: Whether to use PTY (auto-detect if None)
|
|
74
|
+
shell: Whether to use shell
|
|
75
|
+
encoding: Text encoding
|
|
76
|
+
**kwargs: Additional subprocess arguments
|
|
77
|
+
"""
|
|
78
|
+
self.command = command
|
|
79
|
+
self.cwd = cwd
|
|
80
|
+
self.env = env
|
|
81
|
+
self.encoding = encoding
|
|
82
|
+
self.shell = shell
|
|
83
|
+
|
|
84
|
+
# Determine if we should use PTY
|
|
85
|
+
if use_pty is None:
|
|
86
|
+
self.use_pty = PTY_AVAILABLE and PLATFORM != "Windows"
|
|
87
|
+
else:
|
|
88
|
+
self.use_pty = use_pty and PTY_AVAILABLE
|
|
89
|
+
|
|
90
|
+
# State management
|
|
91
|
+
self.process: Optional[subprocess.Popen] = None
|
|
92
|
+
self.master_fd: Optional[int] = None
|
|
93
|
+
self.slave_fd: Optional[int] = None
|
|
94
|
+
self.winpty_process = None
|
|
95
|
+
|
|
96
|
+
# Threading
|
|
97
|
+
self.output_queue: queue.Queue = queue.Queue()
|
|
98
|
+
self.error_queue: queue.Queue = queue.Queue()
|
|
99
|
+
self.io_thread: Optional[threading.Thread] = None
|
|
100
|
+
self.alive_event = threading.Event()
|
|
101
|
+
self.started_event = threading.Event()
|
|
102
|
+
|
|
103
|
+
# Monitoring
|
|
104
|
+
self.start_time: Optional[float] = None
|
|
105
|
+
self.end_time: Optional[float] = None
|
|
106
|
+
self.bytes_written = 0
|
|
107
|
+
self.bytes_read = 0
|
|
108
|
+
|
|
109
|
+
logger.debug(f"InteractiveProcess initialized: use_pty={self.use_pty}, platform={PLATFORM}")
|
|
110
|
+
|
|
111
|
+
def start(self) -> None:
|
|
112
|
+
"""Start the interactive process."""
|
|
113
|
+
if self.is_alive():
|
|
114
|
+
raise CommandExecutionError("Process is already running")
|
|
115
|
+
|
|
116
|
+
self.start_time = time.time()
|
|
117
|
+
self.alive_event.set()
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
if self.use_pty:
|
|
121
|
+
self._start_with_pty()
|
|
122
|
+
elif PLATFORM == "Windows" and WINPTY_AVAILABLE:
|
|
123
|
+
self._start_with_winpty()
|
|
124
|
+
else:
|
|
125
|
+
self._start_with_pipes()
|
|
126
|
+
|
|
127
|
+
# Start I/O thread
|
|
128
|
+
self.io_thread = threading.Thread(target=self._io_worker, daemon=True)
|
|
129
|
+
self.io_thread.start()
|
|
130
|
+
|
|
131
|
+
# Give process a moment to start and check if it's valid
|
|
132
|
+
time.sleep(0.1)
|
|
133
|
+
|
|
134
|
+
# Check if process failed immediately (e.g., command not found)
|
|
135
|
+
if not self.is_alive():
|
|
136
|
+
exit_code = self.exit_code
|
|
137
|
+
if exit_code is not None and exit_code != 0:
|
|
138
|
+
self._cleanup()
|
|
139
|
+
raise CommandExecutionError(f"Interactive process failed to start: exit code {exit_code}")
|
|
140
|
+
|
|
141
|
+
self.started_event.set()
|
|
142
|
+
logger.info(f"Interactive process started: PID {self.pid}")
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
self.alive_event.clear()
|
|
146
|
+
self._cleanup()
|
|
147
|
+
raise CommandExecutionError(f"Failed to start interactive process: {e}")
|
|
148
|
+
|
|
149
|
+
def _start_with_pty(self) -> None:
|
|
150
|
+
"""Start process with PTY (Linux/Mac)."""
|
|
151
|
+
if not PTY_AVAILABLE:
|
|
152
|
+
raise CommandExecutionError("PTY not available on this platform")
|
|
153
|
+
|
|
154
|
+
# Create PTY
|
|
155
|
+
self.master_fd, self.slave_fd = pty.openpty()
|
|
156
|
+
|
|
157
|
+
# Configure PTY
|
|
158
|
+
try:
|
|
159
|
+
# Get current terminal settings
|
|
160
|
+
attrs = termios.tcgetattr(self.slave_fd)
|
|
161
|
+
|
|
162
|
+
# Disable echo to avoid duplicate output
|
|
163
|
+
attrs[3] = attrs[3] & ~termios.ECHO
|
|
164
|
+
|
|
165
|
+
# Set raw mode for better control
|
|
166
|
+
attrs[3] = attrs[3] & ~(termios.ICANON | termios.ISIG)
|
|
167
|
+
|
|
168
|
+
termios.tcsetattr(self.slave_fd, termios.TCSANOW, attrs)
|
|
169
|
+
|
|
170
|
+
# Make master non-blocking
|
|
171
|
+
fcntl.fcntl(self.master_fd, fcntl.F_SETFL, os.O_NONBLOCK)
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.warning(f"Failed to configure PTY: {e}")
|
|
175
|
+
|
|
176
|
+
# Start process with PTY
|
|
177
|
+
try:
|
|
178
|
+
self.process = subprocess.Popen(
|
|
179
|
+
self.command,
|
|
180
|
+
stdin=self.slave_fd,
|
|
181
|
+
stdout=self.slave_fd,
|
|
182
|
+
stderr=self.slave_fd,
|
|
183
|
+
cwd=self.cwd,
|
|
184
|
+
env=self.env,
|
|
185
|
+
shell=self.shell,
|
|
186
|
+
start_new_session=True # This automatically calls setsid(), so no need for preexec_fn
|
|
187
|
+
)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
# If process creation fails, clean up PTY fds
|
|
190
|
+
if self.master_fd is not None:
|
|
191
|
+
os.close(self.master_fd)
|
|
192
|
+
self.master_fd = None
|
|
193
|
+
if self.slave_fd is not None:
|
|
194
|
+
os.close(self.slave_fd)
|
|
195
|
+
self.slave_fd = None
|
|
196
|
+
raise CommandExecutionError(f"Failed to start PTY process: {e}")
|
|
197
|
+
|
|
198
|
+
def _start_with_winpty(self) -> None:
|
|
199
|
+
"""Start process with winpty (Windows)."""
|
|
200
|
+
if not WINPTY_AVAILABLE:
|
|
201
|
+
raise CommandExecutionError("winpty not available")
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
# Convert command to string if needed
|
|
205
|
+
if isinstance(self.command, list):
|
|
206
|
+
cmd_str = ' '.join(self.command)
|
|
207
|
+
else:
|
|
208
|
+
cmd_str = self.command
|
|
209
|
+
|
|
210
|
+
# Create winpty process
|
|
211
|
+
self.winpty_process = winpty.PtyProcess.spawn(
|
|
212
|
+
cmd_str,
|
|
213
|
+
cwd=self.cwd,
|
|
214
|
+
env=self.env
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Create a subprocess wrapper for compatibility
|
|
218
|
+
winpty_proc = self.winpty_process # Capture for closure
|
|
219
|
+
self.process = type('MockProcess', (), {
|
|
220
|
+
'pid': winpty_proc.pid,
|
|
221
|
+
'poll': lambda: winpty_proc.exitstatus if not winpty_proc.isalive() else None,
|
|
222
|
+
'wait': lambda timeout=None: winpty_proc.wait(),
|
|
223
|
+
'terminate': lambda: winpty_proc.terminate(),
|
|
224
|
+
'kill': lambda: winpty_proc.terminate(force=True),
|
|
225
|
+
'returncode': property(lambda s: winpty_proc.exitstatus)
|
|
226
|
+
})() # type: ignore
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
raise CommandExecutionError(f"Failed to start winpty process: {e}")
|
|
230
|
+
|
|
231
|
+
def _start_with_pipes(self) -> None:
|
|
232
|
+
"""Start process with regular pipes."""
|
|
233
|
+
self.process = subprocess.Popen(
|
|
234
|
+
self.command,
|
|
235
|
+
stdin=subprocess.PIPE,
|
|
236
|
+
stdout=subprocess.PIPE,
|
|
237
|
+
stderr=subprocess.PIPE,
|
|
238
|
+
cwd=self.cwd,
|
|
239
|
+
env=self.env,
|
|
240
|
+
shell=self.shell,
|
|
241
|
+
text=True,
|
|
242
|
+
encoding=self.encoding,
|
|
243
|
+
bufsize=0, # Unbuffered
|
|
244
|
+
universal_newlines=True
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Make stdout/stderr non-blocking on Unix
|
|
248
|
+
if PLATFORM != "Windows":
|
|
249
|
+
try:
|
|
250
|
+
import fcntl
|
|
251
|
+
if self.process.stdout:
|
|
252
|
+
fcntl.fcntl(self.process.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
|
|
253
|
+
if self.process.stderr:
|
|
254
|
+
fcntl.fcntl(self.process.stderr.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.warning(f"Failed to set non-blocking I/O: {e}")
|
|
257
|
+
|
|
258
|
+
def _io_worker(self) -> None:
|
|
259
|
+
"""I/O worker thread for handling input/output."""
|
|
260
|
+
try:
|
|
261
|
+
if self.use_pty:
|
|
262
|
+
self._pty_io_worker()
|
|
263
|
+
elif self.winpty_process:
|
|
264
|
+
self._winpty_io_worker()
|
|
265
|
+
else:
|
|
266
|
+
self._pipe_io_worker()
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.error(f"I/O worker error: {e}")
|
|
269
|
+
finally:
|
|
270
|
+
self.alive_event.clear()
|
|
271
|
+
|
|
272
|
+
def _pty_io_worker(self) -> None:
|
|
273
|
+
"""I/O worker for PTY mode."""
|
|
274
|
+
while self.alive_event.is_set():
|
|
275
|
+
try:
|
|
276
|
+
# Check if process is still alive
|
|
277
|
+
if self.process and self.process.poll() is not None:
|
|
278
|
+
self.alive_event.clear()
|
|
279
|
+
break
|
|
280
|
+
|
|
281
|
+
# Use select to check for available data
|
|
282
|
+
ready, _, _ = select.select([self.master_fd], [], [], 0.1)
|
|
283
|
+
|
|
284
|
+
if ready and self.master_fd is not None:
|
|
285
|
+
try:
|
|
286
|
+
data = os.read(self.master_fd, 1024)
|
|
287
|
+
if data:
|
|
288
|
+
text = data.decode(self.encoding, errors='replace')
|
|
289
|
+
self.bytes_read += len(data)
|
|
290
|
+
self.output_queue.put(text)
|
|
291
|
+
else:
|
|
292
|
+
# EOF
|
|
293
|
+
self.alive_event.clear()
|
|
294
|
+
break
|
|
295
|
+
except OSError as e:
|
|
296
|
+
if e.errno in (11, 35): # EAGAIN, EWOULDBLOCK
|
|
297
|
+
continue
|
|
298
|
+
else:
|
|
299
|
+
logger.debug(f"PTY read error: {e}")
|
|
300
|
+
self.alive_event.clear()
|
|
301
|
+
break
|
|
302
|
+
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.debug(f"PTY I/O error: {e}")
|
|
305
|
+
break
|
|
306
|
+
|
|
307
|
+
def _winpty_io_worker(self) -> None:
|
|
308
|
+
"""I/O worker for winpty mode."""
|
|
309
|
+
while self.alive_event.is_set():
|
|
310
|
+
try:
|
|
311
|
+
# Check if process is still alive
|
|
312
|
+
if not self.winpty_process or not self.winpty_process.isalive():
|
|
313
|
+
self.alive_event.clear()
|
|
314
|
+
break
|
|
315
|
+
|
|
316
|
+
# Read output
|
|
317
|
+
try:
|
|
318
|
+
if self.winpty_process:
|
|
319
|
+
data = self.winpty_process.read(timeout=100) # 100ms timeout
|
|
320
|
+
if data:
|
|
321
|
+
self.bytes_read += len(data.encode(self.encoding))
|
|
322
|
+
self.output_queue.put(data)
|
|
323
|
+
except Exception:
|
|
324
|
+
# Timeout or no data available
|
|
325
|
+
continue
|
|
326
|
+
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.debug(f"Winpty I/O error: {e}")
|
|
329
|
+
break
|
|
330
|
+
|
|
331
|
+
def _pipe_io_worker(self) -> None:
|
|
332
|
+
"""I/O worker for pipe mode."""
|
|
333
|
+
while self.alive_event.is_set():
|
|
334
|
+
try:
|
|
335
|
+
# Check if process is still alive
|
|
336
|
+
if self.process and self.process.poll() is not None:
|
|
337
|
+
# Read any remaining output
|
|
338
|
+
self._read_remaining_output()
|
|
339
|
+
self.alive_event.clear()
|
|
340
|
+
break
|
|
341
|
+
|
|
342
|
+
# Read stdout
|
|
343
|
+
if PLATFORM == "Windows":
|
|
344
|
+
# Windows doesn't support select on pipes
|
|
345
|
+
self._read_windows_output()
|
|
346
|
+
else:
|
|
347
|
+
# Unix-like systems can use select
|
|
348
|
+
self._read_unix_output()
|
|
349
|
+
|
|
350
|
+
except Exception as e:
|
|
351
|
+
logger.debug(f"Pipe I/O error: {e}")
|
|
352
|
+
break
|
|
353
|
+
|
|
354
|
+
def _read_windows_output(self) -> None:
|
|
355
|
+
"""Read output on Windows (no select support)."""
|
|
356
|
+
if not self.process:
|
|
357
|
+
return
|
|
358
|
+
|
|
359
|
+
# Use peek to check if data is available without blocking
|
|
360
|
+
try:
|
|
361
|
+
import msvcrt
|
|
362
|
+
import sys
|
|
363
|
+
|
|
364
|
+
# This is a simplified approach - in production you might want
|
|
365
|
+
# to use threading for each pipe or polling
|
|
366
|
+
if self.process.stdout:
|
|
367
|
+
# Try to read with a very short timeout
|
|
368
|
+
line = self.process.stdout.readline()
|
|
369
|
+
if line:
|
|
370
|
+
self.bytes_read += len(line.encode(self.encoding))
|
|
371
|
+
self.output_queue.put(line)
|
|
372
|
+
|
|
373
|
+
if self.process.stderr:
|
|
374
|
+
line = self.process.stderr.readline()
|
|
375
|
+
if line:
|
|
376
|
+
self.bytes_read += len(line.encode(self.encoding))
|
|
377
|
+
self.error_queue.put(line)
|
|
378
|
+
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.debug(f"Windows output read error: {e}")
|
|
381
|
+
|
|
382
|
+
def _read_unix_output(self) -> None:
|
|
383
|
+
"""Read output on Unix-like systems."""
|
|
384
|
+
if not self.process:
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
streams = []
|
|
388
|
+
if self.process.stdout:
|
|
389
|
+
streams.append(self.process.stdout)
|
|
390
|
+
if self.process.stderr:
|
|
391
|
+
streams.append(self.process.stderr)
|
|
392
|
+
|
|
393
|
+
if not streams:
|
|
394
|
+
time.sleep(0.1)
|
|
395
|
+
return
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
ready, _, _ = select.select(streams, [], [], 0.1)
|
|
399
|
+
|
|
400
|
+
for stream in ready:
|
|
401
|
+
try:
|
|
402
|
+
if stream == self.process.stdout:
|
|
403
|
+
data = stream.read(1024)
|
|
404
|
+
if data:
|
|
405
|
+
self.bytes_read += len(data.encode(self.encoding))
|
|
406
|
+
self.output_queue.put(data)
|
|
407
|
+
elif stream == self.process.stderr:
|
|
408
|
+
data = stream.read(1024)
|
|
409
|
+
if data:
|
|
410
|
+
self.bytes_read += len(data.encode(self.encoding))
|
|
411
|
+
self.error_queue.put(data)
|
|
412
|
+
except Exception as e:
|
|
413
|
+
logger.debug(f"Stream read error: {e}")
|
|
414
|
+
|
|
415
|
+
except Exception as e:
|
|
416
|
+
logger.debug(f"Select error: {e}")
|
|
417
|
+
|
|
418
|
+
def _read_remaining_output(self) -> None:
|
|
419
|
+
"""Read any remaining output from terminated process."""
|
|
420
|
+
if not self.process:
|
|
421
|
+
return
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
if self.process.stdout:
|
|
425
|
+
remaining = self.process.stdout.read()
|
|
426
|
+
if remaining:
|
|
427
|
+
self.bytes_read += len(remaining.encode(self.encoding))
|
|
428
|
+
self.output_queue.put(remaining)
|
|
429
|
+
|
|
430
|
+
if self.process.stderr:
|
|
431
|
+
remaining = self.process.stderr.read()
|
|
432
|
+
if remaining:
|
|
433
|
+
self.bytes_read += len(remaining.encode(self.encoding))
|
|
434
|
+
self.error_queue.put(remaining)
|
|
435
|
+
|
|
436
|
+
except Exception as e:
|
|
437
|
+
logger.debug(f"Error reading remaining output: {e}")
|
|
438
|
+
|
|
439
|
+
def write(self, data: str) -> None:
|
|
440
|
+
"""
|
|
441
|
+
Write data to process stdin.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
data: Data to write
|
|
445
|
+
|
|
446
|
+
Raises:
|
|
447
|
+
CommandExecutionError: If process is not running or write fails
|
|
448
|
+
"""
|
|
449
|
+
if not self.is_alive():
|
|
450
|
+
raise CommandExecutionError("Process is not running")
|
|
451
|
+
|
|
452
|
+
if not self.started_event.is_set():
|
|
453
|
+
raise CommandExecutionError("Process has not started yet")
|
|
454
|
+
|
|
455
|
+
try:
|
|
456
|
+
if self.use_pty and self.master_fd is not None:
|
|
457
|
+
# Write to PTY master
|
|
458
|
+
encoded_data = data.encode(self.encoding)
|
|
459
|
+
os.write(self.master_fd, encoded_data)
|
|
460
|
+
self.bytes_written += len(encoded_data)
|
|
461
|
+
|
|
462
|
+
elif self.winpty_process:
|
|
463
|
+
# Write to winpty
|
|
464
|
+
self.winpty_process.write(data)
|
|
465
|
+
self.bytes_written += len(data.encode(self.encoding))
|
|
466
|
+
|
|
467
|
+
elif self.process and self.process.stdin:
|
|
468
|
+
# Write to pipe
|
|
469
|
+
self.process.stdin.write(data)
|
|
470
|
+
self.process.stdin.flush()
|
|
471
|
+
self.bytes_written += len(data.encode(self.encoding))
|
|
472
|
+
|
|
473
|
+
else:
|
|
474
|
+
raise CommandExecutionError("No input stream available")
|
|
475
|
+
|
|
476
|
+
except Exception as e:
|
|
477
|
+
raise CommandExecutionError(f"Failed to write to process: {e}")
|
|
478
|
+
|
|
479
|
+
def read_output(self, timeout: Optional[float] = None) -> Optional[str]:
|
|
480
|
+
"""
|
|
481
|
+
Read output from process.
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
timeout: Timeout in seconds (None for non-blocking)
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
Output string or None if no data available
|
|
488
|
+
"""
|
|
489
|
+
try:
|
|
490
|
+
if timeout is None:
|
|
491
|
+
return self.output_queue.get_nowait()
|
|
492
|
+
else:
|
|
493
|
+
return self.output_queue.get(timeout=timeout)
|
|
494
|
+
except queue.Empty:
|
|
495
|
+
return None
|
|
496
|
+
|
|
497
|
+
def read_error(self, timeout: Optional[float] = None) -> Optional[str]:
|
|
498
|
+
"""
|
|
499
|
+
Read error output from process.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
timeout: Timeout in seconds (None for non-blocking)
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
Error string or None if no data available
|
|
506
|
+
"""
|
|
507
|
+
try:
|
|
508
|
+
if timeout is None:
|
|
509
|
+
return self.error_queue.get_nowait()
|
|
510
|
+
else:
|
|
511
|
+
return self.error_queue.get(timeout=timeout)
|
|
512
|
+
except queue.Empty:
|
|
513
|
+
return None
|
|
514
|
+
|
|
515
|
+
def read_lines(self, timeout: Optional[float] = 1.0) -> Generator[str, None, None]:
|
|
516
|
+
"""
|
|
517
|
+
Generator that yields output lines.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
timeout: Timeout for each read operation
|
|
521
|
+
|
|
522
|
+
Yields:
|
|
523
|
+
Output lines
|
|
524
|
+
"""
|
|
525
|
+
buffer = ""
|
|
526
|
+
|
|
527
|
+
while self.is_alive():
|
|
528
|
+
try:
|
|
529
|
+
data = self.read_output(timeout=timeout)
|
|
530
|
+
if data:
|
|
531
|
+
buffer += data
|
|
532
|
+
|
|
533
|
+
# Yield complete lines
|
|
534
|
+
while '\n' in buffer:
|
|
535
|
+
line, buffer = buffer.split('\n', 1)
|
|
536
|
+
yield line + '\n'
|
|
537
|
+
else:
|
|
538
|
+
# No data available, check if process is still alive
|
|
539
|
+
if not self.is_alive():
|
|
540
|
+
break
|
|
541
|
+
|
|
542
|
+
except Exception as e:
|
|
543
|
+
logger.debug(f"Error reading lines: {e}")
|
|
544
|
+
break
|
|
545
|
+
|
|
546
|
+
# Yield any remaining buffer content
|
|
547
|
+
if buffer:
|
|
548
|
+
yield buffer
|
|
549
|
+
|
|
550
|
+
def send_signal(self, sig: int) -> None:
|
|
551
|
+
"""
|
|
552
|
+
Send signal to process.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
sig: Signal number
|
|
556
|
+
"""
|
|
557
|
+
if not self.is_alive():
|
|
558
|
+
return
|
|
559
|
+
|
|
560
|
+
try:
|
|
561
|
+
if PLATFORM == "Windows":
|
|
562
|
+
if sig == signal.SIGINT:
|
|
563
|
+
# Send Ctrl+C to Windows process
|
|
564
|
+
if self.winpty_process:
|
|
565
|
+
# winpty handles signals differently
|
|
566
|
+
self.winpty_process.terminate()
|
|
567
|
+
elif self.process:
|
|
568
|
+
self.process.send_signal(signal.CTRL_C_EVENT)
|
|
569
|
+
else:
|
|
570
|
+
if self.process:
|
|
571
|
+
self.process.terminate()
|
|
572
|
+
else:
|
|
573
|
+
# Unix-like systems
|
|
574
|
+
if self.process:
|
|
575
|
+
try:
|
|
576
|
+
# Try to send signal to process group first
|
|
577
|
+
os.killpg(self.process.pid, sig)
|
|
578
|
+
except (OSError, ProcessLookupError):
|
|
579
|
+
# Fall back to sending signal to process only
|
|
580
|
+
self.process.send_signal(sig)
|
|
581
|
+
|
|
582
|
+
except Exception as e:
|
|
583
|
+
logger.debug(f"Error sending signal {sig}: {e}")
|
|
584
|
+
|
|
585
|
+
def terminate(self, grace_timeout: float = 5.0) -> bool:
|
|
586
|
+
"""
|
|
587
|
+
Terminate the process gracefully.
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
grace_timeout: Time to wait for graceful termination
|
|
591
|
+
|
|
592
|
+
Returns:
|
|
593
|
+
True if terminated successfully
|
|
594
|
+
"""
|
|
595
|
+
if not self.is_alive():
|
|
596
|
+
return True
|
|
597
|
+
|
|
598
|
+
logger.debug(f"Terminating interactive process PID {self.pid}")
|
|
599
|
+
|
|
600
|
+
try:
|
|
601
|
+
# Send SIGTERM (or equivalent)
|
|
602
|
+
if PLATFORM == "Windows":
|
|
603
|
+
if self.winpty_process:
|
|
604
|
+
self.winpty_process.terminate()
|
|
605
|
+
elif self.process:
|
|
606
|
+
self.process.terminate()
|
|
607
|
+
else:
|
|
608
|
+
self.send_signal(signal.SIGTERM)
|
|
609
|
+
|
|
610
|
+
# Wait for graceful termination
|
|
611
|
+
start_time = time.time()
|
|
612
|
+
while time.time() - start_time < grace_timeout:
|
|
613
|
+
if not self.is_alive():
|
|
614
|
+
break
|
|
615
|
+
time.sleep(0.1)
|
|
616
|
+
|
|
617
|
+
# Force kill if still alive
|
|
618
|
+
if self.is_alive():
|
|
619
|
+
logger.debug(f"Force killing process PID {self.pid}")
|
|
620
|
+
if self.winpty_process:
|
|
621
|
+
self.winpty_process.terminate(force=True)
|
|
622
|
+
elif self.process:
|
|
623
|
+
if PLATFORM == "Windows":
|
|
624
|
+
self.process.kill()
|
|
625
|
+
else:
|
|
626
|
+
# Use process cleanup for thorough cleanup
|
|
627
|
+
cleanup_process_tree(self.process.pid)
|
|
628
|
+
|
|
629
|
+
# Wait for I/O thread to finish
|
|
630
|
+
self.alive_event.clear()
|
|
631
|
+
if self.io_thread and self.io_thread.is_alive():
|
|
632
|
+
self.io_thread.join(timeout=2.0)
|
|
633
|
+
|
|
634
|
+
self._cleanup()
|
|
635
|
+
self.end_time = time.time()
|
|
636
|
+
|
|
637
|
+
return not self.is_alive()
|
|
638
|
+
|
|
639
|
+
except Exception as e:
|
|
640
|
+
logger.error(f"Error terminating process: {e}")
|
|
641
|
+
return False
|
|
642
|
+
|
|
643
|
+
def _cleanup(self) -> None:
|
|
644
|
+
"""Clean up resources."""
|
|
645
|
+
try:
|
|
646
|
+
# Close PTY file descriptors
|
|
647
|
+
if self.master_fd is not None:
|
|
648
|
+
os.close(self.master_fd)
|
|
649
|
+
self.master_fd = None
|
|
650
|
+
|
|
651
|
+
if self.slave_fd is not None:
|
|
652
|
+
os.close(self.slave_fd)
|
|
653
|
+
self.slave_fd = None
|
|
654
|
+
|
|
655
|
+
# Close winpty
|
|
656
|
+
if self.winpty_process:
|
|
657
|
+
try:
|
|
658
|
+
self.winpty_process.terminate()
|
|
659
|
+
except:
|
|
660
|
+
pass
|
|
661
|
+
self.winpty_process = None
|
|
662
|
+
|
|
663
|
+
# Close pipes
|
|
664
|
+
if self.process:
|
|
665
|
+
try:
|
|
666
|
+
if self.process.stdin:
|
|
667
|
+
self.process.stdin.close()
|
|
668
|
+
if self.process.stdout:
|
|
669
|
+
self.process.stdout.close()
|
|
670
|
+
if self.process.stderr:
|
|
671
|
+
self.process.stderr.close()
|
|
672
|
+
except:
|
|
673
|
+
pass
|
|
674
|
+
|
|
675
|
+
except Exception as e:
|
|
676
|
+
logger.debug(f"Cleanup error: {e}")
|
|
677
|
+
|
|
678
|
+
def is_alive(self) -> bool:
|
|
679
|
+
"""Check if process is alive."""
|
|
680
|
+
if self.winpty_process:
|
|
681
|
+
return self.winpty_process.isalive()
|
|
682
|
+
elif self.process:
|
|
683
|
+
return self.process.poll() is None
|
|
684
|
+
else:
|
|
685
|
+
return False
|
|
686
|
+
|
|
687
|
+
@property
|
|
688
|
+
def pid(self) -> Optional[int]:
|
|
689
|
+
"""Get process PID."""
|
|
690
|
+
if self.winpty_process:
|
|
691
|
+
return self.winpty_process.pid
|
|
692
|
+
elif self.process:
|
|
693
|
+
return self.process.pid
|
|
694
|
+
else:
|
|
695
|
+
return None
|
|
696
|
+
|
|
697
|
+
@property
|
|
698
|
+
def exit_code(self) -> Optional[int]:
|
|
699
|
+
"""Get process exit code."""
|
|
700
|
+
if self.winpty_process:
|
|
701
|
+
return self.winpty_process.exitstatus if not self.winpty_process.isalive() else None
|
|
702
|
+
elif self.process:
|
|
703
|
+
return self.process.returncode
|
|
704
|
+
else:
|
|
705
|
+
return None
|
|
706
|
+
|
|
707
|
+
@property
|
|
708
|
+
def duration(self) -> Optional[float]:
|
|
709
|
+
"""Get process duration."""
|
|
710
|
+
if self.start_time is None:
|
|
711
|
+
return None
|
|
712
|
+
|
|
713
|
+
end_time = self.end_time or time.time()
|
|
714
|
+
return end_time - self.start_time
|
|
715
|
+
|
|
716
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
717
|
+
"""Get process statistics."""
|
|
718
|
+
return {
|
|
719
|
+
'pid': self.pid,
|
|
720
|
+
'exit_code': self.exit_code,
|
|
721
|
+
'duration': self.duration,
|
|
722
|
+
'bytes_written': self.bytes_written,
|
|
723
|
+
'bytes_read': self.bytes_read,
|
|
724
|
+
'is_alive': self.is_alive(),
|
|
725
|
+
'use_pty': self.use_pty,
|
|
726
|
+
'platform': PLATFORM
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
def __enter__(self) -> 'InteractiveProcess':
|
|
730
|
+
"""Context manager entry."""
|
|
731
|
+
self.start()
|
|
732
|
+
return self
|
|
733
|
+
|
|
734
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
735
|
+
"""Context manager exit."""
|
|
736
|
+
self.terminate()
|
|
737
|
+
|
|
738
|
+
def __del__(self) -> None:
|
|
739
|
+
"""Destructor - cleanup resources."""
|
|
740
|
+
try:
|
|
741
|
+
if self.is_alive():
|
|
742
|
+
self.terminate(grace_timeout=1.0)
|
|
743
|
+
except Exception:
|
|
744
|
+
pass
|