管理员智能体/技能管理 + 跨标签登录同步 + 上游响应异常重试
- 「设置和更多」新增「智能体管理」「技能管理」入口,与定时任务管理同级; 独立的 AdminAgentsPage/AdminSkillsPage 复用 AgentCard/SkillRow, 管理员可查看并编辑/删除所有用户的智能体与技能。 - 后端 /api/agents、/api/skills 增加 ?admin=true 越权列表;agents 端 放宽 _get_editable_or_raise 让管理员可编辑/删除任意智能体;新增 list_all/update_any/delete_any(agents)与 list_all(skills)。 - 新增 AuthSyncWatcher:监听 localStorage["deerflow.auth"] 的 storage 事件,user_id 变化时自动 reload,修复多标签切换登录后老页面点击 「新对话」偶发 404 的问题。 - LLMErrorHandlingMiddleware 将 bad_response_status_code / JSON decode 等上游网关乱码识别为可重试的 upstream 类故障。 - 侧边栏「任务管理」改名为「定时任务」。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1642c3e694
commit
eda0ccffdb
23 files changed
+660
-115
No files matched your search
@@ -695,11 +695,25 @@ async def list_skills(
|
||||
tag_store: TagStore = Depends(get_tag_store),
|
||||
search: str | None = Query(default=None, description="Case-insensitive fuzzy match on skill name"),
|
||||
tag_ids: list[str] | None = Query(default=None, description="Keep skills carrying any of these tags (OR)"),
|
||||
admin: bool = Query(
|
||||
default=False,
|
||||
description=(
|
||||
"Admin-only override. When ``true`` and the caller is a system admin, "
|
||||
"every custom skill is returned (including unpublished skills owned by "
|
||||
"other users) regardless of the usual ownership filter."
|
||||
),
|
||||
),
|
||||
) -> SkillsListResponse:
|
||||
try:
|
||||
user_id = _current_user_id(request)
|
||||
if admin and not _is_admin_user(request):
|
||||
raise HTTPException(status_code=403, detail="Only system admins can list all skills")
|
||||
all_skills = get_or_new_skill_storage(app_config=config).load_skills(enabled_only=False)
|
||||
ownership = await _build_ownership_index(skill_store, user_id)
|
||||
if admin:
|
||||
ownership_records = await skill_store.list_all()
|
||||
ownership = {record["name"]: record for record in ownership_records}
|
||||
else:
|
||||
ownership = await _build_ownership_index(skill_store, user_id)
|
||||
display_adapters = await _list_skill_display_adapters([skill.name for skill in all_skills])
|
||||
responses: list[SkillResponse] = []
|
||||
for skill in all_skills:
|
||||
@@ -720,7 +734,11 @@ async def list_skills(
|
||||
continue
|
||||
record = ownership.get(skill.name)
|
||||
if record is None:
|
||||
continue
|
||||
if not admin:
|
||||
continue
|
||||
# Admin sees every custom skill on disk even without an
|
||||
# ownership row (legacy / orphaned), so they can clean it up.
|
||||
record = None
|
||||
responses.append(
|
||||
_skill_to_response(
|
||||
skill,
|
||||
@@ -731,6 +749,8 @@ async def list_skills(
|
||||
)
|
||||
responses = await _attach_and_filter_tags(tag_store, responses, search, tag_ids)
|
||||
return SkillsListResponse(skills=responses)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load skills: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to load skills: {str(e)}")
|
||||
|
||||
Reference in new issue
Block a user