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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-coder
3
- Version: 0.1.336
3
+ Version: 0.1.338
4
4
  Summary: AutoCoder: AutoCoder
5
5
  Author: allwefantasy
6
6
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -4,21 +4,21 @@ autocoder/auto_coder_lang.py,sha256=Rtupq6N3_HT7JRhDKdgCBcwRaiAnyCOR_Gsp4jUomrI,
4
4
  autocoder/auto_coder_rag.py,sha256=NesRm7sIJrRQL1xxm_lbMtM7gi-KrYv9f26RfBuloZE,35386
5
5
  autocoder/auto_coder_rag_client_mcp.py,sha256=QRxUbjc6A8UmDMQ8lXgZkjgqtq3lgKYeatJbDY6rSo0,6270
6
6
  autocoder/auto_coder_rag_mcp.py,sha256=-RrjNwFaS2e5v8XDIrKR-zlUNUE8UBaeOtojffBrvJo,8521
7
- autocoder/auto_coder_runner.py,sha256=W08NHr_I-9ldhXUhatnKrUCBL33_mQpF7KFqzp0zMEI,109203
7
+ autocoder/auto_coder_runner.py,sha256=Xrzeo9u7UIvIjKgaV12O-XItl10dhjogp-7TNIvqAP8,111518
8
8
  autocoder/auto_coder_server.py,sha256=bLORGEclcVdbBVfM140JCI8WtdrU0jbgqdJIVVupiEU,20578
9
9
  autocoder/benchmark.py,sha256=Ypomkdzd1T3GE6dRICY3Hj547dZ6_inqJbBJIp5QMco,4423
10
- autocoder/chat_auto_coder.py,sha256=SzH6fdoH8kJgARYXBu0CTB8J9PY8Q2T9CrIB844PXtM,25841
10
+ autocoder/chat_auto_coder.py,sha256=CthuvdjVjTQOVv-zREsl8OCsZHPSP9OQcIgHULrW2Ro,25842
11
11
  autocoder/chat_auto_coder_lang.py,sha256=RxkYAMWUB5ayX0x03yBOcEkjTcWG_EBsLXBC_bh--cc,22265
12
12
  autocoder/command_args.py,sha256=HxflngkYtTrV17Vfgk6lyUyiG68jP2ftSc7FYr9AXwY,30585
13
13
  autocoder/command_parser.py,sha256=fx1g9E6GaM273lGTcJqaFQ-hoksS_Ik2glBMnVltPCE,10013
14
14
  autocoder/lang.py,sha256=PFtATuOhHRnfpqHQkXr6p4C893JvpsgwTMif3l-GEi0,14321
15
15
  autocoder/models.py,sha256=_SCar82QIeBFTZZBdM2jPS6atKVhHnvE0gX3V0CsxD4,11590
16
16
  autocoder/run_context.py,sha256=IUfSO6_gp2Wt1blFWAmOpN0b0nDrTTk4LmtCYUBIoro,1643
17
- autocoder/version.py,sha256=znLFQLSAKbCRErJEcoRffYh44RYOI_DI4IWhNI0JIy0,23
17
+ autocoder/version.py,sha256=oUXeUGWr7ewx0MgKWn_rfyWO62ZnbdWizzFCRsj8frY,23
18
18
  autocoder/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  autocoder/agent/agentic_edit.py,sha256=XsfePZ-t6M-uBSdG1VLZXk1goqXk2HPeJ_A8IYyBuWQ,58896
20
20
  autocoder/agent/agentic_edit_types.py,sha256=oFcDd_cxJ2yH9Ed1uTpD3BipudgoIEWDMPb5pAkq4gI,3288
21
- autocoder/agent/agentic_filter.py,sha256=yL7ufHJWIJpwmsd5Z5IAdnLopYN89JgB8DlC7NhTYFU,38780
21
+ autocoder/agent/agentic_filter.py,sha256=9Miqj9lVOkKrKRXK9MZDouhkp2n1s_RakpRwdmLfFtY,39516
22
22
  autocoder/agent/auto_demand_organizer.py,sha256=URAq0gSEiHeV_W4zwhOI_83kHz0Ryfj1gcfh5jwCv_w,6501
23
23
  autocoder/agent/auto_filegroup.py,sha256=pBsAkBcpFTff-9L5OwI8xhf2xPKpl-aZwz-skF2B6dc,6296
24
24
  autocoder/agent/auto_guess_query.py,sha256=rDSdhpPHcOGE5MuDXvIrhCXAPR4ARS1LqpyoLsx2Jhw,11374
@@ -54,7 +54,7 @@ autocoder/common/action_yml_file_manager.py,sha256=DdF5P1R_B_chCnnqoA2IgogakWLZk
54
54
  autocoder/common/anything2images.py,sha256=0ILBbWzY02M-CiWB-vzuomb_J1hVdxRcenAfIrAXq9M,25283
55
55
  autocoder/common/anything2img.py,sha256=iZQmg8srXlD7N5uGl5b_ONKJMBjYoW8kPmokkG6ISF0,10118
56
56
  autocoder/common/audio.py,sha256=Kn9nWKQddWnUrAz0a_ZUgjcu4VUU_IcZBigT7n3N3qc,7439
57
- autocoder/common/auto_coder_lang.py,sha256=34SU-Q_JDjioH_i7jc3JsZkX6SjgueBMC54_P8Xwf5U,40478
57
+ autocoder/common/auto_coder_lang.py,sha256=ozoGTy4ZFn3YsO5zWhvAGCu54mK4LtnRfC2yCvrMc_8,42462
58
58
  autocoder/common/auto_configure.py,sha256=D4N-fl9v8bKM5-Ds-uhkC2uGDmHH_ZjLJ759F8KXMKs,13129
59
59
  autocoder/common/buildin_tokenizer.py,sha256=L7d5t39ZFvUd6EoMPXUhYK1toD0FHlRH1jtjKRGokWU,1236
60
60
  autocoder/common/chunk_validation.py,sha256=BrR_ZWavW8IANuueEE7hS8NFAwEvm8TX34WnPx_1hs8,3030
@@ -126,9 +126,10 @@ autocoder/common/v2/code_editblock_manager.py,sha256=G0CIuV9Ki0FqMLnpA8nBT4pnkCN
126
126
  autocoder/common/v2/code_manager.py,sha256=C403bS-f6urixwitlKHcml-J03hci-UyNwHJOqBiY6Q,9182
127
127
  autocoder/common/v2/code_strict_diff_manager.py,sha256=v-J1kDyLg7tLGg_6_lbO9S4fNkx7M_L8Xr2G7fPptiU,9347
128
128
  autocoder/common/v2/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
- autocoder/common/v2/agent/agentic_edit.py,sha256=Vv5e_lgzfd9PZS10bJR0yvFzP4MLAVCZB45s02RWXkU,83440
130
- autocoder/common/v2/agent/agentic_edit_types.py,sha256=ZCgIu0Dj4xPP9s-lWtzh1-wBvoXrSkgu3pan_Oo_Ng0,4433
131
- autocoder/common/v2/agent/agentic_tool_display.py,sha256=O-H7vV5G-Jlkrul_GWmfUEsJZdeUbwtbBlfONlYD7bY,7382
129
+ autocoder/common/v2/agent/agentic_edit.py,sha256=mw85tMint-m6MA7OrPmrwOOZw_uW_N7fboTGfXNQYcE,88790
130
+ autocoder/common/v2/agent/agentic_edit_conversation.py,sha256=2GZrw5f8Sh9GgUDnuFc2v7Q4syrm77kKEfXI6k6uEFY,7310
131
+ autocoder/common/v2/agent/agentic_edit_types.py,sha256=6qBLLmvdlcsbzrpMHsYQVIHqbOWubMXOnmkqTs1pBWQ,4629
132
+ autocoder/common/v2/agent/agentic_tool_display.py,sha256=M-fFfmCzAyYrT916tXbc8HZxbv4ePksR9WqvSrhcZIs,7345
132
133
  autocoder/common/v2/agent/agentic_edit_tools/__init__.py,sha256=wGICCc1dYh07osB21j62zOQ9Ws0PyyOQ12UYRHmHrtI,1229
133
134
  autocoder/common/v2/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py,sha256=pjrukXjWXMIfUAUzoHzr7j2Onf1L7bxmjsUR1gGaFoA,2809
134
135
  autocoder/common/v2/agent/agentic_edit_tools/attempt_completion_tool_resolver.py,sha256=82ZGKeRBSDKeead_XVBW4FxpiE-5dS7tBOk_3RZ6B5s,1511
@@ -138,8 +139,8 @@ autocoder/common/v2/agent/agentic_edit_tools/list_code_definition_names_tool_res
138
139
  autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py,sha256=ERM5E7s2azQ8vcvogan4A_LZci8Pmhmxw1uQaNQhon4,5469
139
140
  autocoder/common/v2/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py,sha256=SZwFUxK6d2BaKWqQXi_c3IVe2iffviF6VUXJA9T9sx0,1492
140
141
  autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py,sha256=9Bh0KVbL0qiIqwChlb77biiBiETQ3zekxGe5Fj7hXAg,2800
141
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py,sha256=tTQpCIGIzh1XO_MZm6wefMvUm_h6cKoa--oPIm-VwXM,7342
142
- autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py,sha256=Q5U5CMS-06vwXlI0DZImtGZRpi-WXTb5GNDeIpjIdxQ,5571
142
+ autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py,sha256=lpD4fCbVR8GTrynqXON69IjM94nPy3nuUL62Ashm5O4,7988
143
+ autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py,sha256=K-TcqY0z7nDupMkTRDAJdqW3z2Y_RUM_wUb-pOEVQRI,6044
143
144
  autocoder/common/v2/agent/agentic_edit_tools/use_mcp_tool_resolver.py,sha256=wM2Xy4bcnD0TSLEmcM8rvvyyWenN5_KQnJMO6hJ8lTE,1716
144
145
  autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py,sha256=UO4SrkDek3WDlRdlHH022W1roSNMdMcipJqDxRBlheM,3044
145
146
  autocoder/compilers/__init__.py,sha256=C0HOms70QA747XD0uZEMmGtRFcIPenohyqECNStv0Bw,1647
@@ -275,9 +276,9 @@ autocoder/utils/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
275
276
  autocoder/utils/auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
276
277
  autocoder/utils/auto_coder_utils/chat_stream_out.py,sha256=KW0mlmcHlStXi8-_6fXZ2-ifeJ5mgP0OV7DQFzCtIsw,14008
277
278
  autocoder/utils/chat_auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
278
- auto_coder-0.1.336.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
279
- auto_coder-0.1.336.dist-info/METADATA,sha256=jFmhNbFpLI9K9ggO4_IYJ7FdnWg2P8j2VSZYQQZJwao,2747
280
- auto_coder-0.1.336.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
281
- auto_coder-0.1.336.dist-info/entry_points.txt,sha256=0nzHtHH4pNcM7xq4EBA2toS28Qelrvcbrr59GqD_0Ak,350
282
- auto_coder-0.1.336.dist-info/top_level.txt,sha256=Jqc0_uJSw2GwoFQAa9iJxYns-2mWla-9ok_Y3Gcznjk,10
283
- auto_coder-0.1.336.dist-info/RECORD,,
279
+ auto_coder-0.1.338.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
280
+ auto_coder-0.1.338.dist-info/METADATA,sha256=szlrczWaHSc5hCrxKrOcI6lPK2aeYHLQX-J42_mlg9o,2747
281
+ auto_coder-0.1.338.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
282
+ auto_coder-0.1.338.dist-info/entry_points.txt,sha256=0nzHtHH4pNcM7xq4EBA2toS28Qelrvcbrr59GqD_0Ak,350
283
+ auto_coder-0.1.338.dist-info/top_level.txt,sha256=Jqc0_uJSw2GwoFQAa9iJxYns-2mWla-9ok_Y3Gcznjk,10
284
+ auto_coder-0.1.338.dist-info/RECORD,,
@@ -259,8 +259,8 @@ class AgenticFilter:
259
259
  3. **深入分析**:
260
260
  * 使用 `read_files` 读取关键文件的内容进行确认。如果文件过大,使用 `line_ranges` 参数分段读取。
261
261
  * 如有必要,使用 `run_python` 或 `execute_shell_command` 执行代码或命令进行更复杂的分析。
262
- 4. **迭代决策**: 根据工具的返回结果,你可能需要多次调用不同的工具来逐步缩小范围或获取更多信息。
263
- 5. **最终响应**: 当你确定了所有需要参考和修改的文件后,**必须**调用 `output_result` 工具,并提供符合其要求格式的JSON字符串作为其 `response` 参数。
262
+ 4. **迭代决策**: 根据工具的返回结果,你可能需要多次调用不同的工具来逐步缩小范围或获取更多信息。
263
+ 6. **最终响应**: 当你确定了所有需要参考和修改的文件后,**必须**调用 `output_result` 工具,并提供符合其要求格式的JSON字符串作为其 `response` 参数。
264
264
  该json格式要求为:
265
265
  ```json
266
266
  {
@@ -273,9 +273,17 @@ class AgenticFilter:
273
273
  "reasoning": "详细说明你是如何通过分析和使用工具得出这个文件列表的。"
274
274
  }
275
275
  ```
276
-
277
-
278
- """
276
+
277
+ {% if enable_active_context %}
278
+ ** 非常非常重要的提示 **
279
+ 每一个目录都有一个描述信息,比如 {{ project_path }}/src/abc/bbc 的目录描述信息会放在 {{ project_path }}/.auto-coder/active-context/src/abc/bbc/active.md 文件中。
280
+ 你可以使用 read_files 函数读取,从而帮助你更好的挑选要详细阅读哪个文件。值得注意的是,active.md 并不会包含该目录下所有的文件信息,只保存最近发生变更的文件的信息。
281
+ {% endif %}
282
+ """
283
+ return {
284
+ "project_path": os.path.abspath(self.args.source_dir),
285
+ "enable_active_context": self.args.enable_active_context,
286
+ }
279
287
 
280
288
  @byzerllm.prompt()
281
289
  def _execute_command_result(self, result: str) -> str:
@@ -590,8 +598,7 @@ class AgenticFilter:
590
598
  - 如果未找到匹配项,会返回提示信息
591
599
 
592
600
  </usage>
593
- </command>
594
-
601
+ </command>
595
602
  <command>
596
603
  <n>execute_mcp_server</n>
597
604
  <description>执行MCP服务器</description>
@@ -22,6 +22,8 @@ from autocoder.common.result_manager import ResultManager
22
22
  from autocoder.version import __version__
23
23
  from autocoder.auto_coder import main as auto_coder_main
24
24
  from autocoder.utils import get_last_yaml_file
