Skip to content
dsh.fish
Bundle

dsh-session-import

Cross-tool session migration plugin for the DeepSeek Harness (dsh): high-fidelity ingest, continue, knowledge extraction, bidirectional export, privacy redaction.

Source
devmom
stars
1 stars
License
MIT
Updated
Updated yesterday

Readme

# session-import

**中文** · [English](README.en.md)

跨工具会话迁移插件(对标/增强 `dsh-chat-import`),现已作为 **真实 dsh bundle 插件** 接入 DeepSeek Harness:

- 15+ 源会话导入(ChatGPT / Claude Code / Codex / Kimi Code / ZCode / WorkBuddy / OpenClaw / Gemini / Cursor / Aider / Continue / Cline / OpenCode / Reasonix / GitHub Copilot Chat / 通用 JSON);
- 导入产出 **原生 dsh session-log 事件**(`turn/start` · `user/message` · `assistant/message`(含 thinking 推理块与 tool-call 块)· `tool/call` · `tool/result` · `turn/end`),经 `ctx.sessions` 写入并随官方 JSONL 持久化落盘 —— 导入的会话在 dsh Web UI 中原样渲染、可续聊;
- 双向导出(dsh 会话 → generic JSON / Markdown / transcript);
- 隐私脱敏检测(API Key / 手机号 / 邮箱 / 身份证 / IP / 银行卡);
- 知识抽取(决策/代码/约定 → 本地记忆去重沉淀,接 `memory` seam 接口)。

**设计文档**:见 [`docs/`](docs/) — [PRD](docs/PRD-session-import.md)(需求/验收)· [ARCH](docs/ARCH-session-import.md)(架构)· [TECH](docs/TECH-session-import.md)(技术细节)。

## 已实现(对照需求 FR/NFR/AC)

| 模块 | 文件 | 覆盖需求 |
|---|---|---|
| 解析适配器(可插拔,14 种源) | `src/parsers/*.ts` | FR-01/02/03/16 |
| 适配器注册与探测 | `src/registry.ts` | FR-02 — `detect()` 嗅探 + 通用兜底 |
| **真实 dsh 会话事件映射** | `src/mapping.ts` | FR-04/05/06 — 原生 `Session.append` 词汇(turn/user/assistant/tool),thinking→reasoning 块,未知工具→`tool/result.meta.unknownTool` |
| 隐私脱敏 | `src/privacy.ts` | FR-15/AC-3 |
| 双向导出(事件逆向) | `src/exporter.ts` | FR-13 |
| 知识抽取 | `src/knowledge.ts`, `src/heuristicJudge.ts` | FR-11/12 |
| 导入编排 | `src/importService.ts` | FR-07/08/09/14/15/16 — 冲突 rename/merge/skip、归档、健壮性 `SkipError` |
| 流式分片导入 | `src/streamImport.ts` | FR-10/NFR-02 |
| **真实 dsh seam 适配** | `src/dsh/dshRuntime.ts` | 对接 `ctx.sessions` / `ctx.sessionTitle` / `ctx.credentials`;归档/历史/记忆走 `$DSH_HOME/session-import` |
| **Cordis 插件入口** | `src/plugin.ts` | `apply(ctx, config)`:提供 `sessionImport` 服务、注册 `/session-import` 命令、挂载 `/api/session-import/*` REST 路由 |
| **bundle 清单** | `cordis.patch.yml`, `package.json#dsh.bundle` | 标准 dsh profile bundle 协议 |
| 独立开发向导(可选) | `src/server.ts`, `public/*` | 无 harness 时的本地 Wizard(`npm run wizard`) |

## 安装到 dsh(推荐)

### 方式 A:从 npm registry 安装(发布后,最简)

```bash
# pnpm 9.x 的 workspace-root 检查需要 -w 标志(dsh plugin 原样转发 pnpm 参数)
dsh plugin --profile web add -w session-import

# dsh 会自动把包加入 profile 的 dsh.profile.bundles(检测到 dsh.bundle 声明),重启 dsh 即生效
```

### 方式 B:本地开发 / 未发布时(link 到插件目录)

```bash
# 在插件目录构建一次(产出 dist/ 与 lib/)
npm install && npm run build && npm run bundle:client

# 安装到你的 profile(web 示例)
dsh plugin --profile web add -w link:D:/work/DeepSeekHarness/Plugins/session-import
# 或手动:profile/package.json dependencies 加 "session-import": "link:<本目录绝对路径>",
# 并在 dsh.profile.bundles 追加 "session-import",然后在该 profile 目录 pnpm install

# 重启 dsh,即可用:
#   - Web UI 输入 /session-import <导出文件路径> [--redact] [--extract-knowledge]
#   - 或调用 REST:POST /api/session-import/import
```

