From eb910bc7b8e986986ab60c3b32750dfe198c2c78 Mon Sep 17 00:00:00 2001 From: ymq Date: Wed, 19 Aug 2026 16:15:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(task-chain):=20PM=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E6=96=AD=E9=93=BE=E4=B8=89=E5=A4=84=E4=BF=AE=E5=A4=8D=E2=80=94?= =?UTF-8?q?=E2=80=94=E6=9C=AA=E8=AF=86=E5=88=AB=E8=BE=93=E5=87=BA=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E9=BB=98=E8=AE=A4completed=E3=80=81review=5Fcomplete?= =?UTF-8?q?=E4=BB=85=E7=BB=88=E7=BB=93=E8=A7=92=E8=89=B2=E5=8F=AF=E7=94=A8?= =?UTF-8?q?=E3=80=81=E8=BF=AD=E4=BB=A3=E8=BE=B9=E7=95=8C=E5=8F=AA=E8=AE=A4?= =?UTF-8?q?=E6=98=BE=E5=BC=8Fcompleted/cancelled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:需求任务验收通过后设计任务未自动创建,项目卡在「无任务运行」的假终态。 1. pm_review_run 的 else 分支把未识别 PM 输出(deliver/纯文本/格式错误)静默当作 review_complete, 任务置 completed 且不创建下一角色任务,任务链断裂。改为追加纠错提示继续下一轮,轮次耗尽兜底 rejected。 2. 新增防御:非终结角色(requirement/design/develop/deploy_test/test)收到 review_complete 时修正为 approved。 3. 迭代边界检查从「非最新迭代即终止」改为「仅迭代状态 completed/cancelled 才终止」, 新建二期迭代不再阻断一期仍在跑的链。 4. PM prompt 澄清 review_complete 仅 deploy_prod 最后阶段使用。 --- pipeline_service/agent_loop.py | 41 +++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/pipeline_service/agent_loop.py b/pipeline_service/agent_loop.py index 4d3efa4..af6792a 100644 --- a/pipeline_service/agent_loop.py +++ b/pipeline_service/agent_loop.py @@ -502,6 +502,7 @@ PM_SYSTEM_PROMPT = """你是项目经理(PM)。你的职责是项目计划 ## 任务链不能断(关键规则) - 审核通过里程碑任务(requirement / design,产出含多个应用/模块,或评审意见指明后续要落地开发)后,必须先规划并用 create_tasks 创建后续开发任务,再 review_approve。 - 系统在 approve 后会自动补一个线性链的下一角色任务作为兜底;里程碑的并发拆分由你显式创建,不要等、不要断链。 +- **review_complete 只在「无下一阶段」时使用**——即最后一个角色 deploy_prod 验收通过后。requirement/design/develop/deploy_test/test 各阶段后面都还有下一角色,验收通过一律用 review_approve(系统自动创建下一角色任务),严禁用 review_complete 收尾,否则任务链断裂、下一阶段任务不会自动创建。 ## 项目信息 工作目录:__WORKSPACE__ @@ -537,7 +538,7 @@ __REPO_STATE__ 派发后续任务:{"action":"tool_call","tool":"create_tasks","params":{"tasks":[{"title":"契约定义","role":"design","key":"contract","description":"定义数据模型/API/目录规范"},{"title":"hr-system 开发","role":"develop","key":"hrsys","depends_on":["contract"],"description":"实现 hr-system 基础能力"},{"title":"hr-org 开发","role":"develop","key":"hrorg","depends_on":["contract"],"description":"实现 hr-org 基础能力"}]}} 批准:{"action":"review_approve","comment":"审核意见","next_task_title":"下阶段标题","next_task_description":"描述"} 驳回:{"action":"review_reject","comment":"原因","questions":"修改要求"} -完成:{"action":"review_complete","comment":"总结"} +完成:{"action":"review_complete","comment":"总结"}(仅 deploy_prod 最后阶段验收通过时使用) 回退:{"action":"review_rollback","rollback_role":"develop","comment":"回退原因"} 审核标准: @@ -1476,8 +1477,15 @@ async def pm_review_run(project_id, agent_id=None, model_name=None): msgs.append({"role": "assistant", "content": raw}) msgs.append({"role": "user", "content": f"工具 {tool} 结果:\n{result}"}) else: - decision = {'status': 'completed', 'comment': raw[:200]} - break + # 未识别的 PM 输出(deliver/纯文本/格式错误)绝不能静默当作 review_complete——那会把任务 + # 置 completed 且不创建下一角色任务,任务链就此断掉(正是「需求验收通过但设计任务不自动创建」的根因)。 + # 正确做法:追加纠错提示继续下一轮,让 PM 重新给出正确决策;轮次耗尽由下方「审核超时」兜底为 rejected。 + logger.warning(f"pm_review_run 未识别 PM 输出 action={act.get('action', '?')}, turn={turn}: {raw[:200]}") + msgs.append({"role": "assistant", "content": raw}) + msgs.append({"role": "user", "content": + "你的输出无法解析。请严格按格式输出单个 JSON:" + "review_approve / review_reject / review_complete / review_rollback 之一,不要输出其它内容。"}) + continue if not decision: decision = {'status': 'rejected', 'comment': '审核超时'} @@ -1485,6 +1493,16 @@ async def pm_review_run(project_id, agent_id=None, model_name=None): status = decision['status'] comment = decision.get('comment', '') + # 防御:非终结角色被 review_complete 收尾 → 修正为 approved(避免断链)。 + # 只有无下一角色(deploy_prod)的 review_complete 才是合法终结。否则 requirement/design/develop/ + # deploy_test/test 被误收尾,下一阶段任务不会自动创建,项目卡在「无任务运行」却非「已结束」的假终态。 + if status == 'completed': + _nr = await _get_next_role(task_role, project_id) + if _nr: + logger.warning(f"pm_review_run 非终结角色 {task_role} 收到 review_complete,修正为 approved 继续任务链 → {_nr}") + status = 'approved' + decision['status'] = 'approved' + if status == 'approved': from appPublic.uniqueID import getID pm_did = getID() @@ -1509,16 +1527,17 @@ async def pm_review_run(project_id, agent_id=None, model_name=None): await approve_task(task_id, project_id, who="agent.pm", agent_id=agent_id, comment=comment) next_role = await _get_next_role(task_role, project_id) if next_role: - # 迭代边界:任务若归属老迭代(非当前迭代=该项目最新创建的迭代), - # 链到此终止,不再自动触发下一角色任务——「新迭代创建后老迭代不再产生新任务」。 + # 迭代边界:仅当任务归属的迭代已显式结束(completed/cancelled)才终止链。 + # 若该迭代仍 planning/in_progress/active,链继续在该迭代内创建下一角色任务—— + # 新建其它迭代(如二期)不应阻断一期仍在跑的链(否则「需求验收通过但设计任务不自动创建」)。 task_iter = _task_iteration_name(task) if task_iter: - cur = await sor.sqlExe( - "SELECT iteration_name FROM sd_iterations WHERE project_id=${pid}$ ORDER BY created_at DESC LIMIT 1", - {"pid": project_id}) - cur_name = getattr(cur[0], 'iteration_name', '') if cur else '' - if cur_name and task_iter != cur_name: - logger.info(f"迭代边界:任务 {task_id} 归属老迭代「{task_iter}」(当前「{cur_name}」),链终止") + _it = await sor.sqlExe( + "SELECT status FROM sd_iterations WHERE project_id=${pid}$ AND iteration_name=${name}$", + {"pid": project_id, "name": task_iter}) + _it_status = getattr(_it[0], 'status', '') if _it else '' + if _it_status in ('completed', 'cancelled'): + logger.info(f"迭代边界:任务 {task_id} 归属迭代「{task_iter}」已 {_it_status},链终止") return {"status": "completed", "task_id": task_id, "comment": comment or "迭代已结束,链终止"} next_tid, next_title = await _create_next_task(sor, project_id, task, next_role, comment) return {"status": "approved", "task_id": task_id, "next_task_id": next_tid, "next_role": next_role, "comment": comment}