25
+ from autocoder.commands.auto_command import CommandAutoTuner, AutoCommandRequest, CommandConfig, MemoryConfig
26
+ from autocoder.common.v2.agent.agentic_edit import AgenticEdit,AgenticEditRequest
25
27
  from autocoder.index.symbols_utils import (
26
28
  extract_symbols,
27
29
  SymbolType,
@@ -60,7 +62,7 @@ from autocoder.common.conf_validator import ConfigValidator
60
62
  from autocoder import command_parser as CommandParser
61
63
  from loguru import logger as global_logger
62
64
  from autocoder.utils.project_structure import EnhancedFileAnalyzer
63
- from autocoder.common import SourceCodeList
65
+ from autocoder.common import SourceCodeList,SourceCode
64
66
 
65
67
 
66
68
  ## 对外API,用于第三方集成 auto-coder 使用。
@@ -2777,22 +2779,69 @@ def conf_import(path: str):
2777
2779
  from autocoder.common.conf_import_export import import_conf
2778
2780
  import_conf(os.getcwd(), path)
2779
2781
 
2782
+ def generate_new_yaml(query: str):
2783
+ memory = get_memory()
2784
+ conf = memory.get("conf",{})
2785
+ current_files = memory.get("current_files",{}).get("files",[])
2786
+ auto_coder_main(["next", "chat_action"])
2787
+ latest_yaml_file = get_last_yaml_file("actions")
2788
+ if latest_yaml_file:
2789
+ yaml_config = {
2790
+ "include_file": ["./base/base.yml"],
2791
+ "auto_merge": conf.get("auto_merge", "editblock"),
2792
+ "human_as_model": conf.get("human_as_model", "false") == "true",
2793
+ "skip_build_index": conf.get("skip_build_index", "true") == "true",
2794
+ "skip_confirm": conf.get("skip_confirm", "true") == "true",
2795
+ "silence": conf.get("silence", "true") == "true",
2796
+ "include_project_structure": conf.get("include_project_structure", "true")
2797
+ == "true",
2798
+ "exclude_files": memory.get("exclude_files", []),
2799
+ }
2800
+ yaml_config["context"] = ""
2801
+ for key, value in conf.items():
2802
+ converted_value = convert_config_value(key, value)
2803
+ if converted_value is not None:
2804
+ yaml_config[key] = converted_value
2805
+
2806
+ yaml_config["urls"] = current_files + get_llm_friendly_package_docs(
2807
+ return_paths=True
2808
+ )
2809
+ # handle image
2810
+ v = Image.convert_image_paths_from(query)
2811
+ yaml_config["query"] = v
2812
+
2813
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
2814
+
2815
+ execute_file = os.path.join("actions", latest_yaml_file)
2816
+ with open(os.path.join(execute_file), "w",encoding="utf-8") as f:
2817
+ f.write(yaml_content)
2818
+ return execute_file,convert_yaml_to_config(execute_file)
2819
+
2780
2820
  @run_in_raw_thread()
2781
2821
  def auto_command(query: str,extra_args: Dict[str,Any]={}):
2782
- """处理/auto指令"""
2783
- from autocoder.commands.auto_command import CommandAutoTuner, AutoCommandRequest, CommandConfig, MemoryConfig
2784
- args = get_final_config()
2785
-
2822
+ """处理/auto指令"""
2823
+ args = get_final_config()
2824
+ memory = get_memory()
2786
2825
  if args.enable_agentic_edit:
2787
- from autocoder.common.v2.agent.agentic_edit import AgenticEdit,AgenticEditRequest
2788
- llm = get_single_llm(args.code_model or args.model,product_mode=args.product_mode)
2789
- agent = AgenticEdit(llm=llm,args=args,files=SourceCodeList(sources=[]),
2790
- conversation_history=[],
2791
- memory_config=MemoryConfig(memory=memory,
2792
- save_memory_func=save_memory), command_config=CommandConfig)
2793
- agent.run_in_terminal(AgenticEditRequest(user_input=query))
2794
- return
2795
-
2826
+ execute_file,args = generate_new_yaml(query)
2827
+ args.file = execute_file
2828
+ current_files = memory.get("current_files",{}).get("files",[])
2829
+ sources = []
2830
+ for file in current_files:
2831
+ with open(file,"r",encoding="utf-8") as f:
2832
+ sources.append(SourceCode(module_name=file,source_code=f.read()))
2833
+
2834
+ llm = get_single_llm(args.code_model or args.model,product_mode=args.product_mode)
2835
+ agent = AgenticEdit(llm=llm,args=args,files=SourceCodeList(sources=sources),
2836
+ conversation_history=[],
2837
+ memory_config=MemoryConfig(memory=memory,
2838
+ save_memory_func=save_memory), command_config=CommandConfig,
2839
+ conversation_name="current"
2840
+ )
2841
+ agent.run_in_terminal(AgenticEditRequest(user_input=query))
2842
+ return
2843
+
2844
+ args = get_final_config()
2796
2845
  # 准备请求参数
2797
2846
  request = AutoCommandRequest(
2798
2847
  user_input=query
@@ -668,8 +668,7 @@ def main():
668
668
  try:
669
669
  # Shutdown all plugins before exiting
670
670
  plugin_manager.shutdown_all()
671
-
672
- save_memory()
671
+ # save_memory()
673
672
  try:
674
673
  if get_mcp_server():
675
674
  get_mcp_server().stop()
@@ -825,10 +825,54 @@ MESSAGES = {
825
825
  "/compile/check/end": {
826
826
  "en": "Finished compile error checking process.",
827
827
  "zh": "结束编译错误检查过程."
828
+ },
829
+ "/agent/edit/objective":{
830
+ "en":"Objective",
831
+ "zh":"目标"
832
+ },
833
+ "/agent/edit/user_query":{
834
+ "en":"User Query",
835
+ "zh":"用户查询"
828
836
  }
829
837
  }
830
838
 
831
839
 
840
+ # 新增 ReplaceInFileToolResolver 国际化消息
841
+ MESSAGES.update({
842
+ "replace_in_file.access_denied": {
843
+ "en": "Error: Access denied. Attempted to modify file outside the project directory: {{file_path}}",
844
+ "zh": "错误:拒绝访问。尝试修改项目目录之外的文件:{{file_path}}"
845
+ },
846
+ "replace_in_file.file_not_found": {
847
+ "en": "Error: File not found at path: {{file_path}}",
848
+ "zh": "错误:未找到文件路径:{{file_path}}"
849
+ },
850
+ "replace_in_file.read_error": {
851
+ "en": "An error occurred while reading the file for replacement: {{error}}",
852
+ "zh": "读取待替换文件时发生错误:{{error}}"
853
+ },
854
+ "replace_in_file.no_valid_blocks": {
855
+ "en": "Error: No valid SEARCH/REPLACE blocks found in the provided diff.",
856
+ "zh": "错误:在提供的diff中未找到有效的SEARCH/REPLACE代码块。"
857
+ },
858
+ "replace_in_file.apply_failed": {
859
+ "en": "Failed to apply any changes. Errors:\n{{errors}}",
860
+ "zh": "未能应用任何更改。错误信息:\n{{errors}}"
861
+ },
862
+ "replace_in_file.apply_success": {
863
+ "en": "Successfully applied {{applied}}/{{total}} changes to file: {{file_path}}.",
864
+ "zh": "成功应用了 {{applied}}/{{total}} 个更改到文件:{{file_path}}。"
865
+ },
866
+ "replace_in_file.apply_success_with_warnings": {
867
+ "en": "Successfully applied {{applied}}/{{total}} changes to file: {{file_path}}.\nWarnings:\n{{errors}}",
868
+ "zh": "成功应用了 {{applied}}/{{total}} 个更改到文件:{{file_path}}。\n警告信息:\n{{errors}}"
869
+ },
870
+ "replace_in_file.write_error": {
871
+ "en": "An error occurred while writing the modified file: {{error}}",
872
+ "zh": "写入修改后的文件时发生错误:{{error}}"
873
+ }
874
+ })
875
+
832
876
  def get_system_language():
833
877
  try:
834
878
  return locale.getdefaultlocale()[0][:2]
@@ -1,3 +1,4 @@
1
+ from autocoder.common.v2.agent.agentic_edit_conversation import AgenticConversation
1
2
  from enum import Enum
2
3
  from enum import Enum
3
4
  import json
@@ -12,7 +13,8 @@ from rich.panel import Panel
12
13
  from pydantic import SkipValidation
13
14
 
14
15
  # Removed ResultManager, stream_out, git_utils, AutoCommandTools, count_tokens, global_cancel, ActionYmlFileManager, get_event_manager, EventContentCreator, get_run_context, AgenticFilterStreamOutType
15
- from autocoder.auto_coder import AutoCoderArgs
16
+ from autocoder.common import AutoCoderArgs, git_utils, SourceCodeList, SourceCode
17
+ from autocoder.common.global_cancel import global_cancel
16
18
  from autocoder.common import detect_env
17
19
  from autocoder.common import shells
18
20
  from loguru import logger
@@ -34,10 +36,13 @@ from rich.syntax import Syntax # Added
34
36
  from rich.markdown import Markdown # Added
35
37
  from autocoder.events.event_manager_singleton import get_event_manager
36
38
  from autocoder.events.event_types import Event, EventType, EventMetadata
39
+ from autocoder.memory.active_context_manager import ActiveContextManager
37
40
  from autocoder.events import event_content as EventContentCreator
38
41
  from autocoder.shadows.shadow_manager import ShadowManager
39
42
  from autocoder.linters.shadow_linter import ShadowLinter
40
43
  from autocoder.compilers.shadow_compiler import ShadowCompiler
44
+ from autocoder.common.action_yml_file_manager import ActionYmlFileManager
45
+ from autocoder.common.auto_coder_lang import get_message
41
46
  # Import the new display function
42
47
  from autocoder.common.v2.agent.agentic_tool_display import get_tool_display_message
43
48
  from autocoder.common.v2.agent.agentic_edit_types import FileChangeEntry
@@ -60,7 +65,7 @@ from autocoder.common.v2.agent.agentic_edit_types import (AgenticEditRequest, To
60
65
  TOOL_MODEL_MAP,
61
66
  # Event Types
62
67
  LLMOutputEvent, LLMThinkingEvent, ToolCallEvent,
63
- ToolResultEvent, CompletionEvent, ErrorEvent,TokenUsageEvent,
68
+ ToolResultEvent, CompletionEvent, PlanModeRespondEvent, ErrorEvent, TokenUsageEvent,
64
69
  # Import specific tool types for display mapping
65
70
  ReadFileTool, WriteToFileTool, ReplaceInFileTool, ExecuteCommandTool,
66
71
  ListFilesTool, SearchFilesTool, ListCodeDefinitionNamesTool,
@@ -83,6 +88,7 @@ TOOL_RESOLVER_MAP: Dict[Type[BaseTool], Type[BaseToolResolver]] = {
83
88
  UseMcpTool: UseMcpToolResolver,
84
89
  }
85
90
 
91
+
86
92
  # --- Tool Display Customization is now handled by agentic_tool_display.py ---
87
93
 
88
94
 
@@ -95,6 +101,7 @@ class AgenticEdit:
95
101
  args: AutoCoderArgs,
96
102
  memory_config: MemoryConfig,
97
103
  command_config: Optional[CommandConfig] = None,
104
+ conversation_name: str = "current"
98
105
  ):
99
106
  self.llm = llm
100
107
  self.args = args
@@ -109,12 +116,15 @@ class AgenticEdit:
109
116
  self.project_type_analyzer = ProjectTypeAnalyzer(
110
117
  args=args, llm=self.llm)
111
118
 
119
+ self.conversation_manager = AgenticConversation(
120
+ args, self.conversation_history, conversation_name=conversation_name)
121
+
112
122
  self.shadow_manager = ShadowManager(
113
123
  args.source_dir, args.event_file, args.ignore_clean_shadows)
114
124
  self.shadow_linter = ShadowLinter(self.shadow_manager, verbose=False)
115
125
  self.shadow_compiler = ShadowCompiler(
116
- self.shadow_manager, verbose=False)
117
-
126
+ self.shadow_manager, verbose=False)
127
+
118
128
  self.mcp_server_info = ""
119
129
  # try:
120
130
  # self.mcp_server = get_mcp_server()
@@ -129,7 +139,7 @@ class AgenticEdit:
129
139
  # logger.error(f"Error getting MCP server info: {str(e)}")
130
140
 
131
141
  # 变更跟踪信息
132
- # 格式: { file_path: FileChangeEntry(...) }
142
+ # 格式: { file_path: FileChangeEntry(...) }
133
143
  self.file_changes: Dict[str, FileChangeEntry] = {}
134
144
 
135
145
  def record_file_change(self, file_path: str, change_type: str, diff: Optional[str] = None, content: Optional[str] = None):
@@ -144,7 +154,8 @@ class AgenticEdit:
144
154
  """
145
155
  entry = self.file_changes.get(file_path)
146
156
  if entry is None:
147
- entry = FileChangeEntry(type=change_type, diffs=[], content=content)
157
+ entry = FileChangeEntry(
158
+ type=change_type, diffs=[], content=content)
148
159
  self.file_changes[file_path] = entry
149
160
  else:
150
161
  # 文件已经存在,可能之前是 added,现在又被 modified,或者多次 modified
@@ -181,8 +192,10 @@ class AgenticEdit:
181
192
  for fname in files:
182
193
  shadow_file_path = os.path.join(root, fname)
183
194
  try:
184
- project_file_path = self.shadow_manager.from_shadow_path(shadow_file_path)
185
- rel_path = os.path.relpath(project_file_path, self.args.source_dir)
195
+ project_file_path = self.shadow_manager.from_shadow_path(
196
+ shadow_file_path)
197
+ rel_path = os.path.relpath(
198
+ project_file_path, self.args.source_dir)
186
199
  changed_files.append(rel_path)
187
200
  except Exception:
188
201
  # 非映射关系,忽略
@@ -387,7 +400,7 @@ class AgenticEdit:
387
400
  Your query here
388
401
  </query>
389
402
  </use_mcp_tool>
390
-
403
+
391
404
  {%if mcp_server_info %}
392
405
  ### MCP_SERVER_LIST
393
406
  {{mcp_server_info}}
@@ -655,6 +668,13 @@ class AgenticEdit:
655
668
  3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within <thinking></thinking> tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
656
669
  4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user. You may also provide a CLI command to showcase the result of your task; this can be particularly useful for web development tasks, where you can run e.g. \`open index.html\` to show the website you've built.
657
670
  5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.
671
+
672
+ {{ enable_active_context }}
673
+ **Very Important Notice**
674
+ Each directory has a description file stored separately. For example, the description for the directory `{{ current_project }}/src/abc/bbc` can be found in the file `{{ current_project }}/.auto-coder/active-context/src/abc/bbc/active.md`.
675
+ You can use the tool `read_file` to read these description files, which helps you decide exactly which files need detailed attention. Note that the `active.md` file does not contain information about all files within the directory—it only includes information
676
+ about the files that were recently changed.
677
+ {{ enable_active_context }}
658
678
  """
659
679
  env_info = detect_env()
660
680
  shell_type = "bash"
@@ -674,6 +694,7 @@ class AgenticEdit:
674
694
  "home_dir": os.path.expanduser("~"),
675
695
  "files": self.files.to_str(),
676
696
  "mcp_server_info": self.mcp_server_info,
697
+ "enable_active_context": self.args.enable_active_context,
677
698
  }
