auto-coder 0.1.363__py3-none-any.whl → 0.1.365__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

Files changed (39) hide show
  1. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/METADATA +2 -2
  2. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/RECORD +39 -23
  3. autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +1 -1
  4. autocoder/auto_coder.py +46 -2
  5. autocoder/auto_coder_runner.py +2 -0
  6. autocoder/common/__init__.py +5 -0
  7. autocoder/common/file_checkpoint/__init__.py +21 -0
  8. autocoder/common/file_checkpoint/backup.py +264 -0
  9. autocoder/common/file_checkpoint/conversation_checkpoint.py +182 -0
  10. autocoder/common/file_checkpoint/examples.py +217 -0
  11. autocoder/common/file_checkpoint/manager.py +611 -0
  12. autocoder/common/file_checkpoint/models.py +156 -0
  13. autocoder/common/file_checkpoint/store.py +383 -0
  14. autocoder/common/file_checkpoint/test_backup.py +242 -0
  15. autocoder/common/file_checkpoint/test_manager.py +570 -0
  16. autocoder/common/file_checkpoint/test_models.py +360 -0
  17. autocoder/common/file_checkpoint/test_store.py +327 -0
  18. autocoder/common/file_checkpoint/test_utils.py +297 -0
  19. autocoder/common/file_checkpoint/utils.py +119 -0
  20. autocoder/common/rulefiles/autocoderrules_utils.py +114 -55
  21. autocoder/common/save_formatted_log.py +76 -5
  22. autocoder/common/utils_code_auto_generate.py +2 -1
  23. autocoder/common/v2/agent/agentic_edit.py +545 -225
  24. autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +83 -43
  25. autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +116 -29
  26. autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +179 -48
  27. autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +101 -56
  28. autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +322 -0
  29. autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +173 -132
  30. autocoder/common/v2/agent/agentic_edit_types.py +4 -0
  31. autocoder/compilers/normal_compiler.py +64 -0
  32. autocoder/events/event_manager_singleton.py +133 -4
  33. autocoder/linters/normal_linter.py +373 -0
  34. autocoder/linters/python_linter.py +4 -2
  35. autocoder/version.py +1 -1
  36. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/LICENSE +0 -0
  37. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/WHEEL +0 -0
  38. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/entry_points.txt +0 -0
  39. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,12 @@
1
1
  import os
2
2
  from typing import Dict, Any, Optional
3
- from autocoder.common.v2.agent.agentic_edit_types import WriteToFileTool, ToolResult # Import ToolResult from types
3
+ from autocoder.common.v2.agent.agentic_edit_types import WriteToFileTool, ToolResult
4
4
  from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
5
5
  from loguru import logger
6
6
  from autocoder.common import AutoCoderArgs
7
7
  from autocoder.common.auto_coder_lang import get_message_with_format
8
+ from autocoder.common.file_checkpoint.models import FileChange as CheckpointFileChange
9
+ from autocoder.common.file_checkpoint.manager import FileChangeManager as CheckpointFileChangeManager
8
10
  import typing
9
11
 
10
12
  if typing.TYPE_CHECKING:
@@ -42,145 +44,184 @@ class WriteToFileToolResolver(BaseToolResolver):
42
44
 
43
45
  return "\n".join(formatted_issues)
44
46
 
45
- def resolve(self) -> ToolResult:
46
- file_path = self.tool.path
47
- content = self.tool.content
48
- source_dir = self.args.source_dir or "."
49
- abs_project_dir = os.path.abspath(source_dir)
50
- abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
47
+ def write_file_with_shadow(self, file_path: str, content: str, source_dir: str, abs_project_dir: str, abs_file_path: str) -> ToolResult:
48
+ """Write file using shadow manager for path translation"""
49
+ try:
50
+ shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
51
+ # Ensure shadow directory exists
52
+ os.makedirs(os.path.dirname(shadow_path), exist_ok=True)
53
+ with open(shadow_path, 'w', encoding='utf-8') as f:
54
+ f.write(content)
55
+ logger.info(f"[Shadow] Successfully wrote shadow file: {shadow_path}")
51
56
 
