# 管理后台会话工作区文件浏览技术方案 - 状态:待实施(2026-10-15 需求共识,7 项决策见 §4;2026-10-16 评审:需求与方案均可行,按 §11 评审记录修订后实施;评审轮不动代码) - 日期:2026-10-15 - 能力底座:后端 `WorkspaceBrowseService` + `/v1/files/workspace-*` 端点族([2026-10-09-workspace-file-manager-technical-design.md](2026-10-09-workspace-file-manager-technical-design.md) 已落地)、admin 会话详情页双栏布局(`admin/src/views/session/SessionDetailView.vue`) ## 1. 需求背景 管理后台会话详情页当前展示会话信息与聊天记录:排障时能看到 Agent"说了什么、调了哪些工具、改过哪些文件"(`FileChangePanel`),但看不到"磁盘上最终留下了什么"——工作区目录里现在有哪些文件、某个产物的完整内容、能否把整个工作区取下来分析,都无从入手。会话 VO 其实已透出 `workspace` 绝对路径(`session-vo.ts` L192/L272),但详情页只把它透传给文件变更面板做相对路径展示(`SessionDetailView.vue` L110),没有浏览入口。 能力底座已经齐备,缺口只在"admin 作用域"这一层: - 后端 `WorkspaceBrowseService`(`backend-ts/src/file/workspace-browse.service.ts`)提供:单层目录列表(上限 500 条、目录优先、带 `mtimeMs`)、递归文件名搜索(跳过 `.git`/`node_modules`,上限 100 条、遍历有界)、文本读取(前 5000 行 / 512KB / UTF-8 严格解码拒二进制)、图片 data_uri(`sharp` 取宽高)、PDF inline 预览、单文件下载、目录 zip 打包(1GB 上限、临时文件流式下发)。所有路径经 `StrictPathResolver` 以会话工作区为唯一根收口,相对穿越、越界绝对路径、符号链接逃逸一律拒绝(service L411-423)。 - 但对外暴露的 `/v1/files/workspace-*`(`file.routes.ts` L113-190)用 `requireOwnedSession` 做严格 DB 属主校验(L50-59:`session.userId !== userId` 即 403)。管理后台查看任意用户会话必然 403;飞书群聊通道会话的工作区按设计由同群多用户共享,但 `session.userId` 固定为建会话者,同群其他用户同样 403。 - admin 域路由(`admin-session.routes.ts`)走权限码鉴权(`session:read` / `session:write`,经 `permissionService.hasPermission`),与文件域的属主校验是两套体系,互不打通。 - 桌面端已有两套完整文件 UI(`FileTree.vue` 侧边懒加载树 + `FileManagerPanel.vue` 面包屑管理器 + `FileViewer.vue` 预览),其信息架构(面包屑 + 名称/大小/修改时间列表 + 递归搜索 + 单文件/zip 下载)已验证好用;但桌面端代码依赖 Electron 与中心 Tab 体系,不能整体搬进 admin,需按 admin 技术栈(Element Plus + 双栏详情页)重写载体。 本方案在管理后台补齐这一缺口:新增一组 admin 作用域的只读文件端点,会话详情页信息卡提供入口,全屏 Dialog 承载文件浏览器。 ## 2. 需求描述 ### 2.1 目标 1. **看见工作区**:会话详情页左侧信息卡新增"工作区"行,展示工作区绝对路径(超长省略、可复制);路径为空显示 `-`。 2. **浏览目录**:CLOUD 会话提供"查看文件"入口,打开全屏 Dialog:面包屑导航 + 单层文件列表(名称/大小/修改时间,目录优先),点目录逐层进入,点文件打开预览。 3. **预览文件**:文本(等宽展示 + "仅显示前 N 行"截断提示)、图片(内联展示 + 后端摘要)、PDF(inline)、Markdown(渲染态/源码双态切换);其余格式明确提示"暂不支持预览,请下载后查看"。 4. **下载取证**:单文件下载;当前目录整包 zip 下载(后端 1GB 上限,超限给明确错误)。 5. **搜索定位**:Dialog 内按文件名递归搜索整个工作区(大小写不敏感子串,上限 100 条),点结果进入该文件所在目录。 6. **聊天记录联动**:聊天记录中文件变更面板(`FileChangePanel`)的文件路径可点击,直接打开该文件预览;已删除(delete)的变更给"文件已删除,无法预览"提示。 7. **安全与留痕**:所有浏览被限制在会话工作区之内(复用 `StrictPathResolver`);鉴权复用 `session:read` 权限码,不新增权限码;审计由既有全局 `onResponse` 钩子承担(`/v1/admin` 前缀全量自动留痕),本方案零显式审计代码(D7)。 ### 2.2 非目标(明确不做) - **不做任何写操作**:上传、删除、重命名、移动、复制、新建文件/目录一律不做。admin 定位是排障查看;写能力桌面端已完整覆盖,且 admin 直接改动用户工作区的风险不可接受。 - **不做 Git 面板**:仓库列表、status、diff、commit/pull/push 等 `workspace-git-*` 能力不接入 admin。 - **不做 LOCAL 会话浏览**:LOCAL 会话工作区在用户桌面本机,服务端文件系统上不存在;只展示路径文本、禁用浏览入口(D4)。 - **不做树形侧边栏**:Dialog 内只用面包屑 + 单层列表,不做桌面端 `FileTree.vue` 那种懒加载树——面包屑方案与后端单层 `listDirectory` 能力对齐,避免深递归在大工作区上的性能与复杂度问题。 - **不做文件编辑 / 不引入 Monaco**:预览一律只读,admin 不引入代码编辑器依赖。 - **不做 WebSocket 实时刷新**:Dialog 打开时拉取一次,提供手动刷新按钮;不订阅文件变更事件。 - **不做回收站 / 快照 / 回滚**:本期完全不涉及写路径。 - **不扩展图片格式支持**:沿用后端 `ImageFileSupport` 现有格式判定(含大小上限),不新增 HEIC/EXIF 等处理。 - **不加权限码、不动角色体系、零 DDL、零新依赖**。 ## 3. 范围界定 ### 3.1 做什么 | 层 | 内容 | |---|---| | backend-ts(新) | `src/session/admin-session-files.routes.ts`:6 个只读端点(directory/search/read/preview/download/download-zip),挂 `/v1/admin/sessions/:id/files/` 前缀 | | backend-ts(改) | `src/create-app.ts`:注册新路由并注入 `workspaceBrowseService`(不注入 `auditService`,D7) | | backend-ts(新) | `src/session/admin-session-files.routes.spec.ts`:权限/模式/越界/正常路径单测 | | admin(新) | `src/views/session/composables/useWorkspaceFiles.ts`:6 个端点的请求封装 + 目录导航/搜索/预览状态机 | | admin(新) | `src/views/session/components/WorkspaceFileDialog.vue`:全屏 Dialog(面包屑 + 列表 + 搜索 + 下载 + 四格式预览) | | admin(新) | `src/views/session/utils/file-format.ts`:`formatSize` / `formatTime` 纯函数(规则对齐桌面端 `utils/file-manager.ts`) | | admin(改) | `src/views/session/SessionDetailView.vue`:信息卡"工作区"行(路径 + 复制 + 入口按钮)、Dialog 挂载与状态、接收文件变更面板的预览联动 | | admin(改) | `src/views/session/components/FileChangePanel.vue` + `MessageGroup.vue`:文件路径可点击,事件透传到详情页 | | admin(复用,零改) | `api` 实例(`src/api/index.ts`,目录/搜索/读取等 JSON 请求)、`utils/download.ts`(`downloadBlob`)、`utils/workspace-path.ts`(`toRelativeWorkspacePath`)、`views/session/composables/useMarkdown.ts`(Markdown 渲染) | | 文档 | CHANGELOG.md、skills/mao-cli/SKILL.md(管理后台手册)、README.md(若列 admin 功能清单则补) | ### 3.2 不做什么 - 不改 `file.routes.ts` 任何现有端点与 `requireOwnedSession` 属主校验——用户侧安全模型零变化。 - 不改 `WorkspaceBrowseService`、`StrictPathResolver` 任何逻辑——纯复用。 - 不改桌面端任何文件 UI / provider / Electron 主进程。 - 不新增权限码、错误码、数据库迁移、npm 依赖。 - 不新增 Playwright E2E 用例:文件 Dialog 属重交互组件,以手工验证 + 构建通过覆盖;admin 既有 E2E(`tests/admin.spec.ts`)回归不受影响。 ## 4. 关键决策(需求共识) | # | 决策点 | 结论 | |---|---|---| | D1 | 后端接入方式 | 新增 admin 作用域只读端点族 `GET /v1/admin/sessions/:id/files/*`,复用 `WorkspaceBrowseService`;鉴权只校验 `session:read` + 会话存在,**不做出属主校验**(admin 查看任意用户会话是本职);`/v1/files/workspace-*` 现有安全模型不动 | | D2 | 功能范围 | 只读 + 预览 + 下载 + 搜索;写操作与 Git 面板明确不做 | | D3 | UI 载体 | 信息卡"工作区"行(路径省略 + 复制)+ "查看文件"按钮 → 全屏 `el-dialog` 承载文件浏览器(面包屑 + 单层列表);不改双栏布局、不加第三栏、不改 tab 结构 | | D4 | LOCAL 会话 | 有路径的会话都展示"工作区"行;仅 CLOUD 渲染可点入口,LOCAL 显示禁用态按钮 + 提示"本地执行会话的文件在用户设备上,服务端不可见";后端对 LOCAL 同样返回明确错误(双保险) | | D5 | 预览格式 | 文本(`
` 等宽 + 截断提示)、图片(``)、PDF(`