fix: conversational delete confirm + fix pipeline_agent_questions table name + cleanup system action bypass
This commit is contained in:
parent
d076a0a9f2
commit
a111c056e5
@ -19,7 +19,7 @@ AGENT_PROMPT = """你是开发产线驾驶舱 Agent。用工具获取数据,
|
||||
工作流:
|
||||
- 切换项目:先 list_projects 看有哪些项目,再 switch_project
|
||||
- 创建项目:不存在则 create_project
|
||||
- 删除项目:delete_project(需输入项目名二次确认)
|
||||
- 删除项目:delete_project → 看到确认提示后,用户回复「确认删除 xxx」→ 再调 _sys_delete_project
|
||||
- 查看进度:list_tasks / task_detail / list_deliverables
|
||||
- 提交任务:先确保在正确项目,再 create_task
|
||||
- 启动Agent:任务提交后调 start_agents 让角色Agent执行
|
||||
@ -38,7 +38,7 @@ TOOLS = [
|
||||
{"name":"list_projects","description":"列出所有可用项目","params":{}},
|
||||
{"name":"switch_project","description":"切换到指定项目(需先list_projects确认项目名)","params":{"project":"项目名称"}},
|
||||
{"name":"create_project","description":"创建新项目","params":{"name":"项目名称","description":"项目描述(可选)"}},
|
||||
{"name":"delete_project","description":"删除项目及其所有关联数据(需二次确认)","params":{"project":"项目名称","confirm":"输入项目名称确认删除"}},
|
||||
{"name":"delete_project","description":"删除项目(返回确认提示,用户确认后再调用_sys_delete_project执行)","params":{"project":"项目名称"}},
|
||||
{"name":"list_tasks","description":"列出当前项目任务","params":{"state":"状态(可选)"}},
|
||||
{"name":"task_detail","description":"查看任务详情(含交付件和问答)","params":{"task_id":"任务ID"}},
|
||||
{"name":"list_deliverables","description":"列出当前项目交付件","params":{"task_id":"任务ID(可选)"}},
|
||||
@ -164,8 +164,7 @@ async def _exec_tool(sor, tool, params, ctx, uid, org_id):
|
||||
if not name: return 'FAIL: 需要项目名称'
|
||||
proj = await _find_project(sor, name)
|
||||
if not proj: return f'FAIL: 项目「{name}」不存在'
|
||||
# Yield Conform widget — actual deletion happens via _sys_delete_project
|
||||
return json.dumps({"widgettype":"Conform","options":{"message":f"确定要删除项目「{name}」吗?此操作不可撤销,将清除所有关联数据。","conform":{"label":"确认删除","name":"conform"},"discard":{"label":"取消","name":"discard"},"_action":"_sys_delete_project","_payload":{"project":name}}},ensure_ascii=False)
|
||||
return f'CONFIRM: 确定要删除项目「{name}」吗?此操作不可撤销,将清除所有关联数据。请回复「确认删除 {name}」继续,回复其他内容取消。'
|
||||
elif tool == '_sys_delete_project':
|
||||
name = (p.get('project','') or '').strip()
|
||||
if not name: return 'FAIL: 缺少项目名称'
|
||||
@ -176,7 +175,7 @@ async def _exec_tool(sor, tool, params, ctx, uid, org_id):
|
||||
tables = [
|
||||
('pipeline_deliverables', 'project_id'),
|
||||
('pipeline_tasks', 'tenant_id'),
|
||||
('pipeline_questions', 'project_id'),
|
||||
('pipeline_agent_questions', 'project_id'),
|
||||
('pipeline_project_agents', 'project_id'),
|
||||
('sd_project_repos', 'project_id'),
|
||||
('sd_iterations', 'project_id'),
|
||||
@ -290,7 +289,7 @@ async def _exec_tool(sor, tool, params, ctx, uid, org_id):
|
||||
t = ts[0]
|
||||
ds = await sor.sqlExe("SELECT deliverable_type,title,review_status,quality_score FROM pipeline_deliverables WHERE task_id=${t}$",{"t":tid})
|
||||
dl = '\n'.join([f" [{getattr(d,'review_status','')}] {getattr(d,'deliverable_type','')}: {getattr(d,'title','')[:60]}" for d in (ds or [])]) or ' 无'
|
||||
qs = await sor.sqlExe("SELECT question,answer,status FROM pipeline_questions WHERE task_id=${t}$ ORDER BY created_at DESC LIMIT 5",{"t":tid})
|
||||
qs = await sor.sqlExe("SELECT question,answer,status FROM pipeline_agent_questions WHERE task_id=${t}$ ORDER BY created_at DESC LIMIT 5",{"t":tid})
|
||||
ql = '\n'.join([f" [{getattr(q,'status','')}] Q:{getattr(q,'question','')[:60]}\n A:{getattr(q,'answer','') or '待回答'}" for q in (qs or [])]) or ' 无'
|
||||
return f"任务: {getattr(t,'title','')}\n状态: {getattr(t,'state','')}\n角色: {getattr(t,'role','')}\n交付件:\n{dl}\n问答:\n{ql}"
|
||||
elif tool == 'list_deliverables':
|
||||
@ -311,7 +310,7 @@ async def _exec_tool(sor, tool, params, ctx, uid, org_id):
|
||||
return f"类型: {getattr(d,'deliverable_type','')}\n标题: {getattr(d,'title','')}\n状态: {getattr(d,'review_status','')} | 评分: {getattr(d,'quality_score','')}\n\n{getattr(d,'content','')[:3000]}"
|
||||
elif tool == 'list_questions':
|
||||
if not ctx['pid']: return 'FAIL: 请先切换到项目'
|
||||
qs = await sor.sqlExe("SELECT id,task_id,question,answer,status,created_at FROM pipeline_questions WHERE project_id=${p}$ AND status='pending' ORDER BY created_at DESC LIMIT 10",{"p":ctx['pid']})
|
||||
qs = await sor.sqlExe("SELECT id,task_id,question,answer,status,created_at FROM pipeline_agent_questions WHERE project_id=${p}$ AND status='pending' ORDER BY created_at DESC LIMIT 10",{"p":ctx['pid']})
|
||||
if not qs: return '无待回答问题'
|
||||
return '\n'.join([f"[{getattr(q,'id','')[:8]}] 任务:{getattr(q,'task_id','')[:8]} Q:{getattr(q,'question','')[:80]}" for q in qs])
|
||||
elif tool == 'answer_question':
|
||||
@ -319,9 +318,9 @@ async def _exec_tool(sor, tool, params, ctx, uid, org_id):
|
||||
ans = p.get('answer','')
|
||||
if not qid: return 'FAIL: 需要问题ID'
|
||||
if not ans: return 'FAIL: 需要回答内容'
|
||||
qs = await sor.sqlExe("SELECT id,status FROM pipeline_questions WHERE id=${q}$",{"q":qid})
|
||||
qs = await sor.sqlExe("SELECT id,status FROM pipeline_agent_questions WHERE id=${q}$",{"q":qid})
|
||||
if not qs: return 'FAIL: 问题不存在'
|
||||
await sor.sqlExe("UPDATE pipeline_questions SET answer=${a}$, status='answered', answered_at=NOW() WHERE id=${q}$",{"a":ans,"q":qid})
|
||||
await sor.sqlExe("UPDATE pipeline_agent_questions SET answer=${a}$, status='answered', answered_at=NOW() WHERE id=${q}$",{"a":ans,"q":qid})
|
||||
return f'OK: 已回答'
|
||||
elif tool == 'start_agents':
|
||||
if not ctx['pid']: return 'FAIL: 请先切换到项目'
|
||||
@ -365,20 +364,6 @@ if action == 'send_message':
|
||||
user_model_id = inner.get('model_id') or inner.get('llmid') or user_model_id
|
||||
if not message_text: return json.dumps({"error":"message_text is required"},ensure_ascii=False)
|
||||
|
||||
# ── System action: bypass LLM, execute directly ──
|
||||
sys_action = p.get('_system_action', '') or (p.get('params', {}) or {}).get('_system_action', '')
|
||||
sys_params = p if p.get('_system_action') else (p.get('params', {}) or {})
|
||||
if sys_action:
|
||||
async def sys_stream():
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
uid = await get_user()
|
||||
org_id = await get_userorgid() or '0'
|
||||
ctx = await _load_ctx(sor, uid)
|
||||
result = await _exec_tool(sor, sys_action, sys_params, ctx, uid, org_id)
|
||||
d = json.dumps(_w_text(result), ensure_ascii=False)+'\n'
|
||||
yield d
|
||||
return await stream_response(request, sys_stream, 'text/plain; charset=utf-8')
|
||||
|
||||
blocked, reason = _security_scan(message_text)
|
||||
if blocked: return json.dumps({"success":True,"agent_reply":f"⚠️ {reason}"},ensure_ascii=False)
|
||||
|
||||
@ -435,11 +420,10 @@ if action == 'send_message':
|
||||
agent_reply = raw; break
|
||||
|
||||
if not agent_reply: agent_reply = "处理超时"
|
||||
if agent_reply != '__widget_handled__':
|
||||
d = json.dumps(_w_text(agent_reply), ensure_ascii=False)+'\n'
|
||||
debug(f"YIELD final: {d[:60]}")
|
||||
yield d
|
||||
await sor.C('pipeline_conversations',{'id':getID(),'role':'agent','content':agent_reply,'msg_type':'text','org_id':org_id,'created_by':'system'})
|
||||
d = json.dumps(_w_text(agent_reply), ensure_ascii=False)+'\n'
|
||||
debug(f"YIELD final: {d[:60]}")
|
||||
yield d
|
||||
await sor.C('pipeline_conversations',{'id':getID(),'role':'agent','content':agent_reply,'msg_type':'text','org_id':org_id,'created_by':'system'})
|
||||
|
||||
return await stream_response(request, agent_stream, 'text/plain; charset=utf-8')
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user