# deep-flow

[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的终端界面（TUI）：一个交互式 Ink REPL，以仓库外（out-of-tree）`dsh` bundle 的形式骑在 `dsh-base` 之上。没有 Host、HTTP 服务或浏览器——一切都在**进程内**直接对组合好的 Cordis 树运行。

已发布到 npm：[`@jkxie/dsh-deep-flow`](https://www.npmjs.com/package/@jkxie/dsh-deep-flow)。English version: [README.md](README.md)。

```
░████  ░█████ ░█████ ░████     ░█████ ░█     ░████ ░█   █
░█   █ ░█     ░█     ░█   █    ░█     ░█     ░█  █ ░█   █
░█   █ ░███   ░███   ░████     ░███   ░█     ░█  █ ░█   █
░█   █ ░█     ░█     ░█        ░█     ░█     ░█  █ ░█ █ █
░████  ░█████ ░█████ ░█        ░█     ░█████ ░████  ░█ █
```

## 特性

- **默认新会话** —— 启动直接进入全新对话；`/sessions` 打开会话选择器。
- **会话管理** —— `/sessions` 打开 Gemini 风格的可搜索会话选择器（新建 / 恢复 / 切换）；当前会话标题显示在输入框上方，可用 `/rename` 固定重命名。
- **流式对话** —— 助手输出实时流入，以 Markdown 渲染，代码块用 `lowlight`（highlight.js）高亮，并在可滚动、自动跟随的 transcript 中显示。
- **丰富的工具卡片** —— 文件编辑显示为内联 diff，读取显示行号 + 高亮，另有终端输出、搜索结果、网页来源，全部由工具 render-intent 契约驱动。
- **内联人工协作** —— 斜杠命令（本地 `/new` `/rename` `/init` `/sessions` `/models` `/keys` `/help` `/exit` 加上 harness 自带命令）、权限提示（`y`/`n`）和提问问答，都在聚焦的对话框层内完成。
- **输入体验** —— `/` 命令补全与 `@path` 补全 + 内联 ghost text（`Tab` 接受），以及 `↑`/`↓` 输入历史。
- **模型切换** —— `/models` 打开基于 `ctx.llm` 的 provider/model 目录的选择器，并通过 default-model 设置持久化。
- **提供商设置** —— `/provider` 打开内置提供商清单（OpenAI / Anthropic / OpenCode）的选择器，随后内联收集 API key（掩码显示）并一步到位配置：key 写入 harness 凭据库，`llm-pi-ai` provider 配置写入用户设置文档，即时生效、无需重启。
- **Gemini 风格主题** —— 语义颜色 token、渐变 logo + spinner、`>` 提示符，以及集中、文档化的快捷键。
- **状态可观测** —— 一条由 `dsh-working-activity` 插件驱动的工作状态行（live working line），下面是分段上下文进度条（system / prompt / assistant / thinking / tools / free）、TPS 仪表 + sparkline，状态行还展示单次运行统计——缓存命中率、reasoning effort、输入 → 输出 token。
- **Git 分支徽标** —— 当工作目录是 git 仓库时，状态行显示当前分支徽标 `⎇ <branch>`（启动时读取，每次模型回复完成后刷新）。
- **会话指标命令** —— `/status`（模型、effort、会话 id、cwd、tokens、上下文占用率、TPS）、`/cost`（input / output / cache read / cache write）、`/tokens`（输入 → 输出）直接在 transcript 中输出报告。
- **Goals / Todos 面板** —— `/goal`、`/todo` 打开面板，把 `goal/change`、`todo/write` 会话事件投影为实时的 goal + todo 列表。
- **轨迹时间线** —— `/trace` 打开可过滤的会话时间线（turn / tool / reasoning / token 分类），`↑`/`↓` 切换过滤器。
- **导出为 Markdown** —— `/export` 将当前会话（user / assistant / tool 段）以 Markdown 写入当前工作目录（cwd）。
- **Agent 预设** —— `/preset` 打开基于 harness agent-preset 清单的选择器；在空白会话上选中即可切换 agent 的 preset（已有历史的会话会提示预设切换需要空会话）。
- **会话模式** —— `Shift+Tab` 循环切换 默认 / 计划 / 完全访问 模式：每种模式都是可选 DSH plane 开关的命名组合——plan 模式（`dsh-plan-mode` `/plan`）、沙箱策略、审批策略。
- **侧问** —— `/btw <问题>` 用当前模型选择发起一次独立的 `llm.stream` 调用，结果显示在面板中，绝不阻塞或打断主回合。
- **Rewind 回退** —— 空输入时双击 `Esc` 打开历史用户消息选择器；选中后把会话 fork 回该点（通过 `sessions.fork` + `agents.create` 换新 agent），并把该消息预填进输入框。
- **启动提示** —— 空会话时在 logo/version 下方显示三条随机使用提示（命令名高亮、描述灰显，中英双语按系统 locale 自动选择；提示内容维护在 `src/tips.txt`，改提示无需动代码）。

## 环境要求

- Node `^22.19.0` 或 `>= 24`（更老的 22.x 缺少 `node:zlib.createZstdDecompress`）。
- `pnpm >= 11` —— `dsh plugin add` 会调用系统 pnpm；pnpm 10.x 会触发 `ERR_PNPM_ADDING_TO_ROOT`。
- `DEEPSEEK_API_KEY` —— 仅发送真实模型请求时需要；启动和界面不需要。

## 安装与启动

```sh
# 前置：全局安装官方 harness CLI
npm install -g @deepseek-ai/dsh

# 全局安装 deep-flow（首次会自动初始化 profile）
npm install -g @jkxie/dsh-deep-flow

# 一键启动
deep-flow
```

`deep-flow` 启动器会自动让 profile 与已安装的包保持同步：

- **首次运行** —— 检测到 profile 未初始化，自动执行
  `dsh plugin --profile deep-flow add @jkxie/dsh-deep-flow@<版本>` 并启动。
- **版本漂移** —— 若 profile 内安装的版本与全局启动器版本不一致，会自动把
  profile 重新固定到启动器版本再启动。因此**升级只需
  `npm install -g @jkxie/dsh-deep-flow@<新版本>` 后执行 `deep-flow`**，profile 会在
  下次启动时自动更新。
- **版本一致** —— 直接启动。

手工 / 高级方式（等价）：

```sh
dsh plugin --profile deep-flow add @jkxie/dsh-deep-flow@latest
dsh --profile deep-flow
```

安装指定版本：

```sh
dsh plugin --profile deep-flow add @jkxie/dsh-deep-flow@0.2.0
```

更新到最新版：

```sh
dsh plugin --profile deep-flow add @jkxie/dsh-deep-flow@latest
```

`@deepseek-ai/*` 包通过 dsh 安装目录的 flat profile fallback 解析（源码启动时走 tsx paths），因此**不是**本包的 npm 依赖——你无需自行安装它们。

## 快捷键

| 界面 | 按键 | 作用 |
|---|---|---|
| **对话** | `↑` / `↓` | 召回之前的输入 |
| | `←` / `→` | 移动光标 |
| | `PgUp` / `PgDn` / 鼠标滚轮 | 滚动 transcript |
| | `Tab` | 接受 `/` 命令或 `@path` 补全 |
| | `Shift+Tab` | 循环切换会话模式（默认 / 计划 / 完全访问） |
| | `Enter` | 发送消息 |
| | `Ctrl-C` | 清空输入 → 取消本轮 → 退出 |
| | `/new` | 新建会话 |
| | `/rename` | 重命名当前会话 |
| | `/init` | 分析当前目录生成 AGENTS.md |
| | `/sessions` | 选择会话 |
| | `/models` | 选择模型 |
| | `/provider` | 设置模型提供商（API key） |
| | `/keys` | 管理 API 密钥 |
| | `/help` | 列出斜杠命令 |
| | `/status` | 显示会话信息 |
| | `/cost` | 显示 token 用量 |
| | `/tokens` | 显示 token 明细 |
| | `/goal` | 显示 goal 面板 |
| | `/todo` | 显示 todo 面板 |
| | `/export` | 将会话导出为 Markdown |
| | `/trace` | 显示会话轨迹时间线 |
| | `Esc Esc` | 终止当前回合 / 空输入时回退到某条历史消息 |
| | `/preset` | 切换 agent preset |
| | `/btw` | 提出侧问（不阻塞主回合） |
| | `/exit` | 退出 |
| **API 密钥** | `↑` / `k` · `↓` / `j` | 移动选择 |
| | `Enter` | 编辑选中的密钥（打码） |
| | `Esc` | 回到会话列表 |
| | `q` / `Ctrl-C` | 退出 |
| **提示** | `y` / `n` | 允许 / 拒绝权限请求 |
| | `1-9` | 选择问题选项 |
| | `c` | 输入自定义答案 |
| | `Enter` | 确认 / 跳过问题 |
| | `Esc` | 取消（撤销请求 / 问题） |

## 工作原理

`deep-flow` 是一个 Cordis bundle（`dsh.bundle.patch` → `cordis.patch.yml`），它禁用了共享的模块热重载 `hmr` 行，并插入 `deep-flow-runner` 插件。runner 注入核心服务（`agents`、`sessions`、`agentDefaultModel`、`tools`、`commands`、`userQuestions`、`approval`），等待 loader 就绪后渲染一棵 Ink 树，它会：

- 通过 `session/event` 读取持久会话日志，并经由 `Channel`（`src/store/channel.ts`）投影成 React 层用 `useSyncExternalStore` 订阅的不可变快照；TPS / 上下文进度条 / token 指标源自该 `session/event` 投影（`assistant/message` 的 usage、`request/header`、`request/context`、`user/message`、`tool/call`），而 `dsh-working-activity` 的实时 `activity/status` 帧仅驱动工作状态行，
- 通过 `agent.followup()` 提交用户输入，
- 通过 `agent.cancel()` 取消进行中的回合，
- 通过 `ctx.agents.create()` / `ctx.agents.resume()` 创建 / 恢复 agent——恢复前会先跑 `src/compat/sessionLog.ts` 就地修复持久化日志，把临时的 `activity/status` 帧标记为 `ignorable`，让 seed 校验接受该会话，
- 通过 `ctx.commands`、`approval/request` 瀑布和 `ctx.userQuestions` 应答交互入口。

| 概念 | 机制 |
|---|---|
| 事件流 | `session/event` |
| 提示 agent | `agent.followup()` |
| 中断 | `agent.cancel()` |
| 创建 / 恢复会话 | `ctx.agents.create()` / `ctx.agents.resume()` |
| 权限 / 命令 / 问答 | `ctx.approval` / `ctx.commands` / `ctx.userQuestions` |
| 模型目录 / 选择 | `ctx.llm` / `ctx.agentDefaultModel` |
| 状态可观测 | `activity/status`（`dsh-working-activity`）→ 仅工作状态行；TPS / 上下文进度条指标来自 `session/event` 投影 |

工作状态行来自 `dsh-working-activity` 插件，它通过 `src/working-activity.ts` 以本包自己的 `@jkxie/dsh-deep-flow/working-activity` 子路径再导出，这样 dsh loader 总能从 profile 的直接依赖里解析到它（pnpm 的隔离 node_modules 不会把传递依赖链进 profile 根目录）。

## 目录结构

```
src/
  index.tsx            入口 — 仅从 plugin.tsx 再导出 name/inject/apply
  plugin.tsx           runner 插件边界（服务、控制器、boot、render）
  app.tsx              App 界面（视图状态、单一 useInput 分发器、对话框接线）
  commands.ts          本地斜杠命令 + 解析器（/status /cost /tokens /goal /todo /export /trace，纯逻辑）
  controller.ts        Controller / HomeSession / CommandOutcome / StatusSnapshot 类型
  prompt.ts            桥接 boot ↔ React 的提示队列
  working-activity.ts  dsh-working-activity 再导出（loader 可解析的子路径）
  store/
    channel.ts         Channel — session/event → transcript 行 + 实时指标快照
    metrics.ts         上下文进度条、TPS 仪表 + sparkline、token 格式化（纯逻辑）
    goal-todo.ts       goal/change + todo/write 事件归约器（纯逻辑，切换会话可重放）
    rewind.ts          rewind 候选 + fork 边界计算（纯逻辑，可重放）
    session-modes.ts   可配置会话模式（plan/沙箱/审批 组合，纯逻辑）
    trace.ts           有界、可过滤的轨迹时间线投影（纯逻辑）
  screens/
    chat.tsx           ChatScreen — transcript + composer + 状态行
    status-line.tsx    状态行 + 分段上下文进度条 footer
  components/
    goal-panel.tsx     goal 面板（/goal）—— 来自 goal/change 事件的实时 goal
    todo-panel.tsx     todo 面板（/todo）—— 整表 todo/write 快照
    trace-view.tsx     /trace 可过滤时间线视图
    btw-panel.tsx      /btw 侧问面板（独立的 llm.stream 调用）
    preset-picker.tsx  /preset agent 预设选择器
    rewind-picker.tsx  双击 Esc 回退选择器（历史用户消息）
    session-picker.tsx /sessions Gemini 风格会话选择器
  hooks/
    useStore.ts        轻量 useSyncExternalStore 封装
  compat/
    sessionLog.ts      恢复前会话日志修复（第三方事件类型）
  transcript-view.tsx  ToolLine / LineView
  header.tsx           渐变 logo + 版本横幅 + 启动提示（transcript 顶部）
  tips.ts              解析 tips.txt、抽取随机提示子集、locale 检测（纯逻辑）
  tips.txt             中英双语启动提示数据（每行一组 cmd|desc|cmd|desc，可随意编辑）
  composer.tsx         边框输入盒 + spinner/footer
  file-completion.ts   @ 路径补全（纯逻辑）
  git-branch.ts        读取 cwd 的 git 分支（纯逻辑）
  init-prompt.ts        /init 分析 prompt（纯逻辑）
  spinner.tsx          渐变颜色循环 spinner
  useTerminalSize.ts   终端尺寸 hook
  markdown.tsx         markdown-it → Ink 渲染器（流式感知）
  highlight.ts         lowlight/highlight.js 语法高亮器（→ Ink token 颜色）
  tool-cards.tsx       read / terminal / search / web 结果卡片
  diff.tsx             内联文件 diff 视图
  dialogs.tsx          approval / question / model-picker / help 对话框
  keys.tsx             API 密钥管理视图（基于 ctx.credentials 的掩码编辑器）
  theme.ts             颜色主题（单套暗色，解耦）
  keymap.ts            集中式快捷键 + 帮助文本
  logo.tsx             启动 ASCII 艺术字（渐变；换品牌时改这里）
cordis.patch.yml      bundle 补丁（禁用 hmr、插入 deep-flow-runner）
```

## 开发

构建（产出 `lib/index.js`，ESM）：

```sh
pnpm run build
```

从 `deepseek-harness` checkout 本地运行（需要先 `pnpm install` 并至少 `pnpm run build:lib:host`）：

```sh
pnpm dsh plugin --profile deep-flow add ../deep-flow
pnpm dsh --profile deep-flow
```

本项目**没有** test / lint / typecheck 脚本——`pnpm run build` 是主要验证命令，另有 `pnpm run verify:metrics` / `verify:goal-todo` / `verify:trace` / `verify:rewind` / `verify:session-mode` 验证纯逻辑（指标、goal/todo 归约器、trace 投影、rewind 候选/边界、会话模式折叠）。渲染通过在假 stdin/stdout 上以 `interactive: false` 挂载组件离线验证；交互行为需真实终端。里程碑计划（M0–M5，全部完成）及沿途记录的教训见 [PLAN.md](PLAN.md)。

## 发布

```sh
npm version patch    # 或 minor / major —— npm 禁止重复发布同一版本号
npm publish --access public --registry https://registry.npmjs.org/
```

`prepublishOnly` 会自动执行 `pnpm run build`。发布预发布版本而不动 `latest` 标签时加 `--tag beta`。

## License

[MIT](LICENSE)
