full-stack-coding-assistant-agent 0.1.0__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.
- agents/__init__.py +0 -0
- agents/audit_agent.py +223 -0
- agents/backend_agent.py +179 -0
- agents/base_agent.py +406 -0
- agents/frontend_agent.py +148 -0
- agents/test_agent.py +155 -0
- coordinator/__init__.py +0 -0
- coordinator/coordinator.py +452 -0
- coordinator/dag.py +147 -0
- executor/__init__.py +0 -0
- executor/cb_integration.py +160 -0
- full_stack_coding_assistant_agent/__init__.py +6 -0
- full_stack_coding_assistant_agent/cli.py +10 -0
- full_stack_coding_assistant_agent/main.py +686 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/METADATA +849 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/RECORD +31 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/WHEEL +5 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/entry_points.txt +2 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/top_level.txt +7 -0
- model/__init__.py +0 -0
- model/config.py +62 -0
- model/model_router.py +150 -0
- storage/__init__.py +0 -0
- storage/context_db.py +274 -0
- utils/__init__.py +0 -0
- utils/agent_selector.py +243 -0
- utils/config_validator.py +143 -0
- utils/logger.py +95 -0
- utils/output_manager.py +1572 -0
- utils/pdf_reader.py +122 -0
- utils/version.py +188 -0
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
"""
|
|
2
|
+
全栈代码助手智能体 - 主入口
|
|
3
|
+
支持 Python 3.13+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import signal
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from coordinator.coordinator import Coordinator
|
|
13
|
+
from utils.agent_selector import AgentSelector
|
|
14
|
+
from utils.config_validator import validate_config
|
|
15
|
+
from utils.logger import debug, error, info, warning
|
|
16
|
+
from utils.output_manager import OutputManager
|
|
17
|
+
from utils.pdf_reader import read_document
|
|
18
|
+
|
|
19
|
+
# 退出命令集合
|
|
20
|
+
EXIT_COMMANDS = {"exit", "quit", "bye", "q"}
|
|
21
|
+
|
|
22
|
+
# Ctrl+C 退出标志
|
|
23
|
+
_interrupt_received = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _signal_handler(sig, frame):
|
|
27
|
+
"""处理 Ctrl+C 信号"""
|
|
28
|
+
global _interrupt_received
|
|
29
|
+
_interrupt_received = True
|
|
30
|
+
info("\n\n收到退出信号,正在退出...")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _is_exit_command(user_input: str) -> bool:
|
|
34
|
+
"""判断用户输入是否为退出命令"""
|
|
35
|
+
return user_input.strip().lower() in EXIT_COMMANDS
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _print_welcome():
|
|
39
|
+
"""打印欢迎信息"""
|
|
40
|
+
info("=" * 60)
|
|
41
|
+
info("全栈代码助手智能体 (Full-Stack Coding Assistant Agent)")
|
|
42
|
+
info("=" * 60)
|
|
43
|
+
info("")
|
|
44
|
+
info("交互模式已启动。输入需求开始开发,输入 exit/quit/bye 退出。")
|
|
45
|
+
info("")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _print_help():
|
|
49
|
+
"""打印帮助信息"""
|
|
50
|
+
info("")
|
|
51
|
+
info("可用命令:")
|
|
52
|
+
info(" help, h, ? - 显示此帮助")
|
|
53
|
+
info(" status, s - 显示当前项目状态")
|
|
54
|
+
info(" agents, a - 显示可用 Agent 列表")
|
|
55
|
+
info(" load <文件> - 加载 PDF/TXT/MD 文档作为需求上下文")
|
|
56
|
+
info(" exit, quit, bye - 退出程序")
|
|
57
|
+
info("")
|
|
58
|
+
info("输入任意文本即可作为新需求进行迭代开发。")
|
|
59
|
+
info("")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _print_agent_list(selector: AgentSelector):
|
|
63
|
+
"""打印可用 Agent 列表"""
|
|
64
|
+
info("")
|
|
65
|
+
info("可用 Agent:")
|
|
66
|
+
for name, desc in selector.AGENT_DESCRIPTIONS.items():
|
|
67
|
+
info(f" - {name}: {desc}")
|
|
68
|
+
info("")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _print_project_status(output_dir: Optional[Path]):
|
|
72
|
+
"""打印当前项目状态"""
|
|
73
|
+
info("")
|
|
74
|
+
if output_dir is None or not output_dir.exists():
|
|
75
|
+
info("当前状态: 未创建项目(请先输入项目描述)")
|
|
76
|
+
info("")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
info(f"输出目录: {output_dir}")
|
|
80
|
+
info("")
|
|
81
|
+
|
|
82
|
+
# 读取元数据
|
|
83
|
+
meta_file = output_dir / ".meta.json"
|
|
84
|
+
if meta_file.exists():
|
|
85
|
+
import json
|
|
86
|
+
|
|
87
|
+
meta = json.loads(meta_file.read_text(encoding="utf-8"))
|
|
88
|
+
info(f"项目描述: {meta.get('task_description', 'N/A')}")
|
|
89
|
+
info(f"创建时间: {meta.get('created_at', 'N/A')}")
|
|
90
|
+
info(f"迭代次数: {meta.get('iteration_count', 0)}")
|
|
91
|
+
info(f"最后任务: {meta.get('last_task_id', 'N/A')}")
|
|
92
|
+
info(f"状态: {meta.get('status', 'N/A')}")
|
|
93
|
+
else:
|
|
94
|
+
info("未找到项目元数据")
|
|
95
|
+
|
|
96
|
+
info("")
|
|
97
|
+
|
|
98
|
+
# 统计文件数量
|
|
99
|
+
for sub_dir in ["backend", "frontend", "tests", "audits"]:
|
|
100
|
+
target = output_dir / sub_dir
|
|
101
|
+
if target.exists():
|
|
102
|
+
files = list(target.iterdir())
|
|
103
|
+
info(f" {sub_dir}: {len(files)} 个文件")
|
|
104
|
+
else:
|
|
105
|
+
info(f" {sub_dir}: 未创建")
|
|
106
|
+
|
|
107
|
+
# 统计 trace 文件
|
|
108
|
+
traces_dir = output_dir / "traces"
|
|
109
|
+
if traces_dir.exists():
|
|
110
|
+
trace_count = sum(1 for _ in traces_dir.rglob("*.md"))
|
|
111
|
+
if trace_count > 0:
|
|
112
|
+
info(
|
|
113
|
+
f" traces: {trace_count} 个审计 trace 文件(Agent 每一步的输入输出记录)"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
info("")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _get_user_input(prompt: str = ">>> ") -> Optional[str]:
|
|
120
|
+
"""
|
|
121
|
+
获取用户输入,处理 Ctrl+C
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
用户输入字符串,如果收到中断则返回 None
|
|
125
|
+
"""
|
|
126
|
+
global _interrupt_received
|
|
127
|
+
|
|
128
|
+
if _interrupt_received:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
user_input = input(prompt).strip()
|
|
133
|
+
if _interrupt_received:
|
|
134
|
+
return None
|
|
135
|
+
return user_input
|
|
136
|
+
except (EOFError, KeyboardInterrupt):
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _get_project_description() -> Optional[str]:
|
|
141
|
+
"""
|
|
142
|
+
首次启动时,引导用户输入项目描述
|
|
143
|
+
|
|
144
|
+
支持:
|
|
145
|
+
- 纯文本描述
|
|
146
|
+
- file:path/to/file.pdf 从 PDF 读取描述
|
|
147
|
+
- file:path/to/file.txt 从 TXT 读取描述
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
项目描述字符串,如果取消则返回 None
|
|
151
|
+
"""
|
|
152
|
+
info("")
|
|
153
|
+
info("欢迎使用全栈代码助手!")
|
|
154
|
+
info("请描述你要开发的项目,或者指定文档文件:")
|
|
155
|
+
info(" 直接输入 - 文本描述项目")
|
|
156
|
+
info(" file:xxx - 从 PDF/TXT 文件读取描述")
|
|
157
|
+
info(" exit - 退出")
|
|
158
|
+
info("")
|
|
159
|
+
|
|
160
|
+
while True:
|
|
161
|
+
user_input = _get_user_input("项目描述> ")
|
|
162
|
+
if user_input is None:
|
|
163
|
+
return None
|
|
164
|
+
if _is_exit_command(user_input):
|
|
165
|
+
return None
|
|
166
|
+
if not user_input:
|
|
167
|
+
warning("项目描述不能为空,请重新输入。")
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# 检查是否为 file: 指令
|
|
171
|
+
if user_input.startswith("file:"):
|
|
172
|
+
file_path = user_input[5:].strip()
|
|
173
|
+
description = _load_document_description(file_path)
|
|
174
|
+
if description is None:
|
|
175
|
+
continue # 加载失败,让用户重新输入
|
|
176
|
+
return description
|
|
177
|
+
|
|
178
|
+
return user_input
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _load_document_description(file_path: str) -> Optional[str]:
|
|
182
|
+
"""
|
|
183
|
+
从文档文件加载原始文本内容
|
|
184
|
+
|
|
185
|
+
支持 .pdf / .txt / .md 格式。
|
|
186
|
+
成功时返回文档的原始全文,失败时打印错误并返回 None。
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
file_path: 文档文件路径
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
文档的原始全文,或 None
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
summary, full_text = read_document(file_path)
|
|
196
|
+
info(f" 已从文件加载: {file_path}")
|
|
197
|
+
info(f" 文档摘要: {summary}")
|
|
198
|
+
info(f" 文档长度: {len(full_text)} 字符")
|
|
199
|
+
return full_text
|
|
200
|
+
except FileNotFoundError:
|
|
201
|
+
error(f"文件不存在: {file_path}")
|
|
202
|
+
return None
|
|
203
|
+
except ValueError as e:
|
|
204
|
+
error(str(e))
|
|
205
|
+
return None
|
|
206
|
+
except ImportError as e:
|
|
207
|
+
error(str(e))
|
|
208
|
+
error("请运行: pip install PyPDF2")
|
|
209
|
+
return None
|
|
210
|
+
except Exception as e:
|
|
211
|
+
error(f"读取文档失败: {str(e)}")
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _merge_description(pdf_path: str, text_desc: str = "") -> str:
|
|
216
|
+
"""
|
|
217
|
+
将从 PDF 加载的内容和文本描述合并为一个完整描述
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
pdf_path: PDF 文件路径
|
|
221
|
+
text_desc: 用户输入的文本描述(可选)
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
合并后的描述文本
|
|
225
|
+
"""
|
|
226
|
+
summary, full_text = read_document(pdf_path)
|
|
227
|
+
if text_desc:
|
|
228
|
+
# 文本描述作为摘要,PDF 内容作为详细需求
|
|
229
|
+
description = (
|
|
230
|
+
f"{text_desc}\n\n"
|
|
231
|
+
f"---\n"
|
|
232
|
+
f"以下为详细需求文档(来自 {pdf_path}):\n\n"
|
|
233
|
+
f"{full_text}"
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
# 纯 PDF,用摘要作为描述
|
|
237
|
+
description = f"{summary}\n\n---\n以下来自文档 {pdf_path}:\n\n{full_text}"
|
|
238
|
+
return description
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _select_all_agents() -> List[str]:
|
|
242
|
+
"""返回全部 Agent 列表"""
|
|
243
|
+
return ["backend", "frontend", "test", "audit"]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _run_task(
|
|
247
|
+
coordinator: Coordinator,
|
|
248
|
+
output_mgr: OutputManager,
|
|
249
|
+
output_dir: Path,
|
|
250
|
+
description: str,
|
|
251
|
+
requirements: str,
|
|
252
|
+
agent_types: List[str],
|
|
253
|
+
is_first_run: bool,
|
|
254
|
+
) -> Dict:
|
|
255
|
+
"""
|
|
256
|
+
执行一次任务(可能只运行选中的 Agent)
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
coordinator: 协调器实例
|
|
260
|
+
output_mgr: 输出管理器实例
|
|
261
|
+
output_dir: 输出目录
|
|
262
|
+
description: 任务描述
|
|
263
|
+
requirements: 详细需求
|
|
264
|
+
agent_types: 选中的 Agent 类型列表
|
|
265
|
+
is_first_run: 是否为首次运行
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
执行结果字典
|
|
269
|
+
"""
|
|
270
|
+
# 重置 DAG
|
|
271
|
+
coordinator.reset_dag()
|
|
272
|
+
|
|
273
|
+
# 提交任务
|
|
274
|
+
if is_first_run:
|
|
275
|
+
# 首次运行:使用 submit_task(完整 DAG)
|
|
276
|
+
main_task_id = coordinator.submit_task(
|
|
277
|
+
description=description,
|
|
278
|
+
requirements=requirements,
|
|
279
|
+
is_iteration=False,
|
|
280
|
+
)
|
|
281
|
+
else:
|
|
282
|
+
# 迭代运行:使用 submit_iteration_task(只创建选中的 Agent)
|
|
283
|
+
main_task_id = coordinator.submit_iteration_task(
|
|
284
|
+
description=description,
|
|
285
|
+
requirements=requirements,
|
|
286
|
+
agent_types=agent_types,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
info(f" 主任务 ID: {main_task_id}")
|
|
290
|
+
info(f" 选中 Agent: {', '.join(agent_types)}")
|
|
291
|
+
debug("任务已提交到 DAG 调度器")
|
|
292
|
+
|
|
293
|
+
# 执行任务
|
|
294
|
+
info("-" * 60)
|
|
295
|
+
results = coordinator.run(main_task_id)
|
|
296
|
+
info("-" * 60)
|
|
297
|
+
debug(f"任务执行完成,结果: {len(results)} 个任务")
|
|
298
|
+
|
|
299
|
+
# 保存迭代记录
|
|
300
|
+
if output_dir is not None:
|
|
301
|
+
try:
|
|
302
|
+
output_mgr.save_iteration(
|
|
303
|
+
output_path=output_dir,
|
|
304
|
+
prompt=description + (" " + requirements if requirements else ""),
|
|
305
|
+
result={
|
|
306
|
+
"task_id": main_task_id,
|
|
307
|
+
"results": {
|
|
308
|
+
k: v.get("status")
|
|
309
|
+
for k, v in results.items()
|
|
310
|
+
if not k.startswith("_")
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
)
|
|
314
|
+
# 更新元数据
|
|
315
|
+
output_mgr.update_meta(
|
|
316
|
+
output_path=output_dir,
|
|
317
|
+
last_task_id=main_task_id,
|
|
318
|
+
status="completed",
|
|
319
|
+
)
|
|
320
|
+
except Exception as e:
|
|
321
|
+
error(f"保存迭代记录失败: {str(e)}")
|
|
322
|
+
|
|
323
|
+
return results
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _print_results(
|
|
327
|
+
results: Dict,
|
|
328
|
+
output_dir: Optional[Path],
|
|
329
|
+
coordinator: Coordinator,
|
|
330
|
+
main_task_id: str,
|
|
331
|
+
):
|
|
332
|
+
"""打印任务执行结果"""
|
|
333
|
+
info("")
|
|
334
|
+
info("执行结果:")
|
|
335
|
+
info("=" * 60)
|
|
336
|
+
for task_suffix, result in results.items():
|
|
337
|
+
if task_suffix.startswith("_"):
|
|
338
|
+
continue
|
|
339
|
+
status = result.get("status", "unknown")
|
|
340
|
+
info(f"\n[{task_suffix}]:")
|
|
341
|
+
info(f" 状态: {status}")
|
|
342
|
+
if result.get("error"):
|
|
343
|
+
error(f" 错误: {result['error']}")
|
|
344
|
+
if result.get("traceback"):
|
|
345
|
+
debug(f" 堆栈: {result['traceback']}")
|
|
346
|
+
files = result.get("files", [])
|
|
347
|
+
if files:
|
|
348
|
+
info(f" 生成文件: {len(files)} 个")
|
|
349
|
+
|
|
350
|
+
# 获取完整状态
|
|
351
|
+
status = coordinator.get_task_status(main_task_id)
|
|
352
|
+
info("")
|
|
353
|
+
info("任务状态汇总:")
|
|
354
|
+
info("=" * 60)
|
|
355
|
+
for agent, agent_info in status["tasks"].items():
|
|
356
|
+
info(f" {agent}: {agent_info['status']}")
|
|
357
|
+
|
|
358
|
+
info("")
|
|
359
|
+
if output_dir:
|
|
360
|
+
info(f"输出目录: {output_dir}")
|
|
361
|
+
# 提示 trace 文件位置
|
|
362
|
+
traces_dir = output_dir / "traces"
|
|
363
|
+
if traces_dir.exists():
|
|
364
|
+
trace_count = sum(1 for _ in traces_dir.rglob("*.md"))
|
|
365
|
+
if trace_count > 0:
|
|
366
|
+
info(f"Agent 执行追溯: {traces_dir} ({trace_count} 个 trace 文件)")
|
|
367
|
+
info("完成!")
|
|
368
|
+
info("=" * 60)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def interactive_mode(
|
|
372
|
+
coordinator: Coordinator,
|
|
373
|
+
output_mgr: OutputManager,
|
|
374
|
+
output_dir: Optional[Path],
|
|
375
|
+
selector: AgentSelector,
|
|
376
|
+
first_description: Optional[str] = None,
|
|
377
|
+
):
|
|
378
|
+
"""
|
|
379
|
+
交互模式主循环
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
coordinator: 协调器实例(持久化,跨迭代复用)
|
|
383
|
+
output_mgr: 输出管理器实例
|
|
384
|
+
output_dir: 输出目录(首次可能为 None)
|
|
385
|
+
selector: Agent 选择器实例
|
|
386
|
+
first_description: 首次运行的项目描述(命令行参数传入)
|
|
387
|
+
"""
|
|
388
|
+
global _interrupt_received
|
|
389
|
+
|
|
390
|
+
_print_welcome()
|
|
391
|
+
|
|
392
|
+
# 情况 1: 命令行已传入描述,先执行一次
|
|
393
|
+
if first_description:
|
|
394
|
+
info(f"[首次运行] 项目描述: {first_description}")
|
|
395
|
+
info("")
|
|
396
|
+
|
|
397
|
+
# 创建输出目录
|
|
398
|
+
output_dir = output_mgr.create_output_dir(first_description)
|
|
399
|
+
coordinator.output_dir = output_dir
|
|
400
|
+
for agent in coordinator.agents.values():
|
|
401
|
+
agent._set_output_dir(output_dir)
|
|
402
|
+
|
|
403
|
+
info("[1/3] 智能选择 Agent...")
|
|
404
|
+
# 首次运行,无已有代码,使用全部 Agent
|
|
405
|
+
agent_types = _select_all_agents()
|
|
406
|
+
info(f" 选中: {', '.join(agent_types)}")
|
|
407
|
+
info("")
|
|
408
|
+
|
|
409
|
+
info("[2/3] 提交并执行任务...")
|
|
410
|
+
results = _run_task(
|
|
411
|
+
coordinator=coordinator,
|
|
412
|
+
output_mgr=output_mgr,
|
|
413
|
+
output_dir=output_dir,
|
|
414
|
+
description=first_description,
|
|
415
|
+
requirements="",
|
|
416
|
+
agent_types=agent_types,
|
|
417
|
+
is_first_run=True,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
info("[3/3] 输出结果...")
|
|
421
|
+
# 获取 main_task_id(从 coordinator)
|
|
422
|
+
main_task_id = coordinator._main_task_id
|
|
423
|
+
_print_results(results, output_dir, coordinator, main_task_id)
|
|
424
|
+
|
|
425
|
+
# 自动生成 README.md
|
|
426
|
+
output_mgr.generate_readme(output_dir)
|
|
427
|
+
info(f" 已生成执行说明: {output_dir / 'README.md'}")
|
|
428
|
+
|
|
429
|
+
# 情况 2: 无参数启动,引导用户输入项目描述
|
|
430
|
+
if output_dir is None:
|
|
431
|
+
project_desc = _get_project_description()
|
|
432
|
+
if project_desc is None or _interrupt_received:
|
|
433
|
+
info("退出程序。")
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
# 创建输出目录
|
|
437
|
+
output_dir = output_mgr.create_output_dir(project_desc)
|
|
438
|
+
coordinator.output_dir = output_dir
|
|
439
|
+
for agent in coordinator.agents.values():
|
|
440
|
+
agent._set_output_dir(output_dir)
|
|
441
|
+
|
|
442
|
+
info("[1/3] 首次运行,使用全部 Agent...")
|
|
443
|
+
agent_types = _select_all_agents()
|
|
444
|
+
|
|
445
|
+
info("[2/3] 提交并执行任务...")
|
|
446
|
+
results = _run_task(
|
|
447
|
+
coordinator=coordinator,
|
|
448
|
+
output_mgr=output_mgr,
|
|
449
|
+
output_dir=output_dir,
|
|
450
|
+
description=project_desc,
|
|
451
|
+
requirements="",
|
|
452
|
+
agent_types=agent_types,
|
|
453
|
+
is_first_run=True,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
main_task_id = coordinator._main_task_id
|
|
457
|
+
info("[3/3] 输出结果...")
|
|
458
|
+
_print_results(results, output_dir, coordinator, main_task_id)
|
|
459
|
+
|
|
460
|
+
# 自动生成 README.md
|
|
461
|
+
output_mgr.generate_readme(output_dir)
|
|
462
|
+
info(f" 已生成执行说明: {output_dir / 'README.md'}")
|
|
463
|
+
|
|
464
|
+
# 进入主循环
|
|
465
|
+
info("")
|
|
466
|
+
info("进入交互模式。输入 help 查看可用命令。")
|
|
467
|
+
info("")
|
|
468
|
+
|
|
469
|
+
while not _interrupt_received:
|
|
470
|
+
user_input = _get_user_input("\n>>> ")
|
|
471
|
+
if user_input is None:
|
|
472
|
+
break
|
|
473
|
+
if not user_input:
|
|
474
|
+
continue
|
|
475
|
+
|
|
476
|
+
# 处理命令
|
|
477
|
+
cmd = user_input.strip().lower()
|
|
478
|
+
|
|
479
|
+
if _is_exit_command(cmd):
|
|
480
|
+
info("再见!")
|
|
481
|
+
break
|
|
482
|
+
|
|
483
|
+
if cmd in ("help", "h", "?"):
|
|
484
|
+
_print_help()
|
|
485
|
+
continue
|
|
486
|
+
|
|
487
|
+
if cmd in ("status", "s"):
|
|
488
|
+
_print_project_status(output_dir)
|
|
489
|
+
continue
|
|
490
|
+
|
|
491
|
+
if cmd in ("agents", "a"):
|
|
492
|
+
_print_agent_list(selector)
|
|
493
|
+
continue
|
|
494
|
+
|
|
495
|
+
if cmd.startswith("load "):
|
|
496
|
+
file_path = user_input.strip()[5:].strip()
|
|
497
|
+
doc_text = _load_document_description(file_path)
|
|
498
|
+
if doc_text is None:
|
|
499
|
+
continue
|
|
500
|
+
info(" 文档已加载,接下来输入的需求将附带此文档内容。")
|
|
501
|
+
info(" 请输入具体的开发需求(基于以上文档):")
|
|
502
|
+
user_input = _get_user_input("\n>>> ")
|
|
503
|
+
if user_input is None or _is_exit_command(user_input.strip().lower()):
|
|
504
|
+
break
|
|
505
|
+
if not user_input:
|
|
506
|
+
continue
|
|
507
|
+
# 将文档内容拼接到用户需求后面,不改变 user_input 用于 Agent 选择(取第一行)
|
|
508
|
+
user_input = f"{user_input}\n\n---\n以下来自文档 {file_path}:\n\n{doc_text}"
|
|
509
|
+
# 继续执行下面的任务流程(不 continue)
|
|
510
|
+
cmd = user_input.strip().lower()
|
|
511
|
+
|
|
512
|
+
# 用户需求:智能选择 Agent 并执行
|
|
513
|
+
info("[1/3] 智能选择 Agent...")
|
|
514
|
+
|
|
515
|
+
# 收集已有代码摘要
|
|
516
|
+
code_summary = ""
|
|
517
|
+
if output_dir is not None:
|
|
518
|
+
code_summary = AgentSelector.format_existing_code_summary(str(output_dir))
|
|
519
|
+
|
|
520
|
+
try:
|
|
521
|
+
selection = selector.select_agents(user_input, code_summary)
|
|
522
|
+
agent_types = selection["agents"]
|
|
523
|
+
reason = selection["reason"]
|
|
524
|
+
info(f" 选中: {', '.join(agent_types)}")
|
|
525
|
+
debug(f" 原因: {reason}")
|
|
526
|
+
except Exception as e:
|
|
527
|
+
warning(f"Agent 选择失败,使用全部 Agent: {e}")
|
|
528
|
+
agent_types = _select_all_agents()
|
|
529
|
+
|
|
530
|
+
info("")
|
|
531
|
+
info("[2/3] 提交并执行任务...")
|
|
532
|
+
|
|
533
|
+
results = _run_task(
|
|
534
|
+
coordinator=coordinator,
|
|
535
|
+
output_mgr=output_mgr,
|
|
536
|
+
output_dir=output_dir,
|
|
537
|
+
description=user_input,
|
|
538
|
+
requirements="",
|
|
539
|
+
agent_types=agent_types,
|
|
540
|
+
is_first_run=False,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
main_task_id = coordinator._main_task_id
|
|
544
|
+
info("[3/3] 输出结果...")
|
|
545
|
+
_print_results(results, output_dir, coordinator, main_task_id)
|
|
546
|
+
|
|
547
|
+
# 自动生成/更新 README.md
|
|
548
|
+
output_mgr.generate_readme(output_dir)
|
|
549
|
+
info(f" 已更新执行说明: {output_dir / 'README.md'}")
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def main():
|
|
553
|
+
"""主函数"""
|
|
554
|
+
info("=" * 60)
|
|
555
|
+
info("全栈代码助手智能体 (Full-Stack Coding Assistant Agent)")
|
|
556
|
+
info("=" * 60)
|
|
557
|
+
info("")
|
|
558
|
+
|
|
559
|
+
# 0. 验证配置
|
|
560
|
+
info("[0/5] 验证配置...")
|
|
561
|
+
if not validate_config(exit_on_error=False):
|
|
562
|
+
error("配置验证失败,请检查 .env 文件")
|
|
563
|
+
error("运行: python utils/config_validator.py 查看详细配置")
|
|
564
|
+
sys.exit(1)
|
|
565
|
+
|
|
566
|
+
# 解析命令行参数
|
|
567
|
+
parser = argparse.ArgumentParser(
|
|
568
|
+
description="全栈代码助手智能体(交互模式)",
|
|
569
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
570
|
+
epilog="""
|
|
571
|
+
示例:
|
|
572
|
+
# 无参数启动:进入交互模式,引导创建项目
|
|
573
|
+
python main.py
|
|
574
|
+
|
|
575
|
+
# 带描述启动:先执行一次,然后进入交互模式
|
|
576
|
+
python main.py "开发一个用户登录功能"
|
|
577
|
+
|
|
578
|
+
# 从 PDF 文件加载项目描述
|
|
579
|
+
python main.py --pdf spec.pdf "开发一个后台管理系统"
|
|
580
|
+
|
|
581
|
+
# 纯 PDF 文件作为描述(自动提取摘要)
|
|
582
|
+
python main.py --pdf spec.pdf
|
|
583
|
+
|
|
584
|
+
# 继续已有项目
|
|
585
|
+
python main.py --continue output/20260530_230000_xxx
|
|
586
|
+
|
|
587
|
+
交互模式命令:
|
|
588
|
+
help, h, ? - 显示帮助
|
|
589
|
+
status, s - 显示项目状态
|
|
590
|
+
agents, a - 显示可用 Agent
|
|
591
|
+
load <文件> - 加载 PDF/TXT/MD 文档作为需求
|
|
592
|
+
exit, quit, bye - 退出
|
|
593
|
+
""",
|
|
594
|
+
)
|
|
595
|
+
parser.add_argument(
|
|
596
|
+
"description", nargs="?", help="项目描述(可选,可与 --pdf 组合使用)"
|
|
597
|
+
)
|
|
598
|
+
parser.add_argument("-r", "--requirements", default="", help="详细需求")
|
|
599
|
+
parser.add_argument("-p", "--pdf", dest="pdf_path", help="PDF 需求文档路径")
|
|
600
|
+
parser.add_argument(
|
|
601
|
+
"-c",
|
|
602
|
+
"--continue",
|
|
603
|
+
dest="continue_dir",
|
|
604
|
+
help="已有项目的输出目录路径(继续开发)",
|
|
605
|
+
)
|
|
606
|
+
args = parser.parse_args()
|
|
607
|
+
|
|
608
|
+
# 注册 Ctrl+C 信号处理
|
|
609
|
+
signal.signal(signal.SIGINT, _signal_handler)
|
|
610
|
+
|
|
611
|
+
# 初始化协调器和输出管理器
|
|
612
|
+
info("[1/5] 初始化协调器...")
|
|
613
|
+
try:
|
|
614
|
+
coordinator = Coordinator()
|
|
615
|
+
debug("协调器初始化成功")
|
|
616
|
+
except Exception as e:
|
|
617
|
+
error(f"协调器初始化失败: {str(e)}")
|
|
618
|
+
sys.exit(1)
|
|
619
|
+
|
|
620
|
+
output_mgr = OutputManager()
|
|
621
|
+
selector = AgentSelector(coordinator.model_router)
|
|
622
|
+
|
|
623
|
+
output_dir = None
|
|
624
|
+
first_description = None
|
|
625
|
+
|
|
626
|
+
# 合并 PDF + 文本描述
|
|
627
|
+
_pdf_content = "" # PDF 原始文本(用于 --continue 模式注入到需求)
|
|
628
|
+
if args.pdf_path:
|
|
629
|
+
info(f"[2/5] 加载 PDF 文档: {args.pdf_path}")
|
|
630
|
+
try:
|
|
631
|
+
summary, _pdf_content = read_document(args.pdf_path)
|
|
632
|
+
if args.description:
|
|
633
|
+
first_description = _merge_description(args.pdf_path, args.description)
|
|
634
|
+
else:
|
|
635
|
+
first_description = _merge_description(args.pdf_path)
|
|
636
|
+
info(f" 文档已加载,摘要: {summary[:60]}...")
|
|
637
|
+
except Exception as e:
|
|
638
|
+
error(f"加载 PDF 失败: {str(e)}")
|
|
639
|
+
sys.exit(1)
|
|
640
|
+
|
|
641
|
+
# 处理 --continue 模式
|
|
642
|
+
if args.continue_dir is not None:
|
|
643
|
+
info(f"[2/5] 加载已有项目: {args.continue_dir}")
|
|
644
|
+
try:
|
|
645
|
+
existing_context = output_mgr.load_output_dir(args.continue_dir)
|
|
646
|
+
output_dir = Path(args.continue_dir).resolve()
|
|
647
|
+
coordinator.output_dir = output_dir
|
|
648
|
+
for agent in coordinator.agents.values():
|
|
649
|
+
agent._set_output_dir(output_dir)
|
|
650
|
+
|
|
651
|
+
info(f" 项目描述: {existing_context['meta'].get('task_description', '')}")
|
|
652
|
+
info(f" 迭代次数: {existing_context['meta'].get('iteration_count', 0)}")
|
|
653
|
+
except FileNotFoundError as e:
|
|
654
|
+
error(str(e))
|
|
655
|
+
sys.exit(1)
|
|
656
|
+
|
|
657
|
+
# --continue + --pdf: PDF 内容作为本次迭代的额外需求
|
|
658
|
+
if _pdf_content:
|
|
659
|
+
first_description = f"{args.description or '请基于已有代码继续开发'}\n\n---\n以下来自文档 {args.pdf_path}:\n\n{_pdf_content}"
|
|
660
|
+
else:
|
|
661
|
+
first_description = args.description or ""
|
|
662
|
+
|
|
663
|
+
elif args.description or _pdf_content:
|
|
664
|
+
# 有描述或 PDF:先执行一次,然后进入交互模式
|
|
665
|
+
if not first_description:
|
|
666
|
+
first_description = args.description
|
|
667
|
+
info(f"[2/5] 准备首次运行: {first_description[:100]}...")
|
|
668
|
+
else:
|
|
669
|
+
# 无参数:进入交互模式,由交互模式引导创建项目
|
|
670
|
+
info("[2/5] 无参数启动,将进入交互模式")
|
|
671
|
+
|
|
672
|
+
info("[3] 启动交互模式...")
|
|
673
|
+
info("=" * 60)
|
|
674
|
+
|
|
675
|
+
# 进入交互模式
|
|
676
|
+
interactive_mode(
|
|
677
|
+
coordinator=coordinator,
|
|
678
|
+
output_mgr=output_mgr,
|
|
679
|
+
output_dir=output_dir,
|
|
680
|
+
selector=selector,
|
|
681
|
+
first_description=first_description,
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
if __name__ == "__main__":
|
|
686
|
+
main()
|