From a2106e4365d4d44a26b0dad707f64408f5a27b7d Mon Sep 17 00:00:00 2001 From: ymq Date: Sun, 9 Aug 2026 20:00:01 +0800 Subject: [PATCH] delete_project: Conform confirm dialog + _sys_delete_project + system action bypass --- wwwroot/api/cockpit_chat.dspy | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/wwwroot/api/cockpit_chat.dspy b/wwwroot/api/cockpit_chat.dspy index 5c64096..6c1db8b 100644 --- a/wwwroot/api/cockpit_chat.dspy +++ b/wwwroot/api/cockpit_chat.dspy @@ -161,9 +161,14 @@ async def _exec_tool(sor, tool, params, ctx, uid, org_id): return f'OK: 已创建并切换到「{name}」' elif tool == 'delete_project': name = (p.get('project','') or '').strip() - confirm = (p.get('confirm','') or '').strip() if not name: return 'FAIL: 需要项目名称' - if confirm != name: return f'FAIL: 确认不匹配(需输入"{name}"确认)' + 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) + elif tool == '_sys_delete_project': + name = (p.get('project','') or '').strip() + if not name: return 'FAIL: 缺少项目名称' proj = await _find_project(sor, name) if not proj: return f'FAIL: 项目「{name}」不存在' pid = proj.id @@ -360,6 +365,19 @@ 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', '') + 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, p, 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) @@ -398,8 +416,12 @@ if action == 'send_message': debug(f"YIELD progress: {d[:60]}") yield d result = await _exec_tool(sor, tool, act.get('params',{}), ctx, uid, org_id) - ok = result.startswith("OK:") - d = json.dumps(_w_card(label, _w_text(result), "success" if ok else "error"), ensure_ascii=False)+'\n' + # If result is a raw widget descriptor, yield it directly + if result.startswith('{"widgettype":'): + d = result + '\n' + else: + ok = result.startswith("OK:") + d = json.dumps(_w_card(label, _w_text(result), "success" if ok else "error"), ensure_ascii=False)+'\n' debug(f"YIELD result: {d[:60]}") yield d msgs.append({"role":"assistant","content":raw})