> Windows 注意:pnpm 9.x 对 `file:D:/...` 绝对路径解析有已知问题(会把盘符拼到 profile 目录下),
> 本地开发请用 `link:<绝对路径>` 说明符(pnpm 会建 junction)。registry 安装不受影响。

### 发布到 npm(维护者)

```bash
npm login                                   # 需要 npm 账号(包名 session-import 已无 scope)
npm version patch|minor|major               # 提升版本
npm publish                                 # prepublishOnly 自动 build:all + test
```

## 在 dsh 中的使用面

0. **Web 导入向导(多选勾选,浏览器 UI 插件)** — 打开任意会话 → 顶部 Tab「会话导入」:
   - **完整候选列表**:自动扫描发现所有已注册源(Codex / Claude Code / …),逐行显示会话名/消息数/大小/时间;
   - **多项勾选**:checkbox 多选 + 全选/清空,确认后逐个导入为独立 dsh 会话;
   - **标题选项**:勾选「无标题会话用首条消息生成标题」后,未命名的 Codex 会话标题取首条用户消息(近似 Codex 自动命名/桌面端预览),不勾选则用时间戳标题;
   - 导入结果即时展示(成功会话 id / 跳过原因),列表自动刷新。
   - 数据通道:浏览器直连宿主 webserver 的 `/api/session-import/scan-dir` 与 `/api/session-import/import-paths`。
1. **对话式发现导入(模型工具)** — 用户直接提要求,agent 自动完成「扫描 → 候选确认 → 导入」:
   - `chat_import_discover`:自动探测本机已知 agent 工具会话目录(注册表见下文),返回带编号的候选列表(会话名/路径/消息数/大小/时间),只读不写入;支持 `titleFromFirstMessage` 选项(同向导)。
   - `chat_import_import`:按用户确认的路径列表导入为新 dsh 会话(支持 `redact` / `extractKnowledge` / `titleFromFirstMessage`),失败文件跳过并说明;**标题自动取源标题**(Codex 线程名 / Claude Code `ai-title`),无标题的默认用可读时间回退,开启 `titleFromFirstMessage` 则取首条用户消息。
   - 对话示例:
     > 用户:**帮我把最近的 Codex 会话导入进来**
     > Agent:`chat_import_discover` → 列出候选 → 用户:**导入 1、3、5** → Agent:`chat_import_import` → 汇总
   - 默认开启;bundle config `enableModelTools: false` 可关闭。
2. **`/session-import` 斜杠命令**(Web UI / 任何 commands 适配器):
   ```
   /session-import C:\path\to\chatgpt\conversations.json          # 单文件
   /session-import C:\Users\PC\.codex\sessions                    # 目录(递归扫描全部会话)
   /session-import C:\Users\PC\.codex\sessions --source codex     # 只导 codex
   /session-import <path> --redact --extract-knowledge            # 脱敏 + 知识抽取
   ```
   目录导入返回汇总(`imported 21/21 session(s) (codex×21)`),坏文件跳过不中断(FR-16)。
3. **`sessionImport` 服务**(其它插件 / 脚本):
   ```ts
   const svc = ctx.get('sessionImport') // { ingest, sources, detectSource, parseCount, scanDirectory, ingestDirectory }
   const preview = await svc.scanDirectory('C:/Users/PC/.codex/sessions', { sourceFilter: ['codex'] })
   const batch = await svc.ingestDirectory('C:/Users/PC/.codex/sessions', { sourceFilter: ['codex'], redact: true })
   // batch: { dir, total, imported: ImportResult[], skipped: {path,reason}[], ignored: number }
   ```
4. **REST API**(web profile,注册在宿主 webserver 上):
   - `GET  /api/session-import/adapters` — 已注册源列表
   - `POST /api/session-import/import` — `{ fileName, text, options }` 单文件导入
   - `POST /api/session-import/scan-dir` — `{ sourceFilter?, maxFiles? }` 发现式扫描(返回带编号/标题的候选组,不写入)
   - `POST /api/session-import/import-dir` — `{ dir, options, sourceFilter?, maxFiles? }` 目录批量导入
   - `POST /api/session-import/import-paths` — `{ paths, options }` 按确认路径逐个导入(向导/对话共用)
   - `GET  /api/session-import/history` — 导入历史
5. **数据落点**:会话本体 → dsh 官方 session-log(`$DSH_HOME/sessions`,JSONL,绑定源 cwd 的 workspace 分组);原文归档/导入历史/本地记忆 → `$DSH_HOME/session-import/`(可用 bundle config `dataDir` 覆盖)。

## 源适配器覆盖(主流 agent 框架)