678
699
 
679
700
  # Removed _execute_command_result and execute_auto_command methods
@@ -724,14 +745,19 @@ class AgenticEdit:
724
745
  executes tools, and yields structured events for visualization until completion or error.
725
746
  """
726
747
  system_prompt = self._analyze.prompt(request)
748
+
727
749
  conversations = [
728
750
  {"role": "system", "content": system_prompt},
729
- {"role": "user", "content": request.user_input}
730
- ]
751
+ ] + self.conversation_manager.get_history()
752
+ conversations.append({
753
+ "role": "user", "content": request.user_input
754
+ })
755
+ self.conversation_manager.add_user_message(request.user_input)
731
756
  logger.debug(
732
757
  f"Initial conversation history size: {len(conversations)}")
733
758
 
734
759
  while True:
760
+ global_cancel.check_and_raise()
735
761
  logger.info(
736
762
  f"Starting LLM interaction cycle. History size: {len(conversations)}")
737
763
  tool_executed = False
@@ -743,12 +769,13 @@ class AgenticEdit:
743
769
  llm_config={}, # Placeholder for future LLM configs
744
770
  args=self.args
745
771
  )
746
-
772
+
747
773
  meta_holder = byzerllm.MetaHolder()
748
774
  parsed_events = self.stream_and_parse_llm_response(
749
- llm_response_gen,meta_holder)
775
+ llm_response_gen, meta_holder)
750
776
 
751
777
  for event in parsed_events:
778
+ global_cancel.check_and_raise()
752
779
  if isinstance(event, (LLMOutputEvent, LLMThinkingEvent)):
753
780
  assistant_buffer += event.text
754
781
  yield event # Yield text/thinking immediately for display
@@ -763,6 +790,8 @@ class AgenticEdit:
763
790
  "role": "assistant",
764
791
  "content": assistant_buffer + tool_xml
765
792
  })
793
+ self.conversation_manager.add_assistant_message(
794
+ assistant_buffer + tool_xml)
766
795
  assistant_buffer = "" # Reset buffer after tool call
767
796
 
768
797
  yield event # Yield the ToolCallEvent for display
@@ -776,6 +805,14 @@ class AgenticEdit:
776
805
  "AgenticEdit analyze loop finished due to AttemptCompletion.")
777
806
  return
778
807
 
808
+ if isinstance(tool_obj, PlanModeRespondTool):
809
+ logger.info(
810
+ "PlanModeRespondTool received. Finalizing session.")
811
+ yield PlanModeRespondEvent(completion=tool_obj, completion_xml=tool_xml)
812
+ logger.info(
813
+ "AgenticEdit analyze loop finished due to PlanModeRespond.")
814
+ return
815
+
779
816
  # Resolve the tool
780
817
  resolver_cls = TOOL_RESOLVER_MAP.get(type(tool_obj))
781
818
  if not resolver_cls:
@@ -830,6 +867,7 @@ class AgenticEdit:
830
867
  "role": "user", # Simulating the user providing the tool result
831
868
  "content": error_xml
832
869
  })
870
+ self.conversation_manager.add_user_message(error_xml)
833
871
  logger.debug(
834
872
  f"Added tool result to conversations for tool {type(tool_obj).__name__}")
835
873
  break # After tool execution and result, break to start a new LLM cycle
@@ -848,6 +886,8 @@ class AgenticEdit:
848
886
  if assistant_buffer:
849
887
  conversations.append(
850
888
  {"role": "assistant", "content": assistant_buffer})
889
+ self.conversation_manager.add_assistant_message(
890
+ assistant_buffer)
851
891
  # If the loop ends without AttemptCompletion, it means the LLM finished talking
852
892
  # without signaling completion. We might just stop or yield a final message.
853
893
  # Let's assume it stops here.
@@ -856,7 +896,7 @@ class AgenticEdit:
856
896
  logger.info("AgenticEdit analyze loop finished.")
857
897
 
858
898
  def stream_and_parse_llm_response(
859
- self, generator: Generator[Tuple[str, Any], None, None],meta_holder: byzerllm.MetaHolder
899
+ self, generator: Generator[Tuple[str, Any], None, None], meta_holder: byzerllm.MetaHolder
860
900
  ) -> Generator[Union[LLMOutputEvent, LLMThinkingEvent, ToolCallEvent, ErrorEvent], None, None]:
861
901
  """
