TencentDB Agent Memory 的两个路由守卫:八条路由一处没接线
翻别人家前端的权限实现,最省事的办法是先找 RouteGuards 这类文件——名字摆在那儿,一看就知道作者想在路由这一层拦什么。TencentDB Agent Memory 的 MemoryPanel 里就有这么一个文件。但把它和路由表放在一起看,会发现一件需要照实记下来的事:守卫定义好了,路由表上一处都没用。
先说清楚版本与分支的前提。本文所有路径都来自该仓库 feat/server_team 分支(这就是它的默认分支,不是 main 也不是 master)在 2026-08-16 的快照。MemoryPanel 这个目录的后端包名是 team-memory-control、版本 0.1.0(MemoryPanel/package.json),前端包名是 community-loop-lab-web、版本同样是 0.1.0 且标了 "private": true(MemoryPanel/web/package.json)。整个项目的主模块还处在 beta 阶段,参数、文件与接口随版本变动,下面写到的每一处位置都请以仓库最新内容为准。
两个守卫本身长什么样
MemoryPanel/web/src/components/RouteGuards.tsx 定义了两个组件:
ResourceGuard(:12-22):admin角色访问资源页时重定向到工作台。MemberManageGuard(:27-41):reviewer不可见。
注意第二个的名字:文件里的函数名逐字是 MemberManageGuard,顺手写成「MemoryManageGuard」是很容易发生的事。写脚本 grep 的时候拼错一个词,就会得到「没有这个守卫」的错误结论。
两个组件都从 @/services/useCurrentRole 取当前角色。角色枚举是三值的 'admin' | 'member' | 'reviewer'(MemoryPanel/web/src/services/useCurrentRole.ts:18),同一组取值在 web/src/lib/api/types.ts:60、web/src/services/backendStore.ts:34、web/src/lib/api/teams.ts:52 里都出现过。useCurrentRole.ts:8-12 的注释把两层角色说得很清楚:admin 是全局角色,与是否创建或加入任何 team 无关,职责是管理 team(建团队、录入成员),不管理具体资源;member 是 team 内角色,负责在 team 内管理 agent、skill、wiki、code、memory 这些资源;判断顺序必须先判全局 admin,再查 active team 里的成员角色。
路由表这一侧
MemoryPanel/web/src/routes/index.tsx:18-33 用 createHashRouter(:39,注释说明是为了兼容旧版 hash 路由、避免刷新 404)注册了 8 条子路由:
| 路径 | 页面组件 |
|---|---|
/(index) | WorkbenchPage |
/wiki | WikiPage |
/code | CodePage |
/skills | SkillsPage |
/memory | ChatMemoryPage |
/team/members | MembersPage |
/team/agents | AgentsPage |
/team/api-keys | ApiKeysPage |
这 8 条 element 里直接放的都是页面组件本身,没有一条被 ResourceGuard 或 MemberManageGuard 包起来。
你自己怎么核这一条
不要信任何人的转述,包括这篇。在你本地的仓库副本里,MemoryPanel/web/src/ 下跑一次这样的检索就够了:
grep -rn "ResourceGuard\|MemberManageGuard" MemoryPanel/web/src
我们跑出来的命中行全部落在 RouteGuards.tsx 自己身上:文件头注释的第 4、5 行,以及两个 export function 的定义行 12 与 27。也就是说,除了它自己,没有第二个文件提到这两个名字。判定动作只有两步:第一步看 grep 的命中文件里有没有 routes/index.tsx;第二步直接打开 routes/index.tsx,逐条看 8 个 element 有没有嵌套包装。两步都对不上,才算复现了这个观察。
反过来,什么情况说明你看到的不是这回事?如果你手里的副本不是 feat/server_team 分支、或者不是这个时间点的快照,路由表随时可能已经改了;如果你 grep 的是仓库根而不是 MemoryPanel/web/src,会把无关目录的同名字符串也捞进来;如果你把函数名拼成了「MemoryManageGuard」,那就是 0 命中,得到的是另一个结论。
那么哪一处按角色过滤是真的在跑
在这一层里,我们能找到的、确实接进渲染路径的按角色过滤只有一处:MemoryPanel/web/src/layouts/ConsoleLayout.tsx:129
if (userRole === 'reviewer' && meta.id === 'team_members') continue;
它作用在菜单生成上——reviewer 角色看不到「成员管理」这一项。菜单侧的 PageId 共 8 个(web/src/constants/menu.tsx:19-27):workbench_board / wiki / code / skills / chat_memory / team_members / team_agents / api_keys,分组顺序是 ['workbench', 'organization', 'assets'](:58),workbench_board 还标了 affix: true,是固定标签页。也就是说,菜单里少一项和路由能不能进是两件事,前者改的是导航的可见性。
围绕 admin 的几处口径,彼此不一致
顺着这条线往下翻,会撞上几处互相对不上的表述。按纪律,这里只摆位置和原文要点,不推断哪个是对的,也不延伸。
第一组,关于 admin 能不能进资源页:
web/src/layouts/ConsoleLayout.tsx:123注释写「admin 可访问所有页面(含资源管理)」。web/src/components/RouteGuards.tsx:4与:13-15写的是「ResourceGuard:admin 角色访问资源页 → 重定向到工作台」。web/src/services/permissions.ts:17提到在 admin 逻辑下ResourcePage会显示AdminResourceLock。- 中文文案
web/src/i18n/zh-CN.ts:186是「资源管理功能暂未对管理员开放」。
四处说法不一致,而其中依赖守卫生效的那一处(ResourceGuard),我们在 8 条路由上一处都没找到接线。
第二组,关于 admin 有没有全局特权:
web/src/services/permissions.ts:9的注释写「全局 admin 判断:admin 拥有所有权限,可见所有内容」。web/src/services/backendStore.ts:334的注释写「admin 不再拥有全局特权,与 member 一致:只能操作自己 owner 的资产」。
对应到实现:isGlobalAdmin(permissions.ts:20-22)的函数体是 return isAdminFlag === true;,唯一权威来源是 auth/verify 响应里的 user.user_type === 'system_admin',由 LoginGate 在登录时写入 AuthState.isAdmin(:11-12);而 canManageAsset(backendStore.ts:327-338)的当前实现是「资产的 owner_user_id === userId 为真,或者同 team 且 isTeamAdmin 为真,其余一律 false」,它的第 4 个入参 _isGlobalAdminFlag 带下划线前缀且未被使用(:331)。两处注释放在一起就是不一致,说到这里为止。
前端这一层不是唯一的校验点
值得同时记住的是,Panel 的后端有自己的入站校验,不依赖前端守卫是否接线。
MemoryPanel/src/panel/http/middleware/validate-panel-headers.ts 是统一入站门:缺 x-tdai-service-id 返回 400 MISSING_INSTANCE_ID(:34-37),缺 x-tdai-user-key 返回 400 MISSING_USER_KEY(:50-53),唯一例外是 auth/verify——该 action 的 user_key 只放 body(:9、:49)。往里一层,MemoryPanel/src/panel/http/routes/knowledge/common.ts 里 resolveCallerUserId(:56-67)拿 ctx.userKey 调内核 auth/verify 反查身份,requireTeamMember(:89-100)在非法 user_key 时返回 401 INVALID_USER_KEY、非成员时返回 403 NOT_TEAM_MEMBER。
Chat Memory 的读权限判定函数是 authorizeChatMemoryRead(MemoryPanel/src/panel/http/routes/chat-memory.ts:1999-2036),三条放行路径任一命中即可:调用者是资产 Owner;资产 visibility === "team" 且调用者是该 team 成员(注释明确写「外 team 用户即便知道 asset_id 也不算可读」);或者该资产已绑定到调用者名下的某个 agent。异常时 fallthrough 到拒绝(:2032-2035)。写与删更紧:/chat-memory/clear 逐条校验后仅放行资产 Owner,任一不符整批拒绝(:1391-1410);/chat-memory/layer-delete 同样仅 Owner,注释给的理由是「读可以借入,但删除内容只允许 Owner」(:1437-1438)。
把这两侧并排放着看就够了:前端守卫的接线状态是一件事,后端端点上写了哪些校验是另一件事,两者各自是什么样,上面都给了确切的文件与行号,你可以自己去核。我们没有部署过这个面板,也没有对任何端点发过一个请求,因此这里不给「够不够」的结论。
还有一批「演示阶段」的自述
顺带说一句读这块代码时的背景。MemoryPanel/web/src/services/ 下有 5 个 store 明确自述为演示阶段的 localStorage 实现,共用底座 storage-utils.ts,其 :12-13 原文写「这些 store 均为前端演示阶段的 localStorage 实现,后端上线后整批替换为 fetch 即可」。资产范围覆盖层 asset-scope-store.ts:14 也写着「后端上线后换成一张 asset_acl 表即可」。前端还有一个能力开关 PANEL_CAPABILITIES = { assets: false }(web/src/lib/api/base.ts:22-24),:18-20 的注释说明为 false 时应展示「暂未开放」占位,不要发起注定 501 的请求。这些都属于原样记录的「阶段性」标注,不能当成已完成能力来读。
所以,如果你正打算基于这个前端做二次开发,本文的可操作结论只有一句:不要因为仓库里有一个 RouteGuards.tsx 就默认路由级拦截已经在跑,先跑一遍上面那条 grep,再打开 routes/index.tsx 逐条看 8 个 element。至于要不要把它们接上、接在哪几条路由上,取决于你自己的角色模型,项目没有给出通用答案。
延伸阅读
- 从头读起:TencentDB Agent Memory 是什么:团队级 Agent 记忆中枢怎么读
- 本专题共 40 篇,完整分组目录见专题页
- TencentDB Agent Memory 的两个 SDK 与路线图上的五项
- TencentDB Agent Memory 的接口条数:面板 55、SDK 54、OpenAPI 54
本文依据 TencentDB Agent Memory 官方仓库(github.com/TencentCloud/TencentDB-Agent-Memory)
feat/server_team 分支上的 README、INSTALL、CHANGELOG、ROADMAP 与四个模块的源码整理,
核对日 2026-08-16,对应仓库快照 97f9465。该仓库的默认分支即为 feat/server_team。
本文内容为仓库源码与文档口径,我们没有部署、也没有运行过该项目的任何一个模块,
因此不涉及运行效果、检索质量与性能的任何描述。
该项目主模块处于 beta 阶段、其余模块版本号仍为 0.1.0,参数与接口随版本变动,请以仓库最新内容为准。
该项目会采集并存储团队的对话、文档与代码,属于敏感数据,是否使用请结合自身合规要求评估。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。