52
- # Security check: ensure the path is within the source directory
53
- if not abs_file_path.startswith(abs_project_dir):
54
- return ToolResult(success=False, message=f"Error: Access denied. Attempted to write file outside the project directory: {file_path}")
57
+ # 回调AgenticEdit,记录变更
58
+ if self.agent:
59
+ rel_path = os.path.relpath(abs_file_path, abs_project_dir)
60
+ self.agent.record_file_change(rel_path, "added", diff=None, content=content)
61
+
62
+ # 新增:执行代码质量检查
63
+ lint_results = None
64
+ lint_message = ""
65
+ formatted_issues = ""
66
+ has_lint_issues = False
67
+
68
+ # 检查是否启用了Lint功能
69
+ enable_lint = self.args.enable_auto_fix_lint
70
+
71
+ if enable_lint:
72
+ try:
73
+ if self.shadow_linter and self.shadow_manager:
74
+ # 对新创建的文件进行 lint 检查
75
+ shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
76
+ lint_results = self.shadow_linter.lint_shadow_file(shadow_path)
77
+
78
+ if lint_results and lint_results.issues:
79
+ has_lint_issues = True
80
+ # 格式化 lint 问题
81
+ formatted_issues = self._format_lint_issues(lint_results)
82
+ lint_message = f"\n\n代码质量检查发现 {len(lint_results.issues)} 个问题:\n{formatted_issues}"
83
+ else:
84
+ lint_message = "\n\n代码质量检查通过,未发现问题。"
85
+ except Exception as e:
86
+ logger.error(f"Lint 检查失败: {str(e)}")
87
+ lint_message = "\n\n尝试进行代码质量检查时出错。"
88
+ else:
89
+ logger.info("代码质量检查已禁用")
90
+
91
+ # 构建包含 lint 结果的返回消息
92
+ message = f"Successfully wrote to file (shadow): {file_path}"
93
+
94
+ # 将 lint 消息添加到结果中,如果启用了Lint
95
+ if enable_lint:
96
+ message += lint_message
97
+
98
+ # 附加 lint 结果到返回内容
99
+ result_content = {
100
+ "content": content,
101
+ }
102
+
103
+ # 只有在启用Lint时才添加Lint结果
104
+ if enable_lint:
105
+ result_content["lint_results"] = {
106
+ "has_issues": has_lint_issues,
107
+ "issues": formatted_issues if has_lint_issues else None
108
+ }
109
+
110
+ return ToolResult(success=True, message=message, content=result_content)
111
+ except Exception as e:
112
+ logger.error(f"Error writing to shadow file '{file_path}': {str(e)}")
113
+ return ToolResult(success=False, message=f"An error occurred while writing to the shadow file: {str(e)}")
55
114
 
115
+ def write_file_normal(self, file_path: str, content: str, source_dir: str, abs_project_dir: str, abs_file_path: str) -> ToolResult:
116
+ """Write file directly without using shadow manager"""
56
117
  try:
57
- if self.shadow_manager:
58
- shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
59
- # Ensure shadow directory exists
60
- os.makedirs(os.path.dirname(shadow_path), exist_ok=True)
61
- with open(shadow_path, 'w', encoding='utf-8') as f:
62
- f.write(content)
63
- logger.info(f"[Shadow] Successfully wrote shadow file: {shadow_path}")
118
+ os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
119
+
120
+ if self.agent:
121
+ rel_path = os.path.relpath(abs_file_path, abs_project_dir)
122
+ self.agent.record_file_change(rel_path, "added", diff=None, content=content)
64
123
 