862
902
  Streamingly parses the LLM response generator, distinguishing between
@@ -929,6 +969,8 @@ class AgenticEdit:
929
969
  return None
930
970
 
931
971
  for content_chunk, metadata in generator:
972
+ global_cancel.check_and_raise()
973
+
932
974
  meta_holder.meta = metadata
933
975
  if not content_chunk:
934
976
  continue
@@ -1065,17 +1107,17 @@ class AgenticEdit:
1065
1107
  Runs the agentic edit process, converting internal events to the
1066
1108
  standard event system format and writing them using the event manager.
1067
1109
  """
1068
- event_manager = get_event_manager(self.args.event_file)
1110
+ event_manager = get_event_manager(self.args.event_file)
1069
1111
 
1070
1112
  try:
1071
1113
  event_stream = self.analyze(request)
1072
1114
  for agent_event in event_stream:
1073
1115
  content = None
1074
1116
  metadata = EventMetadata(
1075
- action_file=self.args.event_file,
1076
- is_streaming=False,
1117
+ action_file=self.args.event_file,
1118
+ is_streaming=False,
1077
1119
  stream_out_type="/agent/edit")
1078
-
1120
+
1079
1121
  if isinstance(agent_event, LLMThinkingEvent):
1080
1122
  content = EventContentCreator.create_stream_thinking(
1081
1123
  content=agent_event.text)
@@ -1118,7 +1160,8 @@ class AgenticEdit:
1118
1160
  try:
1119
1161
  self.apply_changes()
1120
1162
  except Exception as e:
1121
- logger.exception(f"Error merging shadow changes to project: {e}")
1163
+ logger.exception(
1164
+ f"Error merging shadow changes to project: {e}")
1122
1165
 
1123
1166
  metadata.path = "/agent/edit/completion"
1124
1167
  content = EventContentCreator.create_completion(
@@ -1128,7 +1171,8 @@ class AgenticEdit:
1128
1171
  **agent_event.completion.model_dump()
1129
1172
  }
1130
1173
  )
1131
- event_manager.write_completion(content=content.to_dict(), metadata=metadata.to_dict())
1174
+ event_manager.write_completion(
1175
+ content=content.to_dict(), metadata=metadata.to_dict())
1132
1176
  elif isinstance(agent_event, ErrorEvent):
1133
1177
  metadata.path = "/agent/edit/error"
1134
1178
  content = EventContentCreator.create_error(
@@ -1136,7 +1180,8 @@ class AgenticEdit:
1136
1180
  error_message=agent_event.message,
1137
1181
  details={"agent_event_type": "ErrorEvent"}
1138
1182
  )
1139
- event_manager.write_error(content=content.to_dict(), metadata=metadata.to_dict())
1183
+ event_manager.write_error(
1184
+ content=content.to_dict(), metadata=metadata.to_dict())
1140
1185
  else:
1141
1186
  metadata.path = "/agent/edit/error"
1142
1187
  logger.warning(
@@ -1147,7 +1192,8 @@ class AgenticEdit:
1147
1192
  details={"agent_event_type": type(
1148
1193
  agent_event).__name__}
1149
1194
  )
1150
- event_manager.write_error(content=content.to_dict(), metadata=metadata.to_dict())
1195
+ event_manager.write_error(
1196
+ content=content.to_dict(), metadata=metadata.to_dict())
1151
1197
 
1152
1198
  except Exception as e:
1153
1199
  logger.exception(
@@ -1158,19 +1204,61 @@ class AgenticEdit:
1158
1204
  error_message=f"An unexpected error occurred: {str(e)}",
1159
1205
  details={"exception_type": type(e).__name__}
1160
1206
  )
1161
- event_manager.write_error(content=error_content.to_dict(), metadata=metadata.to_dict())
1207
+ event_manager.write_error(
1208
+ content=error_content.to_dict(), metadata=metadata.to_dict())
1162
1209
  # Re-raise the exception if needed, or handle appropriately
1163
- # raise e
1210
+ raise e
1164
1211
 
1165
1212
  def apply_changes(self):
1166
1213
  """
