auto-coder 1.0.0__py3-none-any.whl → 2.0.0__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.

Files changed (574) hide show
  1. auto_coder-2.0.0.dist-info/LICENSE +158 -0
  2. auto_coder-2.0.0.dist-info/METADATA +558 -0
  3. auto_coder-2.0.0.dist-info/RECORD +795 -0
  4. {auto_coder-1.0.0.dist-info → auto_coder-2.0.0.dist-info}/WHEEL +1 -1
  5. {auto_coder-1.0.0.dist-info → auto_coder-2.0.0.dist-info}/entry_points.txt +3 -3
  6. autocoder/__init__.py +31 -0
  7. autocoder/agent/auto_filegroup.py +32 -13
  8. autocoder/agent/auto_learn_from_commit.py +9 -1
  9. autocoder/agent/base_agentic/__init__.py +3 -0
  10. autocoder/agent/base_agentic/agent_hub.py +1 -1
  11. autocoder/agent/base_agentic/base_agent.py +235 -136
  12. autocoder/agent/base_agentic/default_tools.py +119 -118
  13. autocoder/agent/base_agentic/test_base_agent.py +1 -1
  14. autocoder/agent/base_agentic/tool_registry.py +32 -20
  15. autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +24 -3
  16. autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +24 -11
  17. autocoder/agent/base_agentic/types.py +42 -0
  18. autocoder/agent/entry_command_agent/chat.py +73 -59
  19. autocoder/auto_coder.py +31 -40
  20. autocoder/auto_coder_rag.py +11 -1084
  21. autocoder/auto_coder_runner.py +970 -2345
  22. autocoder/auto_coder_terminal.py +26 -0
  23. autocoder/auto_coder_terminal_v3.py +190 -0
  24. autocoder/chat/conf_command.py +224 -124
  25. autocoder/chat/models_command.py +361 -299
  26. autocoder/chat/rules_command.py +79 -31
  27. autocoder/chat_auto_coder.py +988 -398
  28. autocoder/chat_auto_coder_lang.py +23 -732
  29. autocoder/commands/auto_command.py +25 -8
  30. autocoder/commands/auto_web.py +1 -1
  31. autocoder/commands/tools.py +44 -44
  32. autocoder/common/__init__.py +150 -128
  33. autocoder/common/ac_style_command_parser/__init__.py +39 -2
  34. autocoder/common/ac_style_command_parser/config.py +422 -0
  35. autocoder/common/ac_style_command_parser/parser.py +292 -78
  36. autocoder/common/ac_style_command_parser/test_parser.py +241 -16
  37. autocoder/common/ac_style_command_parser/test_typed_parser.py +342 -0
  38. autocoder/common/ac_style_command_parser/typed_parser.py +653 -0
  39. autocoder/common/action_yml_file_manager.py +25 -13
  40. autocoder/common/agent_events/__init__.py +52 -0
  41. autocoder/common/agent_events/agent_event_emitter.py +193 -0
  42. autocoder/common/agent_events/event_factory.py +177 -0
  43. autocoder/common/agent_events/examples.py +307 -0
  44. autocoder/common/agent_events/types.py +113 -0
  45. autocoder/common/agent_events/utils.py +68 -0
  46. autocoder/common/agent_hooks/__init__.py +44 -0
  47. autocoder/common/agent_hooks/examples.py +582 -0
  48. autocoder/common/agent_hooks/hook_executor.py +217 -0
  49. autocoder/common/agent_hooks/hook_manager.py +288 -0
  50. autocoder/common/agent_hooks/types.py +133 -0
  51. autocoder/common/agent_hooks/utils.py +99 -0
  52. autocoder/common/agent_query_queue/queue_executor.py +324 -0
  53. autocoder/common/agent_query_queue/queue_manager.py +325 -0
  54. autocoder/common/agents/__init__.py +11 -0
  55. autocoder/common/agents/agent_manager.py +323 -0
  56. autocoder/common/agents/agent_parser.py +189 -0
  57. autocoder/common/agents/example_usage.py +344 -0
  58. autocoder/common/agents/integration_example.py +330 -0
  59. autocoder/common/agents/test_agent_parser.py +545 -0
  60. autocoder/common/async_utils.py +101 -0
  61. autocoder/common/auto_coder_lang.py +23 -972
  62. autocoder/common/autocoderargs_parser/__init__.py +14 -0
  63. autocoder/common/autocoderargs_parser/parser.py +184 -0
  64. autocoder/common/autocoderargs_parser/tests/__init__.py +1 -0
  65. autocoder/common/autocoderargs_parser/tests/test_args_parser.py +235 -0
  66. autocoder/common/autocoderargs_parser/tests/test_token_parser.py +195 -0
  67. autocoder/common/autocoderargs_parser/token_parser.py +290 -0
  68. autocoder/common/buildin_tokenizer.py +2 -4
  69. autocoder/common/code_auto_generate.py +149 -74
  70. autocoder/common/code_auto_generate_diff.py +163 -70
  71. autocoder/common/code_auto_generate_editblock.py +179 -89
  72. autocoder/common/code_auto_generate_strict_diff.py +167 -72
  73. autocoder/common/code_auto_merge_editblock.py +13 -6
  74. autocoder/common/code_modification_ranker.py +1 -1
  75. autocoder/common/command_completer.py +3 -3
  76. autocoder/common/command_file_manager/manager.py +183 -47
  77. autocoder/common/command_file_manager/test_command_file_manager.py +507 -0
  78. autocoder/common/command_templates.py +1 -1
  79. autocoder/common/conf_utils.py +2 -4
  80. autocoder/common/conversations/config.py +11 -3
  81. autocoder/common/conversations/get_conversation_manager.py +100 -2
  82. autocoder/common/conversations/llm_stats_models.py +264 -0
  83. autocoder/common/conversations/manager.py +112 -28
  84. autocoder/common/conversations/models.py +16 -2
  85. autocoder/common/conversations/storage/index_manager.py +134 -10
  86. autocoder/common/core_config/__init__.py +63 -0
  87. autocoder/common/core_config/agentic_mode_manager.py +109 -0
  88. autocoder/common/core_config/base_manager.py +123 -0
  89. autocoder/common/core_config/compatibility.py +151 -0
  90. autocoder/common/core_config/config_manager.py +156 -0
  91. autocoder/common/core_config/conversation_manager.py +31 -0
  92. autocoder/common/core_config/exclude_manager.py +72 -0
  93. autocoder/common/core_config/file_manager.py +177 -0
  94. autocoder/common/core_config/human_as_model_manager.py +129 -0
  95. autocoder/common/core_config/lib_manager.py +54 -0
  96. autocoder/common/core_config/main_manager.py +81 -0
  97. autocoder/common/core_config/mode_manager.py +126 -0
  98. autocoder/common/core_config/models.py +70 -0
  99. autocoder/common/core_config/test_memory_manager.py +1056 -0
  100. autocoder/common/env_manager.py +282 -0
  101. autocoder/common/env_manager_usage_example.py +211 -0
  102. autocoder/common/file_checkpoint/conversation_checkpoint.py +19 -19
  103. autocoder/common/file_checkpoint/manager.py +264 -48
  104. autocoder/common/file_checkpoint/test_backup.py +1 -18
  105. autocoder/common/file_checkpoint/test_manager.py +270 -1
  106. autocoder/common/file_checkpoint/test_store.py +1 -17
  107. autocoder/common/file_handler/__init__.py +23 -0
  108. autocoder/common/file_handler/active_context_handler.py +159 -0
  109. autocoder/common/file_handler/add_files_handler.py +409 -0
  110. autocoder/common/file_handler/chat_handler.py +180 -0
  111. autocoder/common/file_handler/coding_handler.py +401 -0
  112. autocoder/common/file_handler/commit_handler.py +200 -0
  113. autocoder/common/file_handler/lib_handler.py +156 -0
  114. autocoder/common/file_handler/list_files_handler.py +111 -0
  115. autocoder/common/file_handler/mcp_handler.py +268 -0
  116. autocoder/common/file_handler/models_handler.py +493 -0
  117. autocoder/common/file_handler/remove_files_handler.py +172 -0
  118. autocoder/common/git_utils.py +44 -8
  119. autocoder/common/global_cancel.py +15 -6
  120. autocoder/common/ignorefiles/test_ignore_file_utils.py +1 -1
  121. autocoder/common/international/__init__.py +31 -0
  122. autocoder/common/international/demo_international.py +92 -0
  123. autocoder/common/international/message_manager.py +157 -0
  124. autocoder/common/international/messages/__init__.py +56 -0
  125. autocoder/common/international/messages/async_command_messages.py +507 -0
  126. autocoder/common/international/messages/auto_coder_messages.py +2208 -0
  127. autocoder/common/international/messages/chat_auto_coder_messages.py +1547 -0
  128. autocoder/common/international/messages/command_help_messages.py +986 -0
  129. autocoder/common/international/messages/conversation_command_messages.py +191 -0
  130. autocoder/common/international/messages/git_helper_plugin_messages.py +159 -0
  131. autocoder/common/international/messages/queue_command_messages.py +751 -0
  132. autocoder/common/international/messages/rules_command_messages.py +77 -0
  133. autocoder/common/international/messages/sdk_messages.py +1707 -0
  134. autocoder/common/international/messages/token_helper_plugin_messages.py +361 -0
  135. autocoder/common/international/messages/tool_display_messages.py +1212 -0
  136. autocoder/common/international/messages/workflow_exception_messages.py +473 -0
  137. autocoder/common/international/test_international.py +612 -0
  138. autocoder/common/linter_core/__init__.py +28 -0
  139. autocoder/common/linter_core/base_linter.py +61 -0
  140. autocoder/common/linter_core/config_loader.py +271 -0
  141. autocoder/common/linter_core/formatters/__init__.py +0 -0
  142. autocoder/common/linter_core/formatters/base_formatter.py +38 -0
  143. autocoder/common/linter_core/formatters/raw_formatter.py +17 -0
  144. autocoder/common/linter_core/linter.py +166 -0
  145. autocoder/common/linter_core/linter_factory.py +216 -0
  146. autocoder/common/linter_core/linter_manager.py +333 -0
  147. autocoder/common/linter_core/linters/__init__.py +9 -0
  148. autocoder/common/linter_core/linters/java_linter.py +342 -0
  149. autocoder/common/linter_core/linters/python_linter.py +115 -0
  150. autocoder/common/linter_core/linters/typescript_linter.py +119 -0
  151. autocoder/common/linter_core/models/__init__.py +7 -0
  152. autocoder/common/linter_core/models/lint_result.py +91 -0
  153. autocoder/common/linter_core/models.py +33 -0
  154. autocoder/common/linter_core/tests/__init__.py +3 -0
  155. autocoder/common/linter_core/tests/test_config_loader.py +323 -0
  156. autocoder/common/linter_core/tests/test_config_loading.py +308 -0
  157. autocoder/common/linter_core/tests/test_factory_manager.py +234 -0
  158. autocoder/common/linter_core/tests/test_formatters.py +147 -0
  159. autocoder/common/linter_core/tests/test_integration.py +317 -0
  160. autocoder/common/linter_core/tests/test_java_linter.py +496 -0
  161. autocoder/common/linter_core/tests/test_linters.py +265 -0
  162. autocoder/common/linter_core/tests/test_models.py +81 -0
  163. autocoder/common/linter_core/tests/verify_config_loading.py +296 -0
  164. autocoder/common/linter_core/tests/verify_fixes.py +183 -0
  165. autocoder/common/llm_friendly_package/__init__.py +31 -0
  166. autocoder/common/llm_friendly_package/base_manager.py +102 -0
  167. autocoder/common/llm_friendly_package/docs_manager.py +121 -0
  168. autocoder/common/llm_friendly_package/library_manager.py +171 -0
  169. autocoder/common/{llm_friendly_package.py → llm_friendly_package/main_manager.py} +204 -231
  170. autocoder/common/llm_friendly_package/models.py +40 -0
  171. autocoder/common/llm_friendly_package/test_llm_friendly_package.py +536 -0
  172. autocoder/common/llms/__init__.py +15 -0
  173. autocoder/common/llms/demo_error_handling.py +85 -0
  174. autocoder/common/llms/factory.py +142 -0
  175. autocoder/common/llms/manager.py +264 -0
  176. autocoder/common/llms/pricing.py +121 -0
  177. autocoder/common/llms/registry.py +288 -0
  178. autocoder/common/llms/schema.py +77 -0
  179. autocoder/common/llms/simple_demo.py +45 -0
  180. autocoder/common/llms/test_quick_model.py +116 -0
  181. autocoder/common/llms/test_remove_functionality.py +182 -0
  182. autocoder/common/llms/tests/__init__.py +1 -0
  183. autocoder/common/llms/tests/test_manager.py +330 -0
  184. autocoder/common/llms/tests/test_registry.py +364 -0
  185. autocoder/common/mcp_tools/__init__.py +62 -0
  186. autocoder/common/{mcp_tools.py → mcp_tools/executor.py} +49 -40
  187. autocoder/common/{mcp_hub.py → mcp_tools/hub.py} +42 -68
  188. autocoder/common/{mcp_server_install.py → mcp_tools/installer.py} +16 -28
  189. autocoder/common/{mcp_server.py → mcp_tools/server.py} +176 -48
  190. autocoder/common/mcp_tools/test_keyboard_interrupt.py +93 -0
  191. autocoder/common/mcp_tools/test_mcp_tools.py +391 -0
  192. autocoder/common/{mcp_server_types.py → mcp_tools/types.py} +121 -48
  193. autocoder/common/mcp_tools/verify_functionality.py +202 -0
  194. autocoder/common/model_speed_tester.py +32 -26
  195. autocoder/common/priority_directory_finder/__init__.py +142 -0
  196. autocoder/common/priority_directory_finder/examples.py +230 -0
  197. autocoder/common/priority_directory_finder/finder.py +283 -0
  198. autocoder/common/priority_directory_finder/models.py +236 -0
  199. autocoder/common/priority_directory_finder/test_priority_directory_finder.py +431 -0
  200. autocoder/common/project_scanner/__init__.py +18 -0
  201. autocoder/common/project_scanner/compat.py +77 -0
  202. autocoder/common/project_scanner/scanner.py +436 -0
  203. autocoder/common/project_tracker/__init__.py +27 -0
  204. autocoder/common/project_tracker/api.py +228 -0
  205. autocoder/common/project_tracker/demo.py +272 -0
  206. autocoder/common/project_tracker/tracker.py +487 -0
  207. autocoder/common/project_tracker/types.py +53 -0
  208. autocoder/common/pruner/__init__.py +67 -0
  209. autocoder/common/pruner/agentic_conversation_pruner.py +651 -102
  210. autocoder/common/pruner/conversation_message_ids_api.py +386 -0
  211. autocoder/common/pruner/conversation_message_ids_manager.py +347 -0
  212. autocoder/common/pruner/conversation_message_ids_pruner.py +473 -0
  213. autocoder/common/pruner/conversation_normalizer.py +347 -0
  214. autocoder/common/pruner/conversation_pruner.py +26 -6
  215. autocoder/common/pruner/test_agentic_conversation_pruner.py +554 -112
  216. autocoder/common/pruner/test_conversation_normalizer.py +502 -0
  217. autocoder/common/pruner/test_tool_content_detector.py +324 -0
  218. autocoder/common/pruner/tool_content_detector.py +227 -0
  219. autocoder/common/pruner/tools/__init__.py +18 -0
  220. autocoder/common/pruner/tools/query_message_ids.py +264 -0
  221. autocoder/common/pruner/tools/test_agentic_pruning_logic.py +432 -0
  222. autocoder/common/pruner/tools/test_message_ids_pruning_only.py +192 -0
  223. autocoder/common/pull_requests/__init__.py +9 -1
  224. autocoder/common/pull_requests/utils.py +122 -1
  225. autocoder/common/rag_manager/rag_manager.py +36 -40
  226. autocoder/common/rulefiles/__init__.py +53 -1
  227. autocoder/common/rulefiles/api.py +250 -0
  228. autocoder/common/rulefiles/core/__init__.py +14 -0
  229. autocoder/common/rulefiles/core/manager.py +241 -0
  230. autocoder/common/rulefiles/core/selector.py +805 -0
  231. autocoder/common/rulefiles/models/__init__.py +20 -0
  232. autocoder/common/rulefiles/models/index.py +16 -0
  233. autocoder/common/rulefiles/models/init_rule.py +18 -0
  234. autocoder/common/rulefiles/models/rule_file.py +18 -0
  235. autocoder/common/rulefiles/models/rule_relevance.py +14 -0
  236. autocoder/common/rulefiles/models/summary.py +16 -0
  237. autocoder/common/rulefiles/test_rulefiles.py +776 -0
  238. autocoder/common/rulefiles/utils/__init__.py +34 -0
  239. autocoder/common/rulefiles/utils/monitor.py +86 -0
  240. autocoder/common/rulefiles/utils/parser.py +230 -0
  241. autocoder/common/save_formatted_log.py +67 -10
  242. autocoder/common/search_replace.py +8 -1
  243. autocoder/common/search_replace_patch/__init__.py +24 -0
  244. autocoder/common/search_replace_patch/base.py +115 -0
  245. autocoder/common/search_replace_patch/manager.py +248 -0
  246. autocoder/common/search_replace_patch/patch_replacer.py +304 -0
  247. autocoder/common/search_replace_patch/similarity_replacer.py +306 -0
  248. autocoder/common/search_replace_patch/string_replacer.py +181 -0
  249. autocoder/common/search_replace_patch/tests/__init__.py +3 -0
  250. autocoder/common/search_replace_patch/tests/run_tests.py +126 -0
  251. autocoder/common/search_replace_patch/tests/test_base.py +188 -0
  252. autocoder/common/search_replace_patch/tests/test_empty_line_insert.py +233 -0
  253. autocoder/common/search_replace_patch/tests/test_integration.py +389 -0
  254. autocoder/common/search_replace_patch/tests/test_manager.py +351 -0
  255. autocoder/common/search_replace_patch/tests/test_patch_replacer.py +316 -0
  256. autocoder/common/search_replace_patch/tests/test_regex_replacer.py +306 -0
  257. autocoder/common/search_replace_patch/tests/test_similarity_replacer.py +384 -0
  258. autocoder/common/shell_commands/__init__.py +197 -0
  259. autocoder/common/shell_commands/background_process_notifier.py +346 -0
  260. autocoder/common/shell_commands/command_executor.py +1127 -0
  261. autocoder/common/shell_commands/error_recovery.py +541 -0
  262. autocoder/common/shell_commands/exceptions.py +120 -0
  263. autocoder/common/shell_commands/interactive_executor.py +476 -0
  264. autocoder/common/shell_commands/interactive_pexpect_process.py +623 -0
  265. autocoder/common/shell_commands/interactive_process.py +744 -0
  266. autocoder/common/shell_commands/interactive_session_manager.py +1014 -0
  267. autocoder/common/shell_commands/monitoring.py +529 -0
  268. autocoder/common/shell_commands/process_cleanup.py +386 -0
  269. autocoder/common/shell_commands/process_manager.py +606 -0
  270. autocoder/common/shell_commands/test_interactive_pexpect_process.py +281 -0
  271. autocoder/common/shell_commands/tests/__init__.py +6 -0
  272. autocoder/common/shell_commands/tests/conftest.py +118 -0
  273. autocoder/common/shell_commands/tests/test_background_process_notifier.py +703 -0
  274. autocoder/common/shell_commands/tests/test_command_executor.py +448 -0
  275. autocoder/common/shell_commands/tests/test_error_recovery.py +305 -0
  276. autocoder/common/shell_commands/tests/test_exceptions.py +299 -0
  277. autocoder/common/shell_commands/tests/test_execute_batch.py +588 -0
  278. autocoder/common/shell_commands/tests/test_indented_batch_commands.py +244 -0
  279. autocoder/common/shell_commands/tests/test_integration.py +664 -0
  280. autocoder/common/shell_commands/tests/test_monitoring.py +546 -0
  281. autocoder/common/shell_commands/tests/test_performance.py +632 -0
  282. autocoder/common/shell_commands/tests/test_process_cleanup.py +397 -0
  283. autocoder/common/shell_commands/tests/test_process_manager.py +606 -0
  284. autocoder/common/shell_commands/tests/test_timeout_config.py +343 -0
  285. autocoder/common/shell_commands/tests/test_timeout_manager.py +520 -0
  286. autocoder/common/shell_commands/timeout_config.py +315 -0
  287. autocoder/common/shell_commands/timeout_manager.py +352 -0
  288. autocoder/common/terminal_paste/__init__.py +14 -0
  289. autocoder/common/terminal_paste/demo.py +145 -0
  290. autocoder/common/terminal_paste/demo_paste_functionality.py +95 -0
  291. autocoder/common/terminal_paste/paste_handler.py +200 -0
  292. autocoder/common/terminal_paste/paste_manager.py +118 -0
  293. autocoder/common/terminal_paste/tests/__init__.py +1 -0
  294. autocoder/common/terminal_paste/tests/test_paste_handler.py +182 -0
  295. autocoder/common/terminal_paste/tests/test_paste_manager.py +126 -0
  296. autocoder/common/terminal_paste/utils.py +163 -0
  297. autocoder/common/test_autocoder_args.py +232 -0
  298. autocoder/common/test_env_manager.py +173 -0
  299. autocoder/common/test_env_manager_integration.py +159 -0
  300. autocoder/common/text_similarity/__init__.py +9 -0
  301. autocoder/common/text_similarity/demo.py +216 -0
  302. autocoder/common/text_similarity/examples.py +266 -0
  303. autocoder/common/text_similarity/test_text_similarity.py +306 -0
  304. autocoder/common/text_similarity/text_similarity.py +194 -0
  305. autocoder/common/text_similarity/utils.py +125 -0
  306. autocoder/common/todos/__init__.py +61 -0
  307. autocoder/common/todos/cache/__init__.py +16 -0
  308. autocoder/common/todos/cache/base_cache.py +89 -0
  309. autocoder/common/todos/cache/cache_manager.py +228 -0
  310. autocoder/common/todos/cache/memory_cache.py +225 -0
  311. autocoder/common/todos/config.py +155 -0
  312. autocoder/common/todos/exceptions.py +35 -0
  313. autocoder/common/todos/get_todo_manager.py +161 -0
  314. autocoder/common/todos/manager.py +537 -0
  315. autocoder/common/todos/models.py +239 -0
  316. autocoder/common/todos/storage/__init__.py +14 -0
  317. autocoder/common/todos/storage/base_storage.py +76 -0
  318. autocoder/common/todos/storage/file_storage.py +278 -0
  319. autocoder/common/tokens/counter.py +24 -2
  320. autocoder/common/tools_manager/__init__.py +17 -0
  321. autocoder/common/tools_manager/examples.py +162 -0
  322. autocoder/common/tools_manager/manager.py +385 -0
  323. autocoder/common/tools_manager/models.py +39 -0
  324. autocoder/common/tools_manager/test_tools_manager.py +303 -0
  325. autocoder/common/tools_manager/utils.py +191 -0
  326. autocoder/common/v2/agent/agentic_callbacks.py +270 -0
  327. autocoder/common/v2/agent/agentic_edit.py +2699 -1856
  328. autocoder/common/v2/agent/agentic_edit_change_manager.py +474 -0
  329. autocoder/common/v2/agent/agentic_edit_tools/__init__.py +35 -1
  330. autocoder/common/v2/agent/agentic_edit_tools/ac_mod_list_tool_resolver.py +279 -0
  331. autocoder/common/v2/agent/agentic_edit_tools/ac_mod_write_tool_resolver.py +10 -1
  332. autocoder/common/v2/agent/agentic_edit_tools/background_task_tool_resolver.py +1167 -0
  333. autocoder/common/v2/agent/agentic_edit_tools/base_tool_resolver.py +2 -2
  334. autocoder/common/v2/agent/agentic_edit_tools/conversation_message_ids_read_tool_resolver.py +214 -0
  335. autocoder/common/v2/agent/agentic_edit_tools/conversation_message_ids_write_tool_resolver.py +299 -0
  336. autocoder/common/v2/agent/agentic_edit_tools/count_tokens_tool_resolver.py +290 -0
  337. autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +564 -29
  338. autocoder/common/v2/agent/agentic_edit_tools/execute_workflow_tool_resolver.py +485 -0
  339. autocoder/common/v2/agent/agentic_edit_tools/extract_to_text_tool_resolver.py +225 -0
  340. autocoder/common/v2/agent/agentic_edit_tools/lint_report.py +79 -0
  341. autocoder/common/v2/agent/agentic_edit_tools/linter_config_models.py +343 -0
  342. autocoder/common/v2/agent/agentic_edit_tools/linter_enabled_tool_resolver.py +189 -0
  343. autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +169 -101
  344. autocoder/common/v2/agent/agentic_edit_tools/load_extra_document_tool_resolver.py +349 -0
  345. autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +243 -50
  346. autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +667 -147
  347. autocoder/common/v2/agent/agentic_edit_tools/run_named_subagents_tool_resolver.py +691 -0
  348. autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +410 -86
  349. autocoder/common/v2/agent/agentic_edit_tools/session_interactive_tool_resolver.py +115 -0
  350. autocoder/common/v2/agent/agentic_edit_tools/session_start_tool_resolver.py +190 -0
  351. autocoder/common/v2/agent/agentic_edit_tools/session_stop_tool_resolver.py +76 -0
  352. autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +207 -192
  353. autocoder/common/v2/agent/agentic_edit_tools/todo_read_tool_resolver.py +80 -63
  354. autocoder/common/v2/agent/agentic_edit_tools/todo_write_tool_resolver.py +237 -233
  355. autocoder/common/v2/agent/agentic_edit_tools/use_mcp_tool_resolver.py +2 -2
  356. autocoder/common/v2/agent/agentic_edit_tools/web_crawl_tool_resolver.py +557 -0
  357. autocoder/common/v2/agent/agentic_edit_tools/web_search_tool_resolver.py +600 -0
  358. autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +56 -121
  359. autocoder/common/v2/agent/agentic_edit_types.py +343 -9
  360. autocoder/common/v2/agent/runner/__init__.py +3 -3
  361. autocoder/common/v2/agent/runner/base_runner.py +12 -26
  362. autocoder/common/v2/agent/runner/{event_runner.py → file_based_event_runner.py} +3 -2
  363. autocoder/common/v2/agent/runner/sdk_runner.py +150 -8
  364. autocoder/common/v2/agent/runner/terminal_runner.py +170 -57
  365. autocoder/common/v2/agent/runner/tool_display.py +557 -159
  366. autocoder/common/v2/agent/test_agentic_callbacks.py +265 -0
  367. autocoder/common/v2/agent/test_agentic_edit.py +194 -0
  368. autocoder/common/v2/agent/tool_caller/__init__.py +24 -0
  369. autocoder/common/v2/agent/tool_caller/default_tool_resolver_map.py +135 -0
  370. autocoder/common/v2/agent/tool_caller/integration_test.py +172 -0
  371. autocoder/common/v2/agent/tool_caller/plugins/__init__.py +14 -0
  372. autocoder/common/v2/agent/tool_caller/plugins/base_plugin.py +126 -0
  373. autocoder/common/v2/agent/tool_caller/plugins/examples/__init__.py +13 -0
  374. autocoder/common/v2/agent/tool_caller/plugins/examples/logging_plugin.py +164 -0
  375. autocoder/common/v2/agent/tool_caller/plugins/examples/security_filter_plugin.py +198 -0
  376. autocoder/common/v2/agent/tool_caller/plugins/plugin_interface.py +141 -0
  377. autocoder/common/v2/agent/tool_caller/test_tool_caller.py +278 -0
  378. autocoder/common/v2/agent/tool_caller/tool_call_plugin_manager.py +331 -0
  379. autocoder/common/v2/agent/tool_caller/tool_caller.py +337 -0
  380. autocoder/common/v2/agent/tool_caller/usage_example.py +193 -0
  381. autocoder/common/v2/code_agentic_editblock_manager.py +4 -4
  382. autocoder/common/v2/code_auto_generate.py +136 -78
  383. autocoder/common/v2/code_auto_generate_diff.py +135 -79
  384. autocoder/common/v2/code_auto_generate_editblock.py +174 -99
  385. autocoder/common/v2/code_auto_generate_strict_diff.py +151 -71
  386. autocoder/common/v2/code_auto_merge.py +1 -1
  387. autocoder/common/v2/code_auto_merge_editblock.py +13 -1
  388. autocoder/common/v2/code_diff_manager.py +3 -3
  389. autocoder/common/v2/code_editblock_manager.py +4 -14
  390. autocoder/common/v2/code_manager.py +1 -1
  391. autocoder/common/v2/code_strict_diff_manager.py +2 -2
  392. autocoder/common/wrap_llm_hint/__init__.py +10 -0
  393. autocoder/common/wrap_llm_hint/test_wrap_llm_hint.py +1067 -0
  394. autocoder/common/wrap_llm_hint/utils.py +432 -0
  395. autocoder/common/wrap_llm_hint/wrap_llm_hint.py +323 -0
  396. autocoder/completer/__init__.py +8 -0
  397. autocoder/completer/command_completer_v2.py +1051 -0
  398. autocoder/default_project/__init__.py +501 -0
  399. autocoder/dispacher/__init__.py +4 -12
  400. autocoder/dispacher/actions/action.py +165 -7
  401. autocoder/dispacher/actions/plugins/action_regex_project.py +2 -2
  402. autocoder/index/entry.py +116 -124
  403. autocoder/{agent → index/filter}/agentic_filter.py +322 -333
  404. autocoder/index/filter/normal_filter.py +5 -11
  405. autocoder/index/filter/quick_filter.py +1 -1
  406. autocoder/index/index.py +36 -9
  407. autocoder/index/tests/__init__.py +1 -0
  408. autocoder/index/tests/run_tests.py +195 -0
  409. autocoder/index/tests/test_entry.py +303 -0
  410. autocoder/index/tests/test_index_manager.py +314 -0
  411. autocoder/index/tests/test_module_integration.py +300 -0
  412. autocoder/index/tests/test_symbols_utils.py +183 -0
  413. autocoder/inner/__init__.py +4 -0
  414. autocoder/inner/agentic.py +932 -0
  415. autocoder/inner/async_command_handler.py +992 -0
  416. autocoder/inner/conversation_command_handlers.py +623 -0
  417. autocoder/inner/merge_command_handler.py +213 -0
  418. autocoder/inner/queue_command_handler.py +684 -0
  419. autocoder/models.py +95 -266
  420. autocoder/plugins/git_helper_plugin.py +31 -29
  421. autocoder/plugins/token_helper_plugin.py +65 -46
  422. autocoder/pyproject/__init__.py +32 -29
  423. autocoder/rag/agentic_rag.py +215 -75
  424. autocoder/rag/cache/simple_cache.py +1 -2
  425. autocoder/rag/loaders/image_loader.py +1 -1
  426. autocoder/rag/long_context_rag.py +42 -26
  427. autocoder/rag/qa_conversation_strategy.py +1 -1
  428. autocoder/rag/terminal/__init__.py +17 -0
  429. autocoder/rag/terminal/args.py +581 -0
  430. autocoder/rag/terminal/bootstrap.py +61 -0
  431. autocoder/rag/terminal/command_handlers.py +653 -0
  432. autocoder/rag/terminal/formatters/__init__.py +20 -0
  433. autocoder/rag/terminal/formatters/base.py +70 -0
  434. autocoder/rag/terminal/formatters/json_format.py +66 -0
  435. autocoder/rag/terminal/formatters/stream_json.py +95 -0
  436. autocoder/rag/terminal/formatters/text.py +28 -0
  437. autocoder/rag/terminal/init.py +120 -0
  438. autocoder/rag/terminal/utils.py +106 -0
  439. autocoder/rag/test_agentic_rag.py +389 -0
  440. autocoder/rag/test_doc_filter.py +3 -3
  441. autocoder/rag/test_long_context_rag.py +1 -1
  442. autocoder/rag/test_token_limiter.py +517 -10
  443. autocoder/rag/token_counter.py +3 -0
  444. autocoder/rag/token_limiter.py +19 -15
  445. autocoder/rag/tools/__init__.py +26 -2
  446. autocoder/rag/tools/bochaai_example.py +343 -0
  447. autocoder/rag/tools/bochaai_sdk.py +541 -0
  448. autocoder/rag/tools/metaso_example.py +268 -0
  449. autocoder/rag/tools/metaso_sdk.py +417 -0
  450. autocoder/rag/tools/recall_tool.py +28 -7
  451. autocoder/rag/tools/run_integration_tests.py +204 -0
  452. autocoder/rag/tools/test_all_providers.py +318 -0
  453. autocoder/rag/tools/test_bochaai_integration.py +482 -0
  454. autocoder/rag/tools/test_final_integration.py +215 -0
  455. autocoder/rag/tools/test_metaso_integration.py +424 -0
  456. autocoder/rag/tools/test_metaso_real.py +171 -0
  457. autocoder/rag/tools/test_web_crawl_tool.py +639 -0
  458. autocoder/rag/tools/test_web_search_tool.py +509 -0
  459. autocoder/rag/tools/todo_read_tool.py +202 -0
  460. autocoder/rag/tools/todo_write_tool.py +412 -0
  461. autocoder/rag/tools/web_crawl_tool.py +634 -0
  462. autocoder/rag/tools/web_search_tool.py +558 -0
  463. autocoder/rag/tools/web_tools_example.py +119 -0
  464. autocoder/rag/types.py +16 -0
  465. autocoder/rag/variable_holder.py +4 -2
  466. autocoder/rags.py +86 -79
  467. autocoder/regexproject/__init__.py +23 -21
  468. autocoder/sdk/__init__.py +46 -190
  469. autocoder/sdk/api.py +370 -0
  470. autocoder/sdk/async_runner/__init__.py +26 -0
  471. autocoder/sdk/async_runner/async_executor.py +650 -0
  472. autocoder/sdk/async_runner/async_handler.py +356 -0
  473. autocoder/sdk/async_runner/markdown_processor.py +595 -0
  474. autocoder/sdk/async_runner/task_metadata.py +284 -0
  475. autocoder/sdk/async_runner/worktree_manager.py +438 -0
  476. autocoder/sdk/cli/__init__.py +2 -5
  477. autocoder/sdk/cli/formatters.py +28 -204
  478. autocoder/sdk/cli/handlers.py +77 -44
  479. autocoder/sdk/cli/main.py +154 -171
  480. autocoder/sdk/cli/options.py +95 -22
  481. autocoder/sdk/constants.py +139 -51
  482. autocoder/sdk/core/auto_coder_core.py +484 -109
  483. autocoder/sdk/core/bridge.py +297 -115
  484. autocoder/sdk/exceptions.py +18 -12
  485. autocoder/sdk/formatters/__init__.py +19 -0
  486. autocoder/sdk/formatters/input.py +64 -0
  487. autocoder/sdk/formatters/output.py +247 -0
  488. autocoder/sdk/formatters/stream.py +54 -0
  489. autocoder/sdk/models/__init__.py +6 -5
  490. autocoder/sdk/models/options.py +55 -18
  491. autocoder/sdk/utils/formatters.py +27 -195
  492. autocoder/suffixproject/__init__.py +28 -25
  493. autocoder/terminal/__init__.py +14 -0
  494. autocoder/terminal/app.py +454 -0
  495. autocoder/terminal/args.py +32 -0
  496. autocoder/terminal/bootstrap.py +178 -0
  497. autocoder/terminal/command_processor.py +521 -0
  498. autocoder/terminal/command_registry.py +57 -0
  499. autocoder/terminal/help.py +97 -0
  500. autocoder/terminal/tasks/__init__.py +5 -0
  501. autocoder/terminal/tasks/background.py +77 -0
  502. autocoder/terminal/tasks/task_event.py +70 -0
  503. autocoder/terminal/ui/__init__.py +13 -0
  504. autocoder/terminal/ui/completer.py +268 -0
  505. autocoder/terminal/ui/keybindings.py +75 -0
  506. autocoder/terminal/ui/session.py +41 -0
  507. autocoder/terminal/ui/toolbar.py +64 -0
  508. autocoder/terminal/utils/__init__.py +13 -0
  509. autocoder/terminal/utils/errors.py +18 -0
  510. autocoder/terminal/utils/paths.py +19 -0
  511. autocoder/terminal/utils/shell.py +43 -0
  512. autocoder/terminal_v3/__init__.py +10 -0
  513. autocoder/terminal_v3/app.py +201 -0
  514. autocoder/terminal_v3/handlers/__init__.py +5 -0
  515. autocoder/terminal_v3/handlers/command_handler.py +131 -0
  516. autocoder/terminal_v3/models/__init__.py +6 -0
  517. autocoder/terminal_v3/models/conversation_buffer.py +214 -0
  518. autocoder/terminal_v3/models/message.py +50 -0
  519. autocoder/terminal_v3/models/tool_display.py +247 -0
  520. autocoder/terminal_v3/ui/__init__.py +7 -0
  521. autocoder/terminal_v3/ui/keybindings.py +56 -0
  522. autocoder/terminal_v3/ui/layout.py +141 -0
  523. autocoder/terminal_v3/ui/styles.py +43 -0
  524. autocoder/tsproject/__init__.py +23 -23
  525. autocoder/utils/auto_coder_utils/chat_stream_out.py +1 -1
  526. autocoder/utils/llms.py +88 -80
  527. autocoder/utils/math_utils.py +101 -0
  528. autocoder/utils/model_provider_selector.py +16 -4
  529. autocoder/utils/operate_config_api.py +33 -5
  530. autocoder/utils/thread_utils.py +2 -2
  531. autocoder/version.py +4 -2
  532. autocoder/workflow_agents/__init__.py +84 -0
  533. autocoder/workflow_agents/agent.py +143 -0
  534. autocoder/workflow_agents/exceptions.py +573 -0
  535. autocoder/workflow_agents/executor.py +489 -0
  536. autocoder/workflow_agents/loader.py +737 -0
  537. autocoder/workflow_agents/runner.py +267 -0
  538. autocoder/workflow_agents/types.py +172 -0
  539. autocoder/workflow_agents/utils.py +434 -0
  540. autocoder/workflow_agents/workflow_manager.py +211 -0
  541. auto_coder-1.0.0.dist-info/METADATA +0 -396
  542. auto_coder-1.0.0.dist-info/RECORD +0 -442
  543. auto_coder-1.0.0.dist-info/licenses/LICENSE +0 -201
  544. autocoder/auto_coder_server.py +0 -672
  545. autocoder/benchmark.py +0 -138
  546. autocoder/common/ac_style_command_parser/example.py +0 -7
  547. autocoder/common/cleaner.py +0 -31
  548. autocoder/common/command_completer_v2.py +0 -615
  549. autocoder/common/context_pruner.py +0 -477
  550. autocoder/common/conversation_pruner.py +0 -132
  551. autocoder/common/directory_cache/__init__.py +0 -1
  552. autocoder/common/directory_cache/cache.py +0 -192
  553. autocoder/common/directory_cache/test_cache.py +0 -190
  554. autocoder/common/file_checkpoint/examples.py +0 -217
  555. autocoder/common/llm_friendly_package_example.py +0 -138
  556. autocoder/common/llm_friendly_package_test.py +0 -63
  557. autocoder/common/pull_requests/test_module.py +0 -1
  558. autocoder/common/rulefiles/autocoderrules_utils.py +0 -484
  559. autocoder/common/text.py +0 -30
  560. autocoder/common/v2/agent/agentic_edit_tools/list_package_info_tool_resolver.py +0 -42
  561. autocoder/common/v2/agent/agentic_edit_tools/test_execute_command_tool_resolver.py +0 -70
  562. autocoder/common/v2/agent/agentic_edit_tools/test_search_files_tool_resolver.py +0 -163
  563. autocoder/common/v2/agent/agentic_tool_display.py +0 -183
  564. autocoder/plugins/dynamic_completion_example.py +0 -148
  565. autocoder/plugins/sample_plugin.py +0 -160
  566. autocoder/sdk/cli/__main__.py +0 -26
  567. autocoder/sdk/cli/completion_wrapper.py +0 -38
  568. autocoder/sdk/cli/install_completion.py +0 -301
  569. autocoder/sdk/models/messages.py +0 -209
  570. autocoder/sdk/session/__init__.py +0 -32
  571. autocoder/sdk/session/session.py +0 -106
  572. autocoder/sdk/session/session_manager.py +0 -56
  573. {auto_coder-1.0.0.dist-info → auto_coder-2.0.0.dist-info}/top_level.txt +0 -0
  574. /autocoder/{sdk/example.py → common/agent_query_queue/__init__.py} +0 -0