| 框架 | 源 id | 自动发现目录(env 可覆盖) | 解析器 | 状态 |
|---|---|---|---|---|
| **Codex CLI** | `codex` | `~/.codex/sessions`(`CODEX_HOME`) | 专用(rollout + legacy;标题链:`session_index.jsonl` 线程名 → 首条用户消息(可选)→ 时间戳回退;`session_meta.cwd` 绑定) | ✅ 本机实测(真实会话导入验证) |
| **Claude Code** | `claude_code` | `~/.claude/projects`(`CLAUDE_CONFIG_DIR`) | 专用(session-based + legacy;`ai-title` 标题、`cwd` 绑定) | ✅ 本机实测(真实会话导入验证) |
| **Kimi Code** | `kimi_code` | `~/.kimi-code/sessions`(`KIMI_CODE_HOME`) | 专用(真实 `wire.jsonl` 事件流 v2:`turn.prompt`/`context.append_loop_event`(`content.part` think/text、`tool.call`/`tool.result`);`state.json` 标题 + `session_index.jsonl` 工作目录;子代理 `agents/agent-N` 同样解析) | ✅ 本机实测(真实会话导入验证) |
| **ZCode(智谱 GLM)** | `zcode` | `~/.zcode/cli`(`ZCODE_HOME`) | 专用 v2(真实双格式:子代理 `transcript.jsonl`(`turn_started`/`model_streaming`/`tool_call_scheduled`/`turn_complete`)+ 主会话 `rollout/model-io-sess_*.jsonl`(`model_io` 快照,含 reasoning/toolCalls,过滤 system-reminder/task-notification 噪声);`metadata.json` → 标题 `description` + `cwd` 绑定) | ✅ 本机实测(真实主会话 32 条 + 子代理导入验证) |
| **WorkBuddy / CodeBuddy(腾讯)** | `workbuddy` | `~/.codebuddy`(`CODEBUDDY_HOME`) | OpenAI 消息格式 / 通用 JSON(导出会话) | ✅ 合成会话导入验证(本机无会话数据) |
| **OpenClaw (claw.so)** | `openclaw` | `~/.openclaw/agents`(`OPENCLAW_HOME`) | 专用(真实 session log:`session`(cwd 绑定)/ `message`(text / thinking / toolCall / toolResult 块)) | ✅ 本机实测(真实会话导入验证) |
| **Gemini CLI** | `gemini` | `~/.gemini`(`GEMINI_HOME`) | 专用(`chats.history`) | ✅ 合成会话导入验证(本机未安装) |
| **Cursor** | `cursor` | `~/.cursor`(`CURSOR_HOME`) | 专用(`source:cursor`/`toolCalls`) | ✅ 合成会话导入验证(本机未安装) |
| **Aider** | `aider` | `~/.aider`(`AIDER_HOME`) | 专用(markdown 历史) | ✅ 合成会话导入验证(本机未安装) |
| **Continue** | `continue` | `~/.continue/history`(`CONTINUE_HOME`) | OpenAI 消息格式(目录内文件检测为 `openai_family`) | ✅ 合成会话导入验证(本机未安装) |
| **OpenCode** | `opencode` | `~/.opencode/sessions`(`OPENCODE_HOME`) | OpenAI 消息格式(目录内文件检测为 `openai_family`) | ✅ 合成会话导入验证(本机未安装) |
| **Cline / Copilot Chat 等导出** | `openai_family` | 自定义 `CHAT_IMPORT_OPENAI_DIR` | OpenAI 消息格式 | ✅ 合成会话导入验证 |
| **ChatGPT** | `chatgpt` | 手动指定导出文件 | 专用(`mapping` 图) | ✅ 单元测试覆盖(导出文件) |
| 任意 JSON | `generic_json` | 手动指定文件 | 通用兜底 | ✅ |

> 每个框架在 `src/discovery.ts` 的 `AGENT_SOURCES` 注册表中一行即接入自动发现;
> 会话存储为 SQLite 的框架(Cline/Continue 新版、Trae、通义灵码、DevChat 等)以导出 JSON 方式接入,本地 sqlite 解析留作后续。
> `chat_import_discover` / 向导只会列出**本机实际存在**的框架目录。
> 验证方式:`node scripts/verify-all-sources.mjs` — 对全部已注册框架逐个执行 检测 → 解析 → 导入(本机有真实数据的用真实会话:codex / claude_code / kimi_code / zcode / openclaw;其余用合成夹具:gemini / cursor / aider / continue / opencode / openai_family / workbuddy)。

## 无 harness 开发模式(headless / 独立向导)

```bash
npm test            # 89 个用例(node --import tsx --test)
npm run build       # tsc 严格编译 → dist/
npm run wizard      # 独立导入向导 http://localhost:4173(纯本地文件 seams)
```

