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:
1 parent
0b98d18483
commit
e7b51a30a3
5 files changed
+45
-9
No files matched your search
+10
-1
@@ -32,6 +32,15 @@ class MemoryAgentStore(AgentStore):
|
||||
def _is_visible(agent: dict[str, Any], user_id: str) -> bool:
|
||||
return agent.get("user_id") is None or agent.get("user_id") == user_id or bool(agent.get("published"))
|
||||
|
||||
def _is_fetchable(self, agent: dict[str, Any], user_id: str) -> bool:
|
||||
"""Single-record visibility (``get_visible``) — the union of every
|
||||
listing surface, so a list-visible agent always opens. Mirrors
|
||||
``AgentRepository._visible_filter``: baseline visibility OR
|
||||
private-square (password is the discovery gate, reads are not
|
||||
re-gated) OR favorited (the "我的" listing keeps favorites even after
|
||||
the owner unpublishes)."""
|
||||
return self._is_visible(agent, user_id) or bool(agent.get("is_private")) or (user_id, agent["id"]) in self._favorites
|
||||
|
||||
async def list_visible(self, user_id: str) -> list[dict[str, Any]]:
|
||||
rows = [_copy(agent) for agent in self._agents.values() if self._is_visible(agent, user_id)]
|
||||
rows.sort(key=lambda agent: agent.get("created_at") or "")
|
||||
@@ -39,7 +48,7 @@ class MemoryAgentStore(AgentStore):
|
||||
|
||||
async def get_visible(self, agent_id: str, user_id: str) -> dict[str, Any] | None:
|
||||
agent = self._agents.get(agent_id)
|
||||
if agent is None or not self._is_visible(agent, user_id):
|
||||
if agent is None or not self._is_fetchable(agent, user_id):
|
||||
return None
|
||||
return _copy(agent)
|
||||
|
||||
|
||||
+20
-5
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user