feat(iteration): start_next_iteration 二次确认——当前迭代有未完成任务时须 confirm=true 才作废
当前迭代存在活跃任务时,start_next_iteration 不带 confirm 返回 CONFIRM 提示 ("还有 N 个未完成任务,确认作废并启动下一迭代?"),用户确认后 agent 传 confirm=true 才执行。 无活跃任务时无需确认直接推进。抽 _active_task_ids 供计数与作废复用。
This commit is contained in:
parent
d77910ea2e
commit
ccc7baafbd
@ -180,21 +180,25 @@ async def get_current_iteration(sor, project_id):
|
||||
return None
|
||||
|
||||
|
||||
async def _cancel_active_tasks(sor, project_id, iteration_name):
|
||||
"""作废某迭代内所有活跃任务(非终态 → cancelled + 清 claimed_by)。
|
||||
async def _active_task_ids(sor, project_id, iteration_name):
|
||||
"""某迭代内所有活跃任务的 ID 列表(非终态:submitted/running/review/qc_review/waiting)。
|
||||
|
||||
任务经 params.iteration_id(存迭代名)归属迭代;只作废该迭代的活跃任务,
|
||||
已终态(completed/approved/cancelled/failed)的不动。返回作废数量。
|
||||
任务经 params.iteration_id(存迭代名)归属迭代;已终态(completed/approved/cancelled/failed)不计。
|
||||
"""
|
||||
if not iteration_name:
|
||||
return 0
|
||||
return []
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id FROM pipeline_tasks WHERE tenant_id=${pid}$ "
|
||||
"AND state NOT IN ('completed','approved','cancelled','failed') "
|
||||
"AND JSON_UNQUOTE(JSON_EXTRACT(params,'$.iteration_id'))=${nm}$",
|
||||
{"pid": project_id, "nm": iteration_name})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
ids = [getattr(r, 'id', '') for r in (recs or []) if getattr(r, 'id', '')]
|
||||
return [getattr(r, 'id', '') for r in (recs or []) if getattr(r, 'id', '')]
|
||||
|
||||
|
||||
async def _cancel_active_tasks(sor, project_id, iteration_name):
|
||||
"""作废某迭代内所有活跃任务(非终态 → cancelled + 清 claimed_by)。返回作废数量。"""
|
||||
ids = await _active_task_ids(sor, project_id, iteration_name)
|
||||
if ids:
|
||||
in_clause = ",".join(["'" + x.replace("'", "''") + "'" for x in ids])
|
||||
await sor.sqlExe(
|
||||
@ -203,10 +207,11 @@ async def _cancel_active_tasks(sor, project_id, iteration_name):
|
||||
return len(ids)
|
||||
|
||||
|
||||
async def start_next_iteration(project_id, who=None, agent_id=None):
|
||||
async def start_next_iteration(project_id, who=None, agent_id=None, confirm=False):
|
||||
"""启动下一个迭代(用户指令推进):
|
||||
|
||||
1. 若存在当前迭代(in_progress),先强制完成它 + 作废其活跃任务。
|
||||
1. 若存在当前迭代(in_progress)且有活跃任务,须 confirm=True 才强制完成它 + 作废活跃任务
|
||||
(否则返回 CONFIRM 提示,等用户确认)。
|
||||
2. 找下一个 planning 迭代(seq_no 最小且 > 已结束迭代的最大 seq_no),start 它。
|
||||
返回 (ok, message)。
|
||||
"""
|
||||
@ -214,8 +219,15 @@ async def start_next_iteration(project_id, who=None, agent_id=None):
|
||||
return False, "缺少 project_id"
|
||||
db, dbname = _get_db()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
# 1. 强制完成当前迭代 + 作废其活跃任务
|
||||
cur = await get_current_iteration(sor, project_id)
|
||||
# 二次确认:当前迭代有活跃任务时,须 confirm=True 才作废(否则返回确认提示)
|
||||
if cur and not confirm:
|
||||
cur_name = cur.get('iteration_name', '') or ''
|
||||
active_ids = await _active_task_ids(sor, project_id, cur_name)
|
||||
if active_ids:
|
||||
return False, (f"CONFIRM: 当前迭代「{cur_name}」还有 {len(active_ids)} 个未完成任务,"
|
||||
f"确认作废这些任务并启动下一迭代?请回复「确认」,agent 将传 confirm=true 执行")
|
||||
# 1. 强制完成当前迭代 + 作废其活跃任务
|
||||
if cur:
|
||||
cur_id = cur.get('id', '')
|
||||
cur_name = cur.get('iteration_name', '') or ''
|
||||
|
||||
@ -174,7 +174,7 @@ SDL_TOOLS = [
|
||||
ToolDefinition(name="start_iteration", description="开始迭代(planning→in_progress)", parameters={"iteration_id": "迭代ID"}, category="iteration"),
|
||||
ToolDefinition(name="complete_iteration", description="完成迭代(in_progress→completed)", parameters={"iteration_id": "迭代ID"}, category="iteration"),
|
||||
ToolDefinition(name="cancel_iteration", description="取消迭代(planning/in_progress→cancelled)", parameters={"iteration_id": "迭代ID", "comment": "取消原因(可选)"}, category="iteration"),
|
||||
ToolDefinition(name="start_next_iteration", description="启动下一个迭代(强制完成当前迭代+作废其活跃任务,再启动编号最小的下一个planning迭代)", parameters={}, category="iteration"),
|
||||
ToolDefinition(name="start_next_iteration", description="启动下一个迭代(若当前迭代有未完成任务需二次确认confirm=true,否则强制完成当前+作废其活跃任务+启动下一planning迭代)", parameters={"confirm": "二次确认(当前迭代有未完成任务时必须为true)"}, category="iteration"),
|
||||
# ── 交付件评审 ──
|
||||
ToolDefinition(name="submit_deliverable", description="提交交付件(pending)", parameters={"title": "交付件标题", "deliverable_type": "类型(code/doc/config等)", "content": "交付件内容", "task_id": "关联任务ID(可选)"}, category="deliverable"),
|
||||
ToolDefinition(name="approve_deliverable", description="评审通过交付件(pending→approved)", parameters={"deliverable_id": "交付件ID"}, category="deliverable"),
|
||||
@ -1081,10 +1081,16 @@ async def _h_start_next_iteration(sor, p, ctx):
|
||||
pid = ctx.get("project_id", "")
|
||||
if not pid:
|
||||
return "请先切换到项目"
|
||||
confirm = (str(p.get("confirm", "") or "")).strip().lower() == "true"
|
||||
from .iteration_capability import start_next_iteration
|
||||
ok, msg = await start_next_iteration(pid, who="agent.main_agent",
|
||||
agent_id=ctx.get("user_id", "") or "")
|
||||
return f"OK: {msg}" if ok else f"ERROR: {msg}"
|
||||
agent_id=ctx.get("user_id", "") or "",
|
||||
confirm=confirm)
|
||||
if ok:
|
||||
return f"OK: {msg}"
|
||||
if msg.startswith("CONFIRM:"):
|
||||
return msg # 确认提示原样返回,让 agent 引导用户确认
|
||||
return f"ERROR: {msg}"
|
||||
|
||||
|
||||
async def _h_create_iteration(sor, p, ctx):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user