auto-coder 0.1.301__py3-none-any.whl → 0.1.303__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.301.dist-info → auto_coder-0.1.303.dist-info}/METADATA +2 -1
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/RECORD +33 -25
- autocoder/auto_coder.py +6 -7
- autocoder/auto_coder_runner.py +9 -9
- autocoder/chat_auto_coder.py +6 -1
- autocoder/commands/auto_command.py +313 -205
- autocoder/commands/tools.py +123 -85
- autocoder/common/__init__.py +2 -0
- autocoder/common/action_yml_file_manager.py +28 -6
- autocoder/common/auto_coder_lang.py +2 -2
- autocoder/common/auto_configure.py +9 -4
- autocoder/common/code_auto_merge.py +1 -1
- autocoder/common/code_auto_merge_diff.py +1 -1
- autocoder/common/code_auto_merge_editblock.py +1 -1
- autocoder/common/code_auto_merge_strict_diff.py +1 -1
- autocoder/common/stream_out_type.py +7 -0
- autocoder/dispacher/actions/action.py +221 -101
- autocoder/dispacher/actions/plugins/action_regex_project.py +18 -0
- autocoder/events/__init__.py +57 -0
- autocoder/events/event_content.py +423 -0
- autocoder/events/event_manager.py +327 -0
- autocoder/events/event_manager_singleton.py +245 -0
- autocoder/events/event_store.py +376 -0
- autocoder/events/event_types.py +103 -0
- autocoder/index/entry.py +88 -60
- autocoder/index/filter/quick_filter.py +71 -3
- autocoder/run_context.py +62 -0
- autocoder/utils/auto_coder_utils/chat_stream_out.py +32 -1
- autocoder/version.py +1 -1
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Event manager for handling events between System A and System B.
|
|
3
|
+
"""
|
|
4
|
+
from loguru import logger
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
import threading
|
|
8
|
+
from typing import Optional, List, Dict, Any, Callable, Union, TypeVar, Generic
|
|
9
|
+
|
|
10
|
+
from .event_store import EventStore, JsonlEventStore
|
|
11
|
+
from .event_types import Event, EventType, ResponseEvent
|
|
12
|
+
|
|
13
|
+
T = TypeVar('T')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EventManager:
|
|
17
|
+
"""
|
|
18
|
+
Manager for handling events between two systems.
|
|
19
|
+
System A writes events and can be blocked by special events.
|
|
20
|
+
System B reads events and can respond to special events.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, event_store: EventStore):
|
|
24
|
+
"""
|
|
25
|
+
Initialize an event manager.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
event_store: The event store to use
|
|
29
|
+
"""
|
|
30
|
+
self.event_store = event_store
|
|
31
|
+
self._last_read_event_id: Optional[str] = self.event_store._last_event_id
|
|
32
|
+
self._blocking_events: Dict[str, threading.Event] = {}
|
|
33
|
+
self._response_callbacks: Dict[str, Callable[[str], None]] = {}
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def create(cls, file_path: str) -> "EventManager":
|
|
37
|
+
"""
|
|
38
|
+
Create an event manager with a JSONL event store.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
file_path: Path to the JSONL file
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
An initialized event manager
|
|
45
|
+
"""
|
|
46
|
+
store = JsonlEventStore(file_path)
|
|
47
|
+
return cls(store)
|
|
48
|
+
|
|
49
|
+
def write_result(self, content: Union[Dict[str, Any], Any], metadata: Dict[str, Any] = {}) -> Event:
|
|
50
|
+
"""
|
|
51
|
+
Write a result event.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
content: The content of the result
|
|
55
|
+
metadata: The metadata of the result
|
|
56
|
+
Returns:
|
|
57
|
+
The created event
|
|
58
|
+
"""
|
|
59
|
+
if not isinstance(content, dict):
|
|
60
|
+
content = content.to_dict()
|
|
61
|
+
event = Event(event_type=EventType.RESULT, content=content, metadata=metadata)
|
|
62
|
+
self.event_store.append_event(event)
|
|
63
|
+
return event
|
|
64
|
+
|
|
65
|
+
def write_completion(self, content: Union[Dict[str, Any], Any]) -> Event:
|
|
66
|
+
"""
|
|
67
|
+
Write a completion event.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
content: The content of the completion (CompletionContent or dict)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The created event
|
|
74
|
+
"""
|
|
75
|
+
if not isinstance(content, dict):
|
|
76
|
+
content = content.to_dict()
|
|
77
|
+
event = Event(event_type=EventType.COMPLETION, content=content)
|
|
78
|
+
self.event_store.append_event(event)
|
|
79
|
+
return event
|
|
80
|
+
|
|
81
|
+
def write_error(self, content: Union[Dict[str, Any], Any]) -> Event:
|
|
82
|
+
"""
|
|
83
|
+
Write an error event.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
content: The content of the error (ErrorContent or dict)
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The created event
|
|
90
|
+
"""
|
|
91
|
+
if not isinstance(content, dict):
|
|
92
|
+
content = content.to_dict()
|
|
93
|
+
event = Event(event_type=EventType.ERROR, content=content)
|
|
94
|
+
self.event_store.append_event(event)
|
|
95
|
+
return event
|
|
96
|
+
|
|
97
|
+
def write_stream(self, content: Union[Dict[str, Any], Any], metadata: Dict[str, Any] = {}) -> Event:
|
|
98
|
+
"""
|
|
99
|
+
Write a stream event.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
content: The content of the stream
|
|
103
|
+
metadata: The metadata of the stream
|
|
104
|
+
Returns:
|
|
105
|
+
The created event
|
|
106
|
+
"""
|
|
107
|
+
if not isinstance(content, dict):
|
|
108
|
+
content = content.to_dict()
|
|
109
|
+
event = Event(event_type=EventType.STREAM, content=content, metadata=metadata)
|
|
110
|
+
self.event_store.append_event(event)
|
|
111
|
+
return event
|
|
112
|
+
|
|
113
|
+
def ask_user(self,
|
|
114
|
+
prompt: str,
|
|
115
|
+
options: Optional[List[str]] = None,
|
|
116
|
+
callback: Optional[Callable[[str], None]] = None) -> str:
|
|
117
|
+
"""
|
|
118
|
+
Ask the user a question and wait for response.
|
|
119
|
+
This method blocks until a response is received.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
prompt: The question to ask
|
|
123
|
+
options: Optional list of valid responses
|
|
124
|
+
callback: Optional callback function that will be called with the response
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
The user's response
|
|
128
|
+
"""
|
|
129
|
+
logger.debug(f"ask_user called with prompt: {prompt}")
|
|
130
|
+
content = {"prompt": prompt}
|
|
131
|
+
if options:
|
|
132
|
+
content["options"] = options
|
|
133
|
+
|
|
134
|
+
event = Event(event_type=EventType.ASK_USER, content=content)
|
|
135
|
+
logger.debug(f"Created ASK_USER event with ID: {event.event_id}")
|
|
136
|
+
|
|
137
|
+
# Register a blocking event
|
|
138
|
+
blocker = threading.Event()
|
|
139
|
+
self.event_store.append_event(event)
|
|
140
|
+
self._blocking_events[event.event_id] = blocker
|
|
141
|
+
logger.debug(f"self._blocking_events: {self._blocking_events}")
|
|
142
|
+
if callback:
|
|
143
|
+
self._response_callbacks[event.event_id] = callback
|
|
144
|
+
logger.debug(f"Registered callback for event: {event.event_id}")
|
|
145
|
+
|
|
146
|
+
# Wait for response
|
|
147
|
+
logger.debug(f"Waiting for response to event: {event.event_id}")
|
|
148
|
+
|
|
149
|
+
# 无限等待响应,不设超时
|
|
150
|
+
wait_result = blocker.wait(timeout=60.0) # 设置一个较长的超时时间(60秒)
|
|
151
|
+
|
|
152
|
+
if not wait_result:
|
|
153
|
+
logger.warning(f"Timeout waiting for response to event: {event.event_id}")
|
|
154
|
+
return ""
|
|
155
|
+
|
|
156
|
+
logger.debug(f"Received signal that response is available for: {event.event_id}")
|
|
157
|
+
|
|
158
|
+
# Get the response event
|
|
159
|
+
def is_response(e: Event) -> bool:
|
|
160
|
+
return (isinstance(e, ResponseEvent) and
|
|
161
|
+
e.event_type == EventType.USER_RESPONSE and
|
|
162
|
+
e.response_to == event.event_id)
|
|
163
|
+
|
|
164
|
+
response_event = self.event_store.wait_for_event(is_response, timeout=1.0)
|
|
165
|
+
|
|
166
|
+
# 获取响应内容
|
|
167
|
+
response = ""
|
|
168
|
+
if response_event and "response" in response_event.content:
|
|
169
|
+
response = response_event.content["response"]
|
|
170
|
+
logger.debug(f"Retrieved response: '{response}' from event: {response_event.event_id}")
|
|
171
|
+
else:
|
|
172
|
+
logger.warning("No valid response found in event store")
|
|
173
|
+
|
|
174
|
+
# 清理资源(移到这里确保在获取响应后才清理)
|
|
175
|
+
if event.event_id in self._blocking_events:
|
|
176
|
+
del self._blocking_events[event.event_id]
|
|
177
|
+
logger.debug(f"Clean up blocker for event: {event.event_id}")
|
|
178
|
+
else:
|
|
179
|
+
logger.debug(f"No blocker found for event: {event.event_id} during cleanup")
|
|
180
|
+
|
|
181
|
+
# 注意:回调不在这里清理,而是在respond_to_user中清理
|
|
182
|
+
# 这样回调函数仍然可用
|
|
183
|
+
|
|
184
|
+
return response
|
|
185
|
+
|
|
186
|
+
def respond_to_user(self, ask_event_id: str, response: str) -> ResponseEvent:
|
|
187
|
+
"""
|
|
188
|
+
Respond to a user question.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
ask_event_id: ID of the ASK_USER event
|
|
192
|
+
response: The response to provide
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
The created response event
|
|
196
|
+
"""
|
|
197
|
+
logger.debug(f"respond_to_user called for event: {ask_event_id} with response: '{response}'")
|
|
198
|
+
|
|
199
|
+
# 创建响应事件
|
|
200
|
+
event = ResponseEvent(
|
|
201
|
+
event_type=EventType.USER_RESPONSE,
|
|
202
|
+
content={"response": response},
|
|
203
|
+
response_to=ask_event_id
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# 存储响应事件
|
|
207
|
+
self.event_store.append_event(event)
|
|
208
|
+
logger.debug(f"Response event created and stored with ID: {event.event_id}")
|
|
209
|
+
|
|
210
|
+
# 获取回调和阻塞器
|
|
211
|
+
callback = None
|
|
212
|
+
|
|
213
|
+
# 检查是否有回调
|
|
214
|
+
if ask_event_id in self._response_callbacks:
|
|
215
|
+
callback = self._response_callbacks[ask_event_id]
|
|
216
|
+
# 暂时不删除回调,确保不会丢失
|
|
217
|
+
logger.debug(f"Retrieved callback for event: {ask_event_id}")
|
|
218
|
+
else:
|
|
219
|
+
logger.debug(f"No callback found for event: {ask_event_id}")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# 如果找到了回调,执行它
|
|
223
|
+
if callback:
|
|
224
|
+
try:
|
|
225
|
+
logger.debug(f"Executing callback for event: {ask_event_id}")
|
|
226
|
+
callback(response)
|
|
227
|
+
logger.debug(f"Callback execution completed for event: {ask_event_id}")
|
|
228
|
+
|
|
229
|
+
# 回调执行后再移除它
|
|
230
|
+
if ask_event_id in self._response_callbacks:
|
|
231
|
+
del self._response_callbacks[ask_event_id]
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.error(f"Error in response callback: {e}")
|
|
234
|
+
|
|
235
|
+
# 检查是否存在对应的阻塞事件
|
|
236
|
+
logger.debug(f"self._blocking_events: {self._blocking_events}")
|
|
237
|
+
if ask_event_id in self._blocking_events:
|
|
238
|
+
self._blocking_events[ask_event_id].set()
|
|
239
|
+
logger.debug(f"Unblocked event: {ask_event_id}")
|
|
240
|
+
else:
|
|
241
|
+
logger.warning(f"No blocking event found for event_id: {ask_event_id}")
|
|
242
|
+
|
|
243
|
+
return event
|
|
244
|
+
|
|
245
|
+
def read_events(self,
|
|
246
|
+
event_types: Optional[List[EventType]] = None,
|
|
247
|
+
block: bool = False,
|
|
248
|
+
timeout: Optional[float] = None) -> List[Event]:
|
|
249
|
+
"""
|
|
250
|
+
Read events from the store.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
event_types: Only get events of these types
|
|
254
|
+
block: Whether to block until new events are available
|
|
255
|
+
timeout: Maximum time to wait if blocking
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
List of events
|
|
259
|
+
"""
|
|
260
|
+
events = self.event_store.get_events(
|
|
261
|
+
after_id=self._last_read_event_id,
|
|
262
|
+
event_types=event_types
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if events:
|
|
266
|
+
self._last_read_event_id = events[-1].event_id
|
|
267
|
+
return events
|
|
268
|
+
|
|
269
|
+
# No events and not blocking
|
|
270
|
+
if not block:
|
|
271
|
+
return []
|
|
272
|
+
|
|
273
|
+
# Wait for new events
|
|
274
|
+
def is_new_event(e: Event) -> bool:
|
|
275
|
+
if event_types and e.event_type not in event_types:
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
return self._last_read_event_id is None or e.event_id != self._last_read_event_id
|
|
279
|
+
|
|
280
|
+
event = self.event_store.wait_for_event(is_new_event, timeout=timeout)
|
|
281
|
+
|
|
282
|
+
if event:
|
|
283
|
+
events = self.event_store.get_events(
|
|
284
|
+
after_id=self._last_read_event_id,
|
|
285
|
+
event_types=event_types
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if events:
|
|
289
|
+
self._last_read_event_id = events[-1].event_id
|
|
290
|
+
return events
|
|
291
|
+
|
|
292
|
+
return []
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def close(self) -> None:
|
|
296
|
+
"""
|
|
297
|
+
Close the event manager and its event store.
|
|
298
|
+
"""
|
|
299
|
+
# Unblock any waiting threads
|
|
300
|
+
for blocker in self._blocking_events.values():
|
|
301
|
+
blocker.set()
|
|
302
|
+
|
|
303
|
+
# Close the event store
|
|
304
|
+
if isinstance(self.event_store, JsonlEventStore):
|
|
305
|
+
self.event_store.close()
|
|
306
|
+
|
|
307
|
+
def truncate(self) -> None:
|
|
308
|
+
"""
|
|
309
|
+
Clear all events from the event store.
|
|
310
|
+
This truncates the underlying file and resets the last read event ID.
|
|
311
|
+
|
|
312
|
+
Warning: This operation cannot be undone and will permanently delete all events.
|
|
313
|
+
"""
|
|
314
|
+
if not hasattr(self.event_store, 'truncate'):
|
|
315
|
+
raise NotImplementedError("The current event store does not support truncation")
|
|
316
|
+
|
|
317
|
+
# Reset the last read event ID
|
|
318
|
+
self._last_read_event_id = None
|
|
319
|
+
|
|
320
|
+
# Clear blocking events and callbacks
|
|
321
|
+
self._blocking_events.clear()
|
|
322
|
+
self._response_callbacks.clear()
|
|
323
|
+
|
|
324
|
+
# Truncate the store
|
|
325
|
+
self.event_store.truncate()
|
|
326
|
+
|
|
327
|
+
logger.debug("Event store has been truncated")
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import threading
|
|
3
|
+
from typing import Optional, Dict, Any, List
|
|
4
|
+
import uuid
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
import glob
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import byzerllm
|
|
11
|
+
|
|
12
|
+
from .event_manager import EventManager
|
|
13
|
+
from .event_store import EventStore, JsonlEventStore
|
|
14
|
+
from .event_types import Event, EventType
|
|
15
|
+
from loguru import logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EventManagerSingleton:
|
|
19
|
+
"""
|
|
20
|
+
EventManager的单例包装器。确保整个应用程序中只有一个EventManager实例
|
|
21
|
+
"""
|
|
22
|
+
_default_instance: Optional[EventManager] = None
|
|
23
|
+
_instances: Dict[str, EventManager] = {}
|
|
24
|
+
_lock = threading.RLock() # 用于线程安全操作的锁
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def get_instance(cls, event_file: Optional[str] = None) -> EventManager:
|
|
28
|
+
"""
|
|
29
|
+
Get an EventManager instance for the specified event file.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
event_file: Event file path to use as key. If None, returns the default instance.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
EventManager: The appropriate EventManager instance
|
|
36
|
+
"""
|
|
37
|
+
if event_file is None:
|
|
38
|
+
# Use default instance logic
|
|
39
|
+
if cls._default_instance is None:
|
|
40
|
+
cls._default_instance = EventManager(JsonlEventStore(os.path.join(".auto-coder", "events", "events.jsonl")))
|
|
41
|
+
return cls._default_instance
|
|
42
|
+
|
|
43
|
+
# If event_file is provided, use it as a key to store/retrieve EventManager instances
|
|
44
|
+
if event_file not in cls._instances:
|
|
45
|
+
cls._instances[event_file] = EventManager(JsonlEventStore(event_file))
|
|
46
|
+
|
|
47
|
+
return cls._instances[event_file]
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def reset_instance(cls) -> None:
|
|
51
|
+
"""
|
|
52
|
+
重置单例实例。主要用于测试或需要更改事件文件时。
|
|
53
|
+
"""
|
|
54
|
+
with cls._lock:
|
|
55
|
+
cls._default_instance = None
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def set_default_event_file(cls, event_file: str) -> None:
|
|
59
|
+
"""
|
|
60
|
+
设置默认事件文件路径。
|
|
61
|
+
仅在实例尚未创建时有效。
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
event_file: 新的默认事件文件路径
|
|
65
|
+
"""
|
|
66
|
+
if cls._default_instance is None:
|
|
67
|
+
with cls._lock:
|
|
68
|
+
cls._default_event_file = event_file
|
|
69
|
+
else:
|
|
70
|
+
logger.warning("尝试更改默认事件文件,但实例已存在。请先调用reset_instance()。")
|
|
71
|
+
|
|
72
|
+
def get_event_file_path(file_id:str,project_path: Optional[str] = None) -> str:
|
|
73
|
+
if project_path is None:
|
|
74
|
+
return os.path.join(".auto-coder", "events", f"{file_id}.jsonl")
|
|
75
|
+
else:
|
|
76
|
+
return os.path.join(project_path, ".auto-coder", "events", f"{file_id}.jsonl")
|
|
77
|
+
|
|
78
|
+
def gengerate_event_file_path(project_path: Optional[str] = None) -> (str,str):
|
|
79
|
+
"""
|
|
80
|
+
生成一个格式为 uuid_timestamp.jsonl 的事件文件名。
|
|
81
|
+
timestamp 格式为 YYYYMMDD-HHMMSS。
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
生成的事件文件路径
|
|
85
|
+
"""
|
|
86
|
+
# 生成 UUID
|
|
87
|
+
unique_id = str(uuid.uuid4())
|
|
88
|
+
|
|
89
|
+
# 生成当前时间戳,格式为 YYYYMMDD-HHMMSS
|
|
90
|
+
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
91
|
+
|
|
92
|
+
file_id = f"{unique_id}_{timestamp}"
|
|
93
|
+
# 组合文件名
|
|
94
|
+
file_name = f"{file_id}.jsonl"
|
|
95
|
+
|
|
96
|
+
if project_path is None:
|
|
97
|
+
return os.path.join(".auto-coder", "events", file_name),file_id
|
|
98
|
+
else:
|
|
99
|
+
return os.path.join(project_path, ".auto-coder", "events", file_name),file_id
|
|
100
|
+
|
|
101
|
+
@byzerllm.prompt()
|
|
102
|
+
def _format_events_prompt(event_files: List[Dict]) -> str:
|
|
103
|
+
"""
|
|
104
|
+
<recent_events>
|
|
105
|
+
最近的事件记录,从最新到最旧排序:
|
|
106
|
+
|
|
107
|
+
{% for event_file in event_files %}
|
|
108
|
+
## 事件文件 {{ loop.index }}: {{ event_file.file_name }}
|
|
109
|
+
|
|
110
|
+
{% if event_file.timestamp %}
|
|
111
|
+
**时间**: {{ event_file.timestamp }}
|
|
112
|
+
{% endif %}
|
|
113
|
+
|
|
114
|
+
{% if event_file.events %}
|
|
115
|
+
**事件内容**:
|
|
116
|
+
{% for event in event_file.events %}
|
|
117
|
+
- 类型: {{ event.event_type }}
|
|
118
|
+
{% if event.content %}
|
|
119
|
+
内容:
|
|
120
|
+
```
|
|
121
|
+
{{ event.content }}
|
|
122
|
+
```
|
|
123
|
+
{% endif %}
|
|
124
|
+
{% if event.metadata %}
|
|
125
|
+
元数据: {{ event.metadata }}
|
|
126
|
+
{% endif %}
|
|
127
|
+
{% endfor %}
|
|
128
|
+
{% endif %}
|
|
129
|
+
|
|
130
|
+
{% if not loop.last %}
|
|
131
|
+
---
|
|
132
|
+
{% endif %}
|
|
133
|
+
{% endfor %}
|
|
134
|
+
</recent_events>
|
|
135
|
+
请注意上述最近的事件记录,以便更好地理解当前系统状态和交互历史。
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def to_events_prompt(limit: int = 5, project_path: Optional[str] = None) -> str:
|
|
139
|
+
"""
|
|
140
|
+
获取最近的N条事件文件并读取其中的事件内容,返回格式化后的提示文本。
|
|
141
|
+
排除类型为 STREAM 的事件,以减少输出内容的冗余。
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
limit: 最多返回的事件文件数量,默认为5
|
|
145
|
+
project_path: 可选的项目路径,如果提供则在该路径下查找事件文件
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
格式化后的事件提示文本
|
|
149
|
+
"""
|
|
150
|
+
# 确定事件文件所在目录
|
|
151
|
+
if project_path is None:
|
|
152
|
+
events_dir = os.path.join(".auto-coder", "events")
|
|
153
|
+
else:
|
|
154
|
+
events_dir = os.path.join(project_path, ".auto-coder", "events")
|
|
155
|
+
|
|
156
|
+
# 确保目录存在
|
|
157
|
+
if not os.path.exists(events_dir):
|
|
158
|
+
logger.warning(f"事件目录不存在: {events_dir}")
|
|
159
|
+
return "未找到任何事件记录。"
|
|
160
|
+
|
|
161
|
+
# 获取所有事件文件
|
|
162
|
+
event_file_pattern = os.path.join(events_dir, "*.jsonl")
|
|
163
|
+
event_files = glob.glob(event_file_pattern)
|
|
164
|
+
|
|
165
|
+
if not event_files:
|
|
166
|
+
logger.warning(f"未找到任何事件文件: {event_file_pattern}")
|
|
167
|
+
return "未找到任何事件记录。"
|
|
168
|
+
|
|
169
|
+
# 解析文件名中的时间戳,格式为 uuid_YYYYMMDD-HHMMSS.jsonl
|
|
170
|
+
def extract_timestamp(file_path):
|
|
171
|
+
file_name = os.path.basename(file_path)
|
|
172
|
+
match = re.search(r'_(\d{8}-\d{6})\.jsonl$', file_name)
|
|
173
|
+
if match:
|
|
174
|
+
timestamp_str = match.group(1)
|
|
175
|
+
try:
|
|
176
|
+
return datetime.strptime(timestamp_str, "%Y%m%d-%H%M%S")
|
|
177
|
+
except ValueError:
|
|
178
|
+
return datetime.fromtimestamp(0) # 默认最早时间
|
|
179
|
+
return datetime.fromtimestamp(0) # 默认最早时间
|
|
180
|
+
|
|
181
|
+
# 按时间戳从新到旧排序
|
|
182
|
+
sorted_files = sorted(event_files, key=extract_timestamp, reverse=True)
|
|
183
|
+
limited_files = sorted_files[:limit]
|
|
184
|
+
|
|
185
|
+
# 读取和解析事件文件
|
|
186
|
+
event_data = []
|
|
187
|
+
for file_path in limited_files:
|
|
188
|
+
file_name = os.path.basename(file_path)
|
|
189
|
+
timestamp = extract_timestamp(file_path).strftime("%Y-%m-%d %H:%M:%S")
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
events = []
|
|
193
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
194
|
+
for line in f:
|
|
195
|
+
line = line.strip()
|
|
196
|
+
if line:
|
|
197
|
+
try:
|
|
198
|
+
event_json = json.loads(line)
|
|
199
|
+
event = Event.from_dict(event_json)
|
|
200
|
+
|
|
201
|
+
# 排除 STREAM 类型的事件
|
|
202
|
+
if event.event_type == EventType.STREAM:
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
events.append({
|
|
206
|
+
"event_type": event.event_type.name,
|
|
207
|
+
"content": json.dumps(event.content, ensure_ascii=False, indent=2) if event.content else "",
|
|
208
|
+
"metadata": json.dumps(event.metadata, ensure_ascii=False, indent=2) if event.metadata else ""
|
|
209
|
+
})
|
|
210
|
+
except json.JSONDecodeError:
|
|
211
|
+
logger.warning(f"无法解析事件行: {line}")
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.warning(f"处理事件时出错: {e}")
|
|
214
|
+
|
|
215
|
+
event_data.append({
|
|
216
|
+
"file_name": file_name,
|
|
217
|
+
"timestamp": timestamp,
|
|
218
|
+
"events": events
|
|
219
|
+
})
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.error(f"读取事件文件出错 {file_path}: {e}")
|
|
222
|
+
event_data.append({
|
|
223
|
+
"file_name": file_name,
|
|
224
|
+
"timestamp": timestamp,
|
|
225
|
+
"error": str(e)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
# 使用模板格式化事件数据
|
|
229
|
+
return _format_events_prompt(event_data)
|
|
230
|
+
|
|
231
|
+
# 便捷函数,可以直接导入使用
|
|
232
|
+
def get_event_manager(event_file: Optional[str] = None) -> EventManager:
|
|
233
|
+
"""
|
|
234
|
+
获取EventManager的单例实例。
|
|
235
|
+
|
|
236
|
+
如果没有提供event_file,将返回默认的EventManager实例。
|
|
237
|
+
如果提供了event_file,将返回或创建与该文件关联的EventManager实例。
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
event_file: 事件文件路径,如果为None则使用默认路径
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
EventManager实例
|
|
244
|
+
"""
|
|
245
|
+
return EventManagerSingleton.get_instance(event_file)
|