🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

会话导出 guyusky/dsh-export-plugin

DeepSeek Harness
guyusky21 天前有提交已提供中文文档扫描:含敏感能力

把 dsh 会话导出为 Markdown 或 PDF 文件

29.9/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/15 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add guyusky/dsh-export-plugin
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · chat
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 21 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件
查看扫描证据(共 1 处,展示前 1 条)
  • 读取本地文件
    test-inject.mjs:18
    const src = readFileSync(FILE, 'utf8')
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-host-webserver
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DeepSeek Harness 会话导出插件:在 dsh Web 会话的「更多」菜单里加入「导出 Markdown / 导出 PDF」,纯前端注入,零 npm 依赖。从 Web 界面把 DSH 会话导出成 Markdown 或 PDF。

README

由 DeepSeek 最新模型翻译生成
dsh-export-plugin · 会话导出 Markdown + PDF 在 dsh 会话行「更多」菜单里,于「归档会话」下方注入两项: - 导出 Markdown —— 与 PDF 同一套极简问答对(只有用户输入 + 该轮最后一条助手可见回复)转成 .md 下载。 - 导出 PDF —— 只取用户输入 + 该轮最后一条助手可见回复(文字与图片),排成干净的问答对,A4 排版后经系统打印对话框「另存为 PDF」。 设计 复用官方 dsh-session-log-export 包的 /api/session.export route(返回 ZIP,内含 canonical JSONL log + media/<attachmentId>.<ext> 图片),前端 fetch 拿 ZIP → 浏览器原生 DecompressionStream('deflate-raw') 解压 → 迷你 ZIP 解析器找根 session.jsonl(PDF 再解 media/)→ 按 V4 event type 转换 → .md 下载,或渲染打印专用 HTML 到隐藏 iframe srcdoc 并调 contentWindow.print()。
展开完整文档(README 剩余 1 节)
dsh-export-plugin · 会话导出 Markdown + PDF

在 dsh 会话行「更多」菜单里,于「归档会话」下方注入两项:

- 导出 Markdown —— 与 PDF 同一套极简问答对(只有用户输入 + 该轮最后一条助手可见回复)转成 .md 下载。
- 导出 PDF —— 只取用户输入 + 该轮最后一条助手可见回复(文字与图片),排成干净的问答对,A4 排版后经系统打印对话框「另存为 PDF」。

设计

复用官方 dsh-session-log-export 包的 /api/session.export route(返回 ZIP,内含 canonical JSONL log + media/<attachmentId>.<ext> 图片),前端 fetch 拿 ZIP → 浏览器原生 DecompressionStream('deflate-raw') 解压 → 迷你 ZIP 解析器找根 session.jsonl(PDF 再解 media/)→ 按 V4 event type 转换 → .md 下载,或渲染打印专用 HTML 到隐藏 iframe srcdoc 并调 contentWindow.print()。

纯前端注入,零 npm 依赖,不读 zstd(官方已用 persistence read handle 把 log 解好放进 ZIP),不碰 persistence。PDF 走 window.print 而不是 jsPDF/pdfMake:零依赖、中文用系统字体、图片与 CSS 排版交给浏览器。

PDF 导出范围

| 事件 / 内容 | 是否导出 |
| user/message 且 source.kind === 'user'(真实用户输入,含图片) | ✅ |
| agent/inbox/spliced 里 role=user 的插入消息 | ✅ 与上一条是同一个 message id,按 id 去重只出一次 |
| runtime-context / time-context / skill-catalog / 工作区指令等系统注入 | ❌ 它们也走 user/message,按 source.kind 滤掉 |
| assistant/message 的 text block | ✅ 但每轮只留最后一条 |
| assistant/message 的 image block | ✅ |
| 同一轮里工具调用前后的过渡说明文本 | ❌ 被该轮最后一条覆盖 |
| thinking / reasoning block(思考过程) | ❌ |
| tool_use / tool_result / tool/call / tool/result | ❌ |
| system/message / developer/message | ❌ |
| assistant/attempt(半途草稿、流式分片) | ❌ |
| 子代理会话(includeDescendants=false) | ❌ |
| 未知/其它事件类型 | ❌ |

