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.
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/METADATA +2 -2
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/RECORD +39 -23
- autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +1 -1
- autocoder/auto_coder.py +46 -2
- autocoder/auto_coder_runner.py +2 -0
- autocoder/common/__init__.py +5 -0
- autocoder/common/file_checkpoint/__init__.py +21 -0
- autocoder/common/file_checkpoint/backup.py +264 -0
- autocoder/common/file_checkpoint/conversation_checkpoint.py +182 -0
- autocoder/common/file_checkpoint/examples.py +217 -0
- autocoder/common/file_checkpoint/manager.py +611 -0
- autocoder/common/file_checkpoint/models.py +156 -0
- autocoder/common/file_checkpoint/store.py +383 -0
- autocoder/common/file_checkpoint/test_backup.py +242 -0
- autocoder/common/file_checkpoint/test_manager.py +570 -0
- autocoder/common/file_checkpoint/test_models.py +360 -0
- autocoder/common/file_checkpoint/test_store.py +327 -0
- autocoder/common/file_checkpoint/test_utils.py +297 -0
- autocoder/common/file_checkpoint/utils.py +119 -0
- autocoder/common/rulefiles/autocoderrules_utils.py +114 -55
- autocoder/common/save_formatted_log.py +76 -5
- autocoder/common/utils_code_auto_generate.py +2 -1
- autocoder/common/v2/agent/agentic_edit.py +545 -225
- autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +83 -43
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +116 -29
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +179 -48
- autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +101 -56
- autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +322 -0
- autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +173 -132
- autocoder/common/v2/agent/agentic_edit_types.py +4 -0
- autocoder/compilers/normal_compiler.py +64 -0
- autocoder/events/event_manager_singleton.py +133 -4
- autocoder/linters/normal_linter.py +373 -0
- autocoder/linters/python_linter.py +4 -2
- autocoder/version.py +1 -1
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
os.
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
logger.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
222
|
+
full_path = os.path.join(os.getcwd(),".auto-coder", "events", file_name)
|
|
99
223
|
else:
|
|
100
|
-
|
|
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
|
|