1167
1214
  Apply all tracked file changes to the original project directory.
1168
1215
  """
1169
- for change in self.get_all_file_changes():
1170
- file_path = change['file_path']
1171
- content = change['content']
1216
+ for (file_path, change) in self.get_all_file_changes().items():
1172
1217
  with open(file_path, 'w', encoding='utf-8') as f:
1173
- f.write(content)
1218
+ f.write(change.content)
1219
+
1220
+ if len(self.get_all_file_changes()) > 0:
1221
+ if not self.args.skip_commit:
1222
+ try:
1223
+ file_name = os.path.basename(self.args.file)
1224
+ commit_result = git_utils.commit_changes(
1225
+ self.args.source_dir,
1226
+ f"{self.args.query}\nauto_coder_{file_name}",
1227
+ )
1228
+
1229
+ action_yml_file_manager = ActionYmlFileManager(
1230
+ self.args.source_dir)
1231
+ action_file_name = os.path.basename(self.args.file)
1232
+ add_updated_urls = []
1233
+ commit_result.changed_files
1234
+ for file in commit_result.changed_files:
1235
+ add_updated_urls.append(
1236
+ os.path.join(self.args.source_dir, file))
1237
+
1238
+ self.args.add_updated_urls = add_updated_urls
1239
+ update_yaml_success = action_yml_file_manager.update_yaml_field(
1240
+ action_file_name, "add_updated_urls", add_updated_urls)
1241
+ if not update_yaml_success:
1242
+ self.printer.print_in_terminal(
1243
+ "yaml_save_error", style="red", yaml_file=action_file_name)
1244
+
1245
+ if self.args.enable_active_context:
1246
+ active_context_manager = ActiveContextManager(
1247
+ self.llm, self.args.source_dir)
1248
+ task_id = active_context_manager.process_changes(
1249
+ self.args)
1250
+ self.printer.print_in_terminal("active_context_background_task",
1251
+ style="blue",
1252
+ task_id=task_id)
1253
+ git_utils.print_commit_info(commit_result=commit_result)
1254
+ except Exception as e:
1255
+ self.printer.print_str_in_terminal(
1256
+ self.git_require_msg(
1257
+ source_dir=self.args.source_dir, error=str(e)),
1258
+ style="red"
1259
+ )
1260
+ else:
1261
+ self.printer.print_in_terminal("no_changes_made")
1174
1262
 
1175
1263
  def run_in_terminal(self, request: AgenticEditRequest):
1176
1264
  """
@@ -1181,7 +1269,7 @@ class AgenticEdit:
1181
1269
  project_name = os.path.basename(os.path.abspath(self.args.source_dir))
1182
1270
  console.rule(f"[bold cyan]Starting Agentic Edit: {project_name}[/]")
1183
1271
  console.print(Panel(
1184
- f"[bold]User Query:[/bold]\n{request.user_input}", title="Objective", border_style="blue"))
1272
+ f"[bold]{get_message('/agent/edit/user_query')}:[/bold]\n{request.user_input}", title=get_message("/agent/edit/objective"), border_style="blue"))
1185
1273
 
1186
1274
  try:
1187
1275
  event_stream = self.analyze(request)
@@ -1208,18 +1296,26 @@ class AgenticEdit:
1208
1296
  if event.tool_name == "AttemptCompletionTool":
1209
1297
  continue # Do not display AttemptCompletionTool result
1210
1298
 
1299
+ if event.tool_name == "PlanModeRespondTool":
1300
+ continue
1301
+
1211
1302
  result = event.result
1212
1303
  title = f"✅ Tool Result: {event.tool_name}" if result.success else f"❌ Tool Result: {event.tool_name}"
1213
1304
  border_style = "green" if result.success else "red"
1214
1305
  base_content = f"[bold]Status:[/bold] {'Success' if result.success else 'Failure'}\n"
1215
1306
  base_content += f"[bold]Message:[/bold] {result.message}\n"
1216
1307
 
1308
+ def _format_content(content):
1309
+ if len(content) > 200:
1310
+ return f"{content[:100]}\n...\n{content[-100:]}"
1311
+ else:
1312
+ return content
1313
+
1217
1314
  # Prepare panel for base info first
1218
1315
  panel_content = [base_content]
1219
1316
  syntax_content = None
1220
1317
 
1221
1318
  if result.content is not None:
1222
- panel_content.append("[bold]Content:[/bold]\n")
1223
1319
  content_str = ""
1224
1320
  try:
1225
1321
  if isinstance(result.content, (dict, list)):
@@ -1257,16 +1353,17 @@ class AgenticEdit:
1257
1353
  lexer = "text"
1258
1354
 
1259
1355
  syntax_content = Syntax(
1260
- result.content[0:100], lexer, theme="default", line_numbers=True)
1356
+ _format_content(result.content), lexer, theme="default", line_numbers=True)
1261
1357
  else:
1262
1358
  content_str = str(result.content)
1263
1359
  # Append simple string content directly
1264
- panel_content.append(content_str[0:100])
1360
+ panel_content.append(
1361
+ _format_content(content_str))
1265
1362
  except Exception as e:
1266
1363
  logger.warning(
1267
1364
  f"Error formatting tool result content: {e}")
1268
1365
  panel_content.append(
1269
- str(result.content)) # Fallback
1366
+ _format_content(str(result.content))) # Fallback
1270
1367
 
1271
1368
  # Print the base info panel