即 PDF 是一份干净的「我问了什么 / 最终答了什么」记录,不含模型内部过程。配对规则:以 turn/end 作为一轮的边界,把该轮的用户输入与最后一条助手可见回复排成一组;日志末尾再收尾 flush 一次,所以被打断 / 未正常结束的回合也不会丢内容。用户消息里的图片会内嵌到 PDF(缺图时输出 [图片未随会话导出] 占位)。

为什么必须「按 id 去重 + 按 source.kind 过滤」(来自真实会话日志的教训):同一条用户输入在 agent/inbox/spliced 与 user/message 里各记一次、共用同一个 message id,不去重就会每句提问出现两遍;而 runtime-context、time-context、skill-catalog、工作区指令(如 CLAUDE.md,实测上万字符)也是 user/message,不按 source.kind === 'user' 过滤就会冒充用户提问混进 PDF。两者叠加曾是 PDF 里「重复提问 + 莫名其妙的成段内容」的直接原因。

导出的 Markdown 结构

Markdown 与 PDF 共用同一套「极简问答对」(同一份 jsonlToBlocks 输出),只有标题、角色、正文和图片占位:

Session <id>

- Created: <createdAt>
- Workspace: <cwd>

👤 用户

<用户消息>

🤖 助手

<助手回复(保留 Markdown 代码块与列表)>

👤 用户

…(每轮如此)

不再包含工具调用 / 工具结果 / 系统提示词 / 思考过程 / 上下文注入 / 未知事件的 JSON dump。图片位置输出 [图片] 占位(Markdown 不做 base64 内嵌,避免文件膨胀)。

实测(同一会话 session-5494db1e):旧导出 146,662 字节 → 新导出 3,208 字节(约 2.2%)。

需要完整会话日志(含工具调用、系统提示词)时,用会话菜单里官方 dsh-session-log-export 提供的导出项,它仍会给出完整的 ZIP/日志。

菜单挂载的真实 DOM

官方菜单行由 @deepseek-ai/dsh-client-ui-primitives 的 MenuItemButton 渲染(类名是 CSS module 哈希):

归档会话…

注入做法:按文本 归档会话 / 取消归档 找到 [role="menuitem"],再取它的外层 div.itemWrap,克隆整个 itemWrap(不是只克隆 button——.itemWrap 是 position:relative 的整行单元,只克隆 button 会让两个 button 挤在同一个包裹层里),然后改 itemLabel 文字、替换 svg 图标、移除 shortcut、绑定 click,依次插到原行后面(Markdown 在前、PDF 在后)。

文件

- index.js — apply 注入,通过 webServer.tapIndex 把 HTML 注入 前
- inject.mjs — 导出 exportInjectHtml 字符串,含 CSS + JS IIFE(两个导出项 + ZIP 解析 + MD/PDF 两条转换链)
- cordis.patch.yml — 插件 bundle 声明
- package.json — 包配置
- test-inject.mjs — 回归测试(见下)

安装

npx @deepseek-ai/dsh plugin --profile web add D:\dsh-code\export-plugin

插件以目录链接安装(profile 的 dsh-export-plugin → link:D:/dsh-code/export-plugin),改 inject.mjs / index.js 后只需重启 dsh(npx @deepseek-ai/dsh web),不必重新 add;改 package.json 结构字段需重新 add。

inject.mjs 的注入串在服务启动时随模块加载定格,重启前改动不会生效。

自测(改完 inject.mjs 先跑这个)

node test-inject.mjs

它做六件事:

1. 断言外层模板字面量里没有残留的单反斜杠 \n / \. / \d;
2. 提取内联 并编译,捕获语法错误并打印出错行;
3. 断言脚本文本里没有任何结构锚点字面量(head / body / style / script 的开合标签字样);
4. 用 jsdom + 真实菜单 DOM 结构,验证「导出 Markdown」与「导出 PDF」依次插在「归档会话」之后、各自独占一个 itemWrap、用 itemLabel 改文字、移除了 shortcut;
5. 用真实 JSONL 断言导出内容:只产出 user + assistant 三组问答对;思考过程 / 工具调用 / 工具结果 / system / developer 文本 / 三种系统注入(runtime-context、time-context、skill-catalog)都不得出现;同一条用户消息(spliced + user/message 共用 id)只出一次;HTML 已转义、图片按 mediaMap 内嵌或占位、生成的 PDF 文档能被正常解析;Markdown 渲染器同样断言问答结构、内容齐全且无角色外的泄漏;
6. 锚点劫持回归:模拟「另一个插件用正则找第一个 head 结束标签、插入它自己的脚本元素」,断言我们的脚本仍完整、JS 没有泄漏成页面文本;并断言 index.js 的 injectAll 取的是最后一个 head 结束标签。