65
- # 回调AgenticEdit,记录变更
66
- if self.agent:
67
- rel_path = os.path.relpath(abs_file_path, abs_project_dir)
68
- self.agent.record_file_change(rel_path, "added", diff=None, content=content)
69
-
70
- # 新增:执行代码质量检查
71
- lint_results = None
72
- lint_message = ""
73
- formatted_issues = ""
74
- has_lint_issues = False
75
-
76
- # 检查是否启用了Lint功能
77
- enable_lint = self.args.enable_auto_fix_lint
78
-
79
- if enable_lint:
80
- try:
81
- if self.shadow_linter and self.shadow_manager:
82
- # 对新创建的文件进行 lint 检查
83
- shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
84
- lint_results = self.shadow_linter.lint_shadow_file(shadow_path)
85
-
86
- if lint_results and lint_results.issues:
87
- has_lint_issues = True
88
- # 格式化 lint 问题
89
- formatted_issues = self._format_lint_issues(lint_results)
90
- lint_message = f"\n\n代码质量检查发现 {len(lint_results.issues)} 个问题:\n{formatted_issues}"
91
- else:
92
- lint_message = "\n\n代码质量检查通过,未发现问题。"
93
- except Exception as e:
94
- logger.error(f"Lint 检查失败: {str(e)}")
95
- lint_message = "\n\n尝试进行代码质量检查时出错。"
96
- else:
97
- logger.info("代码质量检查已禁用")
98
-
99
- # 构建包含 lint 结果的返回消息
100
- message = f"Successfully wrote to file (shadow): {file_path}"
101
-
102
- # 将 lint 消息添加到结果中,如果启用了Lint
103
- if enable_lint:
104
- message += lint_message
105
-
106
- # 附加 lint 结果到返回内容
107
- result_content = {
108
- "content": content,
124
+ if self.agent and self.agent.checkpoint_manager:
125
+ changes = {
126
+ file_path: CheckpointFileChange(
127
+ file_path=file_path,
128
+ content=content,
129
+ is_deletion=False,
130
+ is_new=True
131
+ )
109
132
  }
110
-
111
- # 只有在启用Lint时才添加Lint结果
112
- if enable_lint:
113
- result_content["lint_results"] = {
114
- "has_issues": has_lint_issues,
115
- "issues": formatted_issues if has_lint_issues else None
116
- }
117
-
118
- return ToolResult(success=True, message=message, content=result_content)
133
+ change_group_id = self.args.event_file
134
+
135
+ self.agent.checkpoint_manager.apply_changes_with_conversation(
136
+ changes=changes,
137
+ conversations=self.agent.current_conversations,
138
+ change_group_id=change_group_id,
139
+ metadata={"event_file": self.args.event_file}
140
+ )
119
141
  else:
120
- # No shadow manager fallback to original file
121
- os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
122
142
  with open(abs_file_path, 'w', encoding='utf-8') as f:
123
143
  f.write(content)
124
- logger.info(f"Successfully wrote to file: {file_path}")
125
-
126
- if self.agent:
127
- rel_path = os.path.relpath(abs_file_path, abs_project_dir)
128
- self.agent.record_file_change(rel_path, "added", diff=None, content=content)
129
-
130
- # 新增:执行代码质量检查
131
- lint_results = None
132
- lint_message = ""
133
- formatted_issues = ""
134
- has_lint_issues = False
135
-
136
- # 检查是否启用了Lint功能
137
- enable_lint = self.args.enable_auto_fix_lint
138
-
139
- if enable_lint:
140
- try:
141
- if self.shadow_linter and self.shadow_manager:
142
- # 对新创建的文件进行 lint 检查
143
- # 由于没有shadow系统,需要先创建shadow文件
144
- shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
145
- os.makedirs(os.path.dirname(shadow_path), exist_ok=True)
146
- with open(shadow_path, 'w', encoding='utf-8') as f:
147
- f.write(content)
148
-
149
- lint_results = self.shadow_linter.lint_shadow_file(shadow_path)
150
-
151
- if lint_results and lint_results.issues:
152
- has_lint_issues = True
153
- # 格式化 lint 问题
154
- formatted_issues = self._format_lint_issues(lint_results)
155
- lint_message = f"\n\n代码质量检查发现 {len(lint_results.issues)} 个问题:\n{formatted_issues}"
156
- else:
157
- lint_message = "\n\n代码质量检查通过,未发现问题。"
158
- except Exception as e:
159
- logger.error(f"Lint 检查失败: {str(e)}")
160
- lint_message = "\n\n尝试进行代码质量检查时出错。"
161
- else:
162
- logger.info("代码质量检查已禁用")
163
-
164
- # 构建包含 lint 结果的返回消息
165
- message = f"Successfully wrote to file: {file_path}"
166
-
167
- # lint 消息添加到结果中,如果启用了Lint
168
- if enable_lint:
169
- message += lint_message
170
-
171
- # 附加 lint 结果到返回内容
172
- result_content = {
173
- "content": content,
144
+ logger.info(f"Successfully wrote to file: {file_path}")
145
+
146
+ # 新增:执行代码质量检查
147
+ lint_results = None
148
+ lint_message = ""
149
+ formatted_issues = ""
150
+ has_lint_issues = False
151
+
152
+ # 检查是否启用了Lint功能
153
+ enable_lint = self.args.enable_auto_fix_lint
154
+
155
+ if enable_lint:
156
+ try:
157
+ if self.shadow_linter and self.shadow_manager:
158
+ # 对新创建的文件进行 lint 检查
159
+ # 由于没有shadow系统,需要先创建shadow文件
160
+ shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
161
+ os.makedirs(os.path.dirname(shadow_path), exist_ok=True)
162
+ with open(shadow_path, 'w', encoding='utf-8') as f:
163
+ f.write(content)
164
+
165
+ lint_results = self.shadow_linter.lint_shadow_file(shadow_path)
166
+
167
+ if lint_results and lint_results.issues:
168
+ has_lint_issues = True
169
+ # 格式化 lint 问题
170
+ formatted_issues = self._format_lint_issues(lint_results)
171
+ lint_message = f"\n\n代码质量检查发现 {len(lint_results.issues)} 个问题:\n{formatted_issues}"
172
+ else:
173
+ lint_message = "\n\n代码质量检查通过,未发现问题。"
174
+ if self.agent.linter:
175
+ lint_results = self.agent.linter.lint_file(file_path)
176
+ if lint_results and lint_results.issues:
177
+ has_lint_issues = True
178
+ # 格式化 lint 问题
179
+ formatted_issues = self._format_lint_issues(lint_results)
180
+ lint_message = f"\n\n代码质量检查发现 {len(lint_results.issues)} 个问题:\n{formatted_issues}"
181
+ except Exception as e:
182
+ logger.error(f"Lint 检查失败: {str(e)}")
183
+ lint_message = "\n\n尝试进行代码质量检查时出错。"
184
+ else:
185
+ logger.info("代码质量检查已禁用")
186
+
187
+ # 构建包含 lint 结果的返回消息
188
+ message = f"{file_path}"
189
+
190
+ # 将 lint 消息添加到结果中,如果启用了Lint
191
+ if enable_lint:
192
+ message += lint_message
193
+
194
+ # 附加 lint 结果到返回内容
195
+ result_content = {
196
+ "content": content,
197
+ }
198
+
199
+ # 只有在启用Lint时才添加Lint结果
200
+ if enable_lint:
201
+ result_content["lint_results"] = {
202
+ "has_issues": has_lint_issues,
203
+ "issues": formatted_issues if has_lint_issues else None
174
204
  }
175
-
176
- # 只有在启用Lint时才添加Lint结果
177
- if enable_lint:
178
- result_content["lint_results"] = {
179
- "has_issues": has_lint_issues,
180
- "issues": formatted_issues if has_lint_issues else None
181
- }
182
-
183
- return ToolResult(success=True, message=message, content=result_content)
205
+
206
+ return ToolResult(success=True, message=message, content=result_content)
184
207
  except Exception as e:
185
208
  logger.error(f"Error writing to file '{file_path}': {str(e)}")
186
- return ToolResult(success=False, message=f"An error occurred while writing to the file: {str(e)}")
209
+ return ToolResult(success=False, message=f"An error occurred while writing to the file: {str(e)}")
210
+
211
+ def resolve(self) -> ToolResult:
212
+ """Resolve the write file tool by calling the appropriate implementation"""
213
+ file_path = self.tool.path
214
+ content = self.tool.content
215
+ source_dir = self.args.source_dir or "."
216
+ abs_project_dir = os.path.abspath(source_dir)
217
+ abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
218
+
219
+ # Security check: ensure the path is within the source directory
220
+ if not abs_file_path.startswith(abs_project_dir):
221
+ return ToolResult(success=False, message=f"Error: Access denied. Attempted to write file outside the project directory: {file_path}")
222
+
223
+ # Choose the appropriate implementation based on whether shadow_manager is available
224
+ if self.shadow_manager:
225
+ return self.write_file_with_shadow(file_path, content, source_dir, abs_project_dir, abs_file_path)
226
+ else:
227
+ return self.write_file_normal(file_path, content, source_dir, abs_project_dir, abs_file_path)
@@ -96,6 +96,10 @@ class ErrorEvent(BaseModel):
96
96
  """Represents an error during the process."""
97
97
  message: str
98
98
 
99
+ class WindowLengthChangeEvent(BaseModel):
100
+ """Represents the token usage in the conversation window."""
101
+ tokens_used: int
102
+
99
103
  # Deprecated: Will be replaced by specific Event types
100
104
  # class PlainTextOutput(BaseModel):
101
105
  # text: str
@@ -0,0 +1,64 @@
1
+ """
2
+ 用于对文件进行编译的模块。
3
+ """
4
+
5
+ import os
6
+ from typing import Optional
7
+ from autocoder.compilers.compiler_factory import CompilerFactory
8
+ from autocoder.compilers.models import ProjectCompilationResult, FileCompilationResult
9
+
10
+ class NormalCompiler:
11
+ """
12
+ 用于对文件进行编译的类。
13
+ """
14
+
15
+ def __init__(self, project_dir: str, verbose: bool = False):
16
+ """
17
+ 初始化编译器。
18
+
19
+ 参数:
20
+ project_dir (str): 项目根目录路径
21
+ verbose (bool): 是否启用详细输出
22
+ """
23
+ self.project_dir = project_dir
24
+ self.compiler = CompilerFactory.create_compiler(
25
+ language="provided",
26
+ config_path=os.path.join(self.project_dir, ".auto-coder", "projects", "compiler.yml")
27
+ )
28
+
29
+ def compile_file(self, file_path: str) -> FileCompilationResult:
30
+ """
31
+ 编译单个文件。
32
+
33
+ 参数:
34
+ file_path (str): 文件路径
35
+
36
+ 返回:
37
+ FileCompilationResult: 编译结果
38
+ """
39
+ return FileCompilationResult(
40
+ file_path=file_path,
41
+ success=True,
42
+ language="provided",
43
+ errors=[],
44
+ error_message=None,
45
+ warning_count=0,
46
+ error_count=0,
47
+ info_count=0,
48
+ execution_time_ms=0,
49
+ output_file=None
50
+ )
51
+
52
+ def compile_all_files(self, target_compiler_name: Optional[str] = None) -> ProjectCompilationResult:
53
+ """
54
+ 编译项目中的所有文件。
55
+
56
+ 参数:
57
+ target_compiler_name (Optional[str]): 目标编译器名称
58
+
59
+ 返回:
60
+ ProjectCompilationResult: 项目编译结果
61
+ """
62
+ result = self.compiler.compile_project(self.project_dir, target_compiler_name)
63
+ result.project_path = self.project_dir
64
+ return result
@@ -6,6 +6,7 @@ from datetime import datetime
6
6
  import glob
7
7
  import json
8
8
  import re
9
+ import time
9
10
  from pathlib import Path
10
11
  import byzerllm
11
12
 
@@ -22,6 +23,10 @@ class EventManagerSingleton:
22
23
  _default_instance: Optional[EventManager] = None
23
24
  _instances: Dict[str, EventManager] = {}
24
25
  _lock = threading.RLock() # 用于线程安全操作的锁
26
+ _cleanup_thread: Optional[threading.Thread] = None
27
+ _stop_cleanup = threading.Event()
28
+ _max_event_files = 100 # 最大保留的事件文件数量
29
+ _cleanup_interval = 60 # 清理线程执行间隔,单位秒
25
30
 
26
31
  @classmethod
27
32
  def get_instance(cls, event_file: Optional[str] = None) -> EventManager:
@@ -34,15 +39,26 @@ class EventManagerSingleton:
34
39
  Returns:
35
40
  EventManager: The appropriate EventManager instance
36
41
  """
42
+ # 确保清理线程已启动
43
+ cls._ensure_cleanup_thread_started()
44
+
37
45
  if event_file is None:
38
46
  # Use default instance logic
39
47
  if cls._default_instance is None:
40
- cls._default_instance = EventManager(JsonlEventStore(os.path.join(".auto-coder", "events", "events.jsonl")))
48
+ logger.info("Creating new default EventManager instance.")
49
+ event_store_path = os.path.join(".auto-coder", "events", "events.jsonl")
50
+ logger.debug(f"Default EventManager using event store: {event_store_path}")
51
+ cls._default_instance = EventManager(JsonlEventStore(event_store_path))
52
+ else:
53
+ logger.debug("Returning existing default EventManager instance.")
41
54
  return cls._default_instance
42
55
 
43
56
  # If event_file is provided, use it as a key to store/retrieve EventManager instances
44
57
  if event_file not in cls._instances:
58
+ logger.info(f"Creating new EventManager instance for event file: {event_file}")
45
59
  cls._instances[event_file] = EventManager(JsonlEventStore(event_file))
60
+ else:
61
+ logger.debug(f"Returning existing EventManager instance for event file: {event_file}")
46
62
 
47
63
  return cls._instances[event_file]
48
64
 
@@ -52,7 +68,11 @@ class EventManagerSingleton:
52
68
  重置单例实例。主要用于测试或需要更改事件文件时。
53
69
  """
54
70
  with cls._lock:
55
- cls._default_instance = None
71
+ if cls._default_instance is not None:
72
+ logger.info("Resetting default EventManager instance.")
73
+ cls._default_instance = None
74
+ else:
75
+ logger.debug("Default EventManager instance was already None. No reset needed.")
56
76
 
57
77
  @classmethod
58
78
  def set_default_event_file(cls, event_file: str) -> None:
@@ -68,6 +88,110 @@ class EventManagerSingleton:
68
88
  cls._default_event_file = event_file
69
89
  else:
70
90
  logger.warning("尝试更改默认事件文件,但实例已存在。请先调用reset_instance()。")
91
+
92
+ @classmethod
93
+ def _ensure_cleanup_thread_started(cls) -> None:
94
+ """
95
+ 确保清理线程已启动。如果尚未启动,则启动线程。
96
+ """
97
+ with cls._lock:
98
+ if cls._cleanup_thread is None or not cls._cleanup_thread.is_alive():
99
+ logger.info("启动事件文件清理线程")
100
+ cls._stop_cleanup.clear()
101
+ cls._cleanup_thread = threading.Thread(
102
+ target=cls._cleanup_event_files_thread,
103
+ daemon=True,
104
+ name="EventFilesCleanupThread"
105
+ )
106
+ cls._cleanup_thread.start()
107
+
108
+ @classmethod
109
+ def _cleanup_event_files_thread(cls) -> None:
110
+ """
111
+ 定时清理线程的主函数。定期执行清理操作,确保事件目录只保留最新的指定数量文件。
112
+ """
113
+ logger.info(f"事件文件清理线程已启动,将保留最新的{cls._max_event_files}个文件")
114
+ while not cls._stop_cleanup.is_set():
115
+ try:
116
+ cls._cleanup_event_files()
117
+ # 等待指定时间或直到停止信号
118
+ cls._stop_cleanup.wait(cls._cleanup_interval)
119
+ except Exception as e:
120
+ logger.error(f"事件文件清理过程中发生错误: {e}")
121
+ # 出错后等待一段时间再重试
122
+ time.sleep(60)
123
+
124
+ @classmethod
125
+ def _cleanup_event_files(cls) -> None:
126
+ """
127
+ 清理事件文件,只保留最新的指定数量文件。
128
+ """
129
+ logger.info("开始清理事件文件...")
130
+
131
+ # 确定事件文件所在目录
132
+ events_dir = os.path.join(os.getcwd(),".auto-coder", "events")
133
+
134
+ # 确保目录存在
135
+ if not os.path.exists(events_dir):
136
+ logger.warning(f"事件目录不存在: {events_dir}")
137
+ return
138
+
139
+ # 获取所有事件文件
140
+ event_file_pattern = os.path.join(events_dir, "*.jsonl")
141
+ event_files = glob.glob(event_file_pattern)
142
+
143
+ if not event_files:
144
+ logger.info(f"未找到任何事件文件: {event_file_pattern}")
145
+ return
146
+
147
+ # 排除默认的events.jsonl文件
148
+ event_files = [f for f in event_files if os.path.basename(f) != "events.jsonl"]
149
+
150
+ if len(event_files) <= cls._max_event_files:
151
+ logger.info(f"事件文件数量({len(event_files)})未超过最大保留数量({cls._max_event_files}),无需清理")
152
+ return
153
+
154
+ # 解析文件名中的时间戳,格式为 uuid_YYYYMMDD-HHMMSS.jsonl
155
+ def extract_timestamp(file_path):
156
+ file_name = os.path.basename(file_path)
157
+ match = re.search(r'_(\d{8}-\d{6})\.jsonl$', file_name)
158
+ if match:
159
+ timestamp_str = match.group(1)
160
+ try:
161
+ return datetime.strptime(timestamp_str, "%Y%m%d-%H%M%S")
162
+ except ValueError:
163
+ return datetime.fromtimestamp(0) # 默认最早时间
164
+ return datetime.fromtimestamp(0) # 默认最早时间
165
+
166
+ # 按时间戳从新到旧排序
167
+ sorted_files = sorted(event_files, key=extract_timestamp, reverse=True)
168
+
169
+ # 保留最新的文件,删除其余文件
170
+ files_to_keep = sorted_files[:cls._max_event_files]
171
+ files_to_delete = sorted_files[cls._max_event_files:]
172
+
173
+ for file_path in files_to_delete:
174
+ try:
175
+ os.remove(file_path)
176
+ logger.info(f"已删除旧事件文件: {file_path}")
177
+ except Exception as e:
178
+ logger.error(f"删除事件文件失败 {file_path}: {e}")
179
+
180
+ logger.info(f"事件文件清理完成,删除了{len(files_to_delete)}个旧文件,保留了{len(files_to_keep)}个最新文件")
181
+
182
+ @classmethod
183
+ def stop_cleanup_thread(cls) -> None:
184
+ """
185
+ 停止清理线程,通常在应用程序退出时调用。
186
+ """
187
+ logger.info("正在停止事件文件清理线程...")
188
+ cls._stop_cleanup.set()
189
+ if cls._cleanup_thread and cls._cleanup_thread.is_alive():
190
+ cls._cleanup_thread.join(timeout=5)
191
+ if cls._cleanup_thread.is_alive():
192
+ logger.warning("清理线程未在指定时间内停止")
193
+ else:
194
+ logger.info("清理线程已成功停止")
71
195
 
72
196
  def get_event_file_path(file_id:str,project_path: Optional[str] = None) -> str:
73
197
  if project_path is None:
@@ -95,9 +219,12 @@ def gengerate_event_file_path(project_path: Optional[str] = None) -> Tuple[str,s
95
219
 
96
220
  # 要使用绝对路径,否则会被认为产生两个event_manager
97
221
  if project_path is None:
98
- return os.path.join(os.getcwd(),".auto-coder", "events", file_name),file_id
222
+ full_path = os.path.join(os.getcwd(),".auto-coder", "events", file_name)
99
223
  else:
100
- return os.path.join(project_path, ".auto-coder", "events", file_name),file_id
224
+ full_path = os.path.join(project_path, ".auto-coder", "events", file_name)
225
+
226
+ logger.info(f"Generated event file path: {full_path}, file_id: {file_id}")
227
+ return full_path, file_id
101
228
 
102
229
  @byzerllm.prompt()
103
230
  def _format_events_prompt(event_files: List[Dict]) -> str:
@@ -148,6 +275,7 @@ def to_events_prompt(limit: int = 5, project_path: Optional[str] = None) -> str:
148
275
  Returns:
149
276
  格式化后的事件提示文本
150
277
  """
278
+ logger.info(f"Generating events prompt with limit={limit}, project_path={project_path}")
151
279
  # 确定事件文件所在目录
152
280
  if project_path is None:
153
281
  events_dir = os.path.join(".auto-coder", "events")
@@ -164,6 +292,7 @@ def to_events_prompt(limit: int = 5, project_path: Optional[str] = None) -> str:
164
292
  event_files = glob.glob(event_file_pattern)
165
293
 
166
294
  if not event_files:
295
+ logger.info(f"No event files found in pattern: {event_file_pattern}")
167
296
  logger.warning(f"未找到任何事件文件: {event_file_pattern}")
168
297
  return "未找到任何事件记录。"
169
298