端到端脚本(真实 dsh profile):
- `node scripts/verify-dsh.mjs` — 服务/命令/导入/持久化断言
- `node scripts/verify-all-sources.mjs` — 全部已注册框架 检测 → 解析 → 导入(真实/合成)
- `node scripts/verify-zcode.mjs` — ZCode 主会话 rollout + 子代理 真实导入验证(标题/cwd 持久化)
- `node scripts/verify-install.mjs` — tarball 安装后的插件可用性验证(`npm pack` → `dsh plugin add` 到临时 DSH_HOME → boot → 导入,自包含)
- `node scripts/demo-codex.mjs <rollout.jsonl>` — 导入单个真实 Codex 会话
- `node scripts/demo-scan.mjs <dir> [--import] [--source codex] [--redact] [--limit N]` — 目录扫描/批量导入演示
- `node scripts/demo-discover.mjs [--import-limit N]` — 模型工具闭环:discover 真实候选 → import → 持久化验证

> 上述脚本会 boot 一个**真实的 dsh 安装**(`dsh-app-boot`)。dsh checkout 路径通过
> `scripts/lib/dsh-env.mjs` 解析:`$DSH_CHECKOUT` 环境变量 > PATH 上的 `dsh` CLI 反推 > npm 全局目录。
> 无需改脚本;把 `dsh` 装进 PATH 或设置 `DSH_CHECKOUT` 即可在其他机器上运行。

CI(GitHub Actions,`.github/workflows/ci.yml`):push/PR 时执行
Node 18/20/22 的 类型检查 → 构建 → 客户端打包 → 单元测试 → `npm pack` 内容校验,
以及一次 dsh 集成 job(安装 dsh CLI 后跑 `verify-dsh` + `verify-all-sources` 合成夹具)。

最小使用示例(无 harness):

```ts
import { buildHeadlessContainer } from './dist/plugin.js';
import { HeuristicLlmJudge } from './dist/heuristicJudge.js';
const { service } = buildHeadlessContainer(new HeuristicLlmJudge());
const result = await service.ingest(
  { path: 'codex-session.jsonl', text: '<Codex transcript JSONL>' },
  { redact: true, extractKnowledge: true },
);
console.log(result.sessionId, result.messageCount);
```

## 端到端验证(真实 dsh)

`scripts/verify-dsh.mjs` 会用与 `dsh` CLI 完全相同的方式 boot 一个含本插件的 profile,
并断言:`sessionImport` 服务可用、`/session-import` 命令已注册、导入产出真实会话事件且 JSONL 持久化成功:

```bash
node scripts/verify-dsh.mjs        # 依赖 $DSH_HOME/profiles/ci-chat-headless(已按上文方式安装)
```

## 卸载(Uninstall)

```bash
dsh plugin --profile web remove session-import
# 或从 profile package.json 删除依赖与 bundles 条目后重启 harness
# 历史数据:删除 $DSH_HOME/session-import 即清除归档/历史/记忆;会话本体保留在 dsh 会话库中
```

## 新增一个源适配器

```ts
import { ParserAdapter, RawInput, msg } from './src/parsers/base.js';
export class MyToolAdapter implements ParserAdapter {
  readonly source = 'mytool';
  readonly schemaVersion = '1.0';
  detect(raw: RawInput) { /* 嗅探特征,避免误判其它源 */ return false; }
  parse(raw: RawInput) { return { source: this.source, messages: [] }; }
}
// 在 src/registry.ts 的 createDefaultRegistry() 中按"具体源优先"顺序 register(new MyToolAdapter())
```

## 已知边界

- 知识抽取默认用离线启发式 `HeuristicLlmJudge`(`defaultExtractKnowledge: true` 时启用);
  接真实 LLM 通道(`ctx.llm`)留作后续。
- 导入的会话以 `import-<uuid>` 命名,避免 store 每进程自增 id 与磁盘既有日志冲突。
- **会话绑定源工作目录**:Codex rollout 的 `session_meta.cwd` 会写入导入会话的
  `SessionHeader.cwd` —— 会话因此落在对应 workspace(侧边栏分组)下,并满足 dsh
  冷恢复/模型选择对 `cwd` 的硬性要求(无 cwd 的会话无法 resume/续聊)。
- **会话标题**:Codex 线程名取自 `~/.codex/session_index.jsonl`(`thread_name`),
  `chat_import_import` 导入时自动作为会话标题(`session/title`,user source 固定,
  不会被回退覆盖);无名字的会话由 dsh 按首条消息回退生成。
- dsh 当前没有 memory 服务 seam,记忆沉淀走插件本地文件;`ICredentials` 已对接真实 `ctx.credentials`。

Install

dsh plugin --profile web add github:devmom/dsh-session-import

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source