@@ -0,0 +1,1127 @@
1
+ """
2
+ Command executor module for shell command execution with timeout support.
3
+
4
+ This module provides the main interface for executing shell commands with
5
+ comprehensive timeout control, process cleanup, and monitoring capabilities.
6
+ """
7
+
8
+ import os
9
+ import platform
10
+ import select
11
+ import subprocess
12
+ import sys
13
+ import time
14
+ import threading
15
+ from typing import Optional, Dict, Tuple, Any, Generator, Union, List
16
+ from loguru import logger as log
17
+
18
+ from .timeout_config import TimeoutConfig
19
+ from .process_manager import ProcessManager, _command_to_string
20
+ from .monitoring import CommandExecutionLogger, PerformanceMonitor, get_logger, get_global_monitor
21
+ from .error_recovery import ErrorRecoveryManager, create_default_error_recovery_manager
22
+ from .exceptions import CommandExecutionError, CommandTimeoutError
23
+ from .timeout_manager import create_timeout_context
24
+
25
+ try:
26
+ import pexpect
27
+ PEXPECT_AVAILABLE = True
28
+ except ImportError:
29
+ log.warning("pexpect not available, interactive command support will be limited")
30
+ PEXPECT_AVAILABLE = False
31
+
32
+
33
+ class CommandExecutor:
34
+ """
35
+ Main command executor with timeout support and process management.
36
+
37
+ This class provides a comprehensive interface for executing shell commands
38
+ with timeout control, process cleanup, monitoring, and error recovery.
39
+
40
+ Attributes:
41
+ config: Timeout configuration
42
+ process_manager: Process manager instance
43
+ logger: Command execution logger
44
+ monitor: Performance monitor
45
+ error_recovery: Error recovery manager
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ config: Optional[TimeoutConfig] = None,
51
+ logger: Optional[CommandExecutionLogger] = None,
52
+ monitor: Optional[PerformanceMonitor] = None,
53
+ error_recovery: Optional[ErrorRecoveryManager] = None,
54
+ verbose: bool = False
55
+ ):
56
+ """
57
+ Initialize command executor.
58
+
59
+ Args:
60
+ config: Timeout configuration (uses default if None)
61
+ logger: Command execution logger (uses global if None)
62
+ monitor: Performance monitor (uses global if None)
63
+ error_recovery: Error recovery manager (uses default if None)
64
+ verbose: Whether to enable verbose logging
65
+ """
66
+ self.config = config or TimeoutConfig()
67
+ self.process_manager = ProcessManager(self.config)
68
+ self.logger = logger or get_logger(verbose)
69
+ self.monitor = monitor or get_global_monitor()
70
+ self.error_recovery = error_recovery or create_default_error_recovery_manager()
71
+ self.verbose = verbose
72
+
73
+ log.debug(f"CommandExecutor initialized (verbose={verbose})")
74
+
75
+ def execute(
76
+ self,
77
+ command: Union[str, List[str]],
78
+ timeout: Optional[float] = None,
79
+ cwd: Optional[str] = None,
80
+ env: Optional[Dict[str, str]] = None,
81
+ capture_output: bool = True,
82
+ text: bool = True,
83
+ encoding: str = 'utf-8',
84
+ shell: Optional[bool] = None,
85
+ **kwargs
86
+ ) -> Tuple[int, str]:
87
+ """
88
+ Execute a command with timeout support.
89
+
90
+ Args:
91
+ command: Command to execute
92
+ timeout: Timeout in seconds (uses config default if None)
93
+ cwd: Working directory
94
+ env: Environment variables
95
+ capture_output: Whether to capture stdout/stderr
96
+ text: Whether to use text mode
97
+ encoding: Text encoding
98
+ shell: Whether to use shell
99
+ **kwargs: Additional arguments for subprocess.Popen
100
+
101
+ Returns:
102
+ Tuple of (exit_code, output)
103
+
104
+ Raises:
105
+ CommandTimeoutError: If command times out
106
+ CommandExecutionError: If command execution fails
107
+ """
108
+ # Convert command to string for timeout config and logging
109
+ command_str = _command_to_string(command)
110
+
111
+ # Determine timeout
112
+ if timeout is None:
113
+ timeout = self.config.get_timeout_for_command(command_str)
114
+
115
+ # Start logging
116
+ metrics = self.logger.log_command_start(command_str, timeout, cwd=cwd)
117
+
118
+ # Auto-determine shell parameter if not specified
119
+ if shell is None:
120
+ shell = isinstance(command, str) # True for strings, False for lists
121
+
122
+ try:
123
+ # Execute command with subprocess
124
+ exit_code, output = self._execute_with_subprocess(
125
+ command, timeout, cwd, env, capture_output, text, encoding, shell, **kwargs
126
+ )
127
+
128
+ # Log completion
129
+ self.logger.log_command_complete(metrics, exit_code, output)
130
+
131
+ # Record metrics
132
+ self.monitor.record_execution(metrics)
133
+
134
+ return exit_code, output
135
+
136
+ except Exception as e:
137
+ # Log error
138
+ self.logger.log_command_complete(metrics, -1, "", e)
139
+
140
+ # Record failed metrics
141
+ self.monitor.record_execution(metrics)
142
+
143
+ raise
144
+
145
+ def _execute_with_subprocess(
146
+ self,
147
+ command: Union[str, List[str]],
148
+ timeout: Optional[float],
149
+ cwd: Optional[str],
150
+ env: Optional[Dict[str, str]],
151
+ capture_output: bool,
152
+ text: bool,
153
+ encoding: str,
154
+ shell: bool,
155
+ **kwargs
156
+ ) -> Tuple[int, str]:
157
+ """
158
+ Execute command using subprocess with timeout support.
159
+
160
+ Args:
161
+ command: Command to execute
162
+ timeout: Timeout in seconds
163
+ cwd: Working directory
164
+ env: Environment variables
165
+ capture_output: Whether to capture output
166
+ text: Whether to use text mode
167
+ encoding: Text encoding
168
+ shell: Whether to use shell
169
+ **kwargs: Additional subprocess arguments
170
+
171
+ Returns:
172
+ Tuple of (exit_code, output)
173
+ """
174
+ # Create process
175
+ process = self.process_manager.create_process(
176
+ command=command,
177
+ timeout=timeout,
178
+ cwd=cwd,
179
+ env=env,
180
+ shell=shell,
181
+ capture_output=capture_output,
182
+ text=text,
183
+ encoding=encoding,
184
+ **kwargs
185
+ )
186
+
187
+ # Convert command to string for logging
188
+ command_str = _command_to_string(command)
189
+ metrics = self.logger.log_command_start(command_str, timeout, process.pid, cwd)
190
+
191
+ # Prepare timeout callback for TimeoutContext
192
+ timeout_occurred = [False] # Use list to allow modification in nested function
193
+ partial_output = [""]
194
+
195
+ def timeout_callback(timed_out_process, timeout_val):
196
+ """Handle timeout event with proper logging and cleanup."""
197
+ timeout_occurred[0] = True
198
+ timeout_val = timeout_val if timeout_val is not None else 0.0
199
+
200
+ # Log timeout
201
+ self.logger.log_command_timeout(metrics, timeout_val, timed_out_process.pid)
202
+ log.warning(f"Command '{command_str}' timed out after {timeout_val} seconds (PID: {timed_out_process.pid})")
203
+
204
+ # Try to get partial output before cleanup
205
+ try:
206
+ stdout, stderr = timed_out_process.communicate(timeout=2)
207
+ partial_output[0] = stdout or ""
208
+ except:
209
+ partial_output[0] = ""
210
+
211
+ # The cleanup will be handled by TimeoutContext's cleanup_process_tree
212
+
213
+ try:
214
+ # Use TimeoutContext for advanced timeout management
215
+ if timeout and timeout > 0:
216
+ with create_timeout_context(process, timeout, callback=timeout_callback):
217
+ stdout, stderr = process.communicate() # No timeout here - managed by TimeoutContext
218
+
219
+ # Check if timeout occurred during execution
220
+ if timeout_occurred[0]:
221
+ # Use partial output if timeout occurred
222
+ output_str = partial_output[0]
223
+ if self.verbose and output_str:
224
+ print(output_str, end="", flush=True)
225
+ raise CommandTimeoutError(command_str, timeout, process.pid)
226
+
227
+ output_str = stdout or ""
228
+ exit_code = process.returncode
229
+ else:
230
+ # No timeout specified, execute normally
231
+ stdout, stderr = process.communicate()
232
+ output_str = stdout or ""
233
+ exit_code = process.returncode
234
+
235
+ # Handle verbose output
236
+ if self.verbose and output_str:
237
+ print(output_str, end="", flush=True)
238
+
239
+ if self.verbose:
240
+ log.info(f"Command completed with exit code {exit_code}")
241
+
242
+ return exit_code, output_str
243
+
244
+ except Exception as e:
245
+ # Ensure process is cleaned up
246
+ try:
247
+ self.process_manager.cleanup_process_tree(process)
248
+ except Exception as cleanup_error:
249
+ log.error(f"Error during cleanup: {cleanup_error}")
250
+
251
+ raise e
252
+
253
+ def execute_generator(
254
+ self,
255
+ command: Union[str, List[str]],
256
+ timeout: Optional[float] = None,
257
+ cwd: Optional[str] = None,
258
+ env: Optional[Dict[str, str]] = None,
259
+ encoding: str = 'utf-8',
260
+ **kwargs
261
+ ) -> Generator[str, None, int]:
262
+ """
263
+ Execute command and yield output in real-time.
264
+
265
+ Args:
266
+ command: Command to execute
267
+ timeout: Timeout in seconds
268
+ cwd: Working directory
269
+ env: Environment variables
270
+ encoding: Text encoding
271
+ **kwargs: Additional subprocess arguments
272
+
273
+ Yields:
274
+ Command output strings
275
+
276
+ Returns:
277
+ Final exit code
278
+
279
+ Raises:
280
+ CommandTimeoutError: If command times out
281
+ CommandExecutionError: If command execution fails
282
+ """
283
+ # Convert command to string for timeout config and logging
284
+ command_str = _command_to_string(command)
285
+
286
+ # Determine timeout
287
+ if timeout is None:
288
+ timeout = self.config.get_timeout_for_command(command_str)
289
+
290
+ # Start logging
291
+ metrics = self.logger.log_command_start(command_str, timeout, cwd=cwd)
292
+
293
+ try:
294
+ # Create process
295
+ process = self.process_manager.create_process(
296
+ command=command,
297
+ timeout=timeout,
298
+ cwd=cwd,
299
+ env=env,
300
+ shell=True,
301
+ capture_output=True,
302
+ text=True,
303
+ encoding=encoding,
304
+ **kwargs
305
+ )
306
+
307
+ metrics.pid = process.pid
308
+
309
+ # Stream output
310
+ output_length = 0
311
+
312
+ while True:
313
+ # Check if process has finished
314
+ if process.poll() is not None:
315
+ # Read any remaining output
316
+ if process.stdout:
317
+ remaining = process.stdout.read()
318
+ if remaining:
319
+ output_length += len(remaining)
320
+ yield remaining
321
+ break
322
+
323
+ # Read output chunk
324
+ if process.stdout:
325
+ if platform.system() != "Windows":
326
+ ready, _, _ = select.select([process.stdout], [], [], 0.1)
327
+ if ready:
328
+ chunk = process.stdout.read(1024)
329
+ if chunk:
330
+ output_length += len(chunk)
331
+ yield chunk
332
+ else:
333
+ try:
334
+ chunk = process.stdout.read(1024)
335
+ if chunk:
336
+ output_length += len(chunk)
337
+ yield chunk
338
+ except Exception:
339
+ pass
340
+
341
+ time.sleep(0.01)
342
+
343
+ # Wait for process completion with TimeoutContext
344
+ timeout_occurred = [False]
345
+
346
+ def generator_timeout_callback(timed_out_process, timeout_val):
347
+ """Handle timeout for generator execution."""
348
+ timeout_occurred[0] = True
349
+ timeout_val = timeout_val if timeout_val is not None else 0.0
350
+ self.logger.log_command_timeout(metrics, timeout_val, timed_out_process.pid)
351
+ log.warning(f"Generator command '{command_str}' timed out after {timeout_val} seconds (PID: {timed_out_process.pid})")
352
+
353
+ try:
354
+ if timeout and timeout > 0:
355
+ with create_timeout_context(process, timeout, callback=generator_timeout_callback):
356
+ exit_code = self.process_manager.wait_for_process(process, None) # No timeout in wait - managed by TimeoutContext
357
+
358
+ if timeout_occurred[0]:
359
+ raise CommandTimeoutError(command_str, timeout, process.pid)
360
+ else:
361
+ exit_code = self.process_manager.wait_for_process(process, None)
362
+ except subprocess.TimeoutExpired:
363
+ # This shouldn't happen with TimeoutContext, but keep as fallback
364
+ timeout_val = timeout if timeout is not None else 0.0
365
+ raise CommandTimeoutError(command_str, timeout_val, process.pid)
366
+
367
+ # Log completion
368
+ metrics.output_length = output_length
369
+ self.logger.log_command_complete(metrics, exit_code)
370
+ self.monitor.record_execution(metrics)
371
+
372
+ return exit_code
373
+
374
+ except Exception as e:
375
+ # Log error
376
+ self.logger.log_command_complete(metrics, -1, "", e)
377
+ self.monitor.record_execution(metrics)
378
+
379
+ raise
380
+
381
+ def execute_background(
382
+ self,
383
+ command: Union[str, List[str]],
384
+ cwd: Optional[str] = None,
385
+ env: Optional[Dict[str, str]] = None,
386
+ shell: bool = True,
387
+ **kwargs
388
+ ) -> Dict[str, Any]:
389
+ """
390
+ Execute a command in the background and return immediately with process info.
391
+
392
+ Args:
393
+ command: Command to execute
394
+ cwd: Working directory
395
+ env: Environment variables
396
+ shell: Whether to use shell
397
+ **kwargs: Additional arguments for subprocess.Popen
398
+
399
+ Returns:
400
+ Dictionary containing process information:
401
+ {
402
+ "pid": int,
403
+ "command": str,
404
+ "working_directory": str,
405
+ "start_time": float,
406
+ "status": "running"
407
+ }
408
+
409
+ Raises:
410
+ CommandExecutionError: If command execution fails
411
+ """
412
+ # Convert command to string for logging
413
+ command_str = _command_to_string(command)
414
+
415
+ # Start logging
416
+ metrics = self.logger.log_command_start(command_str, None, cwd=cwd)
417
+
418
+ try:
419
+ # Create background process
420
+ process = self.process_manager.create_background_process(
421
+ command=command,
422
+ cwd=cwd,
423
+ env=env,
424
+ shell=shell,
425
+ **kwargs
426
+ )
427
+
428
+ # Update metrics with PID
429
+ metrics.pid = process.pid
430
+
431
+ # Note: We don't log completion here since the process is still running
432
+ # Completion will be logged when the process actually finishes
433
+
434
+ # Get process info
435
+ process_info = self.process_manager.get_background_process_info(process.pid)
436
+
437
+ if self.verbose:
438
+ log.info(f"Started background command: {command_str} (PID: {process.pid})")
439
+
440
+ return {
441
+ "pid": process.pid,
442
+ "command": command_str,
443
+ "working_directory": cwd or os.getcwd(),
444
+ "start_time": process_info["start_time"] if process_info else time.time(),
445
+ "status": "running"
446
+ }
447
+
448
+ except Exception as e:
449
+ # Log error
450
+ self.logger.log_command_complete(metrics, -1, "", e)
451
+ self.monitor.record_execution(metrics)
452
+
453
+ raise CommandExecutionError(f"Failed to start background command: {str(e)}")
454
+
455
+ def get_background_processes(self) -> Dict[int, Dict[str, Any]]:
456
+ """
457
+ Get information about all background processes.
458
+
459
+ Returns:
460
+ Dictionary mapping PID to process information
461
+ """
462
+ return self.process_manager.get_background_processes()
463
+
464
+ def get_background_process_info(self, pid: int) -> Optional[Dict[str, Any]]:
465
+ """
466
+ Get information about a specific background process.
467
+
468
+ Args:
469
+ pid: Process ID
470
+
471
+ Returns:
472
+ Process information or None if not found
473
+ """
474
+ return self.process_manager.get_background_process_info(pid)
475
+
476
+ def cleanup_background_process(self, pid: int, timeout: Optional[float] = None) -> bool:
477
+ """
478
+ Clean up a specific background process.
479
+
480
+ Args:
481
+ pid: Process ID to cleanup
482
+ timeout: Timeout for cleanup
483
+
484
+ Returns:
485
+ True if cleanup successful
486
+ """
487
+ return self.process_manager.cleanup_background_process(pid, timeout)
488
+
489
+ def get_status(self) -> Dict[str, Any]:
490
+ """
491
+ Get executor status information.
492
+
493
+ Returns:
494
+ Dictionary with status information
495
+ """
496
+ return {
497
+ 'config': self.config.to_dict(),
498
+ 'active_processes': len(self.process_manager.get_all_processes()),
499
+ 'background_processes': self.process_manager.get_background_process_count(),
500
+ 'process_groups': len(self.process_manager.get_process_groups()),
501
+ 'performance_summary': self.monitor.get_performance_summary(),
502
+ 'recent_alerts': self.monitor.get_alerts(5),
503
+ }
504
+
505
+ def execute_batch(
506
+ self,
507
+ commands: List[Union[str, List[str]]],
508
+ timeout: Optional[float] = None,
509
+ per_command_timeout: Optional[float] = None,
510
+ parallel: bool = True,
511
+ cwd: Optional[str] = None,
512
+ env: Optional[Dict[str, str]] = None,
513
+ capture_output: bool = True,
514
+ text: bool = True,
515
+ encoding: str = 'utf-8',
516
+ shell: Optional[bool] = None,
517
+ **kwargs
518
+ ) -> List[Dict[str, Any]]:
519
+ """
520
+ Execute multiple commands in batch, either in parallel or serial.
521
+
522
+ Args:
523
+ commands: List of commands to execute
524
+ timeout: Overall timeout for all commands (seconds)
525
+ per_command_timeout: Timeout for each individual command (seconds)
526
+ parallel: Whether to execute commands in parallel (True) or serial (False)
527
+ cwd: Working directory
528
+ env: Environment variables
529
+ capture_output: Whether to capture stdout/stderr
530
+ text: Whether to use text mode
531
+ encoding: Text encoding
532
+ shell: Whether to use shell
533
+ **kwargs: Additional arguments for subprocess.Popen
534
+
535
+ Returns:
536
+ List of dictionaries containing results for each command:
537
+ [
538
+ {
539
+ "command": str,
540
+ "index": int,
541
+ "exit_code": int,
542
+ "output": str,
543
+ "error": str or None,
544
+ "timed_out": bool,
545
+ "duration": float,
546
+ "start_time": float,
547
+ "end_time": float
548
+ },
549
+ ...
550
+ ]
551
+
552
+ Raises:
553
+ CommandExecutionError: If batch execution setup fails
554
+ CommandTimeoutError: If overall timeout is exceeded
555
+ """
556
+ if not commands:
557
+ return []
558
+
559
+ # Log batch start
560
+ batch_start_time = time.time()
561
+ log.info(f"Starting batch execution of {len(commands)} commands (parallel={parallel})")
562
+
563
+ # Results list with proper initialization
564
+ results: List[Optional[Dict[str, Any]]] = [None] * len(commands)
565
+
566
+ # Determine per-command timeout
567
+ if per_command_timeout is None:
568
+ # Use config default or overall timeout divided by command count
569
+ if timeout:
570
+ per_command_timeout = timeout / len(commands) if parallel else timeout
571
+ else:
572
+ per_command_timeout = None # Will use config default for each command
573
+
574
+ try:
575
+ if parallel:
576
+ results = self._execute_batch_parallel(
577
+ commands, timeout, per_command_timeout, cwd, env,
578
+ capture_output, text, encoding, shell, **kwargs
579
+ )
580
+ else:
581
+ results = self._execute_batch_serial(
582
+ commands, timeout, per_command_timeout, cwd, env,
583
+ capture_output, text, encoding, shell, **kwargs
584
+ )
585
+
586
+ # Log batch completion
587
+ batch_duration = time.time() - batch_start_time
588
+ successful_count = sum(1 for r in results if r and r.get("exit_code") == 0)
589
+ failed_count = sum(1 for r in results if r and r.get("exit_code") != 0)
590
+ timeout_count = sum(1 for r in results if r and r.get("timed_out", False))
591
+
592
+ log.info(
593
+ f"Batch execution completed in {batch_duration:.2f}s: "
594
+ f"{successful_count} successful, {failed_count} failed, {timeout_count} timed out"
595
+ )
596
+
597
+ # Ensure all results are non-None before returning
598
+ final_results = []
599
+ for i, r in enumerate(results):
600
+ if r is not None:
601
+ final_results.append(r)
602
+ else:
603
+ # This shouldn't happen in normal flow, but handle it
604
+ final_results.append({
605
+ "command": _command_to_string(commands[i]),
606
+ "index": i,
607
+ "exit_code": -1,
608
+ "output": "",
609
+ "error": "Command not executed",
610
+ "timed_out": False,
611
+ "duration": 0.0,
612
+ "start_time": batch_start_time,
613
+ "end_time": time.time()
614
+ })
615
+ return final_results
616
+
617
+ except Exception as e:
618
+ log.error(f"Batch execution failed: {e}")
619
+ # Return partial results if available
620
+ final_results = []
621
+ for i, r in enumerate(results):
622
+ if r is not None:
623
+ final_results.append(r)
624
+ else:
625
+ final_results.append({
626
+ "command": _command_to_string(commands[i]),
627
+ "index": i,
628
+ "exit_code": -1,
629
+ "output": "",
630
+ "error": str(e),
631
+ "timed_out": False,
632
+ "duration": 0.0,
633
+ "start_time": batch_start_time,
634
+ "end_time": time.time()
635
+ })
636
+ return final_results
637
+
638
+ def _execute_batch_serial(
639
+ self,
640
+ commands: List[Union[str, List[str]]],
641
+ timeout: Optional[float],
642
+ per_command_timeout: Optional[float],
643
+ cwd: Optional[str],
644
+ env: Optional[Dict[str, str]],
645
+ capture_output: bool,
646
+ text: bool,
647
+ encoding: str,
648
+ shell: bool,
649
+ **kwargs
650
+ ) -> List[Dict[str, Any]]:
651
+ """Execute commands serially."""
652
+ results = []
653
+ batch_start = time.time()
654
+
655
+ for i, command in enumerate(commands):
656
+ # Calculate remaining time if overall timeout is set
657
+ remaining_time = None
658
+ if timeout:
659
+ elapsed = time.time() - batch_start
660
+ remaining_time = timeout - elapsed
661
+ if remaining_time <= 0:
662
+ # Overall timeout exceeded
663
+ # Fill remaining results with timeout error
664
+ for j in range(i, len(commands)):
665
+ results.append({
666
+ "command": _command_to_string(commands[j]),
667
+ "index": j,
668
+ "exit_code": -1,
669
+ "output": "",
670
+ "error": "Batch timeout exceeded before execution",
671
+ "timed_out": True,
672
+ "duration": 0.0,
673
+ "start_time": time.time(),
674
+ "end_time": time.time()
675
+ })
676
+ break
677
+
678
+ # Use the smaller of per-command timeout and remaining time
679
+ cmd_timeout = per_command_timeout
680
+ if remaining_time is not None:
681
+ cmd_timeout = min(cmd_timeout, remaining_time) if cmd_timeout else remaining_time
682
+
683
+ # Execute command
684
+ start_time = time.time()
685
+ try:
686
+ exit_code, output = self.execute(
687
+ command, timeout=cmd_timeout, cwd=cwd, env=env,
688
+ capture_output=capture_output, text=text, encoding=encoding,
689
+ shell=shell, **kwargs
690
+ )
691
+ end_time = time.time()
692
+
693
+ results.append({
694
+ "command": _command_to_string(command),
695
+ "index": i,
696
+ "exit_code": exit_code,
697
+ "output": output,
698
+ "error": None,
699
+ "timed_out": False,
700
+ "duration": end_time - start_time,
701
+ "start_time": start_time,
702
+ "end_time": end_time
703
+ })
704
+ except CommandTimeoutError as e:
705
+ end_time = time.time()
706
+ results.append({
707
+ "command": _command_to_string(command),
708
+ "index": i,
709
+ "exit_code": -1,
710
+ "output": "",
711
+ "error": str(e),
712
+ "timed_out": True,
713
+ "duration": end_time - start_time,
714
+ "start_time": start_time,
715
+ "end_time": end_time
716
+ })
717
+ except Exception as e:
718
+ end_time = time.time()
719
+ results.append({
720
+ "command": _command_to_string(command),
721
+ "index": i,
722
+ "exit_code": -1,
723
+ "output": "",
724
+ "error": str(e),
725
+ "timed_out": False,
726
+ "duration": end_time - start_time,
727
+ "start_time": start_time,
728
+ "end_time": end_time
729
+ })
730
+
731
+ return results
732
+
733
+ def _execute_batch_parallel(
734
+ self,
735
+ commands: List[Union[str, List[str]]],
736
+ timeout: Optional[float],
737
+ per_command_timeout: Optional[float],
738
+ cwd: Optional[str],
739
+ env: Optional[Dict[str, str]],
740
+ capture_output: bool,
741
+ text: bool,
742
+ encoding: str,
743
+ shell: bool,
744
+ **kwargs
745
+ ) -> List[Dict[str, Any]]:
746
+ """Execute commands in parallel."""
747
+ from concurrent.futures import ThreadPoolExecutor, as_completed
748
+ import concurrent.futures
749
+
750
+ results: List[Optional[Dict[str, Any]]] = [None] * len(commands)
751
+ batch_start = time.time()
752
+
753
+ def execute_single_command(idx: int, cmd: Union[str, List[str]]) -> Tuple[int, Dict[str, Any]]:
754
+ """Execute a single command and return (index, result)."""
755
+ start_time = time.time()
756
+ try:
757
+ exit_code, output = self.execute(
758
+ cmd, timeout=per_command_timeout, cwd=cwd, env=env,
759
+ capture_output=capture_output, text=text, encoding=encoding,
760
+ shell=shell, **kwargs
761
+ )
762
+ end_time = time.time()
763
+
764
+ return idx, {
765
+ "command": _command_to_string(cmd),
766
+ "index": idx,
767
+ "exit_code": exit_code,
768
+ "output": output,
769
+ "error": None,
770
+ "timed_out": False,
771
+ "duration": end_time - start_time,
772
+ "start_time": start_time,
773
+ "end_time": end_time
774
+ }
775
+ except CommandTimeoutError as e:
776
+ end_time = time.time()
777
+ return idx, {
778
+ "command": _command_to_string(cmd),
779
+ "index": idx,
780
+ "exit_code": -1,
781
+ "output": "",
782
+ "error": str(e),
783
+ "timed_out": True,
784
+ "duration": end_time - start_time,
785
+ "start_time": start_time,
786
+ "end_time": end_time
787
+ }
788
+ except Exception as e:
789
+ end_time = time.time()
790
+ return idx, {
791
+ "command": _command_to_string(cmd),
792
+ "index": idx,
793
+ "exit_code": -1,
794
+ "output": "",
795
+ "error": str(e),
796
+ "timed_out": False,
797
+ "duration": end_time - start_time,
798
+ "start_time": start_time,
799
+ "end_time": end_time
800
+ }
801
+
802
+ # Use ThreadPoolExecutor for parallel execution
803
+ max_workers = min(len(commands), os.cpu_count() or 4)
804
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
805
+ # Submit all commands
806
+ futures = {
807
+ executor.submit(execute_single_command, i, cmd): i
808
+ for i, cmd in enumerate(commands)
809
+ }
810
+
811
+ # Wait for completion with overall timeout
812
+ try:
813
+ # Process completed futures as they finish
814
+ for future in as_completed(futures, timeout=timeout):
815
+ idx, result = future.result()
816
+ results[idx] = result
817
+
818
+ except concurrent.futures.TimeoutError:
819
+ # Overall timeout exceeded
820
+ log.warning(f"Batch execution timeout exceeded after {timeout}s")
821
+
822
+ # Cancel remaining futures
823
+ for future in futures:
824
+ if not future.done():
825
+ future.cancel()
826
+
827
+ # Fill in timeout results for uncompleted commands
828
+ for i, result in enumerate(results):
829
+ if result is None:
830
+ results[i] = {
831
+ "command": _command_to_string(commands[i]),
832
+ "index": i,
833
+ "exit_code": -1,
834
+ "output": "",
835
+ "error": "Batch timeout exceeded",
836
+ "timed_out": True,
837
+ "duration": time.time() - batch_start,
838
+ "start_time": batch_start,
839
+ "end_time": time.time()
840
+ }
841
+
842
+ # Ensure all results are non-None before returning
843
+ final_results = []
844
+ for r in results:
845
+ if r is not None:
846
+ final_results.append(r)
847
+ return final_results
848
+
849
+ def cleanup(self) -> None:
850
+ """
851
+ Clean up all resources and processes.
852
+ """
853
+ log.debug("Cleaning up CommandExecutor")
854
+
855
+ # Clean up all processes
856
+ failed_pids = self.process_manager.cleanup_all_processes()
857
+
858
+ if failed_pids:
859
+ log.warning(f"Failed to cleanup {len(failed_pids)} processes: {failed_pids}")
860
+
861
+ log.debug("CommandExecutor cleanup completed")
862
+
863
+ def __del__(self):
864
+ """Cleanup when executor is destroyed."""
865
+ try:
866
+ self.cleanup()
867
+ except Exception:
868
+ pass # Ignore errors during cleanup
869
+
870
+
871
+ # Convenience functions for simple usage
872
+ def execute_command(
873
+ command: Union[str, List[str]],
874
+ timeout: Optional[float] = None,
875
+ cwd: Optional[str] = None,
876
+ verbose: bool = False,
877
+ **kwargs
878
+ ) -> Tuple[int, str]:
879
+ """
880
+ Convenience function to execute a command with timeout support.
881
+
882
+ Args:
883
+ command: Command to execute
884
+ timeout: Timeout in seconds
885
+ cwd: Working directory
886
+ verbose: Whether to enable verbose logging
887
+ **kwargs: Additional arguments
888
+
889
+ Returns:
890
+ Tuple of (exit_code, output)
891
+ """
892
+ # Create a timeout config if timeout is specified
893
+ config = None
894
+ if timeout is not None:
895
+ config = TimeoutConfig(default_timeout=timeout)
896
+
897
+ executor = CommandExecutor(config=config, verbose=verbose)
898
+ try:
899
+ return executor.execute(command, timeout, cwd, **kwargs)
900
+ finally:
901
+ executor.cleanup()
902
+
903
+
904
+ def execute_command_generator(
905
+ command: Union[str, List[str]],
906
+ timeout: Optional[float] = None,
907
+ cwd: Optional[str] = None,
908
+ verbose: bool = False,
909
+ **kwargs
910
+ ) -> Generator[str, None, int]:
911
+ """
912
+ Convenience function to execute a command and stream output.
913
+
914
+ Args:
915
+ command: Command to execute
916
+ timeout: Timeout in seconds
917
+ cwd: Working directory
918
+ verbose: Whether to enable verbose logging
919
+ **kwargs: Additional arguments
920
+
921
+ Yields:
922
+ Command output strings
923
+
924
+ Returns:
925
+ Final exit code
926
+ """
927
+ executor = CommandExecutor(verbose=verbose)
928
+ return executor.execute_generator(command, timeout, cwd, **kwargs)
929
+
930
+
931
+ def execute_command_background(
932
+ command: Union[str, List[str]],
933
+ cwd: Optional[str] = None,
934
+ verbose: bool = False,
935
+ **kwargs
936
+ ) -> Dict[str, Any]:
937
+ """
938
+ Convenience function to execute a command in the background.
939
+
940
+ Args:
941
+ command: Command to execute
942
+ cwd: Working directory
943
+ verbose: Whether to enable verbose logging
944
+ **kwargs: Additional arguments
945
+
946
+ Returns:
947
+ Dictionary containing process information with PID
948
+ """
949
+ import uuid
950
+
951
+ # Use global process manager to maintain background processes
952
+ manager = _get_global_process_manager()
953
+
954
+ # Convert command to string for logging
955
+ command_str = _command_to_string(command)
956
+
957
+ # Generate unique process ID
958
+ process_uniq_id = str(uuid.uuid4())
959
+
960
+ try:
961
+ # Create background process directly with process manager
962
+ process = manager.create_background_process(
963
+ command=command,
964
+ cwd=cwd,
965
+ process_uniq_id=process_uniq_id,
966
+ **kwargs
967
+ )
968
+
969
+ if verbose:
970
+ log.info(f"Started background command: {command_str} (PID: {process.pid}, ID: {process_uniq_id})")
971
+
972
+ return {
973
+ "pid": process.pid,
974
+ "process_uniq_id": process_uniq_id,
975
+ "command": command_str,
976
+ "working_directory": cwd or os.getcwd(),
977
+ "start_time": time.time(),
978
+ "status": "running"
979
+ }
980
+
981
+ except Exception as e:
982
+ raise CommandExecutionError(f"Failed to start background command: {str(e)}")
983
+
984
+
985
+ # Global process manager for background processes
986
+ _global_process_manager: Optional[ProcessManager] = None
987
+ _global_process_manager_lock = threading.Lock()
988
+
989
+
990
+ def _get_global_process_manager() -> ProcessManager:
991
+ """Get or create global process manager for background processes."""
992
+ global _global_process_manager
993
+
994
+ if _global_process_manager is None:
995
+ with _global_process_manager_lock:
996
+ if _global_process_manager is None:
997
+ config = TimeoutConfig()
998
+ _global_process_manager = ProcessManager(config)
999
+
1000
+ return _global_process_manager
1001
+
1002
+
1003
+ def get_background_processes() -> Dict[int, Dict[str, Any]]:
1004
+ """
1005
+ Convenience function to get all background processes.
1006
+
1007
+ Returns:
1008
+ Dictionary mapping PID to process information
1009
+ """
1010
+ # Use global process manager to maintain state across calls
1011
+ manager = _get_global_process_manager()
1012
+ return manager.get_background_processes()
1013
+
1014
+
1015
+ def cleanup_background_process(pid: int, timeout: Optional[float] = None) -> bool:
1016
+ """
1017
+ Convenience function to cleanup a specific background process.
1018
+
1019
+ Args:
1020
+ pid: Process ID to cleanup
1021
+ timeout: Timeout for cleanup
1022
+
1023
+ Returns:
1024
+ True if cleanup successful
1025
+ """
1026
+ # Use global process manager
1027
+ manager = _get_global_process_manager()
1028
+ return manager.cleanup_background_process(pid, timeout)
1029
+
1030
+
1031
+ def get_background_process_info(pid: int) -> Optional[Dict[str, Any]]:
1032
+ """
1033
+ Convenience function to get information about a specific background process.
1034
+
1035
+ Args:
1036
+ pid: Process ID
1037
+
1038
+ Returns:
1039
+ Process information or None if not found
1040
+ """
1041
+ # Use global process manager
1042
+ manager = _get_global_process_manager()
1043
+ return manager.get_background_process_info(pid)
1044
+
1045
+
1046
+ def execute_commands(
1047
+ commands: List[Union[str, List[str]]],
1048
+ timeout: Optional[float] = None,
1049
+ per_command_timeout: Optional[float] = None,
1050
+ parallel: bool = True,
1051
+ cwd: Optional[str] = None,
1052
+ env: Optional[Dict[str, str]] = None,
1053
+ verbose: bool = False,
1054
+ **kwargs
1055
+ ) -> List[Dict[str, Any]]:
1056
+ """
1057
+ Execute multiple commands in batch and return all results.
1058
+
1059
+ This is a convenience function that creates a CommandExecutor and executes
1060
+ a batch of commands either in parallel or serially.
1061
+
1062
+ Args:
1063
+ commands: List of commands to execute
1064
+ timeout: Overall timeout for all commands (seconds)
1065
+ per_command_timeout: Timeout for each individual command (seconds)
1066
+ parallel: Whether to execute commands in parallel (True) or serial (False)
1067
+ cwd: Working directory
1068
+ env: Environment variables
1069
+ verbose: Whether to enable verbose logging
1070
+ **kwargs: Additional arguments for subprocess.Popen
1071
+
1072
+ Returns:
1073
+ List of dictionaries containing results for each command:
1074
+ [
1075
+ {
1076
+ "command": str,
1077
+ "index": int,
1078
+ "exit_code": int,
1079
+ "output": str,
1080
+ "error": str or None,
1081
+ "timed_out": bool,
1082
+ "duration": float,
1083
+ "start_time": float,
1084
+ "end_time": float
1085
+ },
1086
+ ...
1087
+ ]
1088
+
1089
+ Raises:
1090
+ CommandExecutionError: If batch execution setup fails
1091
+
1092
+ Examples:
1093
+ >>> # Execute commands in parallel with overall timeout
1094
+ >>> results = execute_commands(
1095
+ ... ["echo Hello", "echo World"],
1096
+ ... timeout=10.0,
1097
+ ... parallel=True
1098
+ ... )
1099
+ >>> for result in results:
1100
+ ... print(f"{result['command']}: {result['output'].strip()}")
1101
+
1102
+ >>> # Execute commands serially with per-command timeout
1103
+ >>> results = execute_commands(
1104
+ ... ["sleep 1", "echo Done"],
1105
+ ... per_command_timeout=2.0,
1106
+ ... parallel=False
1107
+ ... )
1108
+ """
1109
+ # Create executor with appropriate configuration
1110
+ config = TimeoutConfig()
1111
+ executor = CommandExecutor(config=config, verbose=verbose)
1112
+
1113
+ try:
1114
+ # Execute batch
1115
+ results = executor.execute_batch(
1116
+ commands=commands,
1117
+ timeout=timeout,
1118
+ per_command_timeout=per_command_timeout,
1119
+ parallel=parallel,
1120
+ cwd=cwd,
1121
+ env=env,
1122
+ **kwargs
1123
+ )
1124
+ return results
1125
+ finally:
1126
+ # Clean up
1127
+ executor.cleanup()