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
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def _cancel_active_tasks(sor, project_id, iteration_name):
|
async def _active_task_ids(sor, project_id, iteration_name):
|
||||||
"""作废某迭代内所有活跃任务(非终态 → cancelled + 清 claimed_by)。
|
"""某迭代内所有活跃任务的 ID 列表(非终态:submitted/running/review/qc_review/waiting)。
|
||||||
|
|
||||||
任务经 params.iteration_id(存迭代名)归属迭代;只作废该迭代的活跃任务,
|
任务经 params.iteration_id(存迭代名)归属迭代;已终态(completed/approved/cancelled/failed)不计。
|
||||||
已终态(completed/approved/cancelled/failed)的不动。返回作废数量。
|
|
||||||
"""
|
"""
|
||||||
if not iteration_name:
|
if not iteration_name:
|
||||||
return 0
|
return []
|
||||||
recs = await sor.sqlExe(
|
recs = await sor.sqlExe(
|
||||||
"SELECT id FROM pipeline_tasks WHERE tenant_id=${pid}$ "
|
"SELECT id FROM pipeline_tasks WHERE tenant_id=${pid}$ "
|
||||||
"AND state NOT IN ('completed','approved','cancelled','failed') "
|
"AND state NOT IN ('completed','approved','cancelled','failed') "
|
||||||
"AND JSON_UNQUOTE(JSON_EXTRACT(params,'$.iteration_id'))=${nm}$",
|
"AND JSON_UNQUOTE(JSON_EXTRACT(params,'$.iteration_id'))=${nm}$",
|
||||||
{"pid": project_id, "nm": iteration_name})
|
{"pid": project_id, "nm": iteration_name})
|
||||||
await sor.sqlExe("COMMIT", {})
|
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:
|
if ids:
|
||||||
in_clause = ",".join(["'" + x.replace("'", "''") + "'" for x in ids])
|
in_clause = ",".join(["'" + x.replace("'", "''") + "'" for x in ids])
|
||||||
await sor.sqlExe(
|
await sor.sqlExe(
|
||||||
@ -203,10 +207,11 @@ async def _cancel_active_tasks(sor, project_id, iteration_name):
|
|||||||
return len(ids)
|
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 它。
|
2. 找下一个 planning 迭代(seq_no 最小且 > 已结束迭代的最大 seq_no),start 它。
|
||||||
返回 (ok, message)。
|
返回 (ok, message)。
|
||||||
"""
|
"""
|
||||||
@ -214,8 +219,15 @@ async def start_next_iteration(project_id, who=None, agent_id=None):
|
|||||||
return False, "缺少 project_id"
|
return False, "缺少 project_id"
|
||||||
db, dbname = _get_db()
|
db, dbname = _get_db()
|
||||||
async with db.sqlorContext(dbname) as sor:
|
async with db.sqlorContext(dbname) as sor:
|
||||||
# 1. 强制完成当前迭代 + 作废其活跃任务
|
|
||||||
cur = await get_current_iteration(sor, project_id)
|
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:
|
if cur:
|
||||||
cur_id = cur.get('id', '')
|
cur_id = cur.get('id', '')
|
||||||
cur_name = cur.get('iteration_name', '') or ''
|
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="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="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="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="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"),
|
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", "")
|
pid = ctx.get("project_id", "")
|
||||||
if not pid:
|
if not pid:
|
||||||
return "请先切换到项目"
|
return "请先切换到项目"
|
||||||
|
confirm = (str(p.get("confirm", "") or "")).strip().lower() == "true"
|
||||||
from .iteration_capability import start_next_iteration
|
from .iteration_capability import start_next_iteration
|
||||||
ok, msg = await start_next_iteration(pid, who="agent.main_agent",
|
ok, msg = await start_next_iteration(pid, who="agent.main_agent",
|
||||||
agent_id=ctx.get("user_id", "") or "")
|
agent_id=ctx.get("user_id", "") or "",
|
||||||
return f"OK: {msg}" if ok else f"ERROR: {msg}"
|
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):
|
async def _h_create_iteration(sor, p, ctx):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user