# dsh-session-export [English](./README.md) | [简体中文](./README.zh.md) [![CI](https://github.com/JohnXu22786/session-export/actions/workflows/ci.yml/badge.svg)](https://github.com/JohnXu22786/session-export/actions/workflows/ci.yml) 为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)提供的会话导出与合规归档插件。 把当前或历史会话导出为可验证、**经过脱敏**的多格式归档文件,并在磁盘上管理这些文件,同时提供合规/趋势统计视图——全部以零运行时依赖的 [dsh bundle](#安装) 形式交付。 ## 为什么 导出只有不泄漏信息,才可用于合规或归档。本插件在一个 bundle 中整合了: - **默认开启的确定性脱敏**——API key、token、JWT、`.env` 风格赋值行、URL 凭据与绝对文件路径在写盘*之前*即被掩码。相同输入永远得到相同输出;掩码值带有稳定指纹,归档之间可关联比对,却不会泄漏机密。 - **多格式导出**——可读的 Markdown 叙事、机器可读的 JSONL 完整记录,以及 PDF(打印就绪 HTML + 可选的 headless Chromium 渲染)。 - **归档管理**——导出按会话/日期目录组织、可检索、可删除(带路径穿越防护)。 - **合规元数据**——每次导出都内嵌一个文件头,含会话 id、模型、脱敏级别、计数与可选 SHA-256 内容哈希;趋势/审计视图汇总“导出了什么、何时、敏感程度如何”。 - **不依赖模型**——脱敏基于规则;如需在规则之上叠加可选的 LLM 辅助掩码,内置扩展钩子。 ## 功能特性 ### 1. 会话导出 - 支持当前或历史会话,数据来源为 harness 自身的事件日志(`ctx.sessionPersistence` / `ctx.sessions`)。 - 格式:`markdown`、`jsonl`、`pdf` 或 `all`。 - 事件日志折叠对未知事件类型与跨版本的负载形状保持容错。 - 大会话会被切分为编号分片(带 `parts.json` 清单);Markdown 中可对超长消息截断并给出显式标记(JSONL 始终保留完整记录)。 ### 2. 脱敏(默认开启) | 规则 | 说明 | | --- | --- | | `api-key` | `sk-…`、`pk-…`、GitHub `ghp_/gho_/github_pat_`、`hf_`、`AKIA…`、`AIza…`、长 base64 风格令牌 | | `jwt` | `eyJ…` 令牌 | | `high-entropy-token` | 任意 32+ 位字母数字连续段 | | `env-value` | `KEY=value` 赋值行(例如粘贴的 `.env` 内容) | | `bearer` | `Bearer`/`token` 值 | | `url-credential` | `https://user:pass@host` | | `absolute-path` | Windows 盘符 / UNC / POSIX 家目录路径——掩码,或在 `pathMode: relative` 时相对化为 `<项目根>` | | `email` | 可选开启 | | `custom` | 任意用户自定义正则 | 所有匹配值都被替换为 ` sha256:<指纹>>`,其中指纹为加盐 SHA-256(12 位十六进制)。确定性与可配置的 `salt` 让归档在文件与机器之间可关联,而不会暴露机密:由于指纹仅由机密值本身推导(与规则标签无关),同一机密无论被 `env-value`、`api-key` 还是 `jwt` 捕获,都得到相同的十六进制。`env-value` 规则最先执行,因此 `KEY=sk-…` 行会被一步缩减为 `KEY=`(掩码值 token 或带引号字符串,同时保留其后的叙述与命令文本),之后令牌规则不会再看到它。 ### 3. 归档管理 ``` / sessions/<会话>//_-_<格式>.<扩展名> <文件>.meta.json # 每次导出一个旁侧文件(见 ArchiveEntry) <文件>.parts.json # 导出被分片时存在 archives.json # 可检索索引(丢失时可从旁侧文件重建) ``` 文件名带进程内唯一 tag,同一秒内的两次导出不会互相覆盖。可按会话、格式、ISO 日期区间、全文关键字与数量上限进行列举/检索;可按归档 id、文件名或相对路径删除——严格限定在归档根目录内(拒绝路径穿越)。索引写入在进程内串行化,并发工具调用不会丢失记录。 ### 4. 合规 每次导出的文件头都内嵌元数据: `plugin`(名称、版本)、`session`(id、项目、cwd、创建时间)、`exportedAt`、所用 `model`s、`sanitization`(是否启用、级别、触发的规则、盐指纹)、事件/消息计数、可选 `contentHash` 与告警。`includeHash: true` 时会对脱敏后的对话记录计算可选 SHA-256 哈希。 注意:只有在写入时才得知的告警(大会话分片、PDF 回退)无法注入已渲染的文件头;它们会记录在归档索引条目与 `.meta.json` 旁侧文件中,并在工具结果中再次呈现。 **趋势/审计视图**(`/export-audit`)按天、格式、会话与脱敏级别汇总导出记录。 ### 5. 工具链 - **模型侧工具**(`ctx.tools`):`session_export`、`session_export_list`、`export_delete`、`sanitize_config`。 - **人类斜杠命令**(`ctx.commands`):`/session-export`、`/session-export-list`、`/export-delete`、`/sanitize-config`、`/export-audit`。 - **可复用库**:通过 `dsh-session-export/core` 子路径导入与 harness 解耦的核心。 ## 环境要求 - Node.js 20+(直接运行 `.ts` 测试套件建议 Node 24+)。 - 一个带 base bundle 的 DeepSeek Harness profile(提供 `sessions`、`sessionPersistence`、`tools`、`commands`)。 ## 安装 这是一个标准的 dsh **bundle**:npm 包,其 `package.json` 声明 `dsh.bundle` 清单,附带一个 `cordis.patch.yml` 补丁层与导出 `name`/`inject`/`apply` 的入口模块。 ```sh # 从本仓库 dsh plugin --profile demo add github:JohnXu22786/session-export # 或发布后通过 npm 全局安装 npm install -g dsh-session-export ``` 补丁插入一行配置: ```yaml # cordis.patch.yml - insert: - id: dsh-session-export name: dsh-session-export config: outDir: !!js dshHomePath('exports') ``` `outDir` 默认为 `$DSH_HOME/exports`(即 `~/.dsh/exports`)。如需覆盖任一设置,可在后续补丁层以相同行 id 为目标(按 harness 语义,整行 `config` 会被整体替换)。 ## 配置 ```yaml - id: dsh-session-export name: dsh-session-export config: outDir: !!js dshHomePath('exports') enabled: true defaultFormat: markdown # markdown | jsonl | pdf | auto includeNotes: false # 是否包含 todo/命令/反馈等记录 includeDocuments: true # 是否输出文档树附录 maxChunkBytes: 4194304 # 大会话分片阈值(0 = 从不) maxMessageChars: 0 # Markdown 中对单条消息的显示截断(0 = 从不) prettyToolArgs: false includeHash: true redaction: enabled: true pathMode: mask # mask | relative | off projectRoots: [] # 例如 ['C:\\Users\\you\\work'] maskApiKeys: true maskEnvValues: true maskBearerTokens: true maskUrlCredentials: true maskAbsolutePaths: true maskEmails: false salt: "" # 设置一个密钥以加固可关联指纹 customPatterns: # [{ pattern, flags?, replacement? }] - pattern: '\\d{4}-\\d{4}' replacement: '[CARD]' aiMaskEnabled: false # 是否叠加用户提供的 aiMaskFn pdf: engine: auto # auto | chrome | html chromePath: "" # 显式指定 chromium 系列可执行文件 ``` ## 使用方式 ### 工具(供模型调用) - **`session_export { format?, sessionId?, redact? }`** — 导出会话。返回产物路径与合规摘要。如需单次导出不脱敏可传 `redact: false`。 - **`session_export_list { query?, sessionId?, format?, before?, after?, limit? }`** — 列出/检索归档。 - **`export_delete { target }`** — 按归档 id / 文件名 / 相对路径删除。 - **`sanitize_config { action?, sample? }`** — `show` 显示当前规则,或 `test` 对示例文本执行脱敏。 ### 斜杠命令 ``` /session-export [format] [--id=] [--no-redact] /session-export-list [query] [--format=] [--limit=] [--id=] /export-delete /sanitize-config [--test ] /export-audit ``` ## PDF 生成方案 `pdf` 为可选且刻意保持轻量: 1. 会话先渲染为**打印就绪、自包含**的 HTML 文件(内嵌 CSS、`meta` CSP、无脚本、`@media print` 规则)。 2. 当 `pdf.engine` 为 `chrome`(或 `auto`)时,插件通过 `--headless --print-to-pdf` 驱动 headless Chromium 系列浏览器(优先 `chromePath` 或 `CHROME_PATH`,其次常见安装路径)。 3. 若找不到浏览器二进制,则归档该 HTML 并给出提示——打开后使用系统“打印为 PDF”即可。 这样可避免引入沉重的 PDF 依赖(不随包内嵌原生渲染器)。 ## 合规哈希 `contentHash` 为脱敏后对话记录的规范 JSON-Lines 序列化的 `sha256`(不含文件头),因此同一脱敏记录无论以何种格式导出,哈希都一致。 ## 开发 ```sh npm install npm test # node --test 运行 test/*.test.ts(直接跑 .ts 需 Node 24+) npm run typecheck npm run build # tsc → dist/ ``` 目录结构: ``` src/ index.ts # bundle 入口:name / inject / apply commands.ts # 斜杠命令定义 tools.ts # ctx.tools 定义与注册 adapter/ # 薄薄的 dsh<->core 桥接层(backend、types、defineTool) engine.ts # 导出流水线编排 config.ts # 配置结构 + 归一化 core/ # 与 harness 解耦的库(./core 子路径) fold.ts # 事件日志 → 对话文档 redact.ts # 确定性脱敏引擎 archive.ts # 磁盘归档、索引、分片、路径穿越防护 render/ # markdown / jsonl / html / pdf meta.ts # 合规文件头 audit.ts # 趋势/审计聚合 filenames.ts # 跨平台安全文件名 hash.ts # sha256 / 指纹 ``` ## 许可证 [MIT](./LICENSE)