两个必踩的坑(都真实发生过)

1. 转义坑:内联 JS 写在外层模板字面量里,里面的 \n、正则 \. \d 必须写成 \\n、\\.、\\d,并且不要在内联 JS 里再用反引号模板。漏写会让注入后的脚本报 Invalid or unexpected token,整个 IIFE 不执行 —— 表现为菜单项完全不出现。
2. 锚点劫持坑:webServer.tapIndex 是链式的,各插件都用正则找第一个 head 结束标签来插入自己的内容。如果我们注入的脚本文本(字符串里、注释里都算)出现 这类结构标签字样,排在后面的插件就会把它的脚本元素插进我们的脚本内部,它的闭合标签会提前终止我们的脚本,剩下的 JS 被浏览器当页面文本渲染到页面上(按钮消失 + 页面顶部出现一大段源码)。所以 PDF 渲染里的结构标签全部拆开写:'<sty' + 'le>'、'</he' + 'ad>' 这样;index.js 也改成锚定最后一个 head 结束标签。test-inject.mjs 的第 3、6 项守住这两点。

技术要点

- 菜单挂载:MutationObserver 监听 DOM,文本匹配「归档会话」菜单项,克隆其 itemWrap 注入两项;data-dsh-handled 防重,React 每次重建菜单后重新注入
- sessionId 获取:React fiber 往上遍历找 props.sessionId,降级到 URL / 输入框 fiber 扫描
- ZIP 解析:自写迷你解析器读 EOCD + Central Directory + Local Header,用浏览器原生 DecompressionStream 解 deflate(方法 8)/ store(方法 0);media/<attachmentId>.<ext> 用 lastIndexOf('.') 还原 ID
- event 转换:只认 user/message(且 source.kind === 'user',并按 message id 去重)与 assistant/message 与 spliced 的 user 消息;block 层只取 text 与 image,其余(thinking/tool_use/tool_result/file/未知)全部丢弃;再以 turn/end 为边界排成「用户输入 → 该轮最后一条助手回复」的问答对(末尾收尾 flush,打断的回合不丢)。Markdown 与 PDF 共用这一份 blocks,只是渲染器不同(renderMarkdown / renderPdfHtml)
- PDF 图片:image block 真实形态为 {type:'image', attachment:{attachmentId, mediaType,…}},与 ZIP 条目按 attachmentId 对应;mime 优先取 attachment.mediaType;base64 分块编码(≤0x8000),同一图只编码一次
- PDF 渲染:打印专用 HTML 内联 CSS(A4 / 中文字体栈 / 保留换行 / 图片 max-width:100%),所有动态文本经 escapeHtml 转义

依赖

依赖官方 dsh-session-log-export 包提供的 /api/session.export route(dsh 默认 bundle 自带)。

风险

- 菜单 DOM 结构升级后挂载失效 → MutationObserver 容错 + 30 分钟超时;改版后先跑 test-inject.mjs
- 脚本文本里出现结构标签字样会劫持链上其他插件的注入锚点 → 结构标签一律拼接写法,由 test-inject.mjs 第 3/6 项守住
- V4 event 格式升级 → MD 路径未知 event type 原样保留到 json 代码块;PDF 路径未知类型直接忽略(可接受,PDF 本就是精简版)
- 大 session(>50 轮)内存占用 → 一次性读 ZIP,当前够用
- PDF 图片多导致文件大 / 打印慢 → 默认单图 ≤4MB、20 图/消息;CSS 限制显示高度
- PDF 需要用户在打印对话框里选「另存为 PDF」(浏览器沙箱限制,无法一键直存)
预览分享卡片长什么样 ↓guyusky/dsh-export-plugin
分享 · DeepSeek Harness Hub
guyusky/dsh-export-plugin

把 dsh 会话导出为 Markdown 或 PDF 文件

这是你的仓库?认领条目

回到顶部