opencode-collaboration 0.2.0__tar.gz → 0.2.1__tar.gz
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.
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/PKG-INFO +1 -1
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/opencode_collaboration.egg-info/PKG-INFO +1 -1
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/opencode_collaboration.egg-info/SOURCES.txt +6 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/pyproject.toml +2 -2
- opencode_collaboration-0.2.1/src/cli/__init__.py +0 -0
- opencode_collaboration-0.2.1/src/cli/agent.py +285 -0
- opencode_collaboration-0.2.1/src/cli/main.py +1106 -0
- opencode_collaboration-0.2.1/tests/test_agent_daemon_complete.py +555 -0
- opencode_collaboration-0.2.1/tests/test_agent_daemon_long_running.py +252 -0
- opencode_collaboration-0.2.1/tests/test_agent_daemon_true_long_running.py +266 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/README.md +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/opencode_collaboration.egg-info/dependency_links.txt +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/opencode_collaboration.egg-info/entry_points.txt +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/opencode_collaboration.egg-info/requires.txt +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/opencode_collaboration.egg-info/top_level.txt +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/setup.cfg +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/src/__init__.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/src/main.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_agent_behavior.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_agent_daemon.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_detector.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_doc_generator.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_e2e.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_exception_handler.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_git_monitor.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_state_machine.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_state_manager.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_state_manager_v2.py +0 -0
- {opencode_collaboration-0.2.0 → opencode_collaboration-0.2.1}/tests/test_workflow.py +0 -0
|
@@ -8,8 +8,14 @@ opencode_collaboration.egg-info/requires.txt
|
|
|
8
8
|
opencode_collaboration.egg-info/top_level.txt
|
|
9
9
|
src/__init__.py
|
|
10
10
|
src/main.py
|
|
11
|
+
src/cli/__init__.py
|
|
12
|
+
src/cli/agent.py
|
|
13
|
+
src/cli/main.py
|
|
11
14
|
tests/test_agent_behavior.py
|
|
12
15
|
tests/test_agent_daemon.py
|
|
16
|
+
tests/test_agent_daemon_complete.py
|
|
17
|
+
tests/test_agent_daemon_long_running.py
|
|
18
|
+
tests/test_agent_daemon_true_long_running.py
|
|
13
19
|
tests/test_detector.py
|
|
14
20
|
tests/test_doc_generator.py
|
|
15
21
|
tests/test_e2e.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "opencode-collaboration"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "双Agent协作框架 - 产品经理与开发的分离式协作工具"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -45,7 +45,7 @@ oc-collab = "src.cli.main:main"
|
|
|
45
45
|
|
|
46
46
|
[tool.setuptools.packages.find]
|
|
47
47
|
where = ["."]
|
|
48
|
-
include = ["src"]
|
|
48
|
+
include = ["src", "src.cli"]
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
[tool.pytest.ini_options]
|
|
File without changes
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Agent主模块。"""
|
|
2
|
+
import signal
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional, Callable
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentMode(Enum):
|
|
18
|
+
"""Agent运行模式。"""
|
|
19
|
+
MANUAL = "manual"
|
|
20
|
+
AUTO = "auto"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AgentStatus(Enum):
|
|
24
|
+
"""Agent状态枚举。"""
|
|
25
|
+
IDLE = "idle"
|
|
26
|
+
RUNNING = "running"
|
|
27
|
+
PAUSED = "paused"
|
|
28
|
+
STOPPED = "stopped"
|
|
29
|
+
ERROR = "error"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class AgentConfig:
|
|
34
|
+
"""Agent配置。"""
|
|
35
|
+
agent_id: str = "agent1"
|
|
36
|
+
agent_type: str = "产品经理"
|
|
37
|
+
mode: AgentMode = AgentMode.AUTO
|
|
38
|
+
polling_interval: int = 30
|
|
39
|
+
max_retries: int = 3
|
|
40
|
+
auto_retry: bool = True
|
|
41
|
+
enable_webhook: bool = False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Agent:
|
|
45
|
+
"""Agent主类。"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, config: Optional[AgentConfig] = None):
|
|
48
|
+
"""初始化Agent。"""
|
|
49
|
+
self.config = config or AgentConfig()
|
|
50
|
+
self.state_machine = None
|
|
51
|
+
self.brain_engine = None
|
|
52
|
+
self.task_executor = None
|
|
53
|
+
self.git_monitor = None
|
|
54
|
+
|
|
55
|
+
self.status = AgentStatus.IDLE
|
|
56
|
+
self.current_phase: str = "project_init"
|
|
57
|
+
self._running = False
|
|
58
|
+
self._paused = False
|
|
59
|
+
self._main_thread: Optional[threading.Thread] = None
|
|
60
|
+
self._lock = threading.Lock()
|
|
61
|
+
|
|
62
|
+
self._event_handlers: Dict[str, List[Callable]] = {}
|
|
63
|
+
|
|
64
|
+
signal.signal(signal.SIGINT, self._handle_shutdown)
|
|
65
|
+
signal.signal(signal.SIGTERM, self._handle_shutdown)
|
|
66
|
+
|
|
67
|
+
def initialize(self, project_path: str, state_manager=None) -> None:
|
|
68
|
+
"""初始化Agent组件。"""
|
|
69
|
+
from ..core.state_machine import StateMachine, State
|
|
70
|
+
from ..core.brain_engine import BrainEngine
|
|
71
|
+
from ..core.task_executor import TaskExecutor
|
|
72
|
+
from ..core.git_monitor import GitMonitor, GitConfig
|
|
73
|
+
|
|
74
|
+
self.state_machine = StateMachine(State.PROJECT_INIT)
|
|
75
|
+
self.brain_engine = BrainEngine()
|
|
76
|
+
self.task_executor = TaskExecutor()
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
git_config = GitConfig(polling_interval=self.config.polling_interval)
|
|
80
|
+
self.git_monitor = GitMonitor(project_path, git_config)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.warning(f"Git监控器初始化失败: {e}")
|
|
83
|
+
self.git_monitor = None
|
|
84
|
+
|
|
85
|
+
self.current_phase = "project_init"
|
|
86
|
+
logger.info(f"Agent {self.config.agent_id} 初始化完成")
|
|
87
|
+
|
|
88
|
+
def start(self) -> None:
|
|
89
|
+
"""启动Agent。"""
|
|
90
|
+
if self.status == AgentStatus.RUNNING:
|
|
91
|
+
logger.warning("Agent已在运行中")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
with self._lock:
|
|
95
|
+
self._running = True
|
|
96
|
+
self.status = AgentStatus.RUNNING
|
|
97
|
+
|
|
98
|
+
self._main_thread = threading.Thread(target=self._run_loop, daemon=True)
|
|
99
|
+
self._main_thread.start()
|
|
100
|
+
|
|
101
|
+
logger.info(f"Agent {self.config.agent_id} 已启动")
|
|
102
|
+
|
|
103
|
+
def stop(self) -> None:
|
|
104
|
+
"""停止Agent。"""
|
|
105
|
+
with self._lock:
|
|
106
|
+
if not self._running:
|
|
107
|
+
return
|
|
108
|
+
self._running = False
|
|
109
|
+
|
|
110
|
+
if self.git_monitor:
|
|
111
|
+
self.git_monitor.stop_monitoring()
|
|
112
|
+
|
|
113
|
+
if self._main_thread:
|
|
114
|
+
self._main_thread.join(timeout=5)
|
|
115
|
+
|
|
116
|
+
self.status = AgentStatus.STOPPED
|
|
117
|
+
logger.info(f"Agent {self.config.agent_id} 已停止")
|
|
118
|
+
|
|
119
|
+
def pause(self) -> None:
|
|
120
|
+
"""暂停Agent。"""
|
|
121
|
+
with self._lock:
|
|
122
|
+
self._paused = True
|
|
123
|
+
self.status = AgentStatus.PAUSED
|
|
124
|
+
|
|
125
|
+
if self.git_monitor:
|
|
126
|
+
self.git_monitor.stop_monitoring()
|
|
127
|
+
|
|
128
|
+
logger.info(f"Agent {self.config.agent_id} 已暂停")
|
|
129
|
+
|
|
130
|
+
def resume(self) -> None:
|
|
131
|
+
"""恢复Agent。"""
|
|
132
|
+
with self._lock:
|
|
133
|
+
self._paused = False
|
|
134
|
+
self.status = AgentStatus.RUNNING
|
|
135
|
+
|
|
136
|
+
if self.git_monitor:
|
|
137
|
+
self.git_monitor.start_monitoring()
|
|
138
|
+
|
|
139
|
+
logger.info(f"Agent {self.config.agent_id} 已恢复")
|
|
140
|
+
|
|
141
|
+
def _run_loop(self) -> None:
|
|
142
|
+
"""主循环。"""
|
|
143
|
+
while self._running:
|
|
144
|
+
try:
|
|
145
|
+
if self._paused:
|
|
146
|
+
time.sleep(1)
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
self._execute_cycle()
|
|
150
|
+
|
|
151
|
+
time.sleep(1)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.error(f"执行循环错误: {e}")
|
|
154
|
+
self.status = AgentStatus.ERROR
|
|
155
|
+
self._notify_event("error", {"error": str(e)})
|
|
156
|
+
|
|
157
|
+
def _execute_cycle(self) -> None:
|
|
158
|
+
"""执行一个工作周期。"""
|
|
159
|
+
phase = self.state_machine.current_state.value if self.state_machine else self.current_phase
|
|
160
|
+
|
|
161
|
+
context = {
|
|
162
|
+
"agent_id": self.config.agent_id,
|
|
163
|
+
"agent_type": self.config.agent_type,
|
|
164
|
+
"phase": phase,
|
|
165
|
+
"project_path": getattr(self, '_project_path', '.')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
action, rule = self.brain_engine.get_action(
|
|
169
|
+
agent_type=self.config.agent_type.lower().replace(" ", "_"),
|
|
170
|
+
phase=phase
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if action and action.value != "wait":
|
|
174
|
+
self._execute_action(action, context)
|
|
175
|
+
|
|
176
|
+
self._notify_event("cycle_complete", {"phase": phase, "action": action.value if action else None})
|
|
177
|
+
|
|
178
|
+
def _execute_action(self, action, context: Dict[str, Any]) -> None:
|
|
179
|
+
"""执行动作。"""
|
|
180
|
+
logger.info(f"Agent {self.config.agent_id} 执行动作: {action.value}")
|
|
181
|
+
|
|
182
|
+
result = self.task_executor.execute_action(action.value, context)
|
|
183
|
+
|
|
184
|
+
if result.success:
|
|
185
|
+
logger.info(f"动作执行成功: {result.message}")
|
|
186
|
+
self._notify_event("action_success", {"action": action.value, "result": result})
|
|
187
|
+
else:
|
|
188
|
+
logger.error(f"动作执行失败: {result.message}")
|
|
189
|
+
self._notify_event("action_failed", {"action": action.value, "result": result})
|
|
190
|
+
|
|
191
|
+
def _handle_shutdown(self, signum, frame) -> None:
|
|
192
|
+
"""处理关闭信号。"""
|
|
193
|
+
logger.info(f"收到关闭信号: {signum}")
|
|
194
|
+
self.stop()
|
|
195
|
+
sys.exit(0)
|
|
196
|
+
|
|
197
|
+
def on_event(self, event_type: str, handler: Callable) -> None:
|
|
198
|
+
"""注册事件处理器。"""
|
|
199
|
+
if event_type not in self._event_handlers:
|
|
200
|
+
self._event_handlers[event_type] = []
|
|
201
|
+
self._event_handlers[event_type].append(handler)
|
|
202
|
+
|
|
203
|
+
def _notify_event(self, event_type: str, data: Dict[str, Any]) -> None:
|
|
204
|
+
"""通知事件。"""
|
|
205
|
+
handlers = self._event_handlers.get(event_type, [])
|
|
206
|
+
for handler in handlers:
|
|
207
|
+
try:
|
|
208
|
+
handler(data)
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.error(f"事件处理器执行失败: {e}")
|
|
211
|
+
|
|
212
|
+
def get_status(self) -> Dict[str, Any]:
|
|
213
|
+
"""获取Agent状态。"""
|
|
214
|
+
phase = self.state_machine.current_state.value if self.state_machine else self.current_phase
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
"agent_id": self.config.agent_id,
|
|
218
|
+
"agent_type": self.config.agent_type,
|
|
219
|
+
"status": self.status.value,
|
|
220
|
+
"phase": phase,
|
|
221
|
+
"mode": self.config.mode.value,
|
|
222
|
+
"running": self._running,
|
|
223
|
+
"paused": self._paused
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
def get_summary(self) -> Dict[str, Any]:
|
|
227
|
+
"""获取Agent摘要。"""
|
|
228
|
+
return {
|
|
229
|
+
"config": {
|
|
230
|
+
"agent_id": self.config.agent_id,
|
|
231
|
+
"agent_type": self.config.agent_type,
|
|
232
|
+
"mode": self.config.mode.value,
|
|
233
|
+
"polling_interval": self.config.polling_interval
|
|
234
|
+
},
|
|
235
|
+
"status": self.get_status(),
|
|
236
|
+
"brain_engine": self.brain_engine.get_summary() if self.brain_engine else None,
|
|
237
|
+
"task_executor": self.task_executor.get_summary() if self.task_executor else None,
|
|
238
|
+
"git_monitor": self.git_monitor.get_status_summary() if self.git_monitor else None
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
def switch_mode(self, mode: AgentMode) -> None:
|
|
242
|
+
"""切换运行模式。"""
|
|
243
|
+
self.config.mode = mode
|
|
244
|
+
logger.info(f"Agent {self.config.agent_id} 模式切换为: {mode.value}")
|
|
245
|
+
|
|
246
|
+
def manual_action(self, action_name: str, params: Optional[Dict] = None) -> Dict[str, Any]:
|
|
247
|
+
"""手动执行动作。"""
|
|
248
|
+
if self.config.mode != AgentMode.MANUAL:
|
|
249
|
+
return {
|
|
250
|
+
"success": False,
|
|
251
|
+
"message": "当前不是手动模式,请先切换到手动模式"
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
context = {
|
|
255
|
+
"agent_id": self.config.agent_id,
|
|
256
|
+
"agent_type": self.config.agent_type,
|
|
257
|
+
"phase": self.state_machine.current_state.value if self.state_machine else self.current_phase,
|
|
258
|
+
"params": params or {}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
result = self.task_executor.execute_action(action_name, context)
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
"success": result.success,
|
|
265
|
+
"message": result.message,
|
|
266
|
+
"duration": result.duration,
|
|
267
|
+
"files_created": result.files_created
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
def trigger_action(self, action_name: str, params: Optional[Dict] = None) -> Dict[str, Any]:
|
|
271
|
+
"""触发动作(自动模式下可用)。"""
|
|
272
|
+
context = {
|
|
273
|
+
"agent_id": self.config.agent_id,
|
|
274
|
+
"agent_type": self.config.agent_type,
|
|
275
|
+
"phase": self.state_machine.current_state.value if self.state_machine else self.current_phase,
|
|
276
|
+
"params": params or {}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
result = self.task_executor.execute_action(action_name, context)
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
"success": result.success,
|
|
283
|
+
"message": result.message,
|
|
284
|
+
"duration": result.duration
|
|
285
|
+
}
|