1272
1369
  console.print(Panel("\n".join(
@@ -1274,13 +1371,17 @@ class AgenticEdit:
1274
1371
  # Print syntax highlighted content separately if it exists
1275
1372
  if syntax_content:
1276
1373
  console.print(syntax_content)
1374
+ elif isinstance(event, PlanModeRespondEvent):
1375
+ console.print(Panel(Markdown(event.completion.response),
1376
+ title="🏁 Task Completion", border_style="green", title_align="left"))
1277
1377
 
1278
1378
  elif isinstance(event, CompletionEvent):
1279
1379
  # 在这里完成实际合并
1280
1380
  try:
1281
1381
  self.apply_changes()
1282
1382
  except Exception as e:
1283
- logger.exception(f"Error merging shadow changes to project: {e}")
1383
+ logger.exception(
1384
+ f"Error merging shadow changes to project: {e}")
1284
1385
 
1285
1386
  console.print(Panel(Markdown(event.completion.result),
1286
1387
  title="🏁 Task Completion", border_style="green", title_align="left"))
@@ -1298,5 +1399,6 @@ class AgenticEdit:
1298
1399
  "An unexpected error occurred during agent execution:")
1299
1400
  console.print(Panel(
1300
1401
  f"[bold red]FATAL ERROR:[/bold red]\n{str(e)}", title="🔥 System Error", border_style="red"))
1402
+ raise e
1301
1403
  finally:
1302
1404
  console.rule("[bold cyan]Agentic Edit Finished[/]")
@@ -0,0 +1,178 @@
1
+ # src/autocoder/common/v2/agent/agentic_edit_conversation.py
2
+ import os
3
+ import json
4
+ import uuid
5
+ from typing import List, Dict, Any, Optional
6
+ from autocoder.common import AutoCoderArgs
7
+
8
+ # Define a type alias for a message dictionary
9
+ MessageType = Dict[str, Any]
10
+
11
+ class AgenticConversation:
12
+ """
13
+ Manages the conversation history for an agentic editing process.
14
+
15
+ Handles adding messages (user, assistant, tool calls, tool results)
16
+ and retrieving the history.
17
+ """
18
+
19
+ def __init__(self, args: AutoCoderArgs, initial_history: Optional[List[MessageType]] = None, conversation_name: Optional[str] = None):
20
+ """
21
+ Initializes the conversation history.
22
+
23
+ Args:
24
+ initial_history: An optional list of messages to start with.
25
+ conversation_name: Optional conversation identifier. If provided, history is saved/loaded from a file named after it.
26
+ """
27
+ self.project_path = args.source_dir
28
+ self._history: List[MessageType] = initial_history if initial_history is not None else []
29
+
30
+ # Determine the memory directory
31
+ memory_dir = os.path.join(self.project_path, ".auto-coder", "memory", "agentic_edit_memory")
32
+ os.makedirs(memory_dir, exist_ok=True)
33
+
34
+ # Determine conversation file path
35
+ if conversation_name:
36
+ filename = f"{conversation_name}.json"
37
+ else:
38
+ conversation_name = str(uuid.uuid4())
39
+ filename = f"{conversation_name}.json"
40
+
41
+ self.conversation_name = conversation_name
42
+ self.memory_file_path = os.path.join(memory_dir, filename)
43
+
44
+ # Load existing history if file exists
45
+ self._load_memory()
46
+
47
+ def add_message(self, role: str, content: Any, **kwargs):
48
+ """
49
+ Adds a message to the conversation history.
50
+
51
+ Args:
52
+ role: The role of the message sender (e.g., "user", "assistant", "tool").
53
+ content: The content of the message. Can be None for messages like tool calls.
54
+ **kwargs: Additional key-value pairs to include in the message dictionary (e.g., tool_calls, tool_call_id).
55
+ """
56
+ message: MessageType = {"role": role}
57
+ if content is not None:
58
+ message["content"] = content
59
+ message.update(kwargs)
60
+ self._history.append(message)
61
+ self._save_memory()
62
+
63
+ def add_user_message(self, content: str):
64
+ """Adds a user message."""
65
+ self.add_message(role="user", content=content)
66
+
67
+ def add_assistant_message(self, content: str):
68
+ """Adds an assistant message (potentially containing text response)."""
69
+ self.add_message(role="assistant", content=content)
70
+
71
+ def add_assistant_tool_call_message(self, tool_calls: List[Dict[str, Any]], content: Optional[str] = None):
72
+ """
73
+ Adds a message representing one or more tool calls from the assistant.
74
+ Optionally includes assistant's textual reasoning/content alongside the calls.
75
+ """
76
+ self.add_message(role="assistant", content=content, tool_calls=tool_calls)
77
+
78
+
79
+ def add_tool_result_message(self, tool_call_id: str, content: Any):
80
+ """Adds a message representing the result of a specific tool call."""
81
+ # The content here is typically the output/result from the tool execution.
82
+ self.add_message(role="tool", content=content, tool_call_id=tool_call_id)
83
+
84
+
85
+ def get_history(self) -> List[MessageType]:
86
+ """
87
+ Returns the latest 20 pairs of (user, assistant) conversation history.
88
+ Merges adjacent same-role messages into one, concatenated by newline.
89
+ Ensures that each user message is paired with the subsequent assistant response,
90
+ skips other roles, and that the last message is always assistant (drops trailing user if unpaired).
91
+
92
+ Returns:
93
+ A list of message dictionaries, ordered chronologically.
94
+ """
95
+ paired_history = []
96
+ pair_count = 0
97
+ pending_assistant = None
98
+ pending_user = None
99
+
100
+ # Traverse history in reverse to collect latest pairs with merging
101
+ for msg in reversed(self._history):
102
+ role = msg.get("role")
103
+ if role == "assistant":
104
+ if pending_assistant is None:
105
+ pending_assistant = dict(msg)
106
+ else:
107
+ # Merge with previous assistant
108
+ prev_content = pending_assistant.get("content", "")
109
+ curr_content = msg.get("content", "")
110
+ merged_content = (curr_content.strip() + "\n" + prev_content.strip()).strip()
111
+ pending_assistant["content"] = merged_content
112
+ elif role == "user":
113
+ if pending_user is None:
114
+ pending_user = dict(msg)
115
+ else:
116
+ # Merge with previous user
117
+ prev_content = pending_user.get("content", "")
118
+ curr_content = msg.get("content", "")
119
+ merged_content = (curr_content.strip() + "\n" + prev_content.strip()).strip()
120
+ pending_user["content"] = merged_content
121
+
122
+ if pending_assistant is not None:
123
+ # Have a full pair, insert in order
124
+ paired_history.insert(0, pending_user)
125
+ paired_history.insert(1, pending_assistant)
126
+ pair_count += 1
127
+ pending_assistant = None
128
+ pending_user = None
129
+ if pair_count >= 20:
130
+ break
131
+ else:
132
+ # User without assistant yet, continue accumulating
133
+ continue
134
+ else:
135
+ # Ignore other roles
136
+ continue
137
+
138
+ # Ensure last message is assistant, drop trailing user if unpaired
139
+ if paired_history and paired_history[-1].get("role") == "user":
140
+ paired_history.pop()
141
+
142
+ return paired_history
143
+
144
+ def clear_history(self):
145
+ """Clears the conversation history."""
146
+ self._history = []
147
+
148
+ def __len__(self) -> int:
149
+ """Returns the number of messages in the history."""
150
+ return len(self._history)
151
+
152
+ def __str__(self) -> str:
153
+ """Returns a string representation of the conversation history."""
154
+ # Consider a more readable format if needed for debugging
155
+ return str(self._history)
156
+
157
+ # Potential future enhancements:
158
+ # - Method to limit history size (by tokens or message count)
159
+ # - Method to format history specifically for different LLM APIs
160
+ # - Serialization/deserialization methods
161
+
162
+ def _save_memory(self):
163
+ try:
164
+ os.makedirs(os.path.dirname(self.memory_file_path), exist_ok=True)
165
+ with open(self.memory_file_path, "w", encoding="utf-8") as f:
166
+ json.dump(self._history, f, ensure_ascii=False, indent=2)
167
+ except Exception as e:
168
+ # Optionally log or ignore
169
+ pass
170
+
171
+ def _load_memory(self):
172
+ try:
173
+ if os.path.exists(self.memory_file_path):
174
+ with open(self.memory_file_path, "r", encoding="utf-8") as f:
175
+ self._history = json.load(f)
176
+ except Exception as e:
177
+ # Ignore loading errors, start fresh
178
+ pass
@@ -6,6 +6,7 @@ from autocoder.common import AutoCoderArgs
6
6
  from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
7
7
  from autocoder.common.v2.agent.agentic_edit_types import ReplaceInFileTool, ToolResult # Import ToolResult from types
8
8
  from loguru import logger
9
+ from autocoder.common.auto_coder_lang import get_message_with_format
9
10
  if typing.TYPE_CHECKING:
10
11
  from autocoder.common.v2.agent.agentic_edit import AgenticEdit
11
12
 
@@ -66,7 +67,7 @@ class ReplaceInFileToolResolver(BaseToolResolver):
66
67
 
67
68
  # Security check
68
69
  if not abs_file_path.startswith(abs_project_dir):
69
- return ToolResult(success=False, message=f"Error: Access denied. Attempted to modify file outside the project directory: {file_path}")
70
+ return ToolResult(success=False, message=get_message_with_format("replace_in_file.access_denied", file_path=file_path))
70
71
 
71
72
  # Determine target path: shadow file if shadow_manager exists
72
73
  target_path = abs_file_path
@@ -90,14 +91,14 @@ class ReplaceInFileToolResolver(BaseToolResolver):
90
91
  f.write(original_content)
91
92
  logger.info(f"[Shadow] Initialized shadow file from original: {target_path}")
92
93
  else:
93
- return ToolResult(success=False, message=f"Error: File not found at path: {file_path}")
94
+ return ToolResult(success=False, message=get_message_with_format("replace_in_file.file_not_found", file_path=file_path))
94
95
  except Exception as e:
95
96
  logger.error(f"Error reading file for replace '{file_path}': {str(e)}")
96
- return ToolResult(success=False, message=f"An error occurred while reading the file for replacement: {str(e)}")
97
+ return ToolResult(success=False, message=get_message_with_format("replace_in_file.read_error", error=str(e)))
97
98
 
98
99
  parsed_blocks = self.parse_diff(diff_content)
99
100
  if not parsed_blocks:
100
- return ToolResult(success=False, message="Error: No valid SEARCH/REPLACE blocks found in the provided diff.")
101
+ return ToolResult(success=False, message=get_message_with_format("replace_in_file.no_valid_blocks"))
101
102
 
102
103
  current_content = original_content
103
104
  applied_count = 0
@@ -121,7 +122,7 @@ class ReplaceInFileToolResolver(BaseToolResolver):
121
122
  # continue applying remaining blocks
122
123
 
123
124
  if applied_count == 0 and errors:
124
- return ToolResult(success=False, message=f"Failed to apply any changes. Errors:\n" + "\n".join(errors))
125
+ return ToolResult(success=False, message=get_message_with_format("replace_in_file.apply_failed", errors="\n".join(errors)))
125
126
 
126
127
  try:
127
128
  os.makedirs(os.path.dirname(target_path), exist_ok=True)
@@ -129,9 +130,17 @@ class ReplaceInFileToolResolver(BaseToolResolver):
129
130
  f.write(current_content)
130
131
  logger.info(f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}")
131
132
 
132
- message = f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}."
133
133
  if errors:
134
- message += "\nWarnings:\n" + "\n".join(errors)
134
+ message = get_message_with_format("replace_in_file.apply_success_with_warnings",
135
+ applied=applied_count,
136
+ total=len(parsed_blocks),
137
+ file_path=file_path,
138
+ errors="\n".join(errors))
139
+ else:
140
+ message = get_message_with_format("replace_in_file.apply_success",
141
+ applied=applied_count,
142
+ total=len(parsed_blocks),
143
+ file_path=file_path)
135
144
 
136
145
  # 变更跟踪,回调AgenticEdit
137
146
  if self.agent:
@@ -141,4 +150,4 @@ class ReplaceInFileToolResolver(BaseToolResolver):
141
150
  return ToolResult(success=True, message=message, content=current_content)
142
151
  except Exception as e:
143
152
  logger.error(f"Error writing replaced content to file '{file_path}': {str(e)}")
144
- return ToolResult(success=False, message=f"An error occurred while writing the modified file: {str(e)}")
153
+ return ToolResult(success=False, message=get_message_with_format("replace_in_file.write_error", error=str(e)))
@@ -54,9 +54,14 @@ class SearchFilesToolResolver(BaseToolResolver):
54
54
  compiled_regex = re.compile(regex_pattern)
55
55
  search_glob_pattern = os.path.join(search_base_path, "**", file_pattern)
56
56
 
57
- logger.info(f"Searching for regex '{regex_pattern}' in files matching '{file_pattern}' under '{search_base_path}' (shadow: {shadow_exists})")
57
+ ignored_dirs = ['.git', 'node_modules', '.mvn', '.idea', '__pycache__', '.venv', 'venv', 'dist', 'build', '.gradle']
58
+ logger.info(f"Searching for regex '{regex_pattern}' in files matching '{file_pattern}' under '{search_base_path}' (shadow: {shadow_exists}), ignoring directories: {ignored_dirs}")
58
59
 
59
60
  for filepath in glob.glob(search_glob_pattern, recursive=True):
61
+ normalized_path = filepath.replace("\\", "/") # Normalize for Windows paths
62
+ if any(f"/{ignored_dir}/" in normalized_path or normalized_path.endswith(f"/{ignored_dir}") or f"/{ignored_dir}/" in normalized_path for ignored_dir in ignored_dirs):
63
+ continue
64
+
60
65
  if os.path.isfile(filepath):
61
66
  try:
62
67
  with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
@@ -80,6 +80,11 @@ class TokenUsageEvent(BaseModel):
80
80
  """Represents the result of executing a tool."""
81
81
  usage: Any
82
82
 
83
+ class PlanModeRespondEvent(BaseModel):
84
+ """Represents the LLM attempting to complete the task."""
85
+ completion: SkipValidation[PlanModeRespondTool] # Skip validation
86
+ completion_xml: str
87
+
83
88
  class CompletionEvent(BaseModel):
84
89
  """Represents the LLM attempting to complete the task."""
85
90
  completion: SkipValidation[AttemptCompletionTool] # Skip validation
@@ -93,7 +93,7 @@ TOOL_DISPLAY_MESSAGES: Dict[Type[BaseTool], Dict[str, str]] = {
93
93
  "[dim]工具:[/dim] [blue]{{ tool_name }}[/]\n"
94
94
  "[dim]参数:[/dim] {{ arguments_snippet }}{{ ellipsis }}"
95
95
  )
96
- }
96
+ }
97
97
  # AttemptCompletionTool is handled separately in the display loop
98
98
  }
99
99
 
@@ -129,11 +129,11 @@ def get_tool_display_message(tool: BaseTool) -> str:
129
129
  "ellipsis": '...' if len(tool.content) > 150 else ''
130
130
  }
131
131
  elif isinstance(tool, ReplaceInFileTool):
132
- snippet = tool.diff[:200]
132
+ snippet = tool.diff
133
133
  context = {
134
134
  "path": tool.path,
135
135
  "diff_snippet": snippet,
136
- "ellipsis": '...' if len(tool.diff) > 200 else ''
136
+ "ellipsis": ''
137
137
  }
138
138
  elif isinstance(tool, ExecuteCommandTool):
139
139
  context = {"command": tool.command, "requires_approval": tool.requires_approval}
autocoder/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.336"
1
+ __version__ = "0.1.338"