diff --git a/pipeline_service/init.py b/pipeline_service/init.py index 5457ef9..03d56d0 100644 --- a/pipeline_service/init.py +++ b/pipeline_service/init.py @@ -640,10 +640,14 @@ def load_pipeline_service(): return [] # 候选任务(含 depends_on,LIMIT 放大以容纳「依赖未满足排在前面」的僵尸任务) + # 排除 paused 项目:paused = 完全暂停推进(不派发/不审查/不审核),三个 poller 语义统一, + # 否则会出现「任务照常派发执行、却卡在 PM 审核」的半吊子状态(2026-08 实测)。 recs = await sor.sqlExe( - "SELECT id, tenant_id, role, depends_on FROM pipeline_tasks " - "WHERE state='submitted' AND pipeline_id='role_task' " - "AND claimed_by IS NULL ORDER BY created_at ASC LIMIT 200", + "SELECT t.id, t.tenant_id, t.role, t.depends_on FROM pipeline_tasks t " + "WHERE t.state='submitted' AND t.pipeline_id='role_task' " + "AND t.claimed_by IS NULL " + "AND NOT EXISTS (SELECT 1 FROM sd_projects p WHERE p.id=t.tenant_id AND p.status='paused') " + "ORDER BY t.created_at ASC LIMIT 200", {}) # 依赖门控提前到候选筛选:批量查 depends_on 里所有依赖任务的状态, @@ -814,9 +818,10 @@ def load_pipeline_service(): "WHERE state='qc_review' AND claimed_by IS NOT NULL " "AND updated_at < (NOW() - INTERVAL 20 MINUTE)", {}) recs = await sor.sqlExe( - "SELECT id, tenant_id, role FROM pipeline_tasks " - "WHERE state='qc_review' AND claimed_by IS NULL " - "ORDER BY created_at ASC LIMIT 5", + "SELECT t.id, t.tenant_id, t.role FROM pipeline_tasks t " + "WHERE t.state='qc_review' AND t.claimed_by IS NULL " + "AND NOT EXISTS (SELECT 1 FROM sd_projects p WHERE p.id=t.tenant_id AND p.status='paused') " + "ORDER BY t.created_at ASC LIMIT 5", {}) for rec in (recs or []): tid = getattr(rec, 'id', '') diff --git a/pipeline_service/iteration_capability.py b/pipeline_service/iteration_capability.py index 39d4e2a..a922c34 100644 --- a/pipeline_service/iteration_capability.py +++ b/pipeline_service/iteration_capability.py @@ -264,6 +264,13 @@ async def start_next_iteration(project_id, who=None, agent_id=None, confirm=Fals from_state=S_PLANNING, to_state=S_IN_PROGRESS, who=_normalize_role(who), agent_id=agent_id, sor=sor) logger.info("start_next_iteration: 启动 %s (seq_no=%s)", nxt_name, nxt_seq) + # 切迭代后自动恢复项目推进:旧迭代的 paused(任务达上限触发 failed_poller 自动 pause)诱因 + # 已随活跃任务作废而消除,新迭代是新开始,项目应恢复 active——否则 PM poller 会因 paused + # 跳过新迭代的 review 任务,导致新迭代卡在 PM 审核无人认领(2026-08 实测:切迭代后 design + # 任务卡 review 27 分钟,根因就是旧迭代遗留的 paused 未清除)。 + from .project_capability import resume_project + _rok, _rmsg = await resume_project(project_id, who=_normalize_role(who), agent_id=agent_id) + logger.info("start_next_iteration: resume_project ok=%s (%s)", _rok, _rmsg) return True, f"已启动迭代「{nxt_name}」(seq_no={nxt_seq})"