diff --git a/wwwroot/api/cockpit_chat.dspy b/wwwroot/api/cockpit_chat.dspy index a4e3020..e191281 100644 --- a/wwwroot/api/cockpit_chat.dspy +++ b/wwwroot/api/cockpit_chat.dspy @@ -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')