# 产物中心 Artifact Center — 需求规格说明
> **产品**:`dsh-artifact-center`(中文名「产物中心」)
> **定位**:DSH 插件。把"我这个对话刚才生成的那份文件在哪"从"翻目录"变成"一次点击"。
> **配套文档**:方案 [`plan.zh.md`](plan.zh.md) · 开发注意事项 [`dev-notes.zh.md`](dev-notes.zh.md)
> **状态**:需求已冻结(决策 D1–D10 见方案文档)。本文件是编码与验收的唯一依据。
---
## 1. 产品定义
### 1.1 问题
DSH 用户在每个工作区的对话里不断生成文件——Office 文档、Markdown、HTML、图片、归档、脚本产物。它们散落在各工作区目录下,**没有统一的入口**,事后基本只能靠回忆路径去翻。
### 1.2 核心洞察
**"产物" ≠ "工作区里的文件"。**
实测本机 8 个工作区共 **26,334 个文件**,其中 `.svg` 12,221、`.java` 2,528、`.py` 2,319、`.png` 1,781、`.md` 1,142 —— 绝大多数是源码、依赖和素材,**不是对话产出**。目录优先的索引会把 99% 的噪声端给用户。
> **产物 = 有对话血缘的文件。索引主键是 `(工作区, 会话, 轮次, 路径)`,不是 `路径`。**
### 1.3 差异化
生态里已有 5+ 个"工作区文件浏览器"(`dsh-file-explorer-kit`、`@jaxzhou/dsh-file-explorer`、`dsh-fs-browser`、`@p-dsh-market/dsh-open-workspace` 等),它们回答的是"**这个工作区里有哪些文件**"。
本插件回答的是"**这些文件是哪个对话、哪一轮产出的,我要找的那份在哪**"。同时它是**跨工作区**的——不是单个会话的工作区浏览器。
### 1.4 目标用户与场景
| 场景 | 描述 |
|---|---|
| S1 找刚才的文件 | "五分钟前那个 agent 生成了一份 docx,我要打开它" |
| S2 找回上周的产物 | "上周某个对话做过一份支付方案,我不知道在哪个工作区" |
| S3 交付前确认 | "这次任务一共产出了哪些文件,我要打包交付" |
| S4 清理 | "这些中间产物没用了,删掉" |
| S5 让 Agent 找 | "帮我找一下上次关于数据的分析文档"(模型用 `artifact_search`) |
---
## 2. 范围
### 2.1 做(In scope)
- 自动索引**所有已注册工作区**、**所有历史与进行中对话**产出的文件
- 左侧栏入口 + 主面板(无活动会话也可用)
- 按工作区 / 类型 / 标签分类,按时间与关键字检索
- 面板内预览(零格式转换);打不开的交给"打开方式"
- 移入回收站
- 标签、收藏、备注、版本聚合、失效检测、清单导出
- `artifact_search` Agent 工具
### 2.2 不做(Out of scope,明确排除)
| 不做 | 原因 |
|---|---|
| **任何格式转换(含 Office→PDF)** | 决策 D1。原生什么格式就展示什么格式 |
| **浏览工作区全部文件**(目录树浏览模式) | 决策 D6。与已有插件重叠且噪声高 |
| **硬删除** | 决策 D2。只提供移入回收站 |
| **依赖任何 `@deepseek-ai/dsh-client-ui-*` 包** | 决策 D3。官方禁令 + 升级脆性 |
| 文件编辑 / 重命名 / 移动 | 只做查看与移除,不做内容与命名管理 |
| 云端同步 / 多机索引 | 单机本地索引 |
| 全文内容索引(对所有类型) | 只对文本类(md/txt/html/csv/json/代码)抽取内容 |
---
## 3. 功能需求
> **验收标准**均为可客观判定;标注 ⚠️ 的需要在本机真实环境验证。
### FR-A 索引引擎
| 编号 | 需求 | 验收标准 |
|---|---|---|
| FR-A1 | 读取所有已注册工作区 | 调用 `workspaceRegistry.list()`,面板分类树中出现全部 8 个工作区(名称与顺序与 DSH 左侧工作区列表一致) |
| FR-A2 | 回填历史会话 | 对全部历史会话(本机 289 份)提取产物路径;完成后可按文件名检索到装插件之前产出的文件 |
| FR-A3 | 从会话事件提取写文件路径 | `sessionQuery.listEvents()` 中 `write`/`edit`/`str_replace_editor` 的 `file_path` / `path` 全部入库,来源标 `write`/`edit` |
| FR-A4 | 记录显式交付 | `deliverables/presented` 事件的每个文件入库,来源标 `present`,并保存其 `description` |
| FR-A5 | **快照 diff 捕获 shell 产物**(核心) | 每轮 `turn/end` 后对工作区做全量 stat 快照并与上一轮 diff;`bash`/python/MCP 生成的文件必须能入库,来源标 `snapshot` ⚠️ |
| FR-A6 | 相对路径解析 | 事件里的相对路径用该会话 `header.cwd` 解析为绝对路径后再 stat |
| FR-A7 | 忽略规则 | 默认忽略 `.git`、`node_modules`、`dist`、`build`、`.next`、`__pycache__`、`.venv`、`target`、`.cache`、`.dsh`;可在设置里增删 |
| FR-A8 | 爆量轮折叠 | 单轮新增/修改超过阈值(默认 500)时,存为一条"批量产出"记录(含文件总数与顶层目录分布),不在列表里逐条展开 ⚠️(实测出现过单轮 13,090 个文件) |
| FR-A9 | 索引可重建 | 删除索引存储后重启,能重新回填出等价结果(索引是派生缓存) |
| FR-A10 | 增量与崩溃恢复 | 记录处理游标;重启后只处理新增部分,不整库重扫 |
| FR-A11 | 索引进度可见 | 回填期间面板显示进度(已处理/总数);已索引部分可立即浏览,不阻塞 |
### FR-B 左侧分类树
| 编号 | 需求 | 验收标准 |
|---|---|---|
| FR-B1 | 固定分类维度 | 树中出现全部节点:全部产物 / 最近(今天、7 天、30 天)/ 按工作区 / 按类型 / 按标签 / 收藏 / 失效 |
| FR-B2 | 按工作区 | 展开后列出所有工作区,各自显示产物数量 |
| FR-B3 | 按类型 | 分组:文档、表格、演示、图片、网页、数据、代码、音视频、压缩包、其他;显示数量 |
| FR-B4 | 按标签 | 列出用户标签与自动标签,显示数量;可多选(并集) |
| FR-B5 | 选中联动 | 点击任一节点,右侧列表立即按该条件过滤;当前选中项高亮 |
| FR-B6 | 可折叠 | 树可整体折叠以让出列表宽度 |
### FR-C 产物列表
| 编号 | 需求 | 验收标准 |
|---|---|---|
| FR-C1 | 列:文件名 | 显示文件名(不含目录);点击 = 预览 |
| FR-C2 | 列:工作区 | 显示所属工作区名;点击 = 筛选该工作区 |
| FR-C3 | 列:类型 | 显示类型图标 + 扩展名 |
| FR-C4 | 列:标签 | 显示标签胶囊;可内联增删 |
| FR-C5 | 列:所属对话 | 显示会话标题(`sessionQuery.readTitle*`);悬停显示轮次与时间;点击可跳回该对话 |
| FR-C6 | 列:来源 | 徽章区分 `present`(显式交付)/ `write`/`edit` / `snapshot`(推断)/ `manual`,并体现信任度差异 |
| FR-C7 | 列:大小与时间 | 显示文件大小(人类可读)与产生时间(相对时间,悬停显示绝对时间) |
| FR-C8 | 排序 | 可按时间、大小、名称、工作区排序;默认按时间倒序 |
| FR-C9 | 虚拟滚动 | 列表项 1 万条以上仍可流畅滚动(不卡帧) |
| FR-C10 | 空态 | 该筛选无结果时给出说明与"清除筛选"入口 |
| FR-C11 | 搜索 | 顶部搜索框按文件名模糊匹配;结果实时过滤 |
| FR-C12 | 内容检索 | 开启"搜索内容"后,对文本类文件内容匹配并显示命中片段 |
### FR-D 预览
> **总原则(决策 D1)**:原生输出什么格式就展示什么格式。**不为预览做任何格式转换。**
| 编号 | 类型 | 需求 | 验收标准 |
|---|---|---|---|
| FR-D1 | 图片 png/jpg/gif/webp/bmp/ico/svg | 面板内直接显示,支持缩放 | 打开一张真实 png 能正常显示;svg 用 `
` 渲染,不内联 SVG |
| FR-D2 | PDF | 面板内直接用浏览器 PDF 阅读器显示 | 打开一个真实 PDF 能翻页;**不引入 pdfjs** |
| FR-D3 | 文本 / 代码 / json / yaml / 日志 | 文本视图,代码有基础高亮 | 打开一个真实 .ts 或 .json 能显示且高亮 |
| FR-D4 | CSV / TSV | 以表格呈现(只解析,不改文件) | 打开一个真实 csv 显示为表格 |
| FR-D5 | **Markdown** | **渲染展示**(标题/列表/表格/代码块/链接) | 打开一个真实 .md 显示为渲染后的 HTML,不是源码 |
| FR-D6 | **Mermaid** | `.mmd` 或 md 里的 ```mermaid 围栏渲染为图 | 打开一个真实 .mmd 显示为流程图 ⚠️ |
| FR-D7 | HTML | 沙箱 iframe 渲染,只读 | 打开一个真实 html 能显示;不执行外部脚本;带 CSP |
| FR-D8 | 音视频 | 原生 `