fix: 智能体可见性对齐——列表可见即可打开问答,消除 404

get_visible 此前比列表过滤更严格(仅 内置/本人/已发布),导致私密广场
智能体(转私密强制 published=false)和「我的」里已收藏但被取消发布的
智能体能在列表看到、点开问答却 404(前端连带报 X.map is not a function)。

- AgentRepository._visible_filter / MemoryAgentStore._is_fetchable:
  单条读取放宽为所有列表口径的并集(+is_private 豁免、+本人收藏豁免)
- 详情接口与 _enforce_agent_access 运行门禁:管理员回退 get_any,
  对齐 ?admin=true 全量列表
- 普通未发布草稿对陌生人仍不可见,隐私不放开
- 本地新增 tests/test_agent_visibility.py 覆盖 SQL/Memory 双实现
  (tests/ 目录在本仓库被 gitignore,不随提交)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Fable 5 committed 2026-06-11 21:25:36 +08:00
1 parent 0b98d18483
commit e7b51a30a3
5 files changed
+45 -9

No files matched your search

@@ -40,12 +40,27 @@ class AgentRepository(AgentStore):
def _visible_filter(user_id: str):
"""Visibility for single-record fetches (``get_visible``).
Lenient: a caller who already knows the agent id can fetch it even
when it's flagged ``is_private``. The private-square password is the
*discovery* gate (listings); individual reads are not re-gated, so a
bookmarked / linked private agent stays openable.
Lenient — must cover the union of every listing surface, so any agent
a user can see in a list always opens (detail + chat) instead of
404ing:
- built-in / owned / published — the baseline gallery visibility;
- ``is_private`` — the private-square password is the *discovery*
gate (listings); individual reads are not re-gated, so a private
agent opened from the square (or a bookmark/link) stays openable.
Note moving an agent into the private square forces
``published=false``, so this clause cannot lean on ``published``;
- favorited — the "我的" listing keeps favorited agents even after
the owner unpublishes them, so reads keep honoring the favorite.
"""
return or_(AgentRow.user_id.is_(None), AgentRow.user_id == user_id, AgentRow.published.is_(True))
fav_subq = select(AgentFavoriteRow.agent_id).where(AgentFavoriteRow.user_id == user_id)
return or_(
AgentRow.user_id.is_(None),
AgentRow.user_id == user_id,
AgentRow.published.is_(True),
AgentRow.is_private.is_(True),
AgentRow.id.in_(fav_subq),
)
@staticmethod
def _listing_filter(user_id: str):