@elizaos/sweagent-root 2.0.0-alpha

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.
Files changed (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +270 -0
  3. package/package.json +71 -0
  4. package/python/LICENSE +21 -0
  5. package/python/config/README.md +15 -0
  6. package/python/config/bash_only.yaml +222 -0
  7. package/python/config/benchmarks/250212_sweagent_heavy_sbl.yaml +188 -0
  8. package/python/config/benchmarks/250225_anthropic_filemap_simple_review.yaml +75 -0
  9. package/python/config/benchmarks/250522_anthropic_filemap_simple_review.yaml +92 -0
  10. package/python/config/benchmarks/250526_anthropic_filemap_simple_review_sbl.yaml +93 -0
  11. package/python/config/benchmarks/anthropic_filemap_multilingual.yaml +66 -0
  12. package/python/config/coding_challenge.yaml +104 -0
  13. package/python/config/default.yaml +69 -0
  14. package/python/config/default_backticks.yaml +69 -0
  15. package/python/config/default_mm_no_images.yaml +82 -0
  16. package/python/config/default_mm_with_images.yaml +83 -0
  17. package/python/config/demo/default.yaml +80 -0
  18. package/python/config/demo/no_instructions.yaml +69 -0
  19. package/python/config/demo/only_bash.yaml +60 -0
  20. package/python/config/exotic/default_shell.yaml +52 -0
  21. package/python/config/exotic/windowed_replace.yaml +125 -0
  22. package/python/config/exotic/windowed_replace_late_repro.yaml +127 -0
  23. package/python/config/human/human.yaml +24 -0
  24. package/python/config/human/human_demo.yaml +52 -0
  25. package/python/config/sweagent_0_7/07.yaml +101 -0
  26. package/python/config/sweagent_0_7/07_fcalling.yaml +100 -0
  27. package/python/config/sweagent_0_7/07_from_url.yaml +114 -0
  28. package/python/config/sweagent_0_7/07_thought_action.yaml +102 -0
  29. package/python/config/sweagent_0_7/07_thought_action_xml.yaml +96 -0
  30. package/python/mlc_config.json +44 -0
  31. package/python/pyproject.toml +262 -0
  32. package/python/sweagent/__init__.py +114 -0
  33. package/python/sweagent/__main__.py +4 -0
  34. package/python/sweagent/agent/__init__.py +0 -0
  35. package/python/sweagent/agent/action_sampler.py +317 -0
  36. package/python/sweagent/agent/agents.py +1294 -0
  37. package/python/sweagent/agent/extra/shell_agent.py +106 -0
  38. package/python/sweagent/agent/history_processors.py +399 -0
  39. package/python/sweagent/agent/hooks/__init__.py +0 -0
  40. package/python/sweagent/agent/hooks/abstract.py +139 -0
  41. package/python/sweagent/agent/hooks/status.py +34 -0
  42. package/python/sweagent/agent/models.py +896 -0
  43. package/python/sweagent/agent/problem_statement.py +312 -0
  44. package/python/sweagent/agent/reviewer.py +664 -0
  45. package/python/sweagent/environment/__init__.py +0 -0
  46. package/python/sweagent/environment/hooks/__init__.py +0 -0
  47. package/python/sweagent/environment/hooks/abstract.py +60 -0
  48. package/python/sweagent/environment/hooks/status.py +28 -0
  49. package/python/sweagent/environment/repo.py +219 -0
  50. package/python/sweagent/environment/swe_env.py +276 -0
  51. package/python/sweagent/exceptions.py +54 -0
  52. package/python/sweagent/inspector/README.md +6 -0
  53. package/python/sweagent/inspector/__init__.py +0 -0
  54. package/python/sweagent/inspector/favicon.ico +0 -0
  55. package/python/sweagent/inspector/fileViewer.js +354 -0
  56. package/python/sweagent/inspector/icons/computer.png +0 -0
  57. package/python/sweagent/inspector/icons/edit_icon.svg +11 -0
  58. package/python/sweagent/inspector/icons/swe-agent-logo-50.png +0 -0
  59. package/python/sweagent/inspector/icons/swellama_blue.png +0 -0
  60. package/python/sweagent/inspector/icons/swellama_brown.png +0 -0
  61. package/python/sweagent/inspector/icons/swellama_grey.png +0 -0
  62. package/python/sweagent/inspector/icons/swellama_tan.png +0 -0
  63. package/python/sweagent/inspector/index.html +25 -0
  64. package/python/sweagent/inspector/server.py +354 -0
  65. package/python/sweagent/inspector/static.py +169 -0
  66. package/python/sweagent/inspector/style.css +454 -0
  67. package/python/sweagent/run/__init__.py +0 -0
  68. package/python/sweagent/run/_progress.py +158 -0
  69. package/python/sweagent/run/batch_instances.py +419 -0
  70. package/python/sweagent/run/common.py +387 -0
  71. package/python/sweagent/run/compare_runs.py +123 -0
  72. package/python/sweagent/run/extract_pred.py +19 -0
  73. package/python/sweagent/run/hooks/__init__.py +0 -0
  74. package/python/sweagent/run/hooks/abstract.py +67 -0
  75. package/python/sweagent/run/hooks/apply_patch.py +106 -0
  76. package/python/sweagent/run/hooks/open_pr.py +244 -0
  77. package/python/sweagent/run/hooks/swe_bench_evaluate.py +113 -0
  78. package/python/sweagent/run/inspector_cli.py +493 -0
  79. package/python/sweagent/run/merge_predictions.py +64 -0
  80. package/python/sweagent/run/quick_stats.py +96 -0
  81. package/python/sweagent/run/remove_unfinished.py +63 -0
  82. package/python/sweagent/run/rich_test.py +91 -0
  83. package/python/sweagent/run/run.py +147 -0
  84. package/python/sweagent/run/run_batch.py +442 -0
  85. package/python/sweagent/run/run_replay.py +219 -0
  86. package/python/sweagent/run/run_shell.py +155 -0
  87. package/python/sweagent/run/run_single.py +225 -0
  88. package/python/sweagent/run/run_traj_to_demo.py +85 -0
  89. package/python/sweagent/tools/__init__.py +0 -0
  90. package/python/sweagent/tools/bundle.py +57 -0
  91. package/python/sweagent/tools/commands.py +220 -0
  92. package/python/sweagent/tools/parsing.py +619 -0
  93. package/python/sweagent/tools/tools.py +430 -0
  94. package/python/sweagent/tools/utils.py +108 -0
  95. package/python/sweagent/types.py +102 -0
  96. package/python/sweagent/utils/__init__.py +0 -0
  97. package/python/sweagent/utils/config.py +80 -0
  98. package/python/sweagent/utils/files.py +27 -0
  99. package/python/sweagent/utils/github.py +118 -0
  100. package/python/sweagent/utils/jinja_warnings.py +14 -0
  101. package/python/sweagent/utils/log.py +175 -0
  102. package/python/sweagent/utils/patch_formatter.py +152 -0
  103. package/python/sweagent/utils/serialization.py +45 -0
  104. package/python/tests/__init__.py +0 -0
  105. package/python/tests/conftest.py +191 -0
  106. package/python/tests/test_agent.py +258 -0
  107. package/python/tests/test_batch_instance.py +43 -0
  108. package/python/tests/test_commands/_interactive_dummy.py +35 -0
  109. package/python/tests/test_commands/interactive_dummy_wrapper.sh +29 -0
  110. package/python/tests/test_data/config_files/dummy_interactive.yaml +62 -0
  111. package/python/tests/test_data/data_sources/ctf/crypto/Katy/Dockerfile +20 -0
  112. package/python/tests/test_data/data_sources/ctf/crypto/Katy/README.md +13 -0
  113. package/python/tests/test_data/data_sources/ctf/crypto/Katy/challenge.json +12 -0
  114. package/python/tests/test_data/data_sources/ctf/crypto/Katy/customrandom.c +50 -0
  115. package/python/tests/test_data/data_sources/ctf/crypto/Katy/docker-compose.yml +14 -0
  116. package/python/tests/test_data/data_sources/ctf/crypto/Katy/release +0 -0
  117. package/python/tests/test_data/data_sources/ctf/crypto/Katy/server +0 -0
  118. package/python/tests/test_data/data_sources/ctf/crypto/Katy/solver.py +12 -0
  119. package/python/tests/test_data/data_sources/ctf/forensics/flash/README.md +16 -0
  120. package/python/tests/test_data/data_sources/ctf/forensics/flash/challenge.json +9 -0
  121. package/python/tests/test_data/data_sources/ctf/forensics/flash/flash_c8429a430278283c0e571baebca3d139.zip +0 -0
  122. package/python/tests/test_data/data_sources/ctf/misc/networking_1/README.md +15 -0
  123. package/python/tests/test_data/data_sources/ctf/misc/networking_1/challenge.json +10 -0
  124. package/python/tests/test_data/data_sources/ctf/misc/networking_1/networking.pcap +0 -0
  125. package/python/tests/test_data/data_sources/ctf/pwn/warmup/Dockerfile +28 -0
  126. package/python/tests/test_data/data_sources/ctf/pwn/warmup/README.md +14 -0
  127. package/python/tests/test_data/data_sources/ctf/pwn/warmup/challenge.json +14 -0
  128. package/python/tests/test_data/data_sources/ctf/pwn/warmup/docker-compose.yml +14 -0
  129. package/python/tests/test_data/data_sources/ctf/pwn/warmup/flag.txt +1 -0
  130. package/python/tests/test_data/data_sources/ctf/pwn/warmup/warmup +0 -0
  131. package/python/tests/test_data/data_sources/ctf/pwn/warmup/warmup.c +26 -0
  132. package/python/tests/test_data/data_sources/ctf/pwn/warmup/warmup.py +9 -0
  133. package/python/tests/test_data/data_sources/ctf/rev/rock/README.md +14 -0
  134. package/python/tests/test_data/data_sources/ctf/rev/rock/challenge.json +8 -0
  135. package/python/tests/test_data/data_sources/ctf/rev/rock/rock +0 -0
  136. package/python/tests/test_data/data_sources/ctf/rev/rock/rock.cpp +167 -0
  137. package/python/tests/test_data/data_sources/ctf/rev/rock/solution.cpp +24 -0
  138. package/python/tests/test_data/data_sources/ctf/rev/rock/test_solver/solution.py +6 -0
  139. package/python/tests/test_data/data_sources/ctf/rev/rock/test_solver/test.sh +10 -0
  140. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/000-default.conf +18 -0
  141. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/Dockerfile +20 -0
  142. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/cgi/file.pl +38 -0
  143. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/cgi/forms.pl +40 -0
  144. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/cgi/hello.pl +11 -0
  145. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/challenge.json +12 -0
  146. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/docker-compose.yml +14 -0
  147. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/flag +1 -0
  148. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/index.html +11 -0
  149. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/solution.txt +1 -0
  150. package/python/tests/test_data/data_sources/debug_20240322.json +1 -0
  151. package/python/tests/test_data/data_sources/expert_instances.yaml +16 -0
  152. package/python/tests/test_data/data_sources/human_eval.json +1 -0
  153. package/python/tests/test_data/data_sources/simple_instances.yaml +3 -0
  154. package/python/tests/test_data/data_sources/simple_instances_long.yaml +30 -0
  155. package/python/tests/test_data/data_sources/swe-bench-dev-easy.json +1 -0
  156. package/python/tests/test_data/data_sources/swe-bench-dev-easy_first_only.json +1 -0
  157. package/python/tests/test_data/data_sources/swe-bench-lite-test.json +1 -0
  158. package/python/tests/test_data/trajectories/gpt4__swe-agent-test-repo__default_from_url__t-0.00__p-0.95__c-3.00__install-1/6e44b9__sweagenttestrepo-1c2844.traj +342 -0
  159. package/python/tests/test_data/trajectories/gpt4__swe-agent-test-repo__default_from_url__t-0.00__p-0.95__c-3.00__install-1/solution_missing_colon.py +15 -0
  160. package/python/tests/test_data/trajectories/gpt4__swe-agent__test-repo__default_from_url__t-0.00__p-0.95__c-3.00__install-1/args.yaml +518 -0
  161. package/python/tests/test_data/trajectories/gpt4__swe-agent__test-repo__default_from_url__t-0.00__p-0.95__c-3.00__install-1/swe-agent__test-repo-i1.traj +124 -0
  162. package/python/tests/test_data/trajectories/gpt4__swe-bench-dev-easy_first_only__default__t-0.00__p-0.95__c-3.00__install-1/all_preds.jsonl +1 -0
  163. package/python/tests/test_data/trajectories/gpt4__swe-bench-dev-easy_first_only__default__t-0.00__p-0.95__c-3.00__install-1/args.yaml +520 -0
  164. package/python/tests/test_data/trajectories/gpt4__swe-bench-dev-easy_first_only__default__t-0.00__p-0.95__c-3.00__install-1/patches/pydicom__pydicom-1458.patch +18 -0
  165. package/python/tests/test_data/trajectories/gpt4__swe-bench-dev-easy_first_only__default__t-0.00__p-0.95__c-3.00__install-1/pydicom__pydicom-1458.traj +257 -0
  166. package/python/tests/test_env.py +66 -0
  167. package/python/tests/test_env_utils.py +129 -0
  168. package/python/tests/test_history_processors.py +40 -0
  169. package/python/tests/test_models.py +23 -0
  170. package/python/tests/test_openai_live.py +164 -0
  171. package/python/tests/test_packaging.py +7 -0
  172. package/python/tests/test_parsing.py +131 -0
  173. package/python/tests/test_problem_statement_multimodal.py +111 -0
  174. package/python/tests/test_quick_stats.py +42 -0
  175. package/python/tests/test_run.py +37 -0
  176. package/python/tests/test_run_batch.py +110 -0
  177. package/python/tests/test_run_hooks.py +114 -0
  178. package/python/tests/test_run_replay.py +33 -0
  179. package/python/tests/test_run_single.py +125 -0
  180. package/python/tests/test_tools_command_parsing.py +193 -0
  181. package/python/tests/test_utils.py +15 -0
  182. package/python/tests/tools/__init__.py +0 -0
  183. package/python/tests/tools/conftest.py +12 -0
  184. package/python/tests/tools/test_default_utils.py +153 -0
  185. package/python/tests/tools/test_edit_replace.py +0 -0
  186. package/python/tests/tools/test_split_string.py +82 -0
  187. package/python/tests/utils.py +29 -0
  188. package/python/tools/diff_state/bin/_state_diff_state +52 -0
  189. package/python/tools/diff_state/config.yaml +2 -0
  190. package/python/tools/edit_anthropic/bin/_state_anthropic +21 -0
  191. package/python/tools/edit_anthropic/bin/str_replace_editor +710 -0
  192. package/python/tools/edit_anthropic/config.yaml +56 -0
  193. package/python/tools/edit_anthropic/install.sh +3 -0
  194. package/python/tools/filemap/bin/filemap +45 -0
  195. package/python/tools/filemap/config.yaml +9 -0
  196. package/python/tools/filemap/install.sh +2 -0
  197. package/python/tools/forfeit/bin/exit_forfeit +5 -0
  198. package/python/tools/forfeit/config.yaml +5 -0
  199. package/python/tools/image_tools/bin/view_image +36 -0
  200. package/python/tools/image_tools/config.yaml +9 -0
  201. package/python/tools/multilingual_setup/bin/do_nothing +2 -0
  202. package/python/tools/multilingual_setup/config.yaml +1 -0
  203. package/python/tools/multilingual_setup/install.sh +45 -0
  204. package/python/tools/registry/bin/_read_env +10 -0
  205. package/python/tools/registry/bin/_write_env +10 -0
  206. package/python/tools/registry/config.yaml +1 -0
  207. package/python/tools/registry/install.sh +6 -0
  208. package/python/tools/registry/lib/__init__.py +0 -0
  209. package/python/tools/registry/lib/registry.py +56 -0
  210. package/python/tools/review_on_submit_m/README.md +6 -0
  211. package/python/tools/review_on_submit_m/bin/submit +54 -0
  212. package/python/tools/review_on_submit_m/config.yaml +6 -0
  213. package/python/tools/review_on_submit_m/install.sh +0 -0
  214. package/python/tools/search/bin/find_file +31 -0
  215. package/python/tools/search/bin/search_dir +39 -0
  216. package/python/tools/search/bin/search_file +55 -0
  217. package/python/tools/search/config.yaml +37 -0
  218. package/python/tools/search/install.sh +3 -0
  219. package/python/tools/submit/bin/submit +17 -0
  220. package/python/tools/submit/config.yaml +5 -0
  221. package/python/tools/web_browser/bin/click_mouse +41 -0
  222. package/python/tools/web_browser/bin/close_site +28 -0
  223. package/python/tools/web_browser/bin/double_click_mouse +37 -0
  224. package/python/tools/web_browser/bin/drag_mouse +46 -0
  225. package/python/tools/web_browser/bin/execute_script_on_page +39 -0
  226. package/python/tools/web_browser/bin/get_console_output +48 -0
  227. package/python/tools/web_browser/bin/move_mouse +35 -0
  228. package/python/tools/web_browser/bin/navigate_back +33 -0
  229. package/python/tools/web_browser/bin/navigate_forward +33 -0
  230. package/python/tools/web_browser/bin/open_site +36 -0
  231. package/python/tools/web_browser/bin/press_keys_on_page +51 -0
  232. package/python/tools/web_browser/bin/reload_page +33 -0
  233. package/python/tools/web_browser/bin/run_web_browser_server +394 -0
  234. package/python/tools/web_browser/bin/screenshot_site +38 -0
  235. package/python/tools/web_browser/bin/scroll_on_page +40 -0
  236. package/python/tools/web_browser/bin/set_browser_window_size +40 -0
  237. package/python/tools/web_browser/bin/type_text +34 -0
  238. package/python/tools/web_browser/bin/wait_time +39 -0
  239. package/python/tools/web_browser/config.yaml +155 -0
  240. package/python/tools/web_browser/install.sh +22 -0
  241. package/python/tools/web_browser/lib/browser_manager.py +404 -0
  242. package/python/tools/web_browser/lib/web_browser_config.py +33 -0
  243. package/python/tools/web_browser/lib/web_browser_utils.py +126 -0
  244. package/python/tools/web_browser/test_console.html +1 -0
  245. package/python/tools/windowed/bin/_state +25 -0
  246. package/python/tools/windowed/bin/create +29 -0
  247. package/python/tools/windowed/bin/goto +37 -0
  248. package/python/tools/windowed/bin/open +49 -0
  249. package/python/tools/windowed/bin/scroll_down +12 -0
  250. package/python/tools/windowed/bin/scroll_up +13 -0
  251. package/python/tools/windowed/config.yaml +38 -0
  252. package/python/tools/windowed/install.sh +15 -0
  253. package/python/tools/windowed/lib/__init__.py +0 -0
  254. package/python/tools/windowed/lib/flake8_utils.py +147 -0
  255. package/python/tools/windowed/lib/windowed_file.py +312 -0
  256. package/python/tools/windowed_edit_linting/bin/edit +128 -0
  257. package/python/tools/windowed_edit_linting/config.yaml +31 -0
  258. package/python/tools/windowed_edit_linting/install.sh +5 -0
  259. package/python/tools/windowed_edit_replace/bin/edit +172 -0
  260. package/python/tools/windowed_edit_replace/bin/insert +77 -0
  261. package/python/tools/windowed_edit_replace/config.yaml +60 -0
  262. package/python/tools/windowed_edit_replace/install.sh +5 -0
  263. package/python/tools/windowed_edit_rewrite/bin/edit +78 -0
  264. package/python/tools/windowed_edit_rewrite/config.yaml +11 -0
  265. package/python/tools/windowed_edit_rewrite/install.sh +5 -0
  266. package/python/trajectories/demonstrations/ctf/crypto/BabyEncryption.traj +318 -0
  267. package/python/trajectories/demonstrations/ctf/crypto/BabyTimeCapsule.traj +197 -0
  268. package/python/trajectories/demonstrations/ctf/crypto/eps.traj +289 -0
  269. package/python/trajectories/demonstrations/ctf/crypto/katy.traj +368 -0
  270. package/python/trajectories/demonstrations/ctf/forensics/flash.traj +102 -0
  271. package/python/trajectories/demonstrations/ctf/misc/networking_1.traj +102 -0
  272. package/python/trajectories/demonstrations/ctf/pwn/warmup.traj +159 -0
  273. package/python/trajectories/demonstrations/ctf/rev/rock.traj +251 -0
  274. package/python/trajectories/demonstrations/ctf/web/i_got_id_demo.traj +422 -0
  275. package/python/trajectories/demonstrations/function_calling_simple.traj +151 -0
  276. package/python/trajectories/demonstrations/human_thought__swe-bench-HumanEvalFix-python__lcb__t-0.00__p-0.95__c-4.00__install-0/humanevalfix-python-0.traj +129 -0
  277. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__default__t-0.20__p-0.95__c-2.00__install-1___install_from_source/marshmallow-code__marshmallow-1867.traj +318 -0
  278. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__default_sys-env_cursors_window100__t-0.20__p-0.95__c-2.00__install-1/marshmallow-code__marshmallow-1867.traj +251 -0
  279. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__default_sys-env_window100__t-0.20__p-0.95__c-2.00__install-1/marshmallow-code__marshmallow-1867.traj +399 -0
  280. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__function_calling__install-1/marshmallow-code__marshmallow-1867.traj +594 -0
  281. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__function_calling_replace__install-1/marshmallow-code__marshmallow-1867.traj +592 -0
  282. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__function_calling_replace_from_source/marshmallow-code__marshmallow-1867.traj +3316 -0
  283. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__xml_sys-env_cursors_window100__t-0.20__p-0.95__c-2.00__install-1/marshmallow-code__marshmallow-1867.traj +251 -0
  284. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__xml_sys-env_window100__t-0.20__p-0.95__c-2.00__install-1/marshmallow-code__marshmallow-1867.traj +399 -0
  285. package/python/trajectories/demonstrations/str_replace_anthropic_demo.yaml +432 -0
  286. package/rust/Cargo.toml +100 -0
  287. package/rust/README.md +49 -0
  288. package/rust/src/agent/action_sampler.rs +130 -0
  289. package/rust/src/agent/agents.rs +1029 -0
  290. package/rust/src/agent/history_processors.rs +277 -0
  291. package/rust/src/agent/hooks/mod.rs +208 -0
  292. package/rust/src/agent/mod.rs +24 -0
  293. package/rust/src/agent/models.rs +837 -0
  294. package/rust/src/agent/problem_statement.rs +355 -0
  295. package/rust/src/agent/reviewer.rs +505 -0
  296. package/rust/src/bin/sweagent.rs +784 -0
  297. package/rust/src/environment/deployment.rs +631 -0
  298. package/rust/src/environment/hooks/mod.rs +114 -0
  299. package/rust/src/environment/mod.rs +16 -0
  300. package/rust/src/environment/repo.rs +265 -0
  301. package/rust/src/environment/runtime.rs +237 -0
  302. package/rust/src/environment/swe_env.rs +248 -0
  303. package/rust/src/exceptions.rs +228 -0
  304. package/rust/src/lib.rs +68 -0
  305. package/rust/src/monitoring.rs +482 -0
  306. package/rust/src/run/hooks/mod.rs +134 -0
  307. package/rust/src/run/mod.rs +12 -0
  308. package/rust/src/run/run_batch.rs +563 -0
  309. package/rust/src/run/run_single.rs +196 -0
  310. package/rust/src/tools/bundle.rs +224 -0
  311. package/rust/src/tools/commands.rs +173 -0
  312. package/rust/src/tools/mod.rs +295 -0
  313. package/rust/src/tools/parsing.rs +354 -0
  314. package/rust/src/tools/registry.rs +143 -0
  315. package/rust/src/types.rs +554 -0
  316. package/rust/src/utils/config.rs +105 -0
  317. package/rust/src/utils/files.rs +137 -0
  318. package/rust/src/utils/github.rs +171 -0
  319. package/rust/src/utils/log.rs +65 -0
  320. package/rust/src/utils/mod.rs +17 -0
  321. package/rust/src/utils/serialization.rs +181 -0
  322. package/rust/src/utils/template.rs +173 -0
  323. package/typescript/README.md +335 -0
@@ -0,0 +1,312 @@
1
+ from pathlib import Path
2
+
3
+ try:
4
+ from sweagent import TOOLS_DIR
5
+ except ImportError:
6
+ pass
7
+ else:
8
+ import sys
9
+
10
+ sys.path.append(str(TOOLS_DIR / "registry" / "lib"))
11
+
12
+ from registry import registry
13
+
14
+
15
+ class FileNotOpened(Exception):
16
+ """Raised when no file is opened."""
17
+
18
+
19
+ class TextNotFound(Exception):
20
+ """Raised when the text is not found in the window."""
21
+
22
+
23
+ def _find_all(a_str: str, sub: str):
24
+ start = 0
25
+ while True:
26
+ start = a_str.find(sub, start)
27
+ if start == -1:
28
+ return
29
+ yield start
30
+ start += len(sub)
31
+
32
+
33
+ class ReplacementInfo:
34
+ def __init__(self, first_replaced_line: int, n_search_lines: int, n_replace_lines: int, n_replacements: int):
35
+ self.first_replaced_line = first_replaced_line
36
+ self.n_search_lines = n_search_lines
37
+ self.n_replace_lines = n_replace_lines
38
+ self.n_replacements = n_replacements
39
+
40
+ def __repr__(self):
41
+ return f"ReplacementInfo(first_replaced_line={self.first_replaced_line}, n_search_lines={self.n_search_lines}, n_replace_lines={self.n_replace_lines}, n_replacements={self.n_replacements})"
42
+
43
+
44
+ class InsertInfo:
45
+ def __init__(self, first_inserted_line: int, n_lines_added: int):
46
+ self.first_inserted_line = first_inserted_line
47
+ self.n_lines_added = n_lines_added
48
+
49
+
50
+ class WindowedFile:
51
+ def __init__(
52
+ self,
53
+ path: Path | None = None,
54
+ *,
55
+ first_line: int | None = None,
56
+ window: int | None = None,
57
+ exit_on_exception: bool = True,
58
+ ):
59
+ """
60
+
61
+ Args:
62
+ path: Path to the file to open.
63
+ first_line: First line of the display window.
64
+ window: Number of lines to display.
65
+ exit_on_exception: If False, will raise exception.
66
+ If true, will print an error message and exit.
67
+
68
+ Will create file if not found.
69
+
70
+ Internal convention/notes:
71
+
72
+ * All line numbers are 0-indexed.
73
+ * Previously, we used "current_line" for the internal state
74
+ of the window position, pointing to the middle of the window.
75
+ Now, we use `first_line` for this purpose (it's simpler this way).
76
+ """
77
+ _path = registry.get_if_none(path, "CURRENT_FILE")
78
+ self._exit_on_exception = exit_on_exception
79
+ if not _path:
80
+ if self._exit_on_exception:
81
+ print("No file open. Use the open command first.")
82
+ exit(1)
83
+ raise FileNotOpened
84
+ self.path = Path(_path)
85
+ if self.path.is_dir():
86
+ msg = f"Error: {self.path} is a directory. You can only open files. Use cd or ls to navigate directories."
87
+ if self._exit_on_exception:
88
+ print(msg)
89
+ exit(1)
90
+ raise IsADirectoryError(msg)
91
+ if not self.path.exists():
92
+ msg = f"Error: File {self.path} not found"
93
+ if self._exit_on_exception:
94
+ print(msg)
95
+ exit(1)
96
+ raise FileNotFoundError(msg)
97
+ registry["CURRENT_FILE"] = str(self.path.resolve())
98
+ self.window = int(registry.get_if_none(window, "WINDOW"))
99
+ self.overlap = int(registry.get("OVERLAP", 0))
100
+ # Ensure that we get a valid current line by using the setter
101
+ self._first_line = 0
102
+ self.first_line = int(
103
+ registry.get_if_none(
104
+ first_line,
105
+ "FIRST_LINE",
106
+ 0,
107
+ )
108
+ )
109
+ self.offset_multiplier = 1 / 6
110
+ self._original_text = self.text
111
+ self._original_first_line = self.first_line
112
+
113
+ @property
114
+ def first_line(self) -> int:
115
+ return self._first_line
116
+
117
+ @first_line.setter
118
+ def first_line(self, value: int | float):
119
+ self._original_first_line = self.first_line
120
+ value = int(value)
121
+ self._first_line = max(0, min(value, self.n_lines - self.window))
122
+ registry["FIRST_LINE"] = self.first_line
123
+
124
+ @property
125
+ def text(self) -> str:
126
+ return self.path.read_text()
127
+
128
+ @text.setter
129
+ def text(self, new_text: str):
130
+ self._original_text = self.text
131
+ self.path.write_text(new_text)
132
+
133
+ @property
134
+ def n_lines(self) -> int:
135
+ return len(self.text.splitlines())
136
+
137
+ @property
138
+ def line_range(self) -> tuple[int, int]:
139
+ """Return first and last line (inclusive) of the display window, such
140
+ that exactly `window` many lines are displayed.
141
+ This means `line_range[1] - line_range[0] == window-1` as long as there are
142
+ at least `window` lines in the file. `first_line` does the handling
143
+ of making sure that we don't go out of bounds.
144
+ """
145
+ return self.first_line, min(self.first_line + self.window - 1, self.n_lines - 1)
146
+
147
+ def get_window_text(
148
+ self, *, line_numbers: bool = False, status_line: bool = False, pre_post_line: bool = False
149
+ ) -> str:
150
+ """Get the text in the current display window with optional status/extra information
151
+
152
+ Args:
153
+ line_numbers: include line numbers in the output
154
+ status_line: include the status line in the output (file path, total lines)
155
+ pre_post_line: include the pre/post line in the output (number of lines above/below)
156
+ """
157
+ start_line, end_line = self.line_range
158
+ lines = self.text.split("\n")[start_line : end_line + 1]
159
+ out_lines = []
160
+ if status_line:
161
+ out_lines.append(f"[File: {self.path} ({self.n_lines} lines total)]")
162
+ if pre_post_line:
163
+ if start_line > 0:
164
+ out_lines.append(f"({start_line} more lines above)")
165
+ if line_numbers:
166
+ out_lines.extend(f"{i + start_line + 1}:{line}" for i, line in enumerate(lines))
167
+ else:
168
+ out_lines.extend(lines)
169
+ if pre_post_line:
170
+ if end_line < self.n_lines - 1:
171
+ out_lines.append(f"({self.n_lines - end_line - 1} more lines below)")
172
+ return "\n".join(out_lines)
173
+
174
+ def set_window_text(self, new_text: str, *, line_range: tuple[int, int] | None = None) -> None:
175
+ """Replace the text in the current display window with a new string."""
176
+ text = self.text.split("\n")
177
+ if line_range is not None:
178
+ start, stop = line_range
179
+ else:
180
+ start, stop = self.line_range
181
+
182
+ # Handle empty replacement text (deletion case)
183
+ new_lines = new_text.split("\n") if new_text else []
184
+ text[start : stop + 1] = new_lines
185
+ self.text = "\n".join(text)
186
+
187
+ def replace_in_window(
188
+ self,
189
+ search: str,
190
+ replace: str,
191
+ *,
192
+ reset_first_line: str = "top",
193
+ ) -> "ReplacementInfo":
194
+ """Search and replace in the window.
195
+
196
+ Args:
197
+ search: The string to search for (can be multi-line).
198
+ replace: The string to replace it with (can be multi-line).
199
+ reset_first_line: If "keep", we keep the current line. Otherwise, we
200
+ `goto` the line where the replacement started with this mode.
201
+ """
202
+ window_text = self.get_window_text()
203
+ # Update line number
204
+ index = window_text.find(search)
205
+ if index == -1:
206
+ if self._exit_on_exception:
207
+ print(f"Error: Text not found: {search}")
208
+ exit(1)
209
+ raise TextNotFound
210
+ window_start_line, _ = self.line_range
211
+ replace_start_line = window_start_line + len(window_text[:index].split("\n")) - 1
212
+ new_window_text = window_text.replace(search, replace)
213
+ self.set_window_text(new_window_text)
214
+ if reset_first_line == "keep":
215
+ pass
216
+ else:
217
+ self.goto(replace_start_line, mode=reset_first_line)
218
+ return ReplacementInfo(
219
+ first_replaced_line=replace_start_line,
220
+ n_search_lines=len(search.split("\n")),
221
+ n_replace_lines=len(replace.split("\n")),
222
+ n_replacements=1,
223
+ )
224
+
225
+ def find_all_occurrences(self, search: str, zero_based: bool = True) -> list[int]:
226
+ """Returns the line numbers of all occurrences of the search string."""
227
+ indices = list(_find_all(self.text, search))
228
+ line_numbers = []
229
+ for index in indices:
230
+ line_no = len(self.text[:index].split("\n"))
231
+ if zero_based:
232
+ line_numbers.append(line_no - 1)
233
+ else:
234
+ line_numbers.append(line_no)
235
+ return line_numbers
236
+
237
+ def replace(self, search: str, replace: str, *, reset_first_line: str = "top") -> "ReplacementInfo":
238
+ indices = list(_find_all(self.text, search))
239
+ if not indices:
240
+ if self._exit_on_exception:
241
+ print(f"Error: Text not found: {search}")
242
+ exit(1)
243
+ raise TextNotFound
244
+ replace_start_line = len(self.text[: indices[0]].split("\n"))
245
+ new_text = self.text.replace(search, replace)
246
+ self.text = new_text
247
+ if reset_first_line == "keep":
248
+ pass
249
+ else:
250
+ self.goto(replace_start_line, mode=reset_first_line)
251
+ return ReplacementInfo(
252
+ first_replaced_line=replace_start_line,
253
+ n_search_lines=len(search.split("\n")),
254
+ n_replace_lines=len(replace.split("\n")),
255
+ n_replacements=len(indices),
256
+ )
257
+
258
+ def print_window(self, *, line_numbers: bool = True, status_line: bool = True, pre_post_line: bool = True):
259
+ print(self.get_window_text(line_numbers=line_numbers, status_line=status_line, pre_post_line=pre_post_line))
260
+
261
+ def goto(self, line: int, mode: str = "top"):
262
+ if mode == "top":
263
+ self.first_line = line - self.window * self.offset_multiplier
264
+ else:
265
+ raise NotImplementedError
266
+
267
+ def scroll(self, n_lines: int):
268
+ if n_lines > 0:
269
+ self.first_line += n_lines - self.overlap
270
+ elif n_lines < 0:
271
+ self.first_line += n_lines + self.overlap
272
+
273
+ def undo_edit(self):
274
+ self.text = self._original_text
275
+ self.first_line = self._original_first_line
276
+
277
+ def insert(self, text: str, line: int | None = None, *, reset_first_line: str = "top") -> "InsertInfo":
278
+ # Standardize empty text handling
279
+ if not text:
280
+ return InsertInfo(first_inserted_line=(self.n_lines if line is None else line), n_lines_added=0)
281
+
282
+ # Remove single trailing newline if it exists
283
+ text = text[:-1] if text.endswith("\n") else text
284
+
285
+ if line is None:
286
+ # Append to end of file
287
+ if not self.text:
288
+ new_text = text
289
+ else:
290
+ current_text = self.text[:-1] if self.text.endswith("\n") else self.text
291
+ new_text = current_text + "\n" + text
292
+ insert_line = self.n_lines
293
+ elif line < 0:
294
+ # Insert at start of file
295
+ if not self.text:
296
+ new_text = text
297
+ else:
298
+ current_text = self.text[1:] if self.text.startswith("\n") else self.text
299
+ new_text = text + "\n" + current_text
300
+ insert_line = 0
301
+ else:
302
+ # Insert at specific line
303
+ lines = self.text.split("\n")
304
+ lines.insert(line, text)
305
+ new_text = "\n".join(lines)
306
+ insert_line = line
307
+
308
+ self.text = new_text
309
+ if reset_first_line != "keep":
310
+ self.goto(insert_line, mode=reset_first_line)
311
+
312
+ return InsertInfo(first_inserted_line=insert_line, n_lines_added=len(text.split("\n")))
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import sys
5
+ from typing import Tuple, Union
6
+
7
+ try:
8
+ from sweagent import TOOLS_DIR
9
+ except ImportError:
10
+ pass
11
+ else:
12
+ default_lib = TOOLS_DIR / "windowed" / "lib"
13
+ assert default_lib.is_dir()
14
+ sys.path.append(str(default_lib))
15
+ sys.path.append(str(TOOLS_DIR / "registry" / "lib"))
16
+
17
+ from windowed_file import FileNotOpened, WindowedFile # type: ignore
18
+ from flake8_utils import flake8, format_flake8_output # type: ignore
19
+
20
+ _USAGE_MSG = """Usage: edit <start_line>:<end_line>
21
+ <replacement_text>
22
+ end_of_edit"""
23
+
24
+ _EDIT_SUCCESS_MSG = """File updated. Please review the changes and make sure they are correct
25
+ (correct indentation, no duplicate lines, etc). Edit the file again if necessary."""
26
+
27
+ _LINT_ERROR_TEMPLATE = """Your proposed edit has introduced new syntax error(s). Please read this error message carefully and then retry editing the file.
28
+
29
+ ERRORS:
30
+ {errors}
31
+
32
+ This is how your edit would have looked if applied
33
+ ------------------------------------------------
34
+ {window_applied}
35
+ ------------------------------------------------
36
+
37
+ This is the original code before your edit
38
+ ------------------------------------------------
39
+ {window_original}
40
+ ------------------------------------------------
41
+
42
+ Your changes have NOT been applied. Please fix your edit command and try again.
43
+ DO NOT re-run the same failed edit command. Running it again will lead to the same error."""
44
+
45
+
46
+ def get_parser() -> argparse.ArgumentParser:
47
+ parser = argparse.ArgumentParser()
48
+ parser.add_argument("line_range", help="Line range in format start:end")
49
+ parser.add_argument("replacement_text", help="Text to insert", nargs="?")
50
+ return parser
51
+
52
+
53
+ def parse_line_range(line_range: str) -> Tuple[int, int]:
54
+ try:
55
+ start, end = map(int, line_range.split(":"))
56
+ return start - 1, end - 1
57
+ except ValueError:
58
+ print(_USAGE_MSG)
59
+ exit(1)
60
+
61
+
62
+ def main(line_range: str, replacement_text: Union[str, None] = None):
63
+ # Handle file opening
64
+ try:
65
+ wf = WindowedFile(exit_on_exception=False)
66
+ except FileNotOpened:
67
+ print("No file opened. Use the `open` command first.")
68
+ exit(1)
69
+
70
+ # Parse line range
71
+ start_line, end_line = parse_line_range(line_range)
72
+
73
+ if replacement_text is None:
74
+ # Read replacement text from stdin (e.g., when sent via bash heredoc)
75
+ # if not provided as argument
76
+ replacement_lines = []
77
+ while True:
78
+ try:
79
+ line = input()
80
+ if line == "end_of_edit":
81
+ break
82
+ replacement_lines.append(line)
83
+ except EOFError:
84
+ break
85
+ replacement_text = "\n".join(replacement_lines)
86
+ else:
87
+ if replacement_text.endswith("\n"):
88
+ replacement_text = replacement_text[:-1]
89
+
90
+ if replacement_text is None:
91
+ print(_USAGE_MSG)
92
+ exit(1)
93
+
94
+ # Get pre-edit linting errors
95
+ pre_edit_lint = flake8(wf.path)
96
+
97
+ # Perform the edit
98
+ wf.set_window_text(replacement_text, line_range=(start_line, end_line))
99
+
100
+ # Check for new linting errors
101
+ post_edit_lint = flake8(wf.path)
102
+ new_flake8_output = format_flake8_output(
103
+ post_edit_lint,
104
+ previous_errors_string=pre_edit_lint,
105
+ replacement_window=(start_line, end_line),
106
+ replacement_n_lines=len(replacement_text.splitlines()),
107
+ )
108
+
109
+ if new_flake8_output:
110
+ # Show error and revert changes
111
+ with_edits = wf.get_window_text(line_numbers=True, status_line=True, pre_post_line=True)
112
+ wf.undo_edit()
113
+ without_edits = wf.get_window_text(line_numbers=True, status_line=True, pre_post_line=True)
114
+ print(
115
+ _LINT_ERROR_TEMPLATE.format(
116
+ errors=new_flake8_output, window_applied=with_edits, window_original=without_edits
117
+ )
118
+ )
119
+ exit(1)
120
+
121
+ # Success - update window position and show result
122
+ wf.goto(start_line, mode="top")
123
+ print(_EDIT_SUCCESS_MSG)
124
+ wf.print_window()
125
+
126
+
127
+ if __name__ == "__main__":
128
+ main(**vars(get_parser().parse_args()))
@@ -0,0 +1,31 @@
1
+ tools:
2
+ edit:
3
+ signature: |
4
+ edit <start_line>:<end_line>
5
+ <replacement_text>
6
+ end_of_edit
7
+ # Note: Without function calling we should add back:
8
+ # The replacement text is terminated by a line with only
9
+ # end_of_edit on
10
+ docstring: >
11
+ Replaces lines <start_line> through <end_line> (inclusive) with the given text
12
+ in the open file.
13
+ All of the <replacement text> will be entered, so make
14
+ sure your indentation is formatted properly.
15
+
16
+ Please note that THIS COMMAND REQUIRES PROPER INDENTATION.
17
+ If you'd like to add the line ' print(x)' you must fully write that out, with all those spaces before the code!
18
+ end_name: "end_of_edit"
19
+ arguments:
20
+ - name: start_line
21
+ type: integer
22
+ description: "the line number to start the edit at"
23
+ required: true
24
+ - name: end_line
25
+ type: integer
26
+ description: "the line number to end the edit at (inclusive)"
27
+ required: true
28
+ - name: replacement_text
29
+ type: string
30
+ description: "the text to replace the current selection with"
31
+ required: true
@@ -0,0 +1,5 @@
1
+ _write_env "CURRENT_FILE" "${CURRENT_FILE:-}"
2
+ _write_env "CURRENT_LINE" "${CURRENT_LINE:-0}"
3
+ _write_env "WINDOW" "$WINDOW"
4
+
5
+ pip install flake8
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+
5
+ try:
6
+ from sweagent import TOOLS_DIR
7
+ except ImportError:
8
+ pass
9
+ else:
10
+ import sys
11
+
12
+ default_lib = TOOLS_DIR / "windowed" / "lib"
13
+ assert default_lib.is_dir()
14
+ sys.path.append(str(default_lib))
15
+ sys.path.append(str(TOOLS_DIR / "registry" / "lib"))
16
+
17
+ from windowed_file import FileNotOpened, TextNotFound, WindowedFile # type: ignore
18
+ from flake8_utils import flake8, format_flake8_output # type: ignore
19
+
20
+ RETRY_WITH_OUTPUT_TOKEN = "###SWE-AGENT-RETRY-WITH-OUTPUT###"
21
+
22
+ _NOT_FOUND = """Your edit was not applied (file not modified): Text {search!r} not found in displayed lines (or anywhere in the file).
23
+ Please modify your search string. Did you forget to properly handle whitespace/indentation?
24
+ You can also call `open` again to re-display the file with the correct context.
25
+ """
26
+
27
+ _NOT_FOUND_IN_WINDOW_MSG = """Your edit was not applied (file not modified): Text {search!r} not found in displayed lines.
28
+
29
+ However, we found the following occurrences of your search string in the file:
30
+
31
+ {occurrences}
32
+
33
+ You can use the `goto` command to navigate to these locations before running the edit command again.
34
+ """
35
+
36
+ _MULTIPLE_OCCURRENCES_MSG = """Your edit was not applied (file not modified): Found more than one occurrence of {search!r} in the currently displayed lines.
37
+ Please make your search string more specific (for example, by including more lines of context).
38
+ """
39
+
40
+ _NO_CHANGES_MADE_MSG = """Your search and replace strings are the same. No changes were made. Please modify your search or replace strings."""
41
+
42
+ _SINGLE_EDIT_SUCCESS_MSG = """Text replaced. Please review the changes and make sure they are correct:
43
+
44
+ 1. The edited file is correctly indented
45
+ 2. The edited file does not contain duplicate lines
46
+ 3. The edit does not break existing functionality
47
+
48
+ Edit the file again if necessary."""
49
+
50
+ _MULTIPLE_EDITS_SUCCESS_MSG = """Replaced {n_replacements} occurrences. Please review the changes and make sure they are correct:
51
+
52
+ 1. The edited file is correctly indented
53
+ 2. The edited file does not contain duplicate lines
54
+ 3. The edit does not break existing functionality
55
+
56
+ Edit the file again if necessary."""
57
+
58
+ _LINT_ERROR_TEMPLATE = """Your proposed edit has introduced new syntax error(s). Please read this error message carefully and then retry editing the file.
59
+
60
+ ERRORS:
61
+
62
+ {errors}
63
+
64
+ This is how your edit would have looked if applied
65
+ ------------------------------------------------
66
+ {window_applied}
67
+ ------------------------------------------------
68
+
69
+ This is the original code before your edit
70
+ ------------------------------------------------
71
+ {window_original}
72
+ ------------------------------------------------
73
+
74
+ Your changes have NOT been applied. Please fix your edit command and try again.
75
+ DO NOT re-run the same failed edit command. Running it again will lead to the same error.
76
+ """
77
+
78
+
79
+ def get_parser() -> argparse.ArgumentParser:
80
+ parser = argparse.ArgumentParser()
81
+ parser.add_argument("search", type=str)
82
+ parser.add_argument("replace", type=str)
83
+ parser.add_argument("replace_all", type=bool, nargs="?", default=False)
84
+ return parser
85
+
86
+
87
+ def main(search: str, replace: str, replace_all: bool):
88
+ try:
89
+ wf = WindowedFile(exit_on_exception=False)
90
+ except FileNotOpened:
91
+ print("No file opened. Either `open` or `create` a file first.")
92
+ exit(1)
93
+
94
+ # Turn \\n into \n etc., i.e., undo the escaping
95
+ # args.replace = args.replace.encode("utf8").decode("unicode_escape")
96
+
97
+ if search == replace:
98
+ print(_NO_CHANGES_MADE_MSG)
99
+ print(RETRY_WITH_OUTPUT_TOKEN)
100
+ exit(2)
101
+
102
+ pre_edit_lint = flake8(wf.path)
103
+
104
+ try:
105
+ if not replace_all:
106
+ window_text = wf.get_window_text()
107
+ if window_text.count(search) > 1:
108
+ print(_MULTIPLE_OCCURRENCES_MSG.format(search=search))
109
+ print(RETRY_WITH_OUTPUT_TOKEN)
110
+ exit(4)
111
+ replacement_info = wf.replace_in_window(search, replace)
112
+ # todo: Should warn if more than one occurrence was found?
113
+ else:
114
+ # todo: Give overview of all replaced occurrences/number of replacements
115
+ replacement_info = wf.replace(search, replace)
116
+ except TextNotFound:
117
+ line_no_founds = wf.find_all_occurrences(search, zero_based=False)
118
+ if line_no_founds:
119
+ print(
120
+ _NOT_FOUND_IN_WINDOW_MSG.format(
121
+ search=search, occurrences="\n".join([f"- line {line_no}" for line_no in line_no_founds])
122
+ )
123
+ )
124
+ else:
125
+ print(_NOT_FOUND.format(search=search))
126
+ print(RETRY_WITH_OUTPUT_TOKEN)
127
+ exit(3)
128
+
129
+ post_edit_lint = flake8(wf.path)
130
+
131
+ if not replace_all:
132
+ # Try to filter out pre-existing errors
133
+ replacement_window = (
134
+ replacement_info.first_replaced_line,
135
+ replacement_info.first_replaced_line + replacement_info.n_search_lines - 1,
136
+ )
137
+ # print(f"{replacement_info=}")
138
+ # print(f"{replacement_window=}")
139
+ # print(f"{pre_edit_lint=}")
140
+ # print(f"{post_edit_lint=}")
141
+ new_flake8_output = format_flake8_output(
142
+ post_edit_lint,
143
+ previous_errors_string=pre_edit_lint,
144
+ replacement_window=replacement_window,
145
+ replacement_n_lines=replacement_info.n_replace_lines,
146
+ )
147
+ else:
148
+ # Cannot easily compare the error strings, because line number changes are hard to keep track of
149
+ # So we show all linter errors.
150
+ new_flake8_output = format_flake8_output(post_edit_lint)
151
+
152
+ if new_flake8_output:
153
+ with_edits = wf.get_window_text(line_numbers=True, status_line=True, pre_post_line=True)
154
+ wf.undo_edit()
155
+ without_edits = wf.get_window_text(line_numbers=True, status_line=True, pre_post_line=True)
156
+ print(
157
+ _LINT_ERROR_TEMPLATE.format(
158
+ errors=new_flake8_output, window_applied=with_edits, window_original=without_edits,
159
+ )
160
+ )
161
+ print(RETRY_WITH_OUTPUT_TOKEN)
162
+ exit(4)
163
+ if not replace_all:
164
+ print(_SINGLE_EDIT_SUCCESS_MSG)
165
+ else:
166
+ print(_MULTIPLE_EDITS_SUCCESS_MSG.format(n_replacements=replacement_info.n_replacements))
167
+
168
+ wf.print_window()
169
+
170
+
171
+ if __name__ == "__main__":
172
+ main(**vars(get_parser().parse_args()))