delete_project: Conform confirm dialog + _sys_delete_project + system action bypass

This commit is contained in:
ymq 2026-08-09 20:00:01 +08:00
parent dedb2a60e1
commit a2106e4365

View File

@ -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})