fix: git 版本管理链路跑通 — role agent clone 时机 + git_clone 工具 + repo_dir 兼容

This commit is contained in:
p 2026-08-13 18:57:06 +08:00
parent 011dd9cb95
commit d89ec3c784
2 changed files with 57 additions and 14 deletions

View File

@ -167,6 +167,7 @@ AGENT_TOOLS = [
{"name":"write_file","description":"写入文件(自动创建父目录)","params":{"path":"相对路径","content":"文件内容"}},
{"name":"list_files","description":"列出目录内容","params":{"path":"相对路径(可选,默认工作空间根)"}},
{"name":"run_shell","description":"在工作空间中执行shell命令","params":{"command":"命令"}},
{"name":"git_clone","description":"克隆git仓库到工作空间repos/下","params":{"repo_url":"仓库URL","repo_name":"仓库目录名(可选,默认从URL推断)","branch":"分支(可选,默认main)"}},
{"name":"git_status","description":"查看git仓库状态","params":{"repo_dir":"仓库子目录(可选,默认repos下第一个)"}},
{"name":"git_commit_push","description":"git add + commit + push","params":{"message":"提交信息","repo_dir":"仓库子目录(可选)"}},
{"name":"ask_question","description":"向用户提问(缺少信息时使用)","params":{"question":"问题"}},
@ -473,6 +474,21 @@ def _parse_agent_action(raw):
return {"action": "deliver", "result": raw}
def _resolve_repo_target(workspace_dir, repo_dir):
"""解析仓库目录空→repos/下第一个git仓库'repos/xxx'→直接;'xxx'→repos/xxx。"""
repos_dir = os.path.join(workspace_dir, 'repos')
if repo_dir:
if repo_dir.startswith('repos/') or repo_dir.startswith('repos\\'):
return os.path.join(workspace_dir, repo_dir)
return os.path.join(repos_dir, repo_dir)
if os.path.isdir(repos_dir):
dirs = [d for d in os.listdir(repos_dir)
if os.path.isdir(os.path.join(repos_dir, d, '.git'))]
if dirs:
return os.path.join(repos_dir, dirs[0])
return workspace_dir
async def _exec_agent_tool(tool, params, workspace_dir):
p = params or {}
try:
@ -512,26 +528,21 @@ async def _exec_agent_tool(tool, params, workspace_dir):
if not cmd: return 'FAIL: 需要命令'
r = await _run_shell(cmd, workspace_dir, timeout=120)
return f"rc={r['rc']}\nSTDOUT:\n{r['stdout'][:2000]}\nSTDERR:\n{r['stderr'][:1000]}"
elif tool == 'git_clone':
url = p.get('repo_url', '')
if not url: return 'FAIL: 需要仓库URL'
name = p.get('repo_name', '') or url.rstrip('/').split('/')[-1].replace('.git', '')
target = os.path.join(workspace_dir, 'repos', name)
r = await _git_clone(url, target, p.get('branch', 'main'))
return f"rc={r['rc']} {r['message']}"
elif tool == 'git_status':
repo = p.get('repo_dir', '')
repos_dir = os.path.join(workspace_dir, 'repos')
if repo:
target = os.path.join(workspace_dir, repo)
else:
dirs = [d for d in os.listdir(repos_dir) if os.path.isdir(os.path.join(repos_dir, d, '.git'))] if os.path.isdir(repos_dir) else []
target = os.path.join(repos_dir, dirs[0]) if dirs else workspace_dir
target = _resolve_repo_target(workspace_dir, p.get('repo_dir', ''))
r = await _run_shell('git status --short', target, 10)
r2 = await _run_shell('git log --oneline -3', target, 10)
return f"Status:\n{r['stdout'][:1000] or '(clean)'}\nRecent:\n{r2['stdout'][:500]}"
elif tool == 'git_commit_push':
msg = p.get('message', '') or 'agent update'
repo = p.get('repo_dir', '')
repos_dir = os.path.join(workspace_dir, 'repos')
if repo:
target = os.path.join(workspace_dir, repo)
else:
dirs = [d for d in os.listdir(repos_dir) if os.path.isdir(os.path.join(repos_dir, d, '.git'))] if os.path.isdir(repos_dir) else []
target = os.path.join(repos_dir, dirs[0]) if dirs else workspace_dir
target = _resolve_repo_target(workspace_dir, p.get('repo_dir', ''))
r = await _git_commit_push(target, msg)
return f"rc={r['rc']} {r['message']}"
return f'未实现: {tool}'
@ -565,6 +576,13 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None):
repos_dir = os.path.join(workspace_dir, 'repos')
os.makedirs(repos_dir, exist_ok=True)
# 确保项目关联仓库已 clone幂等已存在则 pull——否则源码写不进 git 仓库
try:
clone_results = await _setup_repos(sor, workspace_dir, project_id)
logger.info(f"role_agent_run setup_repos: {clone_results}")
except Exception as e:
logger.warning(f"role_agent_run setup_repos failed: {e}")
role_specific = ROLE_SPECIFICS.get(role, ROLE_SPECIFICS.get('develop', ''))
qna_section = await _build_qna_section(sor, task_id)
tools_text = json.dumps(AGENT_TOOLS, ensure_ascii=False)

View File

@ -563,6 +563,7 @@ class AgentExecutor:
"answer_question": self._t_answer_question,
"add_repo": self._t_add_repo,
"list_repos": self._t_list_repos,
"clone_repo": self._t_clone_repo,
"run_command": self._t_run_command,
"check_progress": self._t_check_progress,
"add_bug": self._t_add_bug,
@ -919,6 +920,30 @@ class AgentExecutor:
lines.append(f"- {getattr(r, 'repo_name', '')}: {getattr(r, 'repo_url', '')}")
return "\n".join(lines)
async def _t_clone_repo(self, sor, p, pid):
if not pid:
return "请先切换到项目"
import os
from pipeline_service.agent_loop import (
_setup_repos, _get_workspace_dir, _git_clone)
workspace_dir = await _get_workspace_dir(sor, pid)
url = (p.get("repo_url", "") or "").strip()
if url:
name = url.rstrip("/").split("/")[-1].replace(".git", "")
target = os.path.join(workspace_dir, "repos", name)
r = await _git_clone(url, target, p.get("branch", "main"))
return f"rc={r['rc']} {r['message']}"
results = await _setup_repos(sor, workspace_dir, pid)
if not results:
return "无关联仓库可克隆"
return "\n".join(
f"- {x['repo']}: rc={x.get('rc', '?')} {x.get('message', '')}"
for x in results)
async def _t_run_command(self, sor, p, pid):
cmd = p.get("command", "